Skip to main content

s3_wire/client/object/
listing.rs

1use std::collections::HashSet;
2
3use http::Method;
4
5use super::super::S3Client;
6use super::super::request::{OperationDeadline, protocol_error, request_headers};
7use super::headers::{parse_request_ids, push_optional_query};
8use crate::error::{ErrorCategory, RetryClassification, S3Error};
9use crate::operation::{ListObjectsV2Output, ListObjectsV2Request};
10use crate::protocol::parse_list_objects_v2;
11
12impl S3Client {
13    /// Retrieves one page from S3's version-two object listing API.
14    ///
15    /// # Errors
16    ///
17    /// Returns an error when signing, transport, or response parsing fails.
18    pub async fn list_objects_v2(
19        &self,
20        request: ListObjectsV2Request,
21    ) -> Result<ListObjectsV2Output, S3Error> {
22        let deadline = self.deadline();
23        self.list_objects_v2_with_deadline(&request, &deadline)
24            .await
25    }
26
27    /// Retrieves at most `maximum_pages`, rejecting missing or repeating page tokens.
28    ///
29    /// # Errors
30    ///
31    /// Returns an error for a zero page bound, a malformed pagination sequence, an exhausted
32    /// page bound, or any page request failure.
33    pub async fn list_objects_v2_all(
34        &self,
35        mut request: ListObjectsV2Request,
36        maximum_pages: usize,
37    ) -> Result<Vec<ListObjectsV2Output>, S3Error> {
38        if maximum_pages == 0 {
39            return Err(S3Error::configuration(
40                "maximum listing page count must be greater than zero",
41            ));
42        }
43        let deadline = self.deadline();
44        let mut seen = HashSet::new();
45        if let Some(token) = &request.continuation_token {
46            seen.insert(token.clone());
47        }
48        let mut pages = Vec::with_capacity(maximum_pages.min(16));
49        for _ in 0..maximum_pages {
50            let page = self
51                .list_objects_v2_with_deadline(&request, &deadline)
52                .await?;
53            let is_truncated = page.is_truncated;
54            let next = page.next_continuation_token.clone();
55            pages.push(page);
56            if !is_truncated {
57                return Ok(pages);
58            }
59            let next = next.ok_or_else(|| {
60                S3Error::invalid_response(
61                    "truncated listing response omitted its continuation token",
62                )
63            })?;
64            if !seen.insert(next.clone()) {
65                return Err(S3Error::invalid_response(
66                    "listing response repeated a continuation token",
67                ));
68            }
69            request.continuation_token = Some(next);
70            request.start_after = None;
71        }
72        Err(S3Error::new(
73            ErrorCategory::OversizedResponse,
74            "listing exceeded the configured page limit",
75            RetryClassification::Never,
76        ))
77    }
78
79    async fn list_objects_v2_with_deadline(
80        &self,
81        request: &ListObjectsV2Request,
82        deadline: &OperationDeadline,
83    ) -> Result<ListObjectsV2Output, S3Error> {
84        let mut query = vec![
85            ("list-type".to_owned(), "2".to_owned()),
86            ("max-keys".to_owned(), request.max_keys.get().to_string()),
87            ("encoding-type".to_owned(), "url".to_owned()),
88        ];
89        push_optional_query(&mut query, "prefix", request.prefix.as_deref());
90        push_optional_query(&mut query, "delimiter", request.delimiter.as_deref());
91        push_optional_query(
92            &mut query,
93            "continuation-token",
94            request.continuation_token.as_deref(),
95        );
96        push_optional_query(
97            &mut query,
98            "start-after",
99            request
100                .start_after
101                .as_ref()
102                .map(crate::operation::ObjectKey::as_str),
103        );
104        if request.fetch_owner {
105            query.push(("fetch-owner".to_owned(), "true".to_owned()));
106        }
107        let target = self.operation_target(None)?;
108        let response = self
109            .send_signed(
110                Method::GET,
111                target,
112                &query,
113                request_headers(request.headers.clone())?,
114                None,
115                deadline,
116            )
117            .await?;
118        let response_headers = response.headers().clone();
119        let maximum = self.inner.config.max_xml_response_size();
120        let body = self.collect_response(response, maximum, deadline).await?;
121        let mut output = parse_list_objects_v2(&body, maximum).map_err(protocol_error)?;
122        output.request_ids = parse_request_ids(&response_headers)?;
123        Ok(output)
124    }
125}