1use std::{collections::BTreeMap, fmt, num::NonZeroU16};
2
3use super::{
4 ByteRange, Checksum, ChecksumAlgorithm, ChecksumType, Conditions, ObjectKey, RequestIds,
5};
6use crate::stream::{ByteStream, ResponseStream};
7
8#[derive(Clone, Debug, Default, Eq, PartialEq)]
10pub struct ObjectMetadata {
11 pub e_tag: Option<String>,
13 pub content_length: u64,
15 pub content_type: Option<String>,
17 pub last_modified: Option<time::OffsetDateTime>,
19 pub version_id: Option<String>,
21 pub user_metadata: BTreeMap<String, String>,
23 pub checksum: Checksum,
25 pub request_ids: RequestIds,
27}
28
29pub struct PutObjectRequest {
31 pub key: ObjectKey,
33 pub body: ByteStream,
35 pub content_type: Option<String>,
37 pub user_metadata: BTreeMap<String, String>,
39 pub conditions: Conditions,
41 pub checksum_algorithm: Option<ChecksumAlgorithm>,
43}
44
45impl PutObjectRequest {
46 pub fn new(key: ObjectKey, body: ByteStream) -> Self {
48 Self {
49 key,
50 body,
51 content_type: None,
52 user_metadata: BTreeMap::new(),
53 conditions: Conditions::default(),
54 checksum_algorithm: None,
55 }
56 }
57}
58
59impl fmt::Debug for PutObjectRequest {
60 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
61 formatter
62 .debug_struct("PutObjectRequest")
63 .field("key", &self.key)
64 .field("body", &"<stream>")
65 .field("content_type", &self.content_type)
66 .field("user_metadata", &self.user_metadata)
67 .field("conditions", &self.conditions)
68 .field("checksum_algorithm", &self.checksum_algorithm)
69 .finish()
70 }
71}
72
73#[derive(Clone, Debug, Default, Eq, PartialEq)]
75pub struct PutObjectOutput {
76 pub e_tag: Option<String>,
78 pub version_id: Option<String>,
80 pub checksum: Checksum,
82 pub request_ids: RequestIds,
84}
85
86#[derive(Clone, Debug, Eq, PartialEq)]
88pub struct GetObjectRequest {
89 pub key: ObjectKey,
91 pub range: Option<ByteRange>,
93 pub conditions: Conditions,
95 pub version_id: Option<String>,
97}
98
99impl GetObjectRequest {
100 pub fn new(key: ObjectKey) -> Self {
102 Self {
103 key,
104 range: None,
105 conditions: Conditions::default(),
106 version_id: None,
107 }
108 }
109}
110
111pub struct GetObjectOutput {
113 pub metadata: ObjectMetadata,
115 pub body: ResponseStream,
117 pub content_range: Option<(u64, u64, Option<u64>)>,
119}
120
121impl fmt::Debug for GetObjectOutput {
122 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
123 formatter
124 .debug_struct("GetObjectOutput")
125 .field("metadata", &self.metadata)
126 .field("body", &"<stream>")
127 .field("content_range", &self.content_range)
128 .finish()
129 }
130}
131
132#[derive(Clone, Debug, Eq, PartialEq)]
134pub struct HeadObjectRequest {
135 pub key: ObjectKey,
137 pub conditions: Conditions,
139 pub version_id: Option<String>,
141}
142
143impl HeadObjectRequest {
144 pub fn new(key: ObjectKey) -> Self {
146 Self {
147 key,
148 conditions: Conditions::default(),
149 version_id: None,
150 }
151 }
152}
153
154pub type HeadObjectOutput = ObjectMetadata;
156
157#[derive(Clone, Debug, Eq, PartialEq)]
159pub struct DeleteObjectRequest {
160 pub key: ObjectKey,
162 pub version_id: Option<String>,
164 pub if_match: Option<String>,
166}
167
168impl DeleteObjectRequest {
169 pub fn new(key: ObjectKey) -> Self {
171 Self {
172 key,
173 version_id: None,
174 if_match: None,
175 }
176 }
177}
178
179#[derive(Clone, Debug, Default, Eq, PartialEq)]
181pub struct DeleteObjectOutput {
182 pub delete_marker: bool,
184 pub version_id: Option<String>,
186 pub request_ids: RequestIds,
188}
189
190pub const MAX_DELETE_OBJECTS: usize = 1_000;
192
193#[derive(Clone, Debug, Eq, PartialEq)]
195pub struct DeleteObjectsRequest {
196 objects: Vec<DeleteObjectRequest>,
197 pub quiet: bool,
199}
200
201impl DeleteObjectsRequest {
202 pub fn new(objects: Vec<DeleteObjectRequest>) -> Result<Self, DeleteObjectsError> {
204 if objects.is_empty() {
205 return Err(DeleteObjectsError::Empty);
206 }
207 if objects.len() > MAX_DELETE_OBJECTS {
208 return Err(DeleteObjectsError::TooMany {
209 actual: objects.len(),
210 maximum: MAX_DELETE_OBJECTS,
211 });
212 }
213 Ok(Self {
214 objects,
215 quiet: false,
216 })
217 }
218
219 pub fn objects(&self) -> &[DeleteObjectRequest] {
221 &self.objects
222 }
223}
224
225#[derive(Clone, Debug, Eq, PartialEq, thiserror::Error)]
227pub enum DeleteObjectsError {
228 #[error("a multi-object delete request cannot be empty")]
230 Empty,
231 #[error("multi-object delete has {actual} entries; the maximum is {maximum}")]
233 TooMany {
234 actual: usize,
236 maximum: usize,
238 },
239}
240
241#[derive(Clone, Debug, Eq, PartialEq)]
243pub struct DeletedObject {
244 pub key: ObjectKey,
246 pub version_id: Option<String>,
248 pub delete_marker: bool,
250 pub delete_marker_version_id: Option<String>,
252}
253
254#[derive(Clone, Debug, Eq, PartialEq)]
256pub struct DeleteError {
257 pub key: String,
259 pub version_id: Option<String>,
261 pub code: Option<String>,
263 pub message: Option<String>,
265}
266
267#[derive(Clone, Debug, Default, Eq, PartialEq)]
269pub struct DeleteObjectsOutput {
270 pub deleted: Vec<DeletedObject>,
272 pub errors: Vec<DeleteError>,
274 pub request_ids: RequestIds,
276}
277
278#[derive(Clone, Debug, Eq, PartialEq)]
280pub struct CopySource {
281 pub bucket: String,
283 pub key: ObjectKey,
285 pub version_id: Option<String>,
287}
288
289#[derive(Clone, Debug, Default, Eq, PartialEq)]
295pub enum CopyMetadataDirective {
296 #[default]
298 Copy,
299 Replace {
301 content_type: Option<String>,
303 user_metadata: BTreeMap<String, String>,
305 },
306}
307
308#[derive(Clone, Debug, Eq, PartialEq)]
310pub struct CopyObjectRequest {
311 pub source: CopySource,
313 pub destination: ObjectKey,
315 pub source_conditions: Conditions,
317 pub metadata: CopyMetadataDirective,
319}
320
321impl CopyObjectRequest {
322 pub fn new(source: CopySource, destination: ObjectKey) -> Self {
324 Self {
325 source,
326 destination,
327 source_conditions: Conditions::default(),
328 metadata: CopyMetadataDirective::Copy,
329 }
330 }
331}
332
333#[derive(Clone, Debug, Default, Eq, PartialEq)]
335pub struct ObjectOwner {
336 pub id: Option<String>,
338 pub display_name: Option<String>,
340}
341
342#[derive(Clone, Debug, Default, Eq, PartialEq)]
344pub struct CopyObjectOutput {
345 pub e_tag: Option<String>,
347 pub last_modified: Option<time::OffsetDateTime>,
349 pub version_id: Option<String>,
351 pub checksum: Checksum,
353 pub request_ids: RequestIds,
355}
356
357#[derive(Clone, Copy, Debug, Eq, PartialEq)]
359pub struct PageSize(NonZeroU16);
360
361impl PageSize {
362 pub const MAX: u16 = 1_000;
364
365 pub fn new(value: u16) -> Option<Self> {
367 NonZeroU16::new(value)
368 .filter(|value| value.get() <= Self::MAX)
369 .map(Self)
370 }
371
372 pub const fn get(self) -> u16 {
374 self.0.get()
375 }
376}
377
378impl Default for PageSize {
379 fn default() -> Self {
380 Self(NonZeroU16::new(Self::MAX).expect("page-size maximum must be non-zero"))
382 }
383}
384
385#[derive(Clone, Debug, Default, Eq, PartialEq)]
387pub struct ListObjectsV2Request {
388 pub prefix: Option<String>,
390 pub delimiter: Option<String>,
392 pub continuation_token: Option<String>,
394 pub start_after: Option<ObjectKey>,
396 pub max_keys: PageSize,
398 pub fetch_owner: bool,
400}
401
402#[derive(Clone, Debug, Eq, PartialEq)]
404pub struct ListedObject {
405 pub key: ObjectKey,
407 pub last_modified: Option<time::OffsetDateTime>,
409 pub e_tag: Option<String>,
411 pub size: u64,
413 pub storage_class: Option<String>,
415 pub owner: Option<ObjectOwner>,
417 pub checksum_algorithms: Vec<String>,
419 pub checksum_type: Option<ChecksumType>,
421}
422
423#[derive(Clone, Debug, Default, Eq, PartialEq)]
425pub struct ListObjectsV2Output {
426 pub objects: Vec<ListedObject>,
428 pub common_prefixes: Vec<String>,
430 pub is_truncated: bool,
432 pub next_continuation_token: Option<String>,
434 pub key_count: Option<u32>,
436 pub request_ids: RequestIds,
438}
439
440#[cfg(test)]
441mod tests {
442 use super::*;
443
444 #[test]
445 fn delete_batches_are_strictly_bounded() {
446 assert_eq!(
447 DeleteObjectsRequest::new(Vec::new()).unwrap_err(),
448 DeleteObjectsError::Empty
449 );
450 let key = ObjectKey::new("key").unwrap();
451 let too_many = (0..=MAX_DELETE_OBJECTS)
452 .map(|_| DeleteObjectRequest::new(key.clone()))
453 .collect();
454 assert!(matches!(
455 DeleteObjectsRequest::new(too_many),
456 Err(DeleteObjectsError::TooMany { .. })
457 ));
458 }
459
460 #[test]
461 fn page_sizes_reject_zero_and_values_over_service_limit() {
462 assert!(PageSize::new(0).is_none());
463 assert_eq!(PageSize::new(1).unwrap().get(), 1);
464 assert_eq!(PageSize::new(1_000).unwrap().get(), 1_000);
465 assert!(PageSize::new(1_001).is_none());
466 }
467}