Skip to main content

s3_wire/client/multipart/
listing.rs

1use 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    /// Lists one bounded page of parts belonging to an in-progress upload.
16    ///
17    /// # Errors
18    ///
19    /// Returns an error when signing, transport, or bounded XML parsing fails.
20    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    /// Retrieves at most `maximum_pages` of parts under one operation deadline.
26    ///
27    /// Missing, non-advancing, or repeated markers are rejected rather than
28    /// risking an unbounded pagination loop.
29    ///
30    /// # Errors
31    ///
32    /// Returns an error for a zero page bound, invalid pagination, exhaustion
33    /// of the page bound, or any page request failure.
34    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    /// Retrieves at most `maximum_pages` of in-progress uploads under one deadline.
72    ///
73    /// # Errors
74    ///
75    /// Returns an error for a zero page bound, invalid pagination, exhaustion
76    /// of the page bound, or any page request failure.
77    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}