pub trait Storage: Send + Sync {
// Required methods
fn store<'life0, 'life1, 'life2, 'life3, 'async_trait>(
&'life0 self,
filename: &'life1 str,
content_type: &'life2 str,
bytes: &'life3 [u8],
) -> Pin<Box<dyn Future<Output = Result<StoredFile, StorageError>> + Send + 'async_trait>>
where Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait,
'life2: 'async_trait,
'life3: 'async_trait;
fn retrieve<'life0, 'life1, 'async_trait>(
&'life0 self,
key: &'life1 str,
) -> Pin<Box<dyn Future<Output = Result<Vec<u8>, StorageError>> + Send + 'async_trait>>
where Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait;
fn delete<'life0, 'life1, 'async_trait>(
&'life0 self,
key: &'life1 str,
) -> Pin<Box<dyn Future<Output = Result<(), StorageError>> + Send + 'async_trait>>
where Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait;
fn url(&self, key: &str) -> String;
// Provided methods
fn store_stream<'life0, 'life1, 'life2, 'async_trait>(
&'life0 self,
filename: &'life1 str,
content_type: &'life2 str,
body: ByteStream,
) -> Pin<Box<dyn Future<Output = Result<StoredFile, StorageError>> + Send + 'async_trait>>
where Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait,
'life2: 'async_trait { ... }
fn retrieve_stream<'life0, 'life1, 'async_trait>(
&'life0 self,
key: &'life1 str,
) -> Pin<Box<dyn Future<Output = Result<ByteStream, StorageError>> + Send + 'async_trait>>
where Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait { ... }
fn put<'life0, 'life1, 'life2, 'life3, 'async_trait>(
&'life0 self,
key: &'life1 str,
content_type: &'life2 str,
bytes: &'life3 [u8],
) -> Pin<Box<dyn Future<Output = Result<StoredFile, StorageError>> + Send + 'async_trait>>
where Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait,
'life2: 'async_trait,
'life3: 'async_trait { ... }
fn put_stream<'life0, 'life1, 'life2, 'async_trait>(
&'life0 self,
key: &'life1 str,
content_type: &'life2 str,
body: ByteStream,
) -> Pin<Box<dyn Future<Output = Result<StoredFile, StorageError>> + Send + 'async_trait>>
where Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait,
'life2: 'async_trait { ... }
fn exists<'life0, 'life1, 'async_trait>(
&'life0 self,
key: &'life1 str,
) -> Pin<Box<dyn Future<Output = Result<bool, StorageError>> + Send + 'async_trait>>
where Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait { ... }
}Expand description
A storage backend for file bytes.
Implementors persist opaque byte blobs under a generated key and
expose them at a public URL. The default impl ships in umbral-storage
(FsStorage, filesystem-backed); an S3 backend slots in behind the
same trait later (see docs/decisions/2026-06-02-media-and-s3.md).
Signed / auth-gated URLs are deliberately out of scope here: url
returns a public URL only. Private media is a deferred v0.x feature.
Required Methods§
Sourcefn store<'life0, 'life1, 'life2, 'life3, 'async_trait>(
&'life0 self,
filename: &'life1 str,
content_type: &'life2 str,
bytes: &'life3 [u8],
) -> Pin<Box<dyn Future<Output = Result<StoredFile, StorageError>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait,
'life2: 'async_trait,
'life3: 'async_trait,
fn store<'life0, 'life1, 'life2, 'life3, 'async_trait>(
&'life0 self,
filename: &'life1 str,
content_type: &'life2 str,
bytes: &'life3 [u8],
) -> Pin<Box<dyn Future<Output = Result<StoredFile, StorageError>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait,
'life2: 'async_trait,
'life3: 'async_trait,
Persist bytes under a freshly generated, collision-resistant key
derived from filename, returning the key plus its public URL.
content_type is the MIME type the caller declares; backends may
record it (e.g. for an S3 object’s Content-Type) but are not
required to validate it — the upload handler should validate
against an allow-list before calling this.
Sourcefn retrieve<'life0, 'life1, 'async_trait>(
&'life0 self,
key: &'life1 str,
) -> Pin<Box<dyn Future<Output = Result<Vec<u8>, StorageError>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait,
fn retrieve<'life0, 'life1, 'async_trait>(
&'life0 self,
key: &'life1 str,
) -> Pin<Box<dyn Future<Output = Result<Vec<u8>, StorageError>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait,
Read back the bytes stored under key.
Returns StorageError::NotFound if no object exists for key.
Sourcefn delete<'life0, 'life1, 'async_trait>(
&'life0 self,
key: &'life1 str,
) -> Pin<Box<dyn Future<Output = Result<(), StorageError>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait,
fn delete<'life0, 'life1, 'async_trait>(
&'life0 self,
key: &'life1 str,
) -> Pin<Box<dyn Future<Output = Result<(), StorageError>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait,
Remove the object stored under key. Idempotent at the backend’s
discretion; deleting a missing key may succeed or return
StorageError::NotFound.
Provided Methods§
Sourcefn store_stream<'life0, 'life1, 'life2, 'async_trait>(
&'life0 self,
filename: &'life1 str,
content_type: &'life2 str,
body: ByteStream,
) -> Pin<Box<dyn Future<Output = Result<StoredFile, StorageError>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait,
'life2: 'async_trait,
fn store_stream<'life0, 'life1, 'life2, 'async_trait>(
&'life0 self,
filename: &'life1 str,
content_type: &'life2 str,
body: ByteStream,
) -> Pin<Box<dyn Future<Output = Result<StoredFile, StorageError>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait,
'life2: 'async_trait,
Streaming counterpart of store: persist a
body byte-stream without buffering the whole payload in memory.
Additive, with a default impl — an existing backend that does
not override this still works, just buffered: the default collects
the stream into a Vec<u8> (propagating any mid-stream IO error)
and delegates to store. Override it to true-stream
to the backend (the filesystem impl writes chunk-by-chunk to disk).
Size enforcement is a decorator concern, not this method’s: wrap
body with cap_stream before calling so the cap is applied as
bytes flow, never trusting a declared Content-Length.
Sourcefn retrieve_stream<'life0, 'life1, 'async_trait>(
&'life0 self,
key: &'life1 str,
) -> Pin<Box<dyn Future<Output = Result<ByteStream, StorageError>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait,
fn retrieve_stream<'life0, 'life1, 'async_trait>(
&'life0 self,
key: &'life1 str,
) -> Pin<Box<dyn Future<Output = Result<ByteStream, StorageError>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait,
Streaming counterpart of retrieve: read the
object back as a byte-stream without holding the whole blob.
Additive, with a default impl — the default calls
retrieve and wraps the resulting Vec<u8>
as a single-chunk stream. Override it to true-stream from the
backend (the filesystem impl streams the file off disk).
Sourcefn put<'life0, 'life1, 'life2, 'life3, 'async_trait>(
&'life0 self,
key: &'life1 str,
content_type: &'life2 str,
bytes: &'life3 [u8],
) -> Pin<Box<dyn Future<Output = Result<StoredFile, StorageError>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait,
'life2: 'async_trait,
'life3: 'async_trait,
fn put<'life0, 'life1, 'life2, 'life3, 'async_trait>(
&'life0 self,
key: &'life1 str,
content_type: &'life2 str,
bytes: &'life3 [u8],
) -> Pin<Box<dyn Future<Output = Result<StoredFile, StorageError>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait,
'life2: 'async_trait,
'life3: 'async_trait,
Persist bytes at the exact key the caller supplies — the
deterministic-path sibling of store, which
generates a collision-resistant key. Static asset collection needs
this: a CSS file collected to css/app.css must land at that key,
not a uuid-app.css one.
Additive, with a default impl — but the default cannot
generically write-at-exact-key without backend knowledge (the
trait has no “write these bytes here” primitive beyond
store, which owns its own key). So the default
returns StorageError::Unsupported. Backends that can honour an
exact key (the filesystem backend, the future LocalStorage /
S3Storage) override it; media’s store stays
the key-generating path.
content_type is recorded by backends that track it (e.g. an S3
object’s Content-Type); the filesystem backend derives the
served type from the key’s extension instead.
Sourcefn put_stream<'life0, 'life1, 'life2, 'async_trait>(
&'life0 self,
key: &'life1 str,
content_type: &'life2 str,
body: ByteStream,
) -> Pin<Box<dyn Future<Output = Result<StoredFile, StorageError>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait,
'life2: 'async_trait,
fn put_stream<'life0, 'life1, 'life2, 'async_trait>(
&'life0 self,
key: &'life1 str,
content_type: &'life2 str,
body: ByteStream,
) -> Pin<Box<dyn Future<Output = Result<StoredFile, StorageError>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait,
'life2: 'async_trait,
Streaming counterpart of put: persist a body
byte-stream at the exact key without buffering the whole payload.
Additive, with a default impl that mirrors the
store_stream/store
relationship: it collects the stream into a Vec<u8> (propagating
any mid-stream IO error) and delegates to put, so
a backend that overrides put gets a working put_stream for
free. Override it to true-stream to the backend.
Sourcefn exists<'life0, 'life1, 'async_trait>(
&'life0 self,
key: &'life1 str,
) -> Pin<Box<dyn Future<Output = Result<bool, StorageError>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait,
fn exists<'life0, 'life1, 'async_trait>(
&'life0 self,
key: &'life1 str,
) -> Pin<Box<dyn Future<Output = Result<bool, StorageError>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait,
Does an object exist under key?
Additive, with a default impl — Ok(self.retrieve(key).await.is_ok()),
which works for any backend through retrieve.
Backends with a cheaper presence check (an S3 HEAD, a filesystem
metadata stat) override it to avoid reading the whole blob.
Dyn Compatibility§
This trait is dyn compatible.
In older versions of Rust, dyn compatibility was called "object safety".