Skip to main content

s3_wire/operation/multipart/
part.rs

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