s3_wire/client/multipart/
listing.rs1use std::collections::HashSet;
2
3use http::Method;
4
5use super::query::list_parts_query;
6use crate::client::S3Client;
7use crate::client::request::{OperationDeadline, protocol_error, request_headers};
8use crate::error::{ErrorCategory, RetryClassification, S3Error};
9use crate::operation::{
10 ListMultipartUploadsOutput, ListMultipartUploadsRequest, ListPartsOutput, ListPartsRequest,
11};
12use crate::protocol::parse_list_parts;
13
14impl S3Client {
15 pub async fn list_parts(&self, request: ListPartsRequest) -> Result<ListPartsOutput, S3Error> {
21 let deadline = self.deadline();
22 self.list_parts_with_deadline(&request, &deadline).await
23 }
24
25 pub async fn list_parts_all(
35 &self,
36 mut request: ListPartsRequest,
37 maximum_pages: usize,
38 ) -> Result<Vec<ListPartsOutput>, S3Error> {
39 validate_page_bound(maximum_pages)?;
40 let deadline = self.deadline();
41 let mut seen = HashSet::new();
42 if let Some(marker) = request.part_number_marker {
43 seen.insert(marker.get());
44 }
45 let mut pages = Vec::with_capacity(maximum_pages.min(10));
46 for _ in 0..maximum_pages {
47 let page = self.list_parts_with_deadline(&request, &deadline).await?;
48 let truncated = page.is_truncated;
49 let next = page.next_part_number_marker;
50 pages.push(page);
51 if !truncated {
52 return Ok(pages);
53 }
54 let next = next.ok_or_else(|| {
55 S3Error::invalid_response("truncated ListParts response omitted its next marker")
56 })?;
57 if request
58 .part_number_marker
59 .is_some_and(|current| next <= current)
60 || !seen.insert(next.get())
61 {
62 return Err(S3Error::invalid_response(
63 "ListParts response returned a non-advancing marker",
64 ));
65 }
66 request.part_number_marker = Some(next);
67 }
68 Err(page_limit_error("ListParts"))
69 }
70
71 pub async fn list_multipart_uploads_all(
78 &self,
79 mut request: ListMultipartUploadsRequest,
80 maximum_pages: usize,
81 ) -> Result<Vec<ListMultipartUploadsOutput>, S3Error> {
82 validate_page_bound(maximum_pages)?;
83 let deadline = self.deadline();
84 let mut seen = HashSet::new();
85 if let Some(key) = &request.key_marker {
86 seen.insert((key.clone(), request.upload_id_marker.clone()));
87 }
88 let mut pages = Vec::with_capacity(maximum_pages.min(16));
89 for _ in 0..maximum_pages {
90 let page = self
91 .list_multipart_uploads_with_deadline(&request, &deadline)
92 .await?;
93 let truncated = page.is_truncated;
94 let next_key = page.next_key_marker.clone();
95 let next_upload_id = page.next_upload_id_marker.clone();
96 pages.push(page);
97 if !truncated {
98 return Ok(pages);
99 }
100 let next_key = next_key.ok_or_else(|| {
101 S3Error::invalid_response(
102 "truncated multipart-upload listing omitted its next key marker",
103 )
104 })?;
105 if !seen.insert((next_key.clone(), next_upload_id.clone())) {
106 return Err(S3Error::invalid_response(
107 "multipart-upload listing repeated its pagination markers",
108 ));
109 }
110 request.key_marker = Some(next_key);
111 request.upload_id_marker = next_upload_id;
112 }
113 Err(page_limit_error("multipart-upload listing"))
114 }
115
116 pub(in crate::client) async fn list_parts_with_deadline(
117 &self,
118 request: &ListPartsRequest,
119 deadline: &OperationDeadline,
120 ) -> Result<ListPartsOutput, S3Error> {
121 let target = self.operation_target(Some(request.key().as_str()))?;
122 let response = self
123 .send_signed(
124 Method::GET,
125 target,
126 &list_parts_query(request),
127 request_headers(request.headers.clone())?,
128 None,
129 deadline,
130 )
131 .await?;
132 let response_headers = response.headers().clone();
133 let maximum = self.config().max_xml_response_size();
134 let body = self.collect_response(response, maximum, deadline).await?;
135 let mut output = parse_list_parts(&body, maximum).map_err(protocol_error)?;
136 output.request_ids = super::headers::request_ids(&response_headers);
137 Ok(output)
138 }
139}
140
141fn validate_page_bound(maximum_pages: usize) -> Result<(), S3Error> {
142 if maximum_pages == 0 {
143 return Err(S3Error::configuration(
144 "maximum listing page count must be greater than zero",
145 ));
146 }
147 Ok(())
148}
149
150fn page_limit_error(operation: &str) -> S3Error {
151 S3Error::new(
152 ErrorCategory::OversizedResponse,
153 format!("{operation} exceeded the configured page limit"),
154 RetryClassification::Never,
155 )
156}