a06673b
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<PutResult, StorageError>;
async fn get(&self, path: &Path) -> Result<GetResult, StorageError>;
async fn delete(&self, path: &Path) -> Result<(), StorageError>;
async fn head(&self, path: &Path) -> Result<ObjectMeta, StorageError>;
async fn exists(&self, path: &Path) -> Result<bool, StorageError>;
async fn list(&self, prefix: Option<&Path>) -> Result<Vec<ObjectMeta>, StorageError>;
async fn copy(&self, from: &Path, to: &Path) -> Result<(), StorageError>;
async fn presigned_upload(
&self,
path: &Path,
expires_in: Duration,
) -> Result<Url, StorageError>;
async fn presigned_download(
&self,
path: &Path,
expires_in: Duration,
) -> Result<Url, StorageError>;
}
pub fn from_config(settings: &StorageSettings) -> Result<Arc<dyn StorageRepository>, 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;
}
}