fix: emit NextPartNumberMarker only when ListParts is truncated

ListPartsInfo.next_part_number_marker was a non-optional usize that
defaulted to 0 and was only assigned when the response was truncated.
The S3 serializer then emitted it unconditionally as Some(0), causing
AWS SDK paginators to loop infinitely on part_number_marker=0 instead
of terminating.

Change the field to Option<usize> (None by default) and set it only
inside the is_truncated branch. The S3 output layer now uses
.and_then() so NextPartNumberMarker is absent when IsTruncated=false,
matching AWS S3 behavior.

Fixes #8208
This commit is contained in:
Chapman
2026-10-02 00:42:44 +08:00
parent 1efa86b8a3
commit 44de803a38
3 changed files with 5 additions and 5 deletions
+1 -1
View File
@@ -2125,7 +2125,7 @@ impl crate::storage_api_contracts::multipart::MultipartOperations for SetDisks {
if object_parts.len() > ret.parts.len() {
ret.is_truncated = true;
ret.next_part_number_marker = ret.parts.last().map(|v| v.part_num).unwrap_or_default();
ret.next_part_number_marker = ret.parts.last().map(|v| v.part_num);
}
ensure_multipart_bucket_lifecycle_lock_held(bucket, object, opts)?;
+1 -1
View File
@@ -61,7 +61,7 @@ pub struct ListPartsInfo {
pub upload_id: String,
pub storage_class: String,
pub part_number_marker: usize,
pub next_part_number_marker: usize,
pub next_part_number_marker: Option<usize>,
pub max_parts: usize,
pub is_truncated: bool,
pub parts: Vec<PartInfo>,
+3 -3
View File
@@ -71,7 +71,7 @@ pub(crate) fn build_list_parts_output(res: ListPartsInfo) -> ListPartsOutput {
owner: Some(owner),
initiator: Some(initiator),
is_truncated: Some(res.is_truncated),
next_part_number_marker: res.next_part_number_marker.try_into().ok(),
next_part_number_marker: res.next_part_number_marker.and_then(|v| v.try_into().ok()),
max_parts: res.max_parts.try_into().ok(),
part_number_marker: res.part_number_marker.try_into().ok(),
storage_class: Some(
@@ -228,7 +228,7 @@ mod tests {
upload_id: "upload-a".to_string(),
storage_class: "STANDARD".to_string(),
part_number_marker: 1,
next_part_number_marker: 2,
next_part_number_marker: Some(2),
max_parts: 1000,
is_truncated: true,
parts: vec![PartInfo {
@@ -374,7 +374,7 @@ mod tests {
let input = ListPartsInfo {
storage_class: "STANDARD_IA".to_string(),
part_number_marker: usize::MAX,
next_part_number_marker: usize::MAX,
next_part_number_marker: Some(usize::MAX),
max_parts: usize::MAX,
parts: vec![PartInfo {
part_num: usize::MAX,