openserve 2.0.3

A modern, high-performance, AI-enhanced file server built in Rust
Documentation
//! Configuration Module
//!
//! This module defines configuration structures for the application,
//! including server, AI, search, and telemetry settings.

use anyhow::Result;
use config::{Config as ConfigBuilder, Environment, File};
use serde::{Deserialize, Serialize};
use std::path::PathBuf;

/// Main application configuration
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Config {
    /// Server configuration settings
    pub server: ServerConfig,
    /// AI service configuration
    pub ai: AiConfig,
    /// Search engine configuration
    pub search: SearchConfig,
    /// Authentication configuration
    pub auth: AuthConfig,
    /// Telemetry configuration
    pub telemetry: TelemetryConfig,
}

/// Server configuration settings
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ServerConfig {
    /// Host address to bind the server to
    pub host: String,
    /// Port number to listen on
    pub port: u16,
    /// Root directory to serve files from
    pub root_dir: PathBuf,
    /// Maximum file upload size in bytes
    pub max_upload_size: u64,
    /// Maximum number of concurrent connections
    pub max_connections: usize,
    /// Request timeout in seconds
    pub timeout: u64,
    /// Whether to enable CORS
    pub cors_enabled: bool,
    /// CORS allowed origins
    pub cors_origins: Vec<String>,
}

/// AI service configuration
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AiConfig {
    /// Whether AI features are enabled
    pub enabled: bool,
    /// OpenAI API key
    pub api_key: String,
    /// AI model to use
    pub model: String,
    /// Maximum number of tokens for AI requests
    pub max_tokens: u32,
    /// Temperature for AI generation
    pub temperature: f32,
    /// Request timeout in seconds
    pub timeout: u64,
}

/// Authentication configuration
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AuthConfig {
    /// Whether authentication is enabled
    pub enabled: bool,
    /// JWT secret key
    pub jwt_secret: String,
    /// JWT token expiration time in seconds
    pub token_expiration: u64,
    /// Whether to allow user registration
    pub allow_registration: bool,
    /// Default role for new users
    pub default_role: String,
}

/// Telemetry configuration
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TelemetryConfig {
    /// Whether telemetry is enabled
    pub enabled: bool,
    /// Log level (trace, debug, info, warn, error)
    pub log_level: String,
    /// Log format (json, plain)
    pub log_format: String,
    /// Whether metrics collection is enabled
    pub metrics_enabled: bool,
    /// Metrics export interval in seconds
    pub metrics_interval: u64,
    /// Whether tracing is enabled
    pub tracing_enabled: bool,
    /// Tracing endpoint URL
    pub tracing_endpoint: Option<String>,
}

/// Search engine configuration
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SearchConfig {
    /// Whether search functionality is enabled
    pub enabled: bool,
    /// Directory to store search index
    pub index_dir: PathBuf,
    /// Maximum number of search results
    pub max_results: usize,
    /// Whether to enable fuzzy search
    pub fuzzy_search: bool,
    /// Search index refresh interval in seconds
    pub refresh_interval: u64,
}

/// Provides default settings for the main `Config`.
impl Default for Config {
    fn default() -> Self {
        Self {
            server: ServerConfig::default(),
            ai: AiConfig::default(),
            search: SearchConfig::default(),
            auth: AuthConfig::default(),
            telemetry: TelemetryConfig::default(),
        }
    }
}

/// Provides default settings for `ServerConfig`.
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, // 100MB
            max_connections: 1000,
            timeout: 30,
            cors_enabled: true,
            cors_origins: vec!["*".to_string()],
        }
    }
}

/// Provides default settings for `AiConfig`.
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,
        }
    }
}

/// Provides default settings for `AuthConfig`.
impl Default for AuthConfig {
    fn default() -> Self {
        Self {
            enabled: false,
            jwt_secret: "your-secret-key".to_string(),
            token_expiration: 3600, // 1 hour
            allow_registration: true,
            default_role: "User".to_string(),
        }
    }
}

/// Provides default settings for `TelemetryConfig`.
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,
        }
    }
}

/// Provides default settings for `SearchConfig`.
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, // 5 minutes
        }
    }
}

impl Config {
    /// Loads configuration from a specified file path.
    ///
    /// This function reads a configuration file (e.g., `config.yaml`) and
    /// merges it with environment variables. Environment variables take
    /// precedence over file settings.
    ///
    /// # Arguments
    ///
    /// * `path` - The path to the configuration file.
    ///
    /// # Returns
    ///
    /// A `Result` containing the loaded `Config` or an error.
    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()?)
    }

    /// Creates a configuration from command-line arguments.
    ///
    /// This is used when no configuration file is provided, allowing for
    /// quick and simple server startup with essential settings derived
    /// from the command-line arguments.
    ///
    /// # Arguments
    ///
    /// * `args` - A reference to the parsed command-line arguments.
    ///
    /// # Returns
    ///
    /// A `Result` containing the generated `Config`.
    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)
    }

    /// Validates the loaded configuration.
    ///
    /// This function checks for logical inconsistencies, such as enabling a
    /// feature without providing its required settings.
    ///
    /// # Returns
    ///
    /// A `Result` which is `Ok(())` if validation passes, or an error if not.
    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(())
    }
}