modo_upload/storage/types.rs
1use crate::file::UploadedFile;
2use crate::stream::BufferedUpload;
3use std::future::Future;
4use std::pin::Pin;
5
6/// Metadata returned after a file has been successfully stored.
7pub struct StoredFile {
8 /// Relative path within the storage backend (e.g. `"avatars/01HXK3Q1A2B3.jpg"`).
9 pub path: String,
10 /// File size in bytes.
11 pub size: u64,
12}
13
14/// Trait for persisting uploaded files to a storage backend.
15///
16/// Both in-memory ([`UploadedFile`]) and chunked ([`BufferedUpload`]) uploads
17/// are supported. Implementors must be `Sync + 'static` so they can be
18/// shared across async tasks behind an `Arc<dyn FileStorageDyn>`.
19///
20/// Use the [`storage()`](crate::storage()) factory function to construct the
21/// backend configured by [`UploadConfig`](crate::UploadConfig), or instantiate
22/// a concrete backend directly (e.g. [`LocalStorage`](super::local::LocalStorage)).
23///
24/// Use [`FileStorageDyn`] (object-safe companion) for trait objects:
25/// `Arc<dyn FileStorageDyn>`. Any type implementing `FileStorage`
26/// automatically implements `FileStorageDyn` via a blanket impl.
27#[trait_variant::make(FileStorageSend: Send)]
28pub trait FileStorage: Sync + 'static {
29 /// Store a buffered in-memory file under `prefix/`.
30 ///
31 /// A ULID-based unique filename is generated automatically.
32 /// Returns the stored path and size on success.
33 async fn store(&self, prefix: &str, file: &UploadedFile) -> Result<StoredFile, modo::Error>;
34
35 /// Store a chunked upload under `prefix/`.
36 ///
37 /// Chunks are consumed from `stream` sequentially.
38 /// Returns the stored path and size on success.
39 async fn store_stream(
40 &self,
41 prefix: &str,
42 stream: &mut BufferedUpload,
43 ) -> Result<StoredFile, modo::Error>;
44
45 /// Delete a file by its storage path (as returned by [`store`](Self::store)).
46 async fn delete(&self, path: &str) -> Result<(), modo::Error>;
47
48 /// Return `true` if a file exists at the given storage path.
49 async fn exists(&self, path: &str) -> Result<bool, modo::Error>;
50}
51
52/// Object-safe companion to [`FileStorage`] for use with `Arc<dyn FileStorageDyn>`.
53///
54/// This trait is automatically implemented for all types that implement
55/// [`FileStorage`] (or `FileStorageSend`).
56pub trait FileStorageDyn: Send + Sync + 'static {
57 /// Store a buffered in-memory file under `prefix/`.
58 ///
59 /// A ULID-based unique filename is generated automatically.
60 /// Returns the stored path and size on success.
61 fn store<'a>(
62 &'a self,
63 prefix: &'a str,
64 file: &'a UploadedFile,
65 ) -> Pin<Box<dyn Future<Output = Result<StoredFile, modo::Error>> + Send + 'a>>;
66
67 /// Store a chunked upload under `prefix/`.
68 ///
69 /// Chunks are consumed from `stream` sequentially.
70 /// Returns the stored path and size on success.
71 fn store_stream<'a>(
72 &'a self,
73 prefix: &'a str,
74 stream: &'a mut BufferedUpload,
75 ) -> Pin<Box<dyn Future<Output = Result<StoredFile, modo::Error>> + Send + 'a>>;
76
77 /// Delete a file by its storage path (as returned by [`store`](Self::store)).
78 fn delete<'a>(
79 &'a self,
80 path: &'a str,
81 ) -> Pin<Box<dyn Future<Output = Result<(), modo::Error>> + Send + 'a>>;
82
83 /// Return `true` if a file exists at the given storage path.
84 fn exists<'a>(
85 &'a self,
86 path: &'a str,
87 ) -> Pin<Box<dyn Future<Output = Result<bool, modo::Error>> + Send + 'a>>;
88}
89
90impl<T: FileStorageSend> FileStorageDyn for T {
91 fn store<'a>(
92 &'a self,
93 prefix: &'a str,
94 file: &'a UploadedFile,
95 ) -> Pin<Box<dyn Future<Output = Result<StoredFile, modo::Error>> + Send + 'a>> {
96 Box::pin(FileStorageSend::store(self, prefix, file))
97 }
98
99 fn store_stream<'a>(
100 &'a self,
101 prefix: &'a str,
102 stream: &'a mut BufferedUpload,
103 ) -> Pin<Box<dyn Future<Output = Result<StoredFile, modo::Error>> + Send + 'a>> {
104 Box::pin(FileStorageSend::store_stream(self, prefix, stream))
105 }
106
107 fn delete<'a>(
108 &'a self,
109 path: &'a str,
110 ) -> Pin<Box<dyn Future<Output = Result<(), modo::Error>> + Send + 'a>> {
111 Box::pin(FileStorageSend::delete(self, path))
112 }
113
114 fn exists<'a>(
115 &'a self,
116 path: &'a str,
117 ) -> Pin<Box<dyn Future<Output = Result<bool, modo::Error>> + Send + 'a>> {
118 Box::pin(FileStorageSend::exists(self, path))
119 }
120}