mountpoint_s3_client/
object_client.rs

1use std::fmt::{self, Debug};
2use std::ops::Range;
3use std::time::SystemTime;
4
5use async_trait::async_trait;
6use auto_impl::auto_impl;
7use futures::Stream;
8use mountpoint_s3_crt::s3::client::BufferPoolUsageStats;
9use std::collections::HashMap;
10use thiserror::Error;
11use time::OffsetDateTime;
12
13use crate::checksums::{
14    self, crc32_to_base64, crc32c_to_base64, crc64nvme_to_base64, sha1_to_base64, sha256_to_base64,
15};
16use crate::error_metadata::{ClientErrorMetadata, ProvideErrorMetadata};
17
18mod etag;
19pub use etag::ETag;
20
21/// A generic interface to S3-like object storage services.
22///
23/// This trait defines the common methods that all object services implement.
24///
25/// This is an async trait defined with the [async-trait](https://crates.io/crates/async-trait)
26/// crate, and so implementations of this trait must use the `#[async_trait::async_trait]`
27/// attribute.
28#[cfg_attr(not(docsrs), async_trait)]
29#[auto_impl(Arc)]
30pub trait ObjectClient {
31    type GetObjectResponse: GetObjectResponse<ClientError = Self::ClientError>;
32    type PutObjectRequest: PutObjectRequest<ClientError = Self::ClientError>;
33    type ClientError: std::error::Error + ProvideErrorMetadata + Send + Sync + 'static;
34
35    /// Query the part size this client uses for GET operations to the object store. This
36    /// can be `None` if the client does not do multi-part operations.
37    fn read_part_size(&self) -> Option<usize>;
38
39    /// Query the part size this client uses for PUT operations to the object store. This
40    /// can be `None` if the client does not do multi-part operations.
41    fn write_part_size(&self) -> Option<usize>;
42
43    /// Query the initial read window size this client uses for backpressure GetObject requests.
44    /// This can be `None` if backpressure is disabled.
45    fn initial_read_window_size(&self) -> Option<usize>;
46
47    /// Query current memory usage stats for the client. This can be `None` if the client
48    /// does not record the stats.
49    fn mem_usage_stats(&self) -> Option<BufferPoolUsageStats>;
50
51    /// Delete a single object from the object store.
52    ///
53    /// DeleteObject will succeed even if the object within the bucket does not exist.
54    async fn delete_object(
55        &self,
56        bucket: &str,
57        key: &str,
58    ) -> ObjectClientResult<DeleteObjectResult, DeleteObjectError, Self::ClientError>;
59
60    /// Create a copy of an existing object. Currently, this functionality has the following limitations:
61    /// - Supported only for copying between matching bucket types:
62    ///     - Standard S3 to Standard S3 buckets.
63    ///     - S3 Express to S3 Express buckets.
64    /// - Host header must use virtual host addressing style (path style is not supported) and both source and dest buckets must have dns compliant name.
65    /// - Only {bucket}/{key} format is supported for source and passing arn as source will not work.
66    /// - Source bucket is assumed to be in the same region as destination bucket.
67    async fn copy_object(
68        &self,
69        source_bucket: &str,
70        source_key: &str,
71        destination_bucket: &str,
72        destination_key: &str,
73        params: &CopyObjectParams,
74    ) -> ObjectClientResult<CopyObjectResult, CopyObjectError, Self::ClientError>;
75
76    /// Get an object from the object store. Returns a stream of body parts of the object. Parts are
77    /// guaranteed to be returned by the stream in order and contiguously.
78    async fn get_object(
79        &self,
80        bucket: &str,
81        key: &str,
82        params: &GetObjectParams,
83    ) -> ObjectClientResult<Self::GetObjectResponse, GetObjectError, Self::ClientError>;
84
85    /// List the objects in a bucket under a given prefix
86    async fn list_objects(
87        &self,
88        bucket: &str,
89        continuation_token: Option<&str>,
90        delimiter: &str,
91        max_keys: usize,
92        prefix: &str,
93    ) -> ObjectClientResult<ListObjectsResult, ListObjectsError, Self::ClientError>;
94
95    /// Retrieve object metadata without retrieving the object contents
96    async fn head_object(
97        &self,
98        bucket: &str,
99        key: &str,
100        params: &HeadObjectParams,
101    ) -> ObjectClientResult<HeadObjectResult, HeadObjectError, Self::ClientError>;
102
103    /// Put an object into the object store. Returns a [PutObjectRequest] for callers
104    /// to provide the content of the object.
105    async fn put_object(
106        &self,
107        bucket: &str,
108        key: &str,
109        params: &PutObjectParams,
110    ) -> ObjectClientResult<Self::PutObjectRequest, PutObjectError, Self::ClientError>;
111
112    /// Put an object into the object store.
113    async fn put_object_single<'a>(
114        &self,
115        bucket: &str,
116        key: &str,
117        params: &PutObjectSingleParams,
118        contents: impl AsRef<[u8]> + Send + 'a,
119    ) -> ObjectClientResult<PutObjectResult, PutObjectError, Self::ClientError>;
120
121    /// Retrieves all the metadata from an object without returning the object contents.
122    async fn get_object_attributes(
123        &self,
124        bucket: &str,
125        key: &str,
126        max_parts: Option<usize>,
127        part_number_marker: Option<usize>,
128        object_attributes: &[ObjectAttribute],
129    ) -> ObjectClientResult<GetObjectAttributesResult, GetObjectAttributesError, Self::ClientError>;
130}
131
132/// The top-level error type returned by calls to an [`ObjectClient`].
133///
134/// Errors that are explicitly modeled on a per-request-type basis are [`ServiceError`]s. Other
135/// generic or unhandled errors are [`ClientError`]s.
136///
137/// The distinction between these two types of error can sometimes be blurry. As a rough heuristic,
138/// [`ServiceError`]s are those that *any reasonable implementation* of an object client would be
139/// capable of experiencing, and [`ClientError`]s are anything else. For example, any object client
140/// could experience a "no such key" error, but only object clients that implement a permissions
141/// system could experience "permission denied" errors. When in doubt, we err towards *not* adding
142/// new [`ServiceError`]s, as they are public API for *every* object client.
143///
144/// [`ServiceError`]: ObjectClientError::ServiceError
145/// [`ClientError`]: ObjectClientError::ClientError
146#[derive(Debug, Error, PartialEq)]
147pub enum ObjectClientError<S, C> {
148    /// An error returned by the service itself
149    #[error("Service error")]
150    ServiceError(#[source] S),
151
152    /// An error within the object client (for example, an unexpected response, or a failure to
153    /// construct the request).
154    #[error("Client error")]
155    ClientError(#[from] C),
156}
157
158impl<S, C> ProvideErrorMetadata for ObjectClientError<S, C>
159where
160    C: ProvideErrorMetadata,
161{
162    fn meta(&self) -> ClientErrorMetadata {
163        match self {
164            Self::ClientError(err) => err.meta(),
165            _ => Default::default(),
166        }
167    }
168}
169
170/// Shorthand type for the result of an object client request
171pub type ObjectClientResult<T, S, C> = Result<T, ObjectClientError<S, C>>;
172
173/// Errors returned by a [`get_object`](ObjectClient::get_object) request
174#[derive(Debug, Error, PartialEq, Eq)]
175#[non_exhaustive]
176pub enum GetObjectError {
177    #[error("The bucket does not exist")]
178    NoSuchBucket,
179
180    #[error("The key does not exist")]
181    NoSuchKey,
182
183    #[error("At least one of the preconditions specified did not hold")]
184    PreconditionFailed,
185}
186
187/// Parameters to a [`get_object`](ObjectClient::get_object) request
188#[derive(Debug, Default, Clone)]
189#[non_exhaustive]
190pub struct GetObjectParams {
191    pub range: Option<Range<u64>>,
192    pub if_match: Option<ETag>,
193    pub checksum_mode: Option<ChecksumMode>,
194}
195
196impl GetObjectParams {
197    /// Create a default [GetObjectParams].
198    pub fn new() -> Self {
199        Self::default()
200    }
201
202    /// Set the range retrieved by the GetObject request
203    pub fn range(mut self, value: Option<Range<u64>>) -> Self {
204        self.range = value;
205        self
206    }
207
208    /// Set the required etag on the object
209    pub fn if_match(mut self, value: Option<ETag>) -> Self {
210        self.if_match = value;
211        self
212    }
213
214    /// Set option to retrieve checksum as part of the GetObject request
215    pub fn checksum_mode(mut self, value: Option<ChecksumMode>) -> Self {
216        self.checksum_mode = value;
217        self
218    }
219}
220
221/// Result of a [`list_objects`](ObjectClient::list_objects) request
222#[derive(Debug)]
223#[non_exhaustive]
224pub struct ListObjectsResult {
225    /// The list of objects.
226    pub objects: Vec<ObjectInfo>,
227
228    /// The list of common prefixes. This rolls up all of the objects with a common prefix up to
229    /// the next instance of the delimiter.
230    pub common_prefixes: Vec<String>,
231
232    /// If present, the continuation token to use to query more results.
233    pub next_continuation_token: Option<String>,
234}
235
236/// Errors returned by a [`list_objects`](ObjectClient::list_objects) request
237#[derive(Debug, Error, PartialEq, Eq)]
238#[non_exhaustive]
239pub enum ListObjectsError {
240    #[error("The bucket does not exist")]
241    NoSuchBucket,
242}
243
244/// Parameters to a [`head_object`](ObjectClient::head_object) request
245#[derive(Debug, Default, Clone)]
246#[non_exhaustive]
247pub struct HeadObjectParams {
248    /// Enable to retrieve checksum as part of the HeadObject request
249    pub checksum_mode: Option<ChecksumMode>,
250}
251
252impl HeadObjectParams {
253    /// Create a default [HeadObjectParams].
254    pub fn new() -> Self {
255        Self::default()
256    }
257
258    /// Set option to retrieve checksum as part of the HeadObject request
259    pub fn checksum_mode(mut self, value: Option<ChecksumMode>) -> Self {
260        self.checksum_mode = value;
261        self
262    }
263}
264
265/// Enable [ChecksumMode] to retrieve object checksums
266#[non_exhaustive]
267#[derive(Clone, Debug, PartialEq)]
268pub enum ChecksumMode {
269    /// Retrieve checksums
270    Enabled,
271}
272
273/// Result of a [`head_object`](ObjectClient::head_object) request
274#[derive(Debug)]
275#[non_exhaustive]
276pub struct HeadObjectResult {
277    /// Size of the object in bytes.
278    ///
279    /// Refers to the `Content-Length` HTTP header for HeadObject.
280    pub size: u64,
281
282    /// The time this object was last modified.
283    pub last_modified: OffsetDateTime,
284
285    /// Entity tag of this object.
286    pub etag: ETag,
287
288    /// Storage class for this object.
289    ///
290    /// The value is optional because HeadObject does not return the storage class in its response
291    /// for objects in the S3 Standard storage class.
292    /// See examples in the
293    /// [Amazon S3 API Reference](https://docs.aws.amazon.com/AmazonS3/latest/API/API_HeadObject.html#API_HeadObject_Examples).
294    pub storage_class: Option<String>,
295
296    /// Objects in flexible retrieval storage classes (such as GLACIER and DEEP_ARCHIVE) are only
297    /// accessible after restoration
298    pub restore_status: Option<RestoreStatus>,
299    /// Checksum of the object.
300    ///
301    /// HeadObject must explicitly request for this field to be included,
302    /// otherwise the values will be empty.
303    pub checksum: Checksum,
304
305    /// Server-side encryption type that was used to store the object.
306    pub sse_type: Option<String>,
307
308    /// Server-side encryption KMS key ID that was used to store the object.
309    pub sse_kms_key_id: Option<String>,
310}
311
312/// Errors returned by a [`head_object`](ObjectClient::head_object) request
313#[derive(Debug, Error, PartialEq, Eq)]
314#[non_exhaustive]
315pub enum HeadObjectError {
316    /// Note that HeadObject cannot distinguish between NoSuchBucket and NoSuchKey errors
317    #[error("The object was not found")]
318    NotFound,
319}
320
321/// Result of a [`delete_object`](ObjectClient::delete_object) request
322///
323/// Note: DeleteObject requests on a non-existent object within a bucket are considered a success.
324// TODO: Populate this struct with return fields from the S3 API, e.g., version id, delete marker.
325#[derive(Debug)]
326#[non_exhaustive]
327pub struct DeleteObjectResult {}
328
329/// Errors returned by a [`delete_object`](ObjectClient::delete_object) request
330#[derive(Debug, Error, PartialEq, Eq)]
331#[non_exhaustive]
332pub enum DeleteObjectError {
333    #[error("The bucket does not exist")]
334    NoSuchBucket,
335}
336
337/// Result of a [`copy_object`](ObjectClient::copy_object) request
338#[derive(Debug)]
339#[non_exhaustive]
340pub struct CopyObjectResult {
341    // TODO: Populate this struct with return fields from the S3 API, e.g., etag.
342}
343
344/// Errors returned by a [`copy_object`](ObjectClient::copy_object) request
345#[derive(Debug, Error, PartialEq, Eq)]
346#[non_exhaustive]
347pub enum CopyObjectError {
348    /// Note that CopyObject cannot distinguish between NoSuchBucket and NoSuchKey errors
349    #[error("The object was not found")]
350    NotFound,
351
352    #[error("The source object of the COPY action is not in the active tier and is only stored in Amazon S3 Glacier.")]
353    ObjectNotInActiveTierError,
354}
355
356/// Parameters to a [`copy_object`](ObjectClient::copy_object) request
357#[derive(Debug, Default, Clone)]
358#[non_exhaustive]
359pub struct CopyObjectParams {
360    // TODO: Populate this struct with fields as and when required to satisfy various use cases.
361}
362
363impl CopyObjectParams {
364    /// Create a default [CopyObjectParams].
365    pub fn new() -> Self {
366        Self::default()
367    }
368}
369
370/// Result of a [`get_object_attributes`](ObjectClient::get_object_attributes) request
371#[derive(Debug, Default)]
372pub struct GetObjectAttributesResult {
373    /// ETag of the object
374    pub etag: Option<String>,
375
376    /// Checksum of the object
377    pub checksum: Option<Checksum>,
378
379    /// Object parts metadata for multi part object
380    pub object_parts: Option<GetObjectAttributesParts>,
381
382    /// Storage class of the object
383    pub storage_class: Option<String>,
384
385    /// Object size
386    pub object_size: Option<u64>,
387}
388
389/// Errors returned by a [`get_object_attributes`](ObjectClient::get_object_attributes) request
390#[derive(Debug, Error, PartialEq, Eq)]
391#[non_exhaustive]
392pub enum GetObjectAttributesError {
393    #[error("The bucket does not exist")]
394    NoSuchBucket,
395
396    #[error("The key does not exist")]
397    NoSuchKey,
398}
399
400pub type ObjectMetadata = HashMap<String, String>;
401
402/// Parameters to a [`put_object`](ObjectClient::put_object) request
403#[derive(Debug, Default, Clone)]
404#[non_exhaustive]
405pub struct PutObjectParams {
406    /// Enable Crc32c trailing checksums.
407    pub trailing_checksums: PutObjectTrailingChecksums,
408    /// Storage class to be used when creating new S3 object
409    pub storage_class: Option<String>,
410    /// The server-side encryption algorithm to be used for this object in Amazon S3 (for example, AES256, aws:kms, aws:kms:dsse)
411    pub server_side_encryption: Option<String>,
412    /// If `server_side_encryption` has a valid value of aws:kms or aws:kms:dsse, this value may be used to specify AWS KMS key ID to be used
413    /// when creating new S3 object
414    pub ssekms_key_id: Option<String>,
415    /// Custom headers to add to the request
416    pub custom_headers: Vec<(String, String)>,
417    /// User-defined object metadata
418    pub object_metadata: ObjectMetadata,
419}
420
421impl PutObjectParams {
422    /// Create a default [PutObjectParams].
423    pub fn new() -> Self {
424        Self::default()
425    }
426
427    /// Set Crc32c trailing checksums.
428    pub fn trailing_checksums(mut self, value: PutObjectTrailingChecksums) -> Self {
429        self.trailing_checksums = value;
430        self
431    }
432
433    /// Set the storage class.
434    pub fn storage_class(mut self, value: String) -> Self {
435        self.storage_class = Some(value);
436        self
437    }
438
439    /// Set server-side encryption type.
440    pub fn server_side_encryption(mut self, value: Option<String>) -> Self {
441        self.server_side_encryption = value;
442        self
443    }
444
445    /// Set KMS key ID to be used for server-side encryption.
446    pub fn ssekms_key_id(mut self, value: Option<String>) -> Self {
447        self.ssekms_key_id = value;
448        self
449    }
450
451    /// Add a custom header to the request.
452    pub fn add_custom_header(mut self, name: String, value: String) -> Self {
453        self.custom_headers.push((name, value));
454        self
455    }
456
457    /// Set user defined object metadata.
458    pub fn object_metadata(mut self, value: ObjectMetadata) -> Self {
459        self.object_metadata = value;
460        self
461    }
462}
463
464/// How CRC32c checksums are used for parts of a multi-part PutObject request
465#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
466pub enum PutObjectTrailingChecksums {
467    /// Checksums are computed, passed to upload review, and also sent to S3
468    Enabled,
469    /// Checksums are computed, passed to upload review, but not sent to S3
470    ReviewOnly,
471    /// Checksums are not computed on the client side
472    #[default]
473    Disabled,
474}
475
476/// Info for the caller to review before an upload completes.
477pub type UploadReview = mountpoint_s3_crt::s3::client::UploadReview;
478
479/// Info about a single part, for the caller to review before the upload completes.
480pub type UploadReviewPart = mountpoint_s3_crt::s3::client::UploadReviewPart;
481
482/// A checksum algorithm used by the object client for integrity checks on uploads and downloads.
483pub type ChecksumAlgorithm = mountpoint_s3_crt::s3::client::ChecksumAlgorithm;
484
485/// Parameters to a [`put_object_single`](ObjectClient::put_object_single) request
486#[derive(Debug, Default, Clone)]
487#[non_exhaustive]
488pub struct PutObjectSingleParams {
489    /// User-provided checksum of the data to upload.
490    pub checksum: Option<UploadChecksum>,
491    /// Storage class to be used when creating new S3 object
492    pub storage_class: Option<String>,
493    /// The server-side encryption algorithm to be used for this object in Amazon S3 (for example, AES256, aws:kms, aws:kms:dsse)
494    pub server_side_encryption: Option<String>,
495    /// If `server_side_encryption` has a valid value of aws:kms or aws:kms:dsse, this value may be used to specify AWS KMS key ID to be used
496    /// when creating new S3 object
497    pub ssekms_key_id: Option<String>,
498    /// Requires pre-existing object to match the given etag in order to perform the request
499    pub if_match: Option<ETag>,
500    /// Offset on the pre-existing object where to append the data in the request
501    pub write_offset_bytes: Option<u64>,
502    /// Custom headers to add to the request
503    pub custom_headers: Vec<(String, String)>,
504    /// User-defined object metadata
505    pub object_metadata: ObjectMetadata,
506}
507
508impl PutObjectSingleParams {
509    /// Create a default [PutObjectSingleParams].
510    pub fn new() -> Self {
511        Self::default()
512    }
513
514    /// Create a [PutObjectSingleParams] for an append request at the given offset.
515    pub fn new_for_append(offset: u64) -> Self {
516        Self::default().write_offset_bytes(offset)
517    }
518
519    /// Set checksum.
520    pub fn checksum(mut self, value: Option<UploadChecksum>) -> Self {
521        self.checksum = value;
522        self
523    }
524
525    /// Set the storage class.
526    pub fn storage_class(mut self, value: String) -> Self {
527        self.storage_class = Some(value);
528        self
529    }
530
531    /// Set server-side encryption type.
532    pub fn server_side_encryption(mut self, value: Option<String>) -> Self {
533        self.server_side_encryption = value;
534        self
535    }
536
537    /// Set KMS key ID to be used for server-side encryption.
538    pub fn ssekms_key_id(mut self, value: Option<String>) -> Self {
539        self.ssekms_key_id = value;
540        self
541    }
542
543    /// Set the required etag on the pre-existing object.
544    pub fn if_match(mut self, value: Option<ETag>) -> Self {
545        self.if_match = value;
546        self
547    }
548
549    /// Set the offset on the pre-existing object where to append the data in the request.
550    pub fn write_offset_bytes(mut self, value: u64) -> Self {
551        self.write_offset_bytes = Some(value);
552        self
553    }
554
555    /// Add a custom header to the request.
556    pub fn add_custom_header(mut self, name: String, value: String) -> Self {
557        self.custom_headers.push((name, value));
558        self
559    }
560
561    /// Set user defined object metadata.
562    pub fn object_metadata(mut self, value: ObjectMetadata) -> Self {
563        self.object_metadata = value;
564        self
565    }
566}
567
568/// A checksum used by the object client for integrity checks on uploads.
569#[derive(Debug, Clone)]
570#[non_exhaustive]
571pub enum UploadChecksum {
572    Crc64nvme(checksums::Crc64nvme),
573    Crc32c(checksums::Crc32c),
574    Crc32(checksums::Crc32),
575    Sha1(checksums::Sha1),
576    Sha256(checksums::Sha256),
577}
578
579impl UploadChecksum {
580    /// The checksum algorithm used to compute this checksum.
581    pub fn checksum_algorithm(&self) -> ChecksumAlgorithm {
582        match self {
583            UploadChecksum::Crc64nvme(_) => ChecksumAlgorithm::Crc64nvme,
584            UploadChecksum::Crc32c(_) => ChecksumAlgorithm::Crc32c,
585            UploadChecksum::Crc32(_) => ChecksumAlgorithm::Crc32,
586            UploadChecksum::Sha1(_) => ChecksumAlgorithm::Sha1,
587            UploadChecksum::Sha256(_) => ChecksumAlgorithm::Sha256,
588        }
589    }
590}
591
592/// A handle for controlling backpressure enabled requests.
593///
594/// If the client was created with `enable_read_backpressure` set true,
595/// each meta request has a flow-control window that shrinks as response
596/// body data is downloaded (headers do not affect the size of the window).
597/// The client's `initial_read_window` determines the starting size of each meta request's window.
598/// If a meta request's flow-control window reaches 0, no further data will be downloaded.
599/// If the `initial_read_window` is 0, the request will not start until the window is incremented.
600/// Maintain a larger window to keep up a high download throughput,
601/// parts cannot download in parallel unless the window is large enough to hold multiple parts.
602/// Maintain a smaller window to limit the amount of data buffered in memory.
603pub trait ClientBackpressureHandle {
604    /// Increment the flow-control read window, so that response data continues downloading.
605    fn increment_read_window(&mut self, len: usize);
606
607    /// Move the upper bound of the read window to the given offset if it's not already there.
608    fn ensure_read_window(&mut self, desired_end_offset: u64);
609
610    /// Get the upper bound of the read window. When backpressure is enabled, [GetObjectRequest] can
611    /// return data up to this offset *exclusively*.
612    fn read_window_end_offset(&self) -> u64;
613}
614
615/// A streaming response to a GetObject request.
616///
617/// This struct implements [`futures::Stream`], which you can use to read the body of the object.
618/// Each item of the stream is a part of the object body together with the part's offset within the
619/// object.
620#[cfg_attr(not(docsrs), async_trait)]
621pub trait GetObjectResponse:
622    Stream<Item = ObjectClientResult<GetBodyPart, GetObjectError, Self::ClientError>> + Send + Sync
623{
624    type BackpressureHandle: ClientBackpressureHandle + Clone + Send + Sync;
625    type ClientError: std::error::Error + Send + Sync + 'static;
626
627    /// Take the backpressure handle from the response.
628    ///
629    /// If `enable_read_backpressure` is false this call will return `None`,
630    /// no backpressure is being applied and data is being downloaded as fast as possible.
631    fn backpressure_handle(&mut self) -> Option<&mut Self::BackpressureHandle>;
632
633    /// Get the object's user defined metadata.
634    fn get_object_metadata(&self) -> ObjectMetadata;
635
636    /// Get the object's checksum, if uploaded with one
637    fn get_object_checksum(&self) -> Result<Checksum, ObjectChecksumError>;
638}
639
640/// Failures to return object checksum
641#[derive(Debug, Error)]
642pub enum ObjectChecksumError {
643    #[error("requested object checksums, but did not specify it in the request")]
644    DidNotRequestChecksums,
645    #[error("object checksum could not be retrieved from headers")]
646    HeadersError(#[source] Box<dyn std::error::Error + Send + Sync + 'static>),
647}
648
649/// A single element of a [`get_object`](ObjectClient::get_object) response stream is a pair of
650/// offset within the object and the bytes starting at that offset.
651pub type GetBodyPart = (u64, Box<[u8]>);
652
653/// A streaming put request which allows callers to asynchronously write the body of the request.
654///
655/// You can call the [`write`](Self::write) method to write data to the object, and then call
656/// [`complete`](Self::complete) to complete the upload. Alternatively, you can call
657/// [`review_and_complete`](Self::review_and_complete) to review the upload before completing it,
658/// giving the chance to cancel the request if the upload is not as expected.
659///
660/// This is an async trait defined with the [async-trait](https://crates.io/crates/async-trait)
661/// crate, and so implementations of this trait must use the `#[async_trait::async_trait]`
662/// attribute.
663#[cfg_attr(not(docsrs), async_trait)]
664pub trait PutObjectRequest: Send {
665    type ClientError: std::error::Error + Send + Sync + 'static;
666
667    /// Write the given slice to the put request body.
668    async fn write(&mut self, slice: &[u8]) -> ObjectClientResult<(), PutObjectError, Self::ClientError>;
669
670    /// Complete the put request and return a [`PutObjectResult`].
671    async fn complete(self) -> ObjectClientResult<PutObjectResult, PutObjectError, Self::ClientError>;
672
673    /// Review and complete the put request and return a [`PutObjectResult`].
674    async fn review_and_complete(
675        self,
676        review_callback: impl FnOnce(UploadReview) -> bool + Send + 'static,
677    ) -> ObjectClientResult<PutObjectResult, PutObjectError, Self::ClientError>;
678}
679
680/// Result of a [ObjectClient::put_object] request
681#[derive(Debug)]
682#[non_exhaustive]
683pub struct PutObjectResult {
684    /// ETag of the uploaded object
685    pub etag: ETag,
686    /// Server-side encryption type that was used to store new object (reported by S3)
687    pub sse_type: Option<String>,
688    /// Server-side encryption KMS key ID that was used to store new object (reported by S3)
689    pub sse_kms_key_id: Option<String>,
690}
691
692/// Errors returned by a [`put_object`](ObjectClient::put_object) request
693#[derive(Debug, Error, PartialEq, Eq)]
694#[non_exhaustive]
695pub enum PutObjectError {
696    #[error("The bucket does not exist")]
697    NoSuchBucket,
698
699    #[error("The key does not exist")]
700    NoSuchKey,
701
702    #[error("Request body cannot be empty when write offset is specified")]
703    EmptyBody,
704
705    #[error("The offset does not match the current object size")]
706    InvalidWriteOffset,
707
708    #[error("The provided checksum does not match the data")]
709    BadChecksum,
710
711    #[error("The provided checksum is not valid or does not match the existing checksum algorithm")]
712    InvalidChecksumType,
713
714    #[error("At least one of the pre-conditions you specified did not hold")]
715    PreconditionFailed,
716
717    #[error("The server does not support the functionality required to fulfill the request")]
718    NotImplemented,
719}
720
721/// Restoration status for S3 objects in flexible retrieval storage classes.
722///
723/// See [Checking restore status and expiration
724/// date](https://docs.aws.amazon.com/AmazonS3/latest/userguide/restoring-objects.html#restore-archived-objects-status)
725/// in the *Amazon S3 User Guide* for more details.
726#[derive(Debug, Clone, Copy)]
727pub enum RestoreStatus {
728    /// S3 returns this status after it accepted a restoration request, but not have completed it yet.
729    /// Objects with this status are not readable.
730    InProgress,
731
732    /// This status means that restoration is fully completed. Note that restored objects are stored only
733    /// for the number of days that was specified in the request.
734    Restored { expiry: SystemTime },
735}
736
737/// Metadata about a single S3 object.
738///
739/// See [Object](https://docs.aws.amazon.com/AmazonS3/latest/API/API_Object.html) in the *Amazon S3
740/// API Reference* for more details.
741#[derive(Debug, Clone)]
742#[non_exhaustive]
743pub struct ObjectInfo {
744    /// Key for this object.
745    pub key: String,
746
747    /// Size of this object in bytes.
748    pub size: u64,
749
750    /// The time this object was last modified.
751    pub last_modified: OffsetDateTime,
752
753    /// Storage class for this object. Optional because head_object does not return
754    /// the storage class in its response for Standard objects. See examples in the [*Amazon S3 API
755    /// Reference*](https://docs.aws.amazon.com/AmazonS3/latest/API/API_HeadObject.html#API_HeadObject_Examples)
756    pub storage_class: Option<String>,
757
758    /// Objects in flexible retrieval storage classes (such as GLACIER and DEEP_ARCHIVE) are only
759    /// accessible after restoration
760    pub restore_status: Option<RestoreStatus>,
761
762    /// Entity tag of this object.
763    pub etag: String,
764
765    /// The algorithm that was used to create a checksum of the object.
766    ///
767    /// The [Amazon S3 API Reference] specifies this field as a list of strings,
768    /// so we return here a [Vec] of [ChecksumAlgorithm].
769    ///
770    /// [Amazon S3 API Reference]:
771    ///     https://docs.aws.amazon.com/AmazonS3/latest/API/API_Object.html#AmazonS3-Type-Object-ChecksumAlgorithm
772    pub checksum_algorithms: Vec<ChecksumAlgorithm>,
773}
774
775/// All possible object attributes that can be retrived from [ObjectClient::get_object_attributes].
776/// Fields that you do not specify are not returned.
777#[derive(Debug)]
778pub enum ObjectAttribute {
779    /// ETag of the object
780    ETag,
781
782    /// Checksum of the object
783    Checksum,
784
785    /// Object parts metadata for multi part object
786    ObjectParts,
787
788    /// Storage class of the object
789    StorageClass,
790
791    /// Object size
792    ObjectSize,
793}
794
795impl fmt::Display for ObjectAttribute {
796    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
797        let attr_name = match self {
798            ObjectAttribute::ETag => "ETag",
799            ObjectAttribute::Checksum => "Checksum",
800            ObjectAttribute::ObjectParts => "ObjectParts",
801            ObjectAttribute::StorageClass => "StorageClass",
802            ObjectAttribute::ObjectSize => "ObjectSize",
803        };
804        write!(f, "{}", attr_name)
805    }
806}
807
808/// Metadata about object checksum.
809///
810/// See [Checksum](https://docs.aws.amazon.com/AmazonS3/latest/API/API_Checksum.html) in the *Amazon
811/// S3 API Reference* for more details.
812#[derive(Clone, Debug, PartialEq, Eq)]
813pub struct Checksum {
814    /// Base64-encoded, 64-bit CRC64NVME checksum of the object
815    pub checksum_crc64nvme: Option<String>,
816
817    /// Base64-encoded, 32-bit CRC32 checksum of the object
818    pub checksum_crc32: Option<String>,
819
820    /// Base64-encoded, 32-bit CRC32C checksum of the object
821    pub checksum_crc32c: Option<String>,
822
823    /// Base64-encoded, 160-bit SHA-1 digest of the object
824    pub checksum_sha1: Option<String>,
825
826    /// Base64-encoded, 256-bit SHA-256 digest of the object
827    pub checksum_sha256: Option<String>,
828}
829
830impl Checksum {
831    /// Construct an empty [Checksum]
832    pub fn empty() -> Self {
833        Self {
834            checksum_crc64nvme: None,
835            checksum_crc32: None,
836            checksum_crc32c: None,
837            checksum_sha1: None,
838            checksum_sha256: None,
839        }
840    }
841
842    /// Provide [ChecksumAlgorithm]s for the [Checksum], if set and recognized.
843    pub fn algorithms(&self) -> Vec<ChecksumAlgorithm> {
844        // We assume that at most one checksum will be set.
845        let mut algorithms = Vec::with_capacity(1);
846
847        // Pattern match forces us to accomodate any new fields when added.
848        let Self {
849            checksum_crc64nvme,
850            checksum_crc32,
851            checksum_crc32c,
852            checksum_sha1,
853            checksum_sha256,
854        } = &self;
855
856        if checksum_crc64nvme.is_some() {
857            algorithms.push(ChecksumAlgorithm::Crc64nvme);
858        }
859        if checksum_crc32.is_some() {
860            algorithms.push(ChecksumAlgorithm::Crc32);
861        }
862        if checksum_crc32c.is_some() {
863            algorithms.push(ChecksumAlgorithm::Crc32c);
864        }
865        if checksum_sha1.is_some() {
866            algorithms.push(ChecksumAlgorithm::Sha1);
867        }
868        if checksum_sha256.is_some() {
869            algorithms.push(ChecksumAlgorithm::Sha256);
870        }
871
872        algorithms
873    }
874}
875
876impl From<Option<UploadChecksum>> for Checksum {
877    fn from(value: Option<UploadChecksum>) -> Self {
878        let mut checksum = Checksum::empty();
879        match value.as_ref() {
880            Some(UploadChecksum::Crc64nvme(crc64)) => checksum.checksum_crc64nvme = Some(crc64nvme_to_base64(crc64)),
881            Some(UploadChecksum::Crc32c(crc32c)) => checksum.checksum_crc32c = Some(crc32c_to_base64(crc32c)),
882            Some(UploadChecksum::Crc32(crc32)) => checksum.checksum_crc32 = Some(crc32_to_base64(crc32)),
883            Some(UploadChecksum::Sha1(sha1)) => checksum.checksum_sha1 = Some(sha1_to_base64(sha1)),
884            Some(UploadChecksum::Sha256(sha256)) => checksum.checksum_sha256 = Some(sha256_to_base64(sha256)),
885            None => {}
886        };
887        checksum
888    }
889}
890
891/// Metadata about object parts from GetObjectAttributes API.
892///
893/// See [GetObjectAttributesParts](https://docs.aws.amazon.com/AmazonS3/latest/API/API_GetObjectAttributesParts.html)
894/// in the *Amazon S3 API Reference* for more details.
895#[derive(Debug)]
896pub struct GetObjectAttributesParts {
897    /// Indicates whether the returned list of parts is truncated
898    pub is_truncated: Option<bool>,
899
900    /// Maximum number of parts allowed in the response
901    pub max_parts: Option<usize>,
902
903    /// When a list is truncated, this element specifies the next marker
904    pub next_part_number_marker: Option<usize>,
905
906    /// The marker for the current part
907    pub part_number_marker: Option<usize>,
908
909    /// Array of metadata for particular parts
910    pub parts: Option<Vec<ObjectPart>>,
911
912    /// Total number of parts
913    pub total_parts_count: Option<usize>,
914}
915
916/// Metadata for an individual object part.
917///
918/// See [ObjectPart](https://docs.aws.amazon.com/AmazonS3/latest/API/API_ObjectPart.html) in the
919/// *Amazon S3 API Reference* for more details.
920#[derive(Debug)]
921pub struct ObjectPart {
922    /// Checksum of the object
923    pub checksum: Option<Checksum>,
924
925    /// Number of the part, this value is a positive integer between 1 and 10,000
926    pub part_number: usize,
927
928    /// Size of the part in bytes
929    pub size: usize,
930}
931
932#[cfg(test)]
933mod tests {
934    use super::*;
935
936    #[test]
937    fn test_checksum_algorithm_one_set() {
938        let checksum = Checksum {
939            checksum_crc64nvme: None,
940            checksum_crc32: None,
941            checksum_crc32c: None,
942            checksum_sha1: Some("checksum_sha1".to_string()),
943            checksum_sha256: None,
944        };
945        assert_eq!(checksum.algorithms(), vec![ChecksumAlgorithm::Sha1]);
946    }
947
948    #[test]
949    fn test_checksum_algorithm_none_set() {
950        let checksum = Checksum {
951            checksum_crc64nvme: None,
952            checksum_crc32: None,
953            checksum_crc32c: None,
954            checksum_sha1: None,
955            checksum_sha256: None,
956        };
957        assert_eq!(checksum.algorithms(), vec![]);
958    }
959
960    #[test]
961    fn test_checksum_algorithm_many_set() {
962        // Amazon S3 doesn't support more than one algorithm today, but just in case... let's show we don't panic.
963        let checksum = Checksum {
964            checksum_crc64nvme: None,
965            checksum_crc32: None,
966            checksum_crc32c: Some("checksum_crc32c".to_string()),
967            checksum_sha1: Some("checksum_sha1".to_string()),
968            checksum_sha256: None,
969        };
970        assert_eq!(
971            checksum.algorithms(),
972            vec![ChecksumAlgorithm::Crc32c, ChecksumAlgorithm::Sha1],
973        );
974    }
975}