//! Pluggable blob storage. //! //! Handlers depend on the `Storage` trait, never on a concrete backend. //! Add new backends (S3, GCS, …) as new impls in this module and wire //! them up in `app::build` based on config. mod local; use std::io; use std::pin::Pin; use async_trait::async_trait; use bytes::Bytes; use futures_core::Stream; pub use local::LocalStorage; #[derive(thiserror::Error, Debug)] pub enum StorageError { #[error(transparent)] Io(#[from] io::Error), #[error("not found")] NotFound, #[error("invalid storage key")] BadKey, } /// Boxed byte stream returned by `Storage::get_stream` so the trait stays /// object-safe regardless of the concrete reader behind it. pub type ByteStream = Pin> + Send>>; pub struct StreamingFile { pub stream: ByteStream, pub size_bytes: u64, } #[async_trait] pub trait Storage: Send + Sync { async fn put(&self, key: &str, bytes: &[u8]) -> Result<(), StorageError>; /// Reads the entire blob into memory. Convenient for small assets /// (covers, thumbnails). For pages and other large blobs, use /// `get_stream` so axum can pipe bytes straight to the client. async fn get(&self, key: &str) -> Result, StorageError>; async fn get_stream(&self, key: &str) -> Result; async fn delete(&self, key: &str) -> Result<(), StorageError>; async fn exists(&self, key: &str) -> Result; }