s3_signer/objects/
list.rs1use 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 #[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}