Skip to main content

s3_wire/operation/multipart/
managed.rs

1use std::collections::BTreeMap;
2use std::fmt;
3use std::path::{Path, PathBuf};
4
5use bytes::Bytes;
6
7use super::{CompletedPart, MultipartError, UploadId};
8use crate::operation::{ChecksumAlgorithm, ObjectKey, RequestIds};
9
10/// Request to initiate a multipart upload.
11#[derive(Clone, Debug, Eq, PartialEq)]
12pub struct CreateMultipartUploadRequest {
13    /// Destination object key.
14    pub key: ObjectKey,
15    /// Optional object media type.
16    pub content_type: Option<String>,
17    /// User-defined object metadata.
18    pub user_metadata: BTreeMap<String, String>,
19    /// Checksum algorithm applied to uploaded parts.
20    pub checksum_algorithm: Option<ChecksumAlgorithm>,
21}
22
23impl CreateMultipartUploadRequest {
24    /// Constructs a request without optional metadata.
25    pub fn new(key: ObjectKey) -> Self {
26        Self {
27            key,
28            content_type: None,
29            user_metadata: BTreeMap::new(),
30            checksum_algorithm: None,
31        }
32    }
33}
34
35/// Result of initiating a multipart upload.
36#[derive(Clone, Debug, Eq, PartialEq)]
37pub struct CreateMultipartUploadOutput {
38    /// Bucket reported by the service.
39    pub bucket: Option<String>,
40    /// Object key reported by the service.
41    pub key: ObjectKey,
42    /// Opaque upload identifier required by later operations.
43    upload_id: UploadId,
44    /// Checksum algorithm selected by the service.
45    pub checksum_algorithm: Option<String>,
46    /// Service request identifiers.
47    pub request_ids: RequestIds,
48}
49
50impl CreateMultipartUploadOutput {
51    pub(crate) fn new(
52        bucket: Option<String>,
53        key: ObjectKey,
54        upload_id: UploadId,
55        checksum_algorithm: Option<String>,
56    ) -> Self {
57        Self {
58            bucket,
59            key,
60            upload_id,
61            checksum_algorithm,
62            request_ids: RequestIds::default(),
63        }
64    }
65
66    /// Returns the validated upload identifier required by later operations.
67    pub const fn upload_id(&self) -> &UploadId {
68        &self.upload_id
69    }
70}
71
72/// Explicit state needed to resume or clean up an in-progress multipart upload.
73#[derive(Clone, Debug, Eq, PartialEq)]
74pub struct MultipartUpload {
75    key: ObjectKey,
76    upload_id: UploadId,
77    /// Parts that have completed successfully, in ascending order.
78    completed_parts: Vec<CompletedPart>,
79}
80
81pub(crate) enum MultipartUploadSource {
82    Bytes(Bytes),
83    File(PathBuf),
84}
85
86/// Request for a bounded, automatically cleaned-up multipart upload.
87///
88/// In-memory sources retain the caller's complete byte buffer for the duration
89/// of the operation. File sources use only the configured in-flight part byte
90/// budget in memory; their immutable snapshot is disk-backed.
91pub struct ManagedMultipartUploadRequest {
92    pub(crate) key: ObjectKey,
93    pub(crate) source: MultipartUploadSource,
94    pub(crate) content_type: Option<String>,
95    pub(crate) user_metadata: BTreeMap<String, String>,
96}
97
98impl ManagedMultipartUploadRequest {
99    /// Constructs a request with a replayable in-memory body.
100    pub fn from_bytes(key: ObjectKey, bytes: impl Into<Bytes>) -> Self {
101        Self {
102            key,
103            source: MultipartUploadSource::Bytes(bytes.into()),
104            content_type: None,
105            user_metadata: BTreeMap::new(),
106        }
107    }
108
109    /// Constructs a request with a replayable regular-file body.
110    ///
111    /// The file is copied into a private disk-backed snapshot before S3 creates
112    /// the multipart upload, so later changes to the source path have no effect.
113    pub fn from_path(key: ObjectKey, path: impl AsRef<Path>) -> Self {
114        Self {
115            key,
116            source: MultipartUploadSource::File(path.as_ref().to_owned()),
117            content_type: None,
118            user_metadata: BTreeMap::new(),
119        }
120    }
121
122    /// Sets the object's media type.
123    pub fn with_content_type(mut self, content_type: impl Into<String>) -> Self {
124        self.content_type = Some(content_type.into());
125        self
126    }
127
128    /// Adds user-defined object metadata.
129    pub fn with_metadata(mut self, name: impl Into<String>, value: impl Into<String>) -> Self {
130        self.user_metadata.insert(name.into(), value.into());
131        self
132    }
133
134    /// Returns the destination object key.
135    pub const fn key(&self) -> &ObjectKey {
136        &self.key
137    }
138}
139
140impl fmt::Debug for ManagedMultipartUploadRequest {
141    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
142        formatter
143            .debug_struct("ManagedMultipartUploadRequest")
144            .field("key", &self.key)
145            .field(
146                "source",
147                &match self.source {
148                    MultipartUploadSource::Bytes(_) => "bytes",
149                    MultipartUploadSource::File(_) => "file",
150                },
151            )
152            .field("content_type", &self.content_type)
153            .field(
154                "user_metadata_names",
155                &self.user_metadata.keys().collect::<Vec<_>>(),
156            )
157            .finish()
158    }
159}
160
161impl MultipartUpload {
162    /// Starts tracking a newly created upload.
163    pub fn new(key: ObjectKey, upload_id: UploadId) -> Self {
164        Self {
165            key,
166            upload_id,
167            completed_parts: Vec::new(),
168        }
169    }
170
171    /// Records a completed part while retaining canonical ascending order.
172    pub fn record_part(&mut self, part: CompletedPart) -> Result<(), MultipartError> {
173        match self
174            .completed_parts
175            .binary_search_by_key(&part.part_number(), CompletedPart::part_number)
176        {
177            Ok(_) => Err(MultipartError::DuplicatePart(part.part_number())),
178            Err(index) => {
179                self.completed_parts.insert(index, part);
180                Ok(())
181            }
182        }
183    }
184
185    /// Returns completed parts in validated ascending order.
186    pub fn completed_parts(&self) -> &[CompletedPart] {
187        &self.completed_parts
188    }
189
190    /// Returns the destination object key.
191    pub const fn key(&self) -> &ObjectKey {
192        &self.key
193    }
194
195    /// Returns the non-empty service-issued upload identifier.
196    pub const fn upload_id(&self) -> &UploadId {
197        &self.upload_id
198    }
199}