pub mod error; pub mod local; pub mod s3; use std::time::Duration; use async_trait::async_trait; pub use aws_sdk_s3::primitives::ByteStream; use aws_sdk_s3::primitives::ByteStreamError; pub use error::{StorageError, StorageResult}; pub use local::{LocalStorage, LocalStorageConfig}; pub use s3::{S3Storage, S3StorageConfig}; #[derive(Clone, Debug)] pub enum AppStorageConfig { Local(LocalStorageConfig), S3(S3StorageConfig), } #[derive(Clone)] pub enum AppStorage { Local(LocalStorage), S3(S3Storage), } #[derive(Clone, Debug, Default)] pub struct PutObjectOptions { pub content_type: Option, pub content_length: Option, pub cache_control: Option, } #[derive(Clone, Debug)] pub struct StoredObject { pub key: String, pub url: String, pub e_tag: Option, pub version_id: Option, } #[derive(Debug)] pub struct StorageObjectStream { pub body: ByteStream, pub content_length: Option, pub content_type: Option, pub e_tag: Option, } #[derive(Clone, Debug)] pub struct StorageObject { pub bytes: Vec, pub content_length: Option, pub content_type: Option, pub e_tag: Option, } #[async_trait] pub trait ObjectStorage: Send + Sync { async fn put_stream( &self, key: &str, body: ByteStream, options: PutObjectOptions, ) -> StorageResult; async fn put_bytes( &self, key: &str, bytes: Vec, options: PutObjectOptions, ) -> StorageResult; async fn get_stream(&self, key: &str) -> StorageResult; async fn get_bytes(&self, key: &str) -> StorageResult; async fn delete(&self, key: &str) -> StorageResult<()>; fn public_url(&self, key: &str) -> StorageResult>; async fn presigned_get_url( &self, key: &str, expires_in: Duration, ) -> StorageResult; } impl AppStorage { pub async fn init(config: AppStorageConfig) -> StorageResult { match config { AppStorageConfig::Local(config) => { Ok(Self::Local(LocalStorage::connect(config).await?)) } AppStorageConfig::S3(config) => { Ok(Self::S3(S3Storage::connect(config).await?)) } } } } #[async_trait] impl ObjectStorage for AppStorage { async fn put_stream( &self, key: &str, body: ByteStream, options: PutObjectOptions, ) -> StorageResult { match self { Self::Local(storage) => { storage.put_stream(key, body, options).await } Self::S3(storage) => storage.put_stream(key, body, options).await, } } async fn put_bytes( &self, key: &str, bytes: Vec, options: PutObjectOptions, ) -> StorageResult { match self { Self::Local(storage) => { storage.put_bytes(key, bytes, options).await } Self::S3(storage) => storage.put_bytes(key, bytes, options).await, } } async fn get_stream( &self, key: &str, ) -> StorageResult { match self { Self::Local(storage) => storage.get_stream(key).await, Self::S3(storage) => storage.get_stream(key).await, } } async fn get_bytes(&self, key: &str) -> StorageResult { match self { Self::Local(storage) => storage.get_bytes(key).await, Self::S3(storage) => storage.get_bytes(key).await, } } async fn delete(&self, key: &str) -> StorageResult<()> { match self { Self::Local(storage) => storage.delete(key).await, Self::S3(storage) => storage.delete(key).await, } } fn public_url(&self, key: &str) -> StorageResult> { match self { Self::Local(storage) => storage.public_url(key), Self::S3(storage) => storage.public_url(key), } } async fn presigned_get_url( &self, key: &str, expires_in: Duration, ) -> StorageResult { match self { Self::Local(storage) => { storage.presigned_get_url(key, expires_in).await } Self::S3(storage) => { storage.presigned_get_url(key, expires_in).await } } } } pub async fn collect_byte_stream( body: ByteStream, ) -> Result, ByteStreamError> { body.collect().await.map(|data| data.to_vec()) } impl TryFrom<&config::AppConfig> for AppStorageConfig { type Error = StorageError; fn try_from(config: &config::AppConfig) -> Result { let backend = config .storage_backend() .map_err(|error| StorageError::Config(error.to_string()))?; match backend.as_str() { "local" | "fs" | "filesystem" => { Ok(Self::Local(LocalStorageConfig::try_from(config)?)) } "s3" => Ok(Self::S3(S3StorageConfig::try_from(config)?)), backend => Err(StorageError::Config(format!( "unsupported storage backend: {backend}" ))), } } }