1use std::{collections::BTreeMap, fmt, num::NonZeroU16};
2
3use super::{ByteRange, Checksum, ChecksumAlgorithm, Conditions, ObjectKey, RequestIds};
4use crate::stream::{ByteStream, ResponseStream};
5
6#[derive(Clone, Debug, Default, Eq, PartialEq)]
8pub struct ObjectMetadata {
9 pub e_tag: Option<String>,
11 pub content_length: u64,
13 pub content_type: Option<String>,
15 pub last_modified: Option<time::OffsetDateTime>,
17 pub version_id: Option<String>,
19 pub user_metadata: BTreeMap<String, String>,
21 pub checksum: Checksum,
23 pub request_ids: RequestIds,
25}
26
27pub struct PutObjectRequest {
29 pub key: ObjectKey,
31 pub body: ByteStream,
33 pub content_type: Option<String>,
35 pub user_metadata: BTreeMap<String, String>,
37 pub conditions: Conditions,
39 pub checksum_algorithm: Option<ChecksumAlgorithm>,
41}
42
43impl PutObjectRequest {
44 pub fn new(key: ObjectKey, body: ByteStream) -> Self {
46 Self {
47 key,
48 body,
49 content_type: None,
50 user_metadata: BTreeMap::new(),
51 conditions: Conditions::default(),
52 checksum_algorithm: None,
53 }
54 }
55}
56
57impl fmt::Debug for PutObjectRequest {
58 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
59 formatter
60 .debug_struct("PutObjectRequest")
61 .field("key", &self.key)
62 .field("body", &"<stream>")
63 .field("content_type", &self.content_type)
64 .field("user_metadata", &self.user_metadata)
65 .field("conditions", &self.conditions)
66 .field("checksum_algorithm", &self.checksum_algorithm)
67 .finish()
68 }
69}
70
71#[derive(Clone, Debug, Default, Eq, PartialEq)]
73pub struct PutObjectOutput {
74 pub e_tag: Option<String>,
76 pub version_id: Option<String>,
78 pub checksum: Checksum,
80 pub request_ids: RequestIds,
82}
83
84#[derive(Clone, Debug, Eq, PartialEq)]
86pub struct GetObjectRequest {
87 pub key: ObjectKey,
89 pub range: Option<ByteRange>,
91 pub conditions: Conditions,
93 pub version_id: Option<String>,
95}
96
97impl GetObjectRequest {
98 pub fn new(key: ObjectKey) -> Self {
100 Self {
101 key,
102 range: None,
103 conditions: Conditions::default(),
104 version_id: None,
105 }
106 }
107}
108
109pub struct GetObjectOutput {
111 pub metadata: ObjectMetadata,
113 pub body: ResponseStream,
115 pub content_range: Option<(u64, u64, Option<u64>)>,
117}
118
119impl fmt::Debug for GetObjectOutput {
120 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
121 formatter
122 .debug_struct("GetObjectOutput")
123 .field("metadata", &self.metadata)
124 .field("body", &"<stream>")
125 .field("content_range", &self.content_range)
126 .finish()
127 }
128}
129
130#[derive(Clone, Debug, Eq, PartialEq)]
132pub struct HeadObjectRequest {
133 pub key: ObjectKey,
135 pub conditions: Conditions,
137 pub version_id: Option<String>,
139}
140
141impl HeadObjectRequest {
142 pub fn new(key: ObjectKey) -> Self {
144 Self {
145 key,
146 conditions: Conditions::default(),
147 version_id: None,
148 }
149 }
150}
151
152pub type HeadObjectOutput = ObjectMetadata;
154
155#[derive(Clone, Debug, Eq, PartialEq)]
157pub struct DeleteObjectRequest {
158 pub key: ObjectKey,
160 pub version_id: Option<String>,
162 pub if_match: Option<String>,
164}
165
166impl DeleteObjectRequest {
167 pub fn new(key: ObjectKey) -> Self {
169 Self {
170 key,
171 version_id: None,
172 if_match: None,
173 }
174 }
175}
176
177#[derive(Clone, Debug, Default, Eq, PartialEq)]
179pub struct DeleteObjectOutput {
180 pub delete_marker: bool,
182 pub version_id: Option<String>,
184 pub request_ids: RequestIds,
186}
187
188pub const MAX_DELETE_OBJECTS: usize = 1_000;
190
191#[derive(Clone, Debug, Eq, PartialEq)]
193pub struct DeleteObjectsRequest {
194 objects: Vec<DeleteObjectRequest>,
195 pub quiet: bool,
197}
198
199impl DeleteObjectsRequest {
200 pub fn new(objects: Vec<DeleteObjectRequest>) -> Result<Self, DeleteObjectsError> {
202 if objects.is_empty() {
203 return Err(DeleteObjectsError::Empty);
204 }
205 if objects.len() > MAX_DELETE_OBJECTS {
206 return Err(DeleteObjectsError::TooMany {
207 actual: objects.len(),
208 maximum: MAX_DELETE_OBJECTS,
209 });
210 }
211 Ok(Self {
212 objects,
213 quiet: false,
214 })
215 }
216
217 pub fn objects(&self) -> &[DeleteObjectRequest] {
219 &self.objects
220 }
221}
222
223#[derive(Clone, Debug, Eq, PartialEq, thiserror::Error)]
225pub enum DeleteObjectsError {
226 #[error("a multi-object delete request cannot be empty")]
228 Empty,
229 #[error("multi-object delete has {actual} entries; the maximum is {maximum}")]
231 TooMany {
232 actual: usize,
234 maximum: usize,
236 },
237}
238
239#[derive(Clone, Debug, Eq, PartialEq)]
241pub struct DeletedObject {
242 pub key: ObjectKey,
244 pub version_id: Option<String>,
246 pub delete_marker: bool,
248 pub delete_marker_version_id: Option<String>,
250}
251
252#[derive(Clone, Debug, Eq, PartialEq)]
254pub struct DeleteError {
255 pub key: String,
257 pub version_id: Option<String>,
259 pub code: Option<String>,
261 pub message: Option<String>,
263}
264
265#[derive(Clone, Debug, Default, Eq, PartialEq)]
267pub struct DeleteObjectsOutput {
268 pub deleted: Vec<DeletedObject>,
270 pub errors: Vec<DeleteError>,
272 pub request_ids: RequestIds,
274}
275
276#[derive(Clone, Debug, Eq, PartialEq)]
278pub struct CopySource {
279 pub bucket: String,
281 pub key: ObjectKey,
283 pub version_id: Option<String>,
285}
286
287#[derive(Clone, Debug, Eq, PartialEq)]
289pub struct CopyObjectRequest {
290 pub source: CopySource,
292 pub destination: ObjectKey,
294 pub source_conditions: Conditions,
296 pub content_type: Option<String>,
298 pub user_metadata: Option<BTreeMap<String, String>>,
300}
301
302#[derive(Clone, Debug, Default, Eq, PartialEq)]
304pub struct CopyObjectOutput {
305 pub e_tag: Option<String>,
307 pub last_modified: Option<time::OffsetDateTime>,
309 pub version_id: Option<String>,
311 pub checksum: Checksum,
313 pub request_ids: RequestIds,
315}
316
317#[derive(Clone, Copy, Debug, Eq, PartialEq)]
319pub struct PageSize(NonZeroU16);
320
321impl PageSize {
322 pub const MAX: u16 = 1_000;
324
325 pub fn new(value: u16) -> Option<Self> {
327 NonZeroU16::new(value)
328 .filter(|value| value.get() <= Self::MAX)
329 .map(Self)
330 }
331
332 pub const fn get(self) -> u16 {
334 self.0.get()
335 }
336}
337
338impl Default for PageSize {
339 fn default() -> Self {
340 Self(NonZeroU16::new(Self::MAX).expect("page-size maximum must be non-zero"))
342 }
343}
344
345#[derive(Clone, Debug, Default, Eq, PartialEq)]
347pub struct ListObjectsV2Request {
348 pub prefix: Option<String>,
350 pub delimiter: Option<String>,
352 pub continuation_token: Option<String>,
354 pub start_after: Option<ObjectKey>,
356 pub max_keys: PageSize,
358 pub fetch_owner: bool,
360}
361
362#[derive(Clone, Debug, Eq, PartialEq)]
364pub struct ListedObject {
365 pub key: ObjectKey,
367 pub last_modified: Option<time::OffsetDateTime>,
369 pub e_tag: Option<String>,
371 pub size: u64,
373 pub storage_class: Option<String>,
375}
376
377#[derive(Clone, Debug, Default, Eq, PartialEq)]
379pub struct ListObjectsV2Output {
380 pub objects: Vec<ListedObject>,
382 pub common_prefixes: Vec<String>,
384 pub is_truncated: bool,
386 pub next_continuation_token: Option<String>,
388 pub key_count: Option<u32>,
390 pub request_ids: RequestIds,
392}
393
394#[cfg(test)]
395mod tests {
396 use super::*;
397
398 #[test]
399 fn delete_batches_are_strictly_bounded() {
400 assert_eq!(
401 DeleteObjectsRequest::new(Vec::new()).unwrap_err(),
402 DeleteObjectsError::Empty
403 );
404 let key = ObjectKey::new("key").unwrap();
405 let too_many = (0..=MAX_DELETE_OBJECTS)
406 .map(|_| DeleteObjectRequest::new(key.clone()))
407 .collect();
408 assert!(matches!(
409 DeleteObjectsRequest::new(too_many),
410 Err(DeleteObjectsError::TooMany { .. })
411 ));
412 }
413
414 #[test]
415 fn page_sizes_reject_zero_and_values_over_service_limit() {
416 assert!(PageSize::new(0).is_none());
417 assert_eq!(PageSize::new(1).unwrap().get(), 1);
418 assert_eq!(PageSize::new(1_000).unwrap().get(), 1_000);
419 assert!(PageSize::new(1_001).is_none());
420 }
421}