kegani 0.1.1

A developer-friendly, ergonomic, production-ready Rust web framework
Documentation
//! Storage backends for Kegani
//!
//! Provides local and S3 storage implementations.

use async_trait::async_trait;

/// Storage trait
#[async_trait]
pub trait Storage: Send + Sync {
    /// Upload a file
    async fn upload(&self, key: &str, data: &[u8], content_type: &str) -> Result<(), StorageError>;

    /// Download a file
    async fn download(&self, key: &str) -> Result<Vec<u8>, StorageError>;

    /// Delete a file
    async fn delete(&self, key: &str) -> Result<(), StorageError>;

    /// Check if file exists
    async fn exists(&self, key: &str) -> Result<bool, StorageError>;

    /// Get public URL for a file
    fn url(&self, key: &str) -> String;
}

/// Storage error
#[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)
    }
}

/// Local file system storage
pub struct LocalStorage {
    base_path: String,
    base_url: String,
}

impl LocalStorage {
    /// Create a new local storage
    pub fn new(base_path: &str, base_url: &str) -> Self {
        Self {
            base_path: base_path.to_string(),
            base_url: base_url.to_string(),
        }
    }

    /// Get full path for a key
    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);

        // Create parent directories
        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)
    }
}