use std::collections::HashMap;
use serde::Deserialize;
#[derive(Debug, Clone, Deserialize)]
pub struct FileConfig {
pub path: Option<String>,
#[serde(default = "default_allowed_types")]
pub allowed_types: Vec<String>,
#[serde(default = "default_max_size")]
pub max_size: String,
#[serde(default = "default_validate_magic")]
pub validate_magic_bytes: bool,
#[serde(default = "default_storage")]
pub storage: String,
pub bucket_env: Option<String>,
#[serde(default = "default_public")]
pub public: bool,
pub cache: Option<String>,
pub url_expiry: Option<String>,
#[serde(default)]
pub scan_malware: bool,
pub processing: Option<ProcessingConfig>,
pub on_upload: Option<UploadCallbackConfig>,
}
fn default_allowed_types() -> Vec<String> {
vec![
"image/jpeg".to_string(),
"image/png".to_string(),
"image/webp".to_string(),
"image/gif".to_string(),
"application/pdf".to_string(),
]
}
fn default_max_size() -> String {
"10MB".to_string()
}
fn default_validate_magic() -> bool {
true
}
fn default_storage() -> String {
"default".to_string()
}
fn default_public() -> bool {
true
}
impl Default for FileConfig {
fn default() -> Self {
Self {
path: None,
allowed_types: default_allowed_types(),
max_size: default_max_size(),
validate_magic_bytes: default_validate_magic(),
storage: default_storage(),
bucket_env: None,
public: default_public(),
cache: None,
url_expiry: None,
scan_malware: false,
processing: None,
on_upload: None,
}
}
}
#[derive(Debug, Clone, Deserialize)]
pub struct ProcessingConfig {
#[serde(default)]
pub strip_exif: bool,
pub output_format: Option<String>,
pub quality: Option<u8>,
#[serde(default)]
pub variants: Vec<VariantConfig>,
}
#[derive(Debug, Clone, Deserialize)]
pub struct VariantConfig {
pub name: String,
pub width: u32,
pub height: u32,
#[serde(default = "default_mode")]
pub mode: String,
}
fn default_mode() -> String {
"fit".to_string()
}
#[derive(Debug, Clone, Deserialize)]
pub struct UploadCallbackConfig {
pub function: String,
#[serde(default)]
pub mapping: HashMap<String, String>,
}
#[derive(Debug, Clone, Deserialize)]
pub struct StorageConfig {
pub backend: String,
pub region: Option<String>,
pub bucket_env: Option<String>,
pub access_key_env: Option<String>,
pub secret_key_env: Option<String>,
pub endpoint_env: Option<String>,
pub account_id_env: Option<String>,
pub project_id_env: Option<String>,
pub credentials_env: Option<String>,
pub base_path: Option<String>,
pub serve_path: Option<String>,
pub public_url: Option<String>,
}
pub fn parse_size(size_str: &str) -> Result<usize, String> {
let size_str = size_str.trim().to_uppercase();
let (num_part, unit) =
if let Some(pos) = size_str.find(|c: char| !c.is_ascii_digit() && c != '.') {
(&size_str[..pos], &size_str[pos..])
} else {
(size_str.as_str(), "")
};
let num: f64 = num_part.parse().map_err(|_| format!("Invalid number: {}", num_part))?;
let multiplier = match unit.trim() {
"" | "B" => 1,
"KB" => 1024,
"MB" => 1024 * 1024,
"GB" => 1024 * 1024 * 1024,
_ => return Err(format!("Unknown unit: {}", unit)),
};
Ok((num * multiplier as f64) as usize)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_parse_size() {
assert_eq!(parse_size("100").unwrap(), 100);
assert_eq!(parse_size("100B").unwrap(), 100);
assert_eq!(parse_size("10KB").unwrap(), 10 * 1024);
assert_eq!(parse_size("10MB").unwrap(), 10 * 1024 * 1024);
assert_eq!(parse_size("1GB").unwrap(), 1024 * 1024 * 1024);
}
}