use std::sync::Arc; use std::time::Duration; use async_trait::async_trait; use bytes::Bytes; use object_store::path::Path; use object_store::{GetResult, ObjectMeta, PutResult}; use url::Url; use crate::config::{BackendType, StorageSettings}; #[derive(Debug, thiserror::Error)] pub enum StorageError { #[error("invalid storage config: {0}")] Config(String), #[error("object store error: {0}")] ObjectStore(#[from] object_store::Error), #[error("unsupported operation: {0}")] Unsupported(String), } #[async_trait] pub trait StorageRepository: Send + Sync { async fn put(&self, path: &Path, data: Bytes) -> Result; async fn get(&self, path: &Path) -> Result; async fn delete(&self, path: &Path) -> Result<(), StorageError>; async fn head(&self, path: &Path) -> Result; async fn exists(&self, path: &Path) -> Result; async fn list(&self, prefix: Option<&Path>) -> Result, StorageError>; async fn copy(&self, from: &Path, to: &Path) -> Result<(), StorageError>; async fn presigned_upload( &self, path: &Path, expires_in: Duration, ) -> Result; async fn presigned_download( &self, path: &Path, expires_in: Duration, ) -> Result; } pub fn from_config(settings: &StorageSettings) -> Result, StorageError> { tracing::debug!(backend = ?settings.get_backend(), "initializing storage repository"); match settings.get_backend() { BackendType::Local => Ok(Arc::new(crate::local::LocalStorage::from_config(settings)?)), BackendType::S3 => Ok(Arc::new(crate::s3::S3Storage::from_config(settings)?)), } } #[cfg(test)] mod tests { use super::*; use crate::config::{LocalStorageSettings, S3StorageSettings}; #[test] fn from_config_dispatches_local() { let settings = StorageSettings::default(); let _repo = from_config(&settings).unwrap(); } #[test] fn from_config_dispatches_s3() { let mut settings = StorageSettings::default(); settings.set_backend(BackendType::S3); let mut s3 = S3StorageSettings::default(); s3.set_bucket("test-bucket".to_string()); settings.set_s3(s3); assert!(from_config(&settings).is_ok()); } #[test] fn from_config_rejects_empty_s3_bucket() { let mut settings = StorageSettings::default(); settings.set_backend(BackendType::S3); settings.set_s3(S3StorageSettings::default()); assert!(matches!( from_config(&settings), Err(StorageError::Config(_)) )); } #[test] fn local_settings_roundtrip() { let mut settings = StorageSettings::default(); let mut local = LocalStorageSettings::default(); local.set_root_dir(std::path::PathBuf::from("/tmp/custom")); settings.set_local(local); let repo = from_config(&settings).unwrap(); let _ = repo; } }