Skip to main content

s3_wire/client/object/
listing.rs

1use std::collections::HashSet;
2
3use http::{HeaderMap, Method};
4
5use super::super::S3Client;
6use super::super::request::{OperationDeadline, protocol_error};
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        ];
88        push_optional_query(&mut query, "prefix", request.prefix.as_deref());
89        push_optional_query(&mut query, "delimiter", request.delimiter.as_deref());
90        push_optional_query(
91            &mut query,
92            "continuation-token",
93            request.continuation_token.as_deref(),
94        );
95        push_optional_query(
96            &mut query,
97            "start-after",
98            request
99                .start_after
100                .as_ref()
101                .map(crate::operation::ObjectKey::as_str),
102        );
103        if request.fetch_owner {
104            query.push(("fetch-owner".to_owned(), "true".to_owned()));
105        }
106        let target = self.operation_target(None)?;
107        let response = self
108            .send_signed(
109                Method::GET,
110                target,
111                &query,
112                HeaderMap::new(),
113                None,
114                deadline,
115            )
116            .await?;
117        let response_headers = response.headers().clone();
118        let maximum = self.inner.config.max_xml_response_size();
119        let body = self.collect_response(response, maximum, deadline).await?;
120        let mut output = parse_list_objects_v2(&body, maximum).map_err(protocol_error)?;
121        output.request_ids = parse_request_ids(&response_headers)?;
122        Ok(output)
123    }
124}