use anyhow::Result;
use config::{Config as ConfigBuilder, Environment, File};
use serde::{Deserialize, Serialize};
use std::path::PathBuf;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Config {
pub server: ServerConfig,
pub ai: AiConfig,
pub search: SearchConfig,
pub auth: AuthConfig,
pub telemetry: TelemetryConfig,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ServerConfig {
pub host: String,
pub port: u16,
pub root_dir: PathBuf,
pub max_upload_size: u64,
pub max_connections: usize,
pub timeout: u64,
pub cors_enabled: bool,
pub cors_origins: Vec<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AiConfig {
pub enabled: bool,
pub api_key: String,
pub model: String,
pub max_tokens: u32,
pub temperature: f32,
pub timeout: u64,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AuthConfig {
pub enabled: bool,
pub jwt_secret: String,
pub token_expiration: u64,
pub allow_registration: bool,
pub default_role: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TelemetryConfig {
pub enabled: bool,
pub log_level: String,
pub log_format: String,
pub metrics_enabled: bool,
pub metrics_interval: u64,
pub tracing_enabled: bool,
pub tracing_endpoint: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SearchConfig {
pub enabled: bool,
pub index_dir: PathBuf,
pub max_results: usize,
pub fuzzy_search: bool,
pub refresh_interval: u64,
}
impl Default for Config {
fn default() -> Self {
Self {
server: ServerConfig::default(),
ai: AiConfig::default(),
search: SearchConfig::default(),
auth: AuthConfig::default(),
telemetry: TelemetryConfig::default(),
}
}
}
impl Default for ServerConfig {
fn default() -> Self {
Self {
host: "127.0.0.1".to_string(),
port: 8080,
root_dir: PathBuf::from("."),
max_upload_size: 100 * 1024 * 1024, max_connections: 1000,
timeout: 30,
cors_enabled: true,
cors_origins: vec!["*".to_string()],
}
}
}
impl Default for AiConfig {
fn default() -> Self {
Self {
enabled: false,
api_key: String::new(),
model: "gpt-3.5-turbo".to_string(),
max_tokens: 1000,
temperature: 0.7,
timeout: 30,
}
}
}
impl Default for AuthConfig {
fn default() -> Self {
Self {
enabled: false,
jwt_secret: "your-secret-key".to_string(),
token_expiration: 3600, allow_registration: true,
default_role: "User".to_string(),
}
}
}
impl Default for TelemetryConfig {
fn default() -> Self {
Self {
enabled: true,
log_level: "info".to_string(),
log_format: "plain".to_string(),
metrics_enabled: false,
metrics_interval: 60,
tracing_enabled: false,
tracing_endpoint: None,
}
}
}
impl Default for SearchConfig {
fn default() -> Self {
Self {
enabled: true,
index_dir: PathBuf::from("search_index"),
max_results: 100,
fuzzy_search: true,
refresh_interval: 300, }
}
}
impl Config {
pub fn from_file(path: &str) -> Result<Self> {
let config = ConfigBuilder::builder()
.add_source(File::with_name(path))
.add_source(Environment::with_prefix("OPENSERVE").separator("__"))
.build()?;
Ok(config.try_deserialize()?)
}
pub fn from_args(args: &crate::Args) -> Result<Self> {
let mut config = Config::default();
config.server.root_dir = PathBuf::from(&args.path);
config.server.port = args.port;
config.server.host = args.host.clone();
config.ai.enabled = args.ai;
if let Some(api_key) = &args.openai_api_key {
config.ai.api_key = api_key.clone();
}
config.telemetry.log_level = args.log_level.clone();
Ok(config)
}
pub fn validate(&self) -> Result<()> {
if self.ai.enabled && self.ai.api_key.is_empty() {
return Err(anyhow::anyhow!("AI features are enabled, but API key is not provided."));
}
Ok(())
}
}