1use std::{collections::BTreeMap, fmt, num::NonZeroU16};
2
3use super::{ByteRange, Checksum, ChecksumAlgorithm, Conditions, ObjectKey, RequestIds};
4
5#[derive(Clone, Debug, Default, Eq, PartialEq)]
7pub struct ObjectMetadata {
8 pub e_tag: Option<String>,
10 pub content_length: u64,
12 pub content_type: Option<String>,
14 pub last_modified: Option<time::OffsetDateTime>,
16 pub version_id: Option<String>,
18 pub user_metadata: BTreeMap<String, String>,
20 pub checksum: Checksum,
22 pub request_ids: RequestIds,
24}
25
26pub struct PutObjectRequest<B> {
28 pub key: ObjectKey,
30 pub body: B,
32 pub content_type: Option<String>,
34 pub user_metadata: BTreeMap<String, String>,
36 pub conditions: Conditions,
38 pub checksum_algorithm: Option<ChecksumAlgorithm>,
40}
41
42impl<B> PutObjectRequest<B> {
43 pub fn new(key: ObjectKey, body: B) -> Self {
45 Self {
46 key,
47 body,
48 content_type: None,
49 user_metadata: BTreeMap::new(),
50 conditions: Conditions::default(),
51 checksum_algorithm: None,
52 }
53 }
54}
55
56impl<B> fmt::Debug for PutObjectRequest<B> {
57 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
58 formatter
59 .debug_struct("PutObjectRequest")
60 .field("key", &self.key)
61 .field("body", &"<stream>")
62 .field("content_type", &self.content_type)
63 .field("user_metadata", &self.user_metadata)
64 .field("conditions", &self.conditions)
65 .field("checksum_algorithm", &self.checksum_algorithm)
66 .finish()
67 }
68}
69
70#[derive(Clone, Debug, Default, Eq, PartialEq)]
72pub struct PutObjectOutput {
73 pub e_tag: Option<String>,
75 pub version_id: Option<String>,
77 pub checksum: Checksum,
79 pub request_ids: RequestIds,
81}
82
83#[derive(Clone, Debug, Eq, PartialEq)]
85pub struct GetObjectRequest {
86 pub key: ObjectKey,
88 pub range: Option<ByteRange>,
90 pub conditions: Conditions,
92 pub version_id: Option<String>,
94}
95
96impl GetObjectRequest {
97 pub fn new(key: ObjectKey) -> Self {
99 Self {
100 key,
101 range: None,
102 conditions: Conditions::default(),
103 version_id: None,
104 }
105 }
106}
107
108pub struct GetObjectOutput<B> {
110 pub metadata: ObjectMetadata,
112 pub body: B,
114 pub content_range: Option<(u64, u64, Option<u64>)>,
116}
117
118impl<B> fmt::Debug for GetObjectOutput<B> {
119 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
120 formatter
121 .debug_struct("GetObjectOutput")
122 .field("metadata", &self.metadata)
123 .field("body", &"<stream>")
124 .field("content_range", &self.content_range)
125 .finish()
126 }
127}
128
129#[derive(Clone, Debug, Eq, PartialEq)]
131pub struct HeadObjectRequest {
132 pub key: ObjectKey,
134 pub conditions: Conditions,
136 pub version_id: Option<String>,
138}
139
140impl HeadObjectRequest {
141 pub fn new(key: ObjectKey) -> Self {
143 Self {
144 key,
145 conditions: Conditions::default(),
146 version_id: None,
147 }
148 }
149}
150
151pub type HeadObjectOutput = ObjectMetadata;
153
154#[derive(Clone, Debug, Eq, PartialEq)]
156pub struct DeleteObjectRequest {
157 pub key: ObjectKey,
159 pub version_id: Option<String>,
161 pub if_match: Option<String>,
163}
164
165impl DeleteObjectRequest {
166 pub fn new(key: ObjectKey) -> Self {
168 Self {
169 key,
170 version_id: None,
171 if_match: None,
172 }
173 }
174}
175
176#[derive(Clone, Debug, Default, Eq, PartialEq)]
178pub struct DeleteObjectOutput {
179 pub delete_marker: bool,
181 pub version_id: Option<String>,
183 pub request_ids: RequestIds,
185}
186
187pub const MAX_DELETE_OBJECTS: usize = 1_000;
189
190#[derive(Clone, Debug, Eq, PartialEq)]
192pub struct DeleteObjectsRequest {
193 objects: Vec<DeleteObjectRequest>,
194 pub quiet: bool,
196}
197
198impl DeleteObjectsRequest {
199 pub fn new(objects: Vec<DeleteObjectRequest>) -> Result<Self, DeleteObjectsError> {
201 if objects.is_empty() {
202 return Err(DeleteObjectsError::Empty);
203 }
204 if objects.len() > MAX_DELETE_OBJECTS {
205 return Err(DeleteObjectsError::TooMany {
206 actual: objects.len(),
207 maximum: MAX_DELETE_OBJECTS,
208 });
209 }
210 Ok(Self {
211 objects,
212 quiet: false,
213 })
214 }
215
216 pub fn objects(&self) -> &[DeleteObjectRequest] {
218 &self.objects
219 }
220}
221
222#[derive(Clone, Debug, Eq, PartialEq, thiserror::Error)]
224pub enum DeleteObjectsError {
225 #[error("a multi-object delete request cannot be empty")]
227 Empty,
228 #[error("multi-object delete has {actual} entries; the maximum is {maximum}")]
230 TooMany {
231 actual: usize,
233 maximum: usize,
235 },
236}
237
238#[derive(Clone, Debug, Eq, PartialEq)]
240pub struct DeletedObject {
241 pub key: ObjectKey,
243 pub version_id: Option<String>,
245 pub delete_marker: bool,
247 pub delete_marker_version_id: Option<String>,
249}
250
251#[derive(Clone, Debug, Eq, PartialEq)]
253pub struct DeleteError {
254 pub key: String,
256 pub version_id: Option<String>,
258 pub code: Option<String>,
260 pub message: Option<String>,
262}
263
264#[derive(Clone, Debug, Default, Eq, PartialEq)]
266pub struct DeleteObjectsOutput {
267 pub deleted: Vec<DeletedObject>,
269 pub errors: Vec<DeleteError>,
271 pub request_ids: RequestIds,
273}
274
275#[derive(Clone, Debug, Eq, PartialEq)]
277pub struct CopySource {
278 pub bucket: String,
280 pub key: ObjectKey,
282 pub version_id: Option<String>,
284}
285
286#[derive(Clone, Debug, Eq, PartialEq)]
288pub struct CopyObjectRequest {
289 pub source: CopySource,
291 pub destination: ObjectKey,
293 pub source_conditions: Conditions,
295 pub content_type: Option<String>,
297 pub user_metadata: Option<BTreeMap<String, String>>,
299}
300
301#[derive(Clone, Debug, Default, Eq, PartialEq)]
303pub struct CopyObjectOutput {
304 pub e_tag: Option<String>,
306 pub last_modified: Option<time::OffsetDateTime>,
308 pub version_id: Option<String>,
310 pub checksum: Checksum,
312 pub request_ids: RequestIds,
314}
315
316#[derive(Clone, Copy, Debug, Eq, PartialEq)]
318pub struct PageSize(NonZeroU16);
319
320impl PageSize {
321 pub const MAX: u16 = 1_000;
323
324 pub fn new(value: u16) -> Option<Self> {
326 NonZeroU16::new(value)
327 .filter(|value| value.get() <= Self::MAX)
328 .map(Self)
329 }
330
331 pub const fn get(self) -> u16 {
333 self.0.get()
334 }
335}
336
337impl Default for PageSize {
338 fn default() -> Self {
339 Self(NonZeroU16::new(Self::MAX).expect("page-size maximum must be non-zero"))
341 }
342}
343
344#[derive(Clone, Debug, Default, Eq, PartialEq)]
346pub struct ListObjectsV2Request {
347 pub prefix: Option<String>,
349 pub delimiter: Option<String>,
351 pub continuation_token: Option<String>,
353 pub start_after: Option<ObjectKey>,
355 pub max_keys: PageSize,
357 pub fetch_owner: bool,
359}
360
361#[derive(Clone, Debug, Eq, PartialEq)]
363pub struct ListedObject {
364 pub key: ObjectKey,
366 pub last_modified: Option<time::OffsetDateTime>,
368 pub e_tag: Option<String>,
370 pub size: u64,
372 pub storage_class: Option<String>,
374}
375
376#[derive(Clone, Debug, Default, Eq, PartialEq)]
378pub struct ListObjectsV2Output {
379 pub objects: Vec<ListedObject>,
381 pub common_prefixes: Vec<String>,
383 pub is_truncated: bool,
385 pub next_continuation_token: Option<String>,
387 pub key_count: Option<u32>,
389 pub request_ids: RequestIds,
391}
392
393#[cfg(test)]
394mod tests {
395 use super::*;
396
397 #[test]
398 fn delete_batches_are_strictly_bounded() {
399 assert_eq!(
400 DeleteObjectsRequest::new(Vec::new()).unwrap_err(),
401 DeleteObjectsError::Empty
402 );
403 let key = ObjectKey::new("key").unwrap();
404 let too_many = (0..=MAX_DELETE_OBJECTS)
405 .map(|_| DeleteObjectRequest::new(key.clone()))
406 .collect();
407 assert!(matches!(
408 DeleteObjectsRequest::new(too_many),
409 Err(DeleteObjectsError::TooMany { .. })
410 ));
411 }
412
413 #[test]
414 fn page_sizes_reject_zero_and_values_over_service_limit() {
415 assert!(PageSize::new(0).is_none());
416 assert_eq!(PageSize::new(1).unwrap().get(), 1);
417 assert_eq!(PageSize::new(1_000).unwrap().get(), 1_000);
418 assert!(PageSize::new(1_001).is_none());
419 }
420}