diff --git a/CHANGELOG.next.toml b/CHANGELOG.next.toml index ee32deb8d32..335a5bccb40 100644 --- a/CHANGELOG.next.toml +++ b/CHANGELOG.next.toml @@ -28,3 +28,15 @@ message = "Reduce verbosity of various debug logs" references = ["smithy-rs#3664"] meta = { "breaking" = false, "tada" = false, "bug" = false, "target" = "client"} author = "landonxjames" + +[[aws-sdk-rust]] +message = "Fix S3 ListParts API paginator infinite loop." +references = ["aws-sdk-rust#1143"] +meta = { "breaking" = false, "tada" = false, "bug" = true } +author = "landonxjames" + +[[smithy-rs]] +message = "Fix S3 ListParts API paginator infinite loop." +references = ["aws-sdk-rust#1143"] +meta = { "breaking" = false, "tada" = false, "bug" = true, "target" = "client"} +author = "landonxjames" diff --git a/aws/sdk/integration-tests/s3/tests/is-truncated-pagination.rs b/aws/sdk/integration-tests/s3/tests/is-truncated-pagination.rs new file mode 100644 index 00000000000..c926ba5c097 --- /dev/null +++ b/aws/sdk/integration-tests/s3/tests/is-truncated-pagination.rs @@ -0,0 +1,86 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +#![cfg(feature = "test-util")] + +use aws_credential_types::provider::SharedCredentialsProvider; +use aws_sdk_s3::Config; +use aws_sdk_s3::{config::Credentials, config::Region, Client}; +use aws_smithy_runtime::client::http::test_util::{ReplayEvent, StaticReplayClient}; +use aws_smithy_types::body::SdkBody; + +fn mk_response(part_marker: u8) -> http::Response { + let (part_num_marker, next_num_marker, is_truncated) = if part_marker < 3 { + (part_marker, part_marker + 1, true) + } else { + (part_marker, 0, false) + }; + let body = format!( + "\n + + foo-bar-bucket + test.txt + N0taR34lUpl0adId + {part_num_marker} + {next_num_marker} + 1 + {is_truncated} + + 4 + 2024-06-03T16:01:05.000Z + "1234" + 5242880 + + " + ); + http::Response::builder().body(SdkBody::from(body)).unwrap() +} + +fn mk_request() -> http::Request { + http::Request::builder() + .uri("https://some-test-bucket.s3.us-east-1.amazonaws.com/test.txt?part-number-marker=PartNumberMarker&uploadId=UploadId") + .body(SdkBody::empty()) + .unwrap() +} + +#[tokio::test] +async fn is_truncated_pagination_does_not_loop() { + let http_client = StaticReplayClient::new(vec![ + ReplayEvent::new(mk_request(), mk_response(0)), + ReplayEvent::new(mk_request(), mk_response(1)), + ReplayEvent::new(mk_request(), mk_response(2)), + ReplayEvent::new(mk_request(), mk_response(3)), + //The events below should never be called because the pagination should + //terminate with the event above + ReplayEvent::new(mk_request(), mk_response(0)), + ReplayEvent::new(mk_request(), mk_response(1)), + ]); + + let config = Config::builder() + .credentials_provider(SharedCredentialsProvider::new( + Credentials::for_tests_with_session_token(), + )) + .region(Region::new("us-east-1")) + .http_client(http_client.clone()) + .with_test_defaults() + .build(); + let client = Client::from_conf(config); + + let list_parts_res = client + .list_parts() + .bucket("some-test-bucket") + .key("test.txt") + .upload_id("N0taR34lUpl0adId") + .max_parts(1) + .into_paginator() + .send() + .collect::>() + .await; + + // Confirm that the pagination stopped calling the http client after the + // first page with is_truncated = false + assert_eq!(list_parts_res.len(), 4) +}