1use rustack_s3_model::{
6 error::S3Error,
7 input::{ListObjectVersionsInput, ListObjectsInput, ListObjectsV2Input},
8 output::{ListObjectVersionsOutput, ListObjectsOutput, ListObjectsV2Output},
9 types::{
10 CommonPrefix, DeleteMarkerEntry, Object, ObjectStorageClass, ObjectVersion,
11 ObjectVersionStorageClass, Owner,
12 },
13};
14use tracing::debug;
15
16use crate::{
17 error::S3ServiceError,
18 provider::RustackS3,
19 state::{keystore::VersionListEntry, object::Owner as InternalOwner},
20 utils::{decode_continuation_token, encode_continuation_token},
21};
22
23const DEFAULT_MAX_KEYS: i32 = 1000;
25
26#[allow(clippy::result_large_err)]
32fn validate_max_keys(max_keys: Option<i32>) -> Result<i32, S3Error> {
33 let value = max_keys.unwrap_or(DEFAULT_MAX_KEYS);
34 if value < 0 {
35 return Err(S3ServiceError::InvalidArgument {
36 message: format!(
37 "Argument max-keys must be an integer between 0 and {DEFAULT_MAX_KEYS}"
38 ),
39 }
40 .into_s3_error());
41 }
42 Ok(value)
43}
44
45#[allow(clippy::cast_possible_wrap)]
47fn to_model_object(obj: &crate::state::object::S3Object) -> Object {
48 let owner = Owner {
49 display_name: Some(obj.owner.display_name.clone()),
50 id: Some(obj.owner.id.clone()),
51 };
52 Object {
53 checksum_algorithm: Vec::new(),
54 checksum_type: None,
55 e_tag: Some(obj.etag.clone()),
56 key: Some(obj.key.clone()),
57 last_modified: Some(obj.last_modified),
58 owner: Some(owner),
59 restore_status: None,
60 size: Some(obj.size as i64),
61 storage_class: Some(ObjectStorageClass::from(obj.storage_class.as_str())),
62 }
63}
64
65fn to_model_owner(owner: &InternalOwner) -> Owner {
67 Owner {
68 display_name: Some(owner.display_name.clone()),
69 id: Some(owner.id.clone()),
70 }
71}
72
73fn to_common_prefixes(prefixes: &[String]) -> Vec<CommonPrefix> {
75 prefixes
76 .iter()
77 .map(|p| CommonPrefix {
78 prefix: Some(p.clone()),
79 })
80 .collect()
81}
82
83#[allow(
86 clippy::cast_possible_wrap,
87 clippy::cast_possible_truncation,
88 clippy::cast_sign_loss,
89 clippy::unused_async
90)]
91impl RustackS3 {
92 pub async fn handle_list_objects(
94 &self,
95 input: ListObjectsInput,
96 ) -> Result<ListObjectsOutput, S3Error> {
97 let bucket_name = input.bucket;
98
99 let bucket = self
100 .state
101 .get_bucket(&bucket_name)
102 .map_err(S3ServiceError::into_s3_error)?;
103
104 let prefix = input.prefix.as_deref().unwrap_or("");
105 let delimiter = input.delimiter.as_deref().unwrap_or("");
106 let marker = input.marker.as_deref().unwrap_or("");
107 let max_keys = validate_max_keys(input.max_keys)?;
108 let max_keys_usize = usize::try_from(max_keys).unwrap_or(1000);
109
110 let store = bucket.objects.read();
111 let result = store.list_objects(prefix, delimiter, marker, max_keys_usize);
112 drop(store);
113 drop(bucket);
114
115 let contents: Vec<Object> = result.objects.iter().map(to_model_object).collect();
116 let common_prefixes = to_common_prefixes(&result.common_prefixes);
117
118 let next_marker = if result.is_truncated {
119 result.next_marker.clone()
120 } else {
121 None
122 };
123
124 debug!(
125 bucket = %bucket_name,
126 prefix = %prefix,
127 count = contents.len(),
128 is_truncated = result.is_truncated,
129 "list_objects completed"
130 );
131
132 Ok(ListObjectsOutput {
133 common_prefixes,
134 contents,
135 delimiter: input.delimiter,
136 encoding_type: input.encoding_type,
137 is_truncated: Some(result.is_truncated),
138 marker: input.marker,
139 max_keys: Some(max_keys),
140 name: Some(bucket_name),
141 next_marker,
142 prefix: input.prefix,
143 request_charged: None,
144 })
145 }
146
147 pub async fn handle_list_objects_v2(
149 &self,
150 input: ListObjectsV2Input,
151 ) -> Result<ListObjectsV2Output, S3Error> {
152 let bucket_name = input.bucket;
153
154 let bucket = self
155 .state
156 .get_bucket(&bucket_name)
157 .map_err(S3ServiceError::into_s3_error)?;
158
159 let prefix = input.prefix.as_deref().unwrap_or("");
160 let delimiter = input.delimiter.as_deref().unwrap_or("");
161 let max_keys = validate_max_keys(input.max_keys)?;
162 let max_keys_usize = usize::try_from(max_keys).unwrap_or(1000);
163 let fetch_owner = input.fetch_owner.unwrap_or(false);
164
165 let decoded_token = if let Some(token) = &input.continuation_token {
167 Some(decode_continuation_token(token).map_err(S3ServiceError::into_s3_error)?)
168 } else {
169 None
170 };
171 let start_after = decoded_token
172 .as_deref()
173 .or(input.start_after.as_deref())
174 .unwrap_or("");
175
176 let store = bucket.objects.read();
177 let result = store.list_objects(prefix, delimiter, start_after, max_keys_usize);
178 drop(store);
179 drop(bucket);
180
181 let contents: Vec<Object> = result
182 .objects
183 .iter()
184 .map(|obj| {
185 let mut s3_obj = to_model_object(obj);
186 if !fetch_owner {
187 s3_obj.owner = None;
188 }
189 s3_obj
190 })
191 .collect();
192 let common_prefixes = to_common_prefixes(&result.common_prefixes);
193
194 let next_continuation_token = if result.is_truncated {
195 result
196 .next_marker
197 .as_ref()
198 .map(|m| encode_continuation_token(m))
199 } else {
200 None
201 };
202
203 let key_count = contents.len() as i32;
204
205 debug!(
206 bucket = %bucket_name,
207 prefix = %prefix,
208 count = key_count,
209 is_truncated = result.is_truncated,
210 "list_objects_v2 completed"
211 );
212
213 Ok(ListObjectsV2Output {
214 common_prefixes,
215 contents,
216 continuation_token: input.continuation_token,
217 delimiter: input.delimiter,
218 encoding_type: input.encoding_type,
219 is_truncated: Some(result.is_truncated),
220 key_count: Some(key_count),
221 max_keys: Some(max_keys),
222 name: Some(bucket_name),
223 next_continuation_token,
224 prefix: input.prefix,
225 request_charged: None,
226 start_after: input.start_after,
227 })
228 }
229
230 pub async fn handle_list_object_versions(
232 &self,
233 input: ListObjectVersionsInput,
234 ) -> Result<ListObjectVersionsOutput, S3Error> {
235 if input.version_id_marker.is_some() && input.key_marker.is_none() {
237 return Err(S3Error::invalid_argument(
238 "A version-id marker cannot be specified without a key marker",
239 ));
240 }
241
242 let bucket_name = input.bucket;
243
244 let bucket = self
245 .state
246 .get_bucket(&bucket_name)
247 .map_err(S3ServiceError::into_s3_error)?;
248
249 let prefix = input.prefix.as_deref().unwrap_or("");
250 let delimiter = input.delimiter.as_deref().unwrap_or("");
251 let key_marker = input.key_marker.as_deref().unwrap_or("");
252 let version_id_marker = input.version_id_marker.as_deref().unwrap_or("");
253 let max_keys = validate_max_keys(input.max_keys)?;
254 let max_keys_usize = usize::try_from(max_keys).unwrap_or(1000);
255
256 let store = bucket.objects.read();
257 let result = store.list_object_versions(
258 prefix,
259 delimiter,
260 key_marker,
261 version_id_marker,
262 max_keys_usize,
263 );
264 drop(store);
265 drop(bucket);
266
267 let (versions, delete_markers) = partition_version_list_entries(&result.versions);
269
270 let common_prefixes = to_common_prefixes(&result.common_prefixes);
271
272 debug!(
273 bucket = %bucket_name,
274 prefix = %prefix,
275 versions = versions.len(),
276 delete_markers = delete_markers.len(),
277 is_truncated = result.is_truncated,
278 "list_object_versions completed"
279 );
280
281 Ok(ListObjectVersionsOutput {
282 common_prefixes,
283 delete_markers,
284 delimiter: input.delimiter,
285 encoding_type: input.encoding_type,
286 is_truncated: Some(result.is_truncated),
287 key_marker: input.key_marker,
288 max_keys: Some(max_keys),
289 name: Some(bucket_name),
290 next_key_marker: result.next_key_marker,
291 next_version_id_marker: result.next_version_id_marker,
292 prefix: input.prefix,
293 request_charged: None,
294 version_id_marker: input.version_id_marker,
295 versions,
296 })
297 }
298}
299
300#[allow(clippy::cast_possible_wrap)]
303fn partition_version_list_entries(
304 entries: &[VersionListEntry],
305) -> (Vec<ObjectVersion>, Vec<DeleteMarkerEntry>) {
306 let mut versions = Vec::new();
307 let mut delete_markers = Vec::new();
308
309 for entry in entries {
310 match &entry.version {
311 crate::state::object::ObjectVersion::Object(obj) => {
312 let owner = to_model_owner(&obj.owner);
313 versions.push(ObjectVersion {
314 checksum_algorithm: Vec::new(),
315 checksum_type: None,
316 e_tag: Some(obj.etag.clone()),
317 is_latest: Some(entry.is_latest),
318 key: Some(obj.key.clone()),
319 last_modified: Some(obj.last_modified),
320 owner: Some(owner),
321 restore_status: None,
322 size: Some(obj.size as i64),
323 storage_class: Some(ObjectVersionStorageClass::from(
324 obj.storage_class.as_str(),
325 )),
326 version_id: Some(obj.version_id.clone()),
327 });
328 }
329 crate::state::object::ObjectVersion::DeleteMarker(dm) => {
330 let owner = to_model_owner(&dm.owner);
331 delete_markers.push(DeleteMarkerEntry {
332 is_latest: Some(entry.is_latest),
333 key: Some(dm.key.clone()),
334 last_modified: Some(dm.last_modified),
335 owner: Some(owner),
336 version_id: Some(dm.version_id.clone()),
337 });
338 }
339 }
340 }
341
342 (versions, delete_markers)
343}