s3_signer/objects/
list.rs

1use serde::{Deserialize, Serialize};
2
3#[derive(Debug, Clone, Deserialize, Serialize)]
4pub struct ListObjectsQueryParameters {
5  pub bucket: String,
6  pub prefix: Option<String>,
7}
8
9pub type ListObjectsResponse = Vec<Object>;
10
11#[derive(Debug, Deserialize, Serialize)]
12#[cfg_attr(feature = "server", derive(utoipa::ToSchema))]
13pub struct Object {
14  pub path: String,
15  pub is_dir: bool,
16}
17
18impl Object {
19  pub fn build(path: &Option<String>, prefix: &Option<String>, is_dir: bool) -> Option<Self> {
20    let prefix_len = prefix.as_ref().map(|s| s.len()).unwrap_or(0);
21    let path = path.clone().unwrap_or_default().split_off(prefix_len);
22
23    if path.is_empty() {
24      return None;
25    }
26
27    Some(Self { path, is_dir })
28  }
29}
30
31#[cfg(feature = "server")]
32pub(crate) mod server {
33  use super::*;
34  use crate::{to_ok_json_response, Error, S3Configuration};
35  use rusoto_credential::{AwsCredentials, StaticProvider};
36  use rusoto_s3::{ListObjectsV2Request, S3Client, S3};
37  use warp::{
38    hyper::{Body, Response},
39    Filter, Rejection, Reply,
40  };
41
42  /// List objects
43  #[utoipa::path(
44    get,
45    path = "/objects",
46    tag = "Objects",
47    responses(
48      (
49        status = 200,
50        description = "Successfully list objects",
51        content_type = "application/json",
52        body = ListObjectsResponse
53      ),
54    ),
55    params(
56      ("bucket" = String, Query, description = "Name of the bucket"),
57      ("prefix" = Option<String>, Query, description = "Prefix to filter objects to list")
58    ),
59  )]
60  pub(crate) fn route(
61    s3_configuration: &S3Configuration,
62  ) -> impl Filter<Extract = (impl Reply,), Error = Rejection> + Clone {
63    let s3_configuration = s3_configuration.clone();
64    warp::path("objects")
65      .and(warp::get())
66      .and(warp::query::<ListObjectsQueryParameters>())
67      .and(warp::any().map(move || s3_configuration.clone()))
68      .and_then(
69        |parameters: ListObjectsQueryParameters, s3_configuration: S3Configuration| async move {
70          handle_list_objects(s3_configuration, parameters.bucket, parameters.prefix).await
71        },
72      )
73  }
74
75  async fn handle_list_objects(
76    s3_configuration: S3Configuration,
77    bucket: String,
78    source_prefix: Option<String>,
79  ) -> Result<Response<Body>, Rejection> {
80    log::info!(
81      "List objects signed URL: bucket={}, source_prefix={:?}",
82      bucket,
83      source_prefix
84    );
85    let credentials = AwsCredentials::from(&s3_configuration);
86
87    let list_objects = ListObjectsV2Request {
88      bucket: bucket.to_string(),
89      delimiter: Some(String::from("/")),
90      prefix: source_prefix.clone(),
91      ..Default::default()
92    };
93
94    let http_client = rusoto_core::request::HttpClient::new()
95      .map_err(|error| warp::reject::custom(Error::S3ConnectionError(error)))?;
96    let credentials: StaticProvider = credentials.into();
97
98    let client = S3Client::new_with(http_client, credentials, s3_configuration.region().clone());
99
100    let response = client
101      .list_objects_v2(list_objects)
102      .await
103      .map_err(|error| warp::reject::custom(Error::ListObjectsError(error)))?;
104
105    let mut objects = response
106      .contents
107      .map(|contents| {
108        contents
109          .iter()
110          .filter_map(|content| Object::build(&content.key, &source_prefix, false))
111          .collect::<ListObjectsResponse>()
112      })
113      .unwrap_or_default();
114
115    let mut folders = response
116      .common_prefixes
117      .map(|prefixes| {
118        prefixes
119          .iter()
120          .filter_map(|prefix| Object::build(&prefix.prefix, &source_prefix, true))
121          .collect::<ListObjectsResponse>()
122      })
123      .unwrap_or_default();
124
125    objects.append(&mut folders);
126
127    to_ok_json_response(&objects)
128  }
129}