Skip to main content

s3_wire/operation/multipart/
completion.rs

1use super::{CompletedPart, PartNumber, UploadId};
2use crate::operation::{Checksum, ObjectKey, RequestIds};
3
4/// Request to complete a multipart upload.
5#[derive(Clone, Debug, Eq, PartialEq)]
6pub struct CompleteMultipartUploadRequest {
7    /// Destination object key.
8    pub key: ObjectKey,
9    upload_id: UploadId,
10    completed_parts: Vec<CompletedPart>,
11}
12
13impl CompleteMultipartUploadRequest {
14    /// Validates that the upload ID is present and parts are non-empty, unique,
15    /// and strictly ascending.
16    pub fn new(
17        key: ObjectKey,
18        upload_id: UploadId,
19        completed_parts: Vec<CompletedPart>,
20    ) -> Result<Self, MultipartError> {
21        validate_completed_parts(&completed_parts)?;
22        Ok(Self {
23            key,
24            upload_id,
25            completed_parts,
26        })
27    }
28
29    /// Returns the validated, ordered completed parts.
30    pub fn completed_parts(&self) -> &[CompletedPart] {
31        &self.completed_parts
32    }
33
34    /// Returns the validated, non-empty upload identifier.
35    pub const fn upload_id(&self) -> &UploadId {
36        &self.upload_id
37    }
38}
39
40/// Result of completing a multipart upload.
41#[derive(Clone, Debug, Default, Eq, PartialEq)]
42pub struct CompleteMultipartUploadOutput {
43    /// Service-provided object location. This is not exposed as a signed URL.
44    pub location: Option<String>,
45    /// Bucket reported by the service.
46    pub bucket: Option<String>,
47    /// Completed object key.
48    pub key: Option<ObjectKey>,
49    /// Entity tag of the completed object.
50    pub e_tag: Option<String>,
51    /// Version identifier, when enabled.
52    pub version_id: Option<String>,
53    /// Checksums returned by the service.
54    pub checksum: Checksum,
55    /// Service request identifiers.
56    pub request_ids: RequestIds,
57}
58
59/// Request to abort an in-progress multipart upload.
60#[derive(Clone, Debug, Eq, PartialEq)]
61pub struct AbortMultipartUploadRequest {
62    key: ObjectKey,
63    upload_id: UploadId,
64}
65
66impl AbortMultipartUploadRequest {
67    /// Constructs an abort request from a validated upload identifier.
68    pub fn new(key: ObjectKey, upload_id: UploadId) -> Self {
69        Self { key, upload_id }
70    }
71
72    /// Returns the destination object key.
73    pub const fn key(&self) -> &ObjectKey {
74        &self.key
75    }
76
77    /// Returns the validated multipart upload identifier.
78    pub const fn upload_id(&self) -> &UploadId {
79        &self.upload_id
80    }
81}
82
83/// Invalid multipart state or descriptor.
84#[derive(Clone, Debug, Eq, PartialEq, thiserror::Error)]
85pub enum MultipartError {
86    /// A part number is outside S3's 1 through 10,000 range.
87    #[error("multipart part number {0} is outside 1..=10000")]
88    InvalidPartNumber(u16),
89    /// An uploaded part has no usable entity tag.
90    #[error("multipart part {part_number} has an empty ETag")]
91    EmptyETag {
92        /// Part with the invalid entity tag.
93        part_number: u16,
94    },
95    /// A completed part number was recorded more than once.
96    #[error("multipart part {0:?} was recorded more than once")]
97    DuplicatePart(PartNumber),
98    /// Completion was attempted without any completed parts.
99    #[error("a multipart upload cannot be completed without parts")]
100    NoCompletedParts,
101    /// Completed parts are duplicated or out of ascending order.
102    #[error("multipart part {next:?} does not follow {previous:?} in ascending order")]
103    OutOfOrder {
104        /// Last accepted part number.
105        previous: PartNumber,
106        /// Rejected part number.
107        next: PartNumber,
108    },
109}
110
111fn validate_completed_parts(parts: &[CompletedPart]) -> Result<(), MultipartError> {
112    if parts.is_empty() {
113        return Err(MultipartError::NoCompletedParts);
114    }
115    for window in parts.windows(2) {
116        if window[1].part_number() <= window[0].part_number() {
117            return Err(MultipartError::OutOfOrder {
118                previous: window[0].part_number(),
119                next: window[1].part_number(),
120            });
121        }
122    }
123    Ok(())
124}