aws_multipart_upload/
uri.rs

1//! `ObjectUri` iterators.
2//!
3//! This module provides types that can help in building iterators of URIs for
4//! a multipart upload type with [`ObjectUriIter`].
5//!
6//! The only thing required to create a new upload is the URI of the object to be
7//! uploaded, so given an iterator of `ObjectUri`s, this defines a sequence of
8//! multipart uploads that can be created as the previous one is completed by
9//! calling `next` on the iterator.  [`OneTimeUse`], an iterator that only
10//! produces one `ObjectUri`, is capable of serving a single multipart upload.
11//!
12//! # Example
13//!
14//! This is an iterator of `ObjectUri`s that writes to a prefix based on the
15//! current date and time.
16//!
17//! ```rust
18//! use aws_multipart_upload::uri::{KeyPrefix, ObjectUriIter, ObjectUriIterExt};
19//!
20//! const BUCKET: &str = "my-bucket";
21//! const PREFIX: &str = "static/object/prefix";
22//!
23//! let iter_pfx = std::iter::repeat_with(|| KeyPrefix::from(PREFIX));
24//! let iter = iter_pfx.map_key(BUCKET, |prefix| {
25//!     let now = chrono::Utc::now();
26//!     let now_str = now.format("%Y/%m/%d/%H").to_string();
27//!     let us = now.timestamp_micros();
28//!     let root = format!("{now_str}/{us}.csv");
29//!     prefix.to_key(&root)
30//! });
31//!
32//! let mut uri = ObjectUriIter::uri_iter(iter);
33//! let new_uri = uri.new_uri().unwrap();
34//!
35//! println!("{new_uri}");
36//! // "s3://my-bucket/static/object/prefix/2025/11/11/11/01/1763683634194850.csv"
37//! ```
38//! [`ObjectUriIter`]: super::ObjectUriIter
39use crate::client::UploadClient;
40use crate::client::request::{CreateRequest, SendCreateUpload};
41
42use std::borrow::Cow;
43use std::fmt::{self, Formatter};
44use std::ops::Deref;
45
46/// The address of an uploaded object in S3.
47#[derive(Debug, Clone, Default, PartialEq, Eq, Hash)]
48pub struct ObjectUri {
49    /// The S3 bucket for the object.
50    ///
51    /// This should be the plain bucket name, e.g., "my-s3-bucket".
52    pub bucket: Bucket,
53    /// The full key of this object within the bucket.
54    pub key: Key,
55}
56
57impl ObjectUri {
58    /// Create a new `ObjectUri` from bucket and object key.
59    pub fn new(bucket: Bucket, key: Key) -> Self {
60        Self { bucket, key }
61    }
62
63    pub(crate) fn is_empty(&self) -> bool {
64        self.bucket.is_empty() || self.key.is_empty()
65    }
66}
67
68impl fmt::Display for ObjectUri {
69    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
70        write!(f, "s3://{}/{}", &self.bucket, &self.key)
71    }
72}
73
74impl<T: Into<Bucket>, U: Into<Key>> From<(T, U)> for ObjectUri {
75    fn from((b, k): (T, U)) -> Self {
76        ObjectUri::new(b.into(), k.into())
77    }
78}
79
80/// The destination bucket for this upload when it is complete.
81#[derive(Debug, Clone, Default, PartialEq, Eq, Hash)]
82pub struct Bucket(Cow<'static, str>);
83
84impl Bucket {
85    /// Create a new `Bucket`.
86    pub fn new<T: Into<Cow<'static, str>>>(bucket: T) -> Self {
87        let bucket: Cow<'static, str> = bucket.into();
88        match bucket.strip_suffix("/") {
89            Some(v) => v.into(),
90            _ => Self(bucket),
91        }
92    }
93
94    pub(crate) fn is_empty(&self) -> bool {
95        self.0.is_empty()
96    }
97}
98
99impl Deref for Bucket {
100    type Target = str;
101
102    fn deref(&self) -> &str {
103        &self.0
104    }
105}
106
107impl AsRef<str> for Bucket {
108    fn as_ref(&self) -> &str {
109        self
110    }
111}
112
113impl fmt::Display for Bucket {
114    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
115        self.0.fmt(f)
116    }
117}
118
119impl From<&str> for Bucket {
120    fn from(value: &str) -> Self {
121        Self::new(value.to_string())
122    }
123}
124
125impl From<String> for Bucket {
126    fn from(value: String) -> Self {
127        Self(Cow::Owned(value))
128    }
129}
130
131/// The key within the associated bucket for this object.
132#[derive(Debug, Clone, Default, PartialEq, Eq, Hash)]
133pub struct Key(Cow<'static, str>);
134
135impl Key {
136    /// Create a new object `Key`.
137    pub fn new<T: Into<Cow<'static, str>>>(key: T) -> Self {
138        Self(key.into())
139    }
140
141    pub(crate) fn is_empty(&self) -> bool {
142        self.0.is_empty()
143    }
144}
145
146impl Deref for Key {
147    type Target = str;
148
149    fn deref(&self) -> &str {
150        &self.0
151    }
152}
153
154impl AsRef<str> for Key {
155    fn as_ref(&self) -> &str {
156        self
157    }
158}
159
160impl fmt::Display for Key {
161    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
162        self.0.fmt(f)
163    }
164}
165
166impl From<&str> for Key {
167    fn from(value: &str) -> Self {
168        Self::new(value.to_string())
169    }
170}
171
172impl From<String> for Key {
173    fn from(value: String) -> Self {
174        Self(Cow::Owned(value))
175    }
176}
177
178/// A prefix of S3 object keys.
179#[derive(Debug, Clone, Default, PartialEq, Eq, Hash)]
180pub struct KeyPrefix(Cow<'static, str>);
181
182impl KeyPrefix {
183    /// Create a new object key prefix.
184    ///
185    /// Normalized to end with a single `'/'` and have no leading `'/'`.
186    pub fn new<T: Into<Cow<'static, str>>>(prefix: T) -> Self {
187        let raw: Cow<'static, str> = prefix.into();
188        let trimmed = raw.trim_matches('/');
189        Self(format!("{trimmed}/").into())
190    }
191
192    /// Extend this prefix by another.
193    pub fn append(&self, other: &KeyPrefix) -> Self {
194        format!("{self}{other}").into()
195    }
196
197    /// Create an object [`Key`] with this prefix and the given suffix.
198    pub fn to_key(&self, suffix: &str) -> Key {
199        format!("{self}{suffix}").into()
200    }
201}
202
203impl Deref for KeyPrefix {
204    type Target = str;
205
206    fn deref(&self) -> &str {
207        &self.0
208    }
209}
210
211impl AsRef<str> for KeyPrefix {
212    fn as_ref(&self) -> &str {
213        self
214    }
215}
216
217impl fmt::Display for KeyPrefix {
218    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
219        self.0.fmt(f)
220    }
221}
222
223impl From<&str> for KeyPrefix {
224    fn from(value: &str) -> Self {
225        Self::new(value.to_string())
226    }
227}
228
229impl From<String> for KeyPrefix {
230    fn from(value: String) -> Self {
231        Self::new(value)
232    }
233}
234
235/// Produce an `ObjectUri` for a new upload from an iterator.
236pub struct ObjectUriIter {
237    inner: Box<dyn Iterator<Item = ObjectUri>>,
238}
239
240impl ObjectUriIter {
241    /// Create a new `ObjectUriIter` from an arbitrary iterator of `ObjectUri`.
242    pub fn new<I>(iter: I) -> Self
243    where
244        I: IntoIterator<Item = ObjectUri> + 'static,
245    {
246        Self {
247            inner: Box::new(iter.into_iter()),
248        }
249    }
250
251    /// Construct the request future to create a new multipart upload using the
252    /// next `ObjectUri` produced by this `ObjectUriIter` value.
253    pub fn next_upload(&mut self, client: &UploadClient) -> Option<SendCreateUpload> {
254        let uri = self.next()?;
255        let req = CreateRequest::new(uri);
256        let fut = SendCreateUpload::new(client, req);
257        Some(fut)
258    }
259}
260
261impl Default for ObjectUriIter {
262    fn default() -> Self {
263        Self::new(EmptyUri)
264    }
265}
266
267impl Iterator for ObjectUriIter {
268    type Item = ObjectUri;
269    fn next(&mut self) -> Option<Self::Item> {
270        self.inner.next()
271    }
272}
273
274impl fmt::Debug for ObjectUriIter {
275    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
276        f.debug_struct("ObjectUriIter")
277            .field("inner", &"Iterator<Item = ObjectUri>")
278            .finish()
279    }
280}
281
282/// Adds the method `map_key` to iterators over `KeyPrefix`.
283pub trait ObjectUriIterExt: Iterator {
284    /// Returns an iterator of `ObjectUri` by applying the function `F` to each
285    /// `KeyPrefix` to produce the object `Key`.
286    fn map_key<B, F>(self, bucket: B, f: F) -> MapKey<Self, F>
287    where
288        Self: Iterator<Item = KeyPrefix> + Sized,
289        F: FnMut(KeyPrefix) -> Key,
290        B: Into<Bucket>,
291    {
292        MapKey::new(self, bucket, f)
293    }
294}
295
296impl<I: Iterator> ObjectUriIterExt for I {}
297
298/// Iterator for [`map_key`](ObjectUriIterExt::map_key).
299pub struct MapKey<I, F> {
300    bucket: Bucket,
301    inner: I,
302    f: F,
303}
304
305impl<I, F> MapKey<I, F> {
306    fn new<B: Into<Bucket>>(inner: I, bucket: B, f: F) -> Self {
307        Self {
308            inner,
309            bucket: bucket.into(),
310            f,
311        }
312    }
313}
314
315impl<I, F> Iterator for MapKey<I, F>
316where
317    I: Iterator<Item = KeyPrefix>,
318    F: FnMut(KeyPrefix) -> Key,
319{
320    type Item = ObjectUri;
321
322    fn next(&mut self) -> Option<Self::Item> {
323        let prefix = self.inner.next()?;
324        let key = (self.f)(prefix);
325        let uri = ObjectUri::new(self.bucket.clone(), key);
326        Some(uri)
327    }
328}
329
330/// An empty iterator of `ObjectUri`s.
331#[derive(Debug, Clone, Copy, Default)]
332pub struct EmptyUri;
333impl IntoIterator for EmptyUri {
334    type IntoIter = std::iter::Empty<ObjectUri>;
335    type Item = ObjectUri;
336
337    fn into_iter(self) -> Self::IntoIter {
338        std::iter::empty()
339    }
340}
341
342/// Iterator that is exhausted after one `ObjectUri`.
343#[derive(Debug, Clone, Default)]
344pub struct OneTimeUse(Option<ObjectUri>);
345
346impl OneTimeUse {
347    /// Use the given `uri` as the one produced.
348    pub fn new(uri: ObjectUri) -> Self {
349        Self(Some(uri))
350    }
351}
352
353impl Iterator for OneTimeUse {
354    type Item = ObjectUri;
355    fn next(&mut self) -> Option<Self::Item> {
356        self.0.take()
357    }
358}