Skip to main content

s3_wire/operation/multipart/
part.rs

1use std::num::NonZeroU16;
2
3use http::HeaderMap;
4
5use super::{MultipartError, UploadId};
6use crate::operation::{Checksum, Conditions, CopySource, ObjectKey, RequestIds};
7use crate::stream::ByteStream;
8
9/// A validated multipart part number in the range 1 through 10,000.
10#[derive(Clone, Copy, Debug, Eq, Ord, PartialEq, PartialOrd)]
11pub struct PartNumber(NonZeroU16);
12
13impl PartNumber {
14    /// Highest part number accepted by S3.
15    pub const MAX: u16 = 10_000;
16
17    /// Constructs a part number in S3's supported range.
18    pub fn new(value: u16) -> Option<Self> {
19        NonZeroU16::new(value)
20            .filter(|value| value.get() <= Self::MAX)
21            .map(Self)
22    }
23
24    /// Returns the validated number.
25    pub const fn get(self) -> u16 {
26        self.0.get()
27    }
28}
29
30/// Request to upload one multipart part.
31#[derive(derive_more::Debug)]
32pub struct UploadPartRequest {
33    key: ObjectKey,
34    upload_id: UploadId,
35    part_number: PartNumber,
36    #[debug("{:?}", "<stream>")]
37    body: ByteStream,
38    checksum: Checksum,
39    /// Additional request headers. Values are signed and repeated values are preserved.
40    /// Generated-name collisions are errors; signing- and transport-owned headers are rejected.
41    #[debug("{:?}", "<redacted>")]
42    pub headers: HeaderMap,
43}
44
45impl UploadPartRequest {
46    /// Constructs an upload-part request from validated identifiers.
47    pub fn new(
48        key: ObjectKey,
49        upload_id: UploadId,
50        part_number: PartNumber,
51        body: ByteStream,
52    ) -> Self {
53        Self {
54            key,
55            upload_id,
56            part_number,
57            body,
58            checksum: Checksum::default(),
59            headers: HeaderMap::new(),
60        }
61    }
62
63    /// Replaces the request's additional headers.
64    pub fn with_headers(mut self, headers: HeaderMap) -> Self {
65        self.headers = headers;
66        self
67    }
68
69    /// Returns the destination object key.
70    pub const fn key(&self) -> &ObjectKey {
71        &self.key
72    }
73
74    /// Returns the validated multipart upload identifier.
75    pub const fn upload_id(&self) -> &UploadId {
76        &self.upload_id
77    }
78
79    /// Returns the validated part number.
80    pub const fn part_number(&self) -> PartNumber {
81        self.part_number
82    }
83
84    /// Returns the part body.
85    pub const fn body(&self) -> &ByteStream {
86        &self.body
87    }
88
89    /// Returns the part body mutably.
90    pub fn body_mut(&mut self) -> &mut ByteStream {
91        &mut self.body
92    }
93
94    /// Returns the optional checksum of the part.
95    pub const fn checksum(&self) -> &Checksum {
96        &self.checksum
97    }
98
99    /// Attaches a checksum to the request.
100    pub fn with_checksum(mut self, checksum: Checksum) -> Self {
101        self.checksum = checksum;
102        self
103    }
104
105    /// Consumes the request and returns its body.
106    pub fn into_body(self) -> ByteStream {
107        self.body
108    }
109}
110
111/// Result of uploading one multipart part.
112#[derive(Clone, Debug, Eq, PartialEq)]
113pub struct UploadPartOutput {
114    /// Part number supplied by the caller.
115    pub part_number: PartNumber,
116    /// Entity tag required when completing the upload.
117    pub e_tag: String,
118    /// Checksums returned by the service.
119    pub checksum: Checksum,
120    /// Service request identifiers.
121    pub request_ids: RequestIds,
122}
123
124/// Inclusive source byte range for one server-side copied part.
125#[derive(Clone, Copy, Debug, Eq, PartialEq)]
126pub struct CopyPartRange {
127    start: u64,
128    end: u64,
129}
130
131impl CopyPartRange {
132    /// Constructs an inclusive range, rejecting an end before its start.
133    pub fn new(start: u64, end: u64) -> Result<Self, crate::operation::RangeError> {
134        if end < start {
135            return Err(crate::operation::RangeError { start, end });
136        }
137        Ok(Self { start, end })
138    }
139
140    /// Returns the first copied byte offset.
141    pub const fn start(self) -> u64 {
142        self.start
143    }
144
145    /// Returns the final copied byte offset, inclusively.
146    pub const fn end(self) -> u64 {
147        self.end
148    }
149
150    pub(crate) fn header_value(self) -> String {
151        format!("bytes={}-{}", self.start, self.end)
152    }
153}
154
155/// Request to populate a multipart part from an existing S3 object.
156#[derive(Clone, derive_more::Debug, Eq, PartialEq)]
157pub struct UploadPartCopyRequest {
158    destination: ObjectKey,
159    upload_id: UploadId,
160    part_number: PartNumber,
161    /// Object copied into this part.
162    pub source: CopySource,
163    /// Optional inclusive range within the source object.
164    pub source_range: Option<CopyPartRange>,
165    /// Preconditions evaluated against the source object.
166    pub source_conditions: Conditions,
167    /// Additional request headers. Values are signed and repeated values are preserved.
168    /// Generated-name collisions are errors; signing- and transport-owned headers are rejected.
169    #[debug("{:?}", "<redacted>")]
170    pub headers: HeaderMap,
171}
172
173impl UploadPartCopyRequest {
174    /// Constructs a full-source copy request for one multipart part.
175    pub fn new(
176        destination: ObjectKey,
177        upload_id: UploadId,
178        part_number: PartNumber,
179        source: CopySource,
180    ) -> Self {
181        Self {
182            destination,
183            upload_id,
184            part_number,
185            source,
186            source_range: None,
187            source_conditions: Conditions::default(),
188            headers: HeaderMap::new(),
189        }
190    }
191
192    /// Replaces the request's additional headers.
193    pub fn with_headers(mut self, headers: HeaderMap) -> Self {
194        self.headers = headers;
195        self
196    }
197
198    /// Returns the destination object key.
199    pub const fn destination(&self) -> &ObjectKey {
200        &self.destination
201    }
202
203    /// Returns the multipart upload identifier.
204    pub const fn upload_id(&self) -> &UploadId {
205        &self.upload_id
206    }
207
208    /// Returns the destination part number.
209    pub const fn part_number(&self) -> PartNumber {
210        self.part_number
211    }
212}
213
214/// Result of copying an existing object or range into one multipart part.
215#[derive(Clone, Debug, Eq, PartialEq)]
216pub struct UploadPartCopyOutput {
217    /// Destination part number supplied by the caller.
218    pub part_number: PartNumber,
219    /// Entity tag required when completing the upload.
220    pub e_tag: String,
221    /// Modification time reported for the copied part.
222    pub last_modified: Option<time::OffsetDateTime>,
223    /// Checksums returned for the copied part.
224    pub checksum: Checksum,
225    /// Service request identifiers.
226    pub request_ids: RequestIds,
227}
228
229impl UploadPartCopyOutput {
230    /// Converts the successful result into a completion descriptor.
231    pub fn completed_part(&self) -> Result<CompletedPart, MultipartError> {
232        CompletedPart::new(self.part_number.get(), self.e_tag.clone())
233            .map(|part| part.with_checksum(self.checksum.clone()))
234    }
235}
236
237/// A validated completed-part descriptor.
238#[derive(Clone, Debug, Eq, PartialEq)]
239pub struct CompletedPart {
240    part_number: PartNumber,
241    e_tag: String,
242    checksum: Checksum,
243}
244
245impl CompletedPart {
246    /// Validates a part number and non-empty entity tag.
247    pub fn new(part_number: u16, e_tag: impl Into<String>) -> Result<Self, MultipartError> {
248        let part_number =
249            PartNumber::new(part_number).ok_or(MultipartError::InvalidPartNumber(part_number))?;
250        let e_tag = e_tag.into();
251        if e_tag.trim().is_empty() {
252            return Err(MultipartError::EmptyETag {
253                part_number: part_number.get(),
254            });
255        }
256        Ok(Self {
257            part_number,
258            e_tag,
259            checksum: Checksum::default(),
260        })
261    }
262
263    /// Returns the validated part number.
264    pub const fn part_number(&self) -> PartNumber {
265        self.part_number
266    }
267
268    /// Returns the non-empty entity tag.
269    pub fn e_tag(&self) -> &str {
270        &self.e_tag
271    }
272
273    /// Returns checksums associated with this part.
274    pub const fn checksum(&self) -> &Checksum {
275        &self.checksum
276    }
277
278    /// Attaches checksums returned by the part upload.
279    pub fn with_checksum(mut self, checksum: Checksum) -> Self {
280        self.checksum = checksum;
281        self
282    }
283}