use crate::error::{AppError, AppResult}; use serde::{Deserialize, Serialize}; use std::collections::HashMap; use std::str::FromStr; use tokio::sync::OnceCell; pub static GLOBAL_CONFIG: OnceCell = OnceCell::const_new(); #[derive(Clone, Debug, Deserialize, Serialize)] pub struct AppConfig { pub env: HashMap, } impl AppConfig { pub fn main_domain(&self) -> AppResult { self.get_env::("APP_MAIN_DOMAIN")? .filter(|s| !s.is_empty()) .ok_or_else(|| AppError::Config("APP_MAIN_DOMAIN is not set".into())) } pub const ENV_FILES: &'static [&'static str] = &[ ".env", ".env.local", ".env.development", ".env.development.local", ".env.test", ".env.test.local", ".env.production", ".env.production.local", ]; pub fn get_env(&self, key: &str) -> AppResult> where ::Err: std::fmt::Display, { match self.env.get(key) { Some(v) if !v.is_empty() => Ok(Some( v.parse::().map_err(|e| AppError::Parse(e.to_string()))?, )), Some(_) => Ok(None), None => Ok(None), } } pub fn get_env_or(&self, key: &str, default: T) -> AppResult where ::Err: std::fmt::Display, { Ok(self.get_env(key)?.unwrap_or(default)) } pub fn load() -> AppConfig { dotenvy::dotenv().ok(); let mut env = HashMap::new(); for env_file in AppConfig::ENV_FILES { if let Err(e) = dotenvy::from_path(env_file) { tracing::debug!(file = %env_file, error = %e, "dotenv load skipped"); } if let Ok(env_file_content) = std::fs::read_to_string(env_file) { for line in env_file_content.lines() { if let Some((key, value)) = line.split_once('=') { env.insert(key.to_string(), value.to_string()); } } } } env = env.into_iter().chain(std::env::vars()).collect(); let this = AppConfig { env }; if let Some(config) = GLOBAL_CONFIG.get() { config.clone() } else { let _ = GLOBAL_CONFIG.set(this); GLOBAL_CONFIG .get() .cloned() .unwrap_or_else(|| AppConfig { env: HashMap::new() }) } } } pub mod aiprovider; pub mod app; pub mod channelaiprovider; pub mod database; pub mod embedaiprovider; pub mod etcd; pub mod lru; pub mod nats; pub mod qdrant; pub mod redis; pub mod rpc; pub mod s3;