pub trait FileStorage: Send + Sync {
// Required methods
fn store<'life0, 'async_trait>(
&'life0 self,
file: UploadedFile,
) -> Pin<Box<dyn Future<Output = StorageResult<StoredFile>> + Send + 'async_trait>>
where Self: 'async_trait,
'life0: 'async_trait;
fn retrieve<'life0, 'life1, 'async_trait>(
&'life0 self,
id: &'life1 str,
) -> Pin<Box<dyn Future<Output = StorageResult<Vec<u8>>> + Send + 'async_trait>>
where Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait;
fn delete<'life0, 'life1, 'async_trait>(
&'life0 self,
id: &'life1 str,
) -> Pin<Box<dyn Future<Output = StorageResult<()>> + Send + 'async_trait>>
where Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait;
fn url<'life0, 'life1, 'async_trait>(
&'life0 self,
id: &'life1 str,
) -> Pin<Box<dyn Future<Output = StorageResult<String>> + Send + 'async_trait>>
where Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait;
fn exists<'life0, 'life1, 'async_trait>(
&'life0 self,
id: &'life1 str,
) -> Pin<Box<dyn Future<Output = StorageResult<bool>> + Send + 'async_trait>>
where Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait;
fn get_metadata<'life0, 'life1, 'async_trait>(
&'life0 self,
id: &'life1 str,
) -> Pin<Box<dyn Future<Output = StorageResult<StoredFile>> + Send + 'async_trait>>
where Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait;
}Expand description
Abstraction for file storage backends
This trait provides a unified interface for storing, retrieving, and managing files across different storage backends (local filesystem, S3, Azure Blob, etc.).
§Design Principles
- Backend Agnostic: Handlers don’t need to know about storage implementation
- Async First: All operations are async for optimal I/O performance
- Type Safe: Strong types prevent common errors
- Production Ready: Built-in support for streaming, validation, and error handling
§Implementation Requirements
Implementations must:
- Generate unique identifiers for stored files (UUIDs recommended)
- Handle concurrent access safely
- Provide atomic operations where possible
- Clean up resources on errors
§Examples
use acton_htmx::storage::{FileStorage, LocalFileStorage, UploadedFile};
use std::path::PathBuf;
// Create storage backend
let storage = LocalFileStorage::new(PathBuf::from("/var/uploads"))?;
// Store a file
let file = UploadedFile::new("avatar.png", "image/png", vec![/* ... */]);
let stored = storage.store(file).await?;
// Retrieve the file
let data = storage.retrieve(&stored.id).await?;
// Get file URL (for serving to clients)
let url = storage.url(&stored.id).await?;
// Delete when no longer needed
storage.delete(&stored.id).await?;Required Methods§
Sourcefn store<'life0, 'async_trait>(
&'life0 self,
file: UploadedFile,
) -> Pin<Box<dyn Future<Output = StorageResult<StoredFile>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
fn store<'life0, 'async_trait>(
&'life0 self,
file: UploadedFile,
) -> Pin<Box<dyn Future<Output = StorageResult<StoredFile>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
Stores an uploaded file and returns metadata about the stored file
This method should:
- Generate a unique ID for the file
- Persist the file data to the storage backend
- Return metadata that can be used to retrieve the file later
§Errors
Returns an error if:
- The storage backend is unavailable
- There’s insufficient storage space
- File I/O fails
§Examples
let file = UploadedFile::new("report.pdf", "application/pdf", vec![/* ... */]);
let stored = storage.store(file).await?;
println!("Stored with ID: {}", stored.id);Sourcefn retrieve<'life0, 'life1, 'async_trait>(
&'life0 self,
id: &'life1 str,
) -> Pin<Box<dyn Future<Output = StorageResult<Vec<u8>>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait,
fn retrieve<'life0, 'life1, 'async_trait>(
&'life0 self,
id: &'life1 str,
) -> Pin<Box<dyn Future<Output = StorageResult<Vec<u8>>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait,
Sourcefn delete<'life0, 'life1, 'async_trait>(
&'life0 self,
id: &'life1 str,
) -> Pin<Box<dyn Future<Output = StorageResult<()>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait,
fn delete<'life0, 'life1, 'async_trait>(
&'life0 self,
id: &'life1 str,
) -> Pin<Box<dyn Future<Output = StorageResult<()>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait,
Deletes a file by ID
This operation should be idempotent - deleting a non-existent file should not return an error.
§Errors
Returns an error if:
- The storage backend is unavailable
- File I/O fails (permissions, etc.)
§Examples
storage.delete("550e8400-e29b-41d4-a716-446655440000").await?;
println!("File deleted successfully");Sourcefn url<'life0, 'life1, 'async_trait>(
&'life0 self,
id: &'life1 str,
) -> Pin<Box<dyn Future<Output = StorageResult<String>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait,
fn url<'life0, 'life1, 'async_trait>(
&'life0 self,
id: &'life1 str,
) -> Pin<Box<dyn Future<Output = StorageResult<String>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait,
Returns a URL for accessing the file
The returned URL format depends on the storage backend:
- Local storage: relative path (e.g., “/uploads/abc123/file.jpg”)
- S3: presigned URL or public URL
- CDN: CDN URL
§Errors
Returns an error if:
- The file doesn’t exist
- URL generation fails (e.g., S3 presigning error)
§Examples
let url = storage.url("550e8400-e29b-41d4-a716-446655440000").await?;
println!("File available at: {}", url);Sourcefn exists<'life0, 'life1, 'async_trait>(
&'life0 self,
id: &'life1 str,
) -> Pin<Box<dyn Future<Output = StorageResult<bool>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait,
fn exists<'life0, 'life1, 'async_trait>(
&'life0 self,
id: &'life1 str,
) -> Pin<Box<dyn Future<Output = StorageResult<bool>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait,
Checks if a file exists
This is useful for validating file references before attempting retrieval.
§Examples
if storage.exists("550e8400-e29b-41d4-a716-446655440000").await? {
println!("File exists!");
}Sourcefn get_metadata<'life0, 'life1, 'async_trait>(
&'life0 self,
id: &'life1 str,
) -> Pin<Box<dyn Future<Output = StorageResult<StoredFile>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait,
fn get_metadata<'life0, 'life1, 'async_trait>(
&'life0 self,
id: &'life1 str,
) -> Pin<Box<dyn Future<Output = StorageResult<StoredFile>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait,
Retrieves file metadata by ID
This method retrieves only the metadata (filename, content type, size, etc.) without reading the actual file data. This is useful for serving files with proper Content-Type headers and other metadata.
§Errors
Returns an error if:
- The file doesn’t exist (
StorageError::NotFound) - The storage backend is unavailable
- Metadata cannot be read
§Examples
let metadata = storage.get_metadata("550e8400-e29b-41d4-a716-446655440000").await?;
println!("Content-Type: {}", metadata.content_type);
println!("Size: {} bytes", metadata.size);