use async_trait::async_trait;
#[async_trait]
pub trait Storage: Send + Sync {
async fn upload(&self, key: &str, data: &[u8], content_type: &str) -> Result<(), StorageError>;
async fn download(&self, key: &str) -> Result<Vec<u8>, StorageError>;
async fn delete(&self, key: &str) -> Result<(), StorageError>;
async fn exists(&self, key: &str) -> Result<bool, StorageError>;
fn url(&self, key: &str) -> String;
}
#[derive(Debug)]
pub enum StorageError {
NotFound,
AccessDenied,
InvalidKey,
Io(std::io::Error),
}
impl std::fmt::Display for StorageError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
StorageError::NotFound => write!(f, "File not found"),
StorageError::AccessDenied => write!(f, "Access denied"),
StorageError::InvalidKey => write!(f, "Invalid key"),
StorageError::Io(e) => write!(f, "IO error: {}", e),
}
}
}
impl std::error::Error for StorageError {}
impl From<std::io::Error> for StorageError {
fn from(e: std::io::Error) -> Self {
StorageError::Io(e)
}
}
pub struct LocalStorage {
base_path: String,
base_url: String,
}
impl LocalStorage {
pub fn new(base_path: &str, base_url: &str) -> Self {
Self {
base_path: base_path.to_string(),
base_url: base_url.to_string(),
}
}
fn full_path(&self, key: &str) -> std::path::PathBuf {
std::path::Path::new(&self.base_path).join(key)
}
}
#[async_trait]
impl Storage for LocalStorage {
async fn upload(&self, key: &str, data: &[u8], _content_type: &str) -> Result<(), StorageError> {
let path = self.full_path(key);
if let Some(parent) = path.parent() {
std::fs::create_dir_all(parent)?;
}
std::fs::write(&path, data)?;
Ok(())
}
async fn download(&self, key: &str) -> Result<Vec<u8>, StorageError> {
let path = self.full_path(key);
if !path.exists() {
return Err(StorageError::NotFound);
}
let data = std::fs::read(&path)?;
Ok(data)
}
async fn delete(&self, key: &str) -> Result<(), StorageError> {
let path = self.full_path(key);
std::fs::remove_file(&path)?;
Ok(())
}
async fn exists(&self, key: &str) -> Result<bool, StorageError> {
let path = self.full_path(key);
Ok(path.exists())
}
fn url(&self, key: &str) -> String {
format!("{}/{}", self.base_url.trim_end_matches('/'), key)
}
}