product-os-server 0.0.55

Product OS : Server provides a full functioning advanced server capable of acting as a web server, command and control distributed network, authentication server, crawling server and more. Fully featured with high level of flexibility.
Documentation
//! Server configuration types
//!
//! Network, certificate, and compression configuration for Product OS Server.

use serde::{Deserialize, Serialize};
use core::default::Default;
use std::prelude::v1::*;
use std::collections::BTreeMap;

use product_os_configuration::{ProductOSConfig, ConfigError};
#[cfg(feature = "core")]
use product_os_configuration::logging::LogLevel;
use product_os_configuration::logging::Logging;
use product_os_net::SocketAddr;

/// Main configuration structure for Product OS Server
///
/// This structure contains all configuration options for running a Product OS server
/// instance, including network settings, security, authentication, data stores, and more.
///
/// Individual domain crates own their own config types. This struct composes them
/// for the server's top-level configuration.
#[derive(Clone, Debug, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct ServerConfig {
    /// Environment name (e.g., "development", "production")
    pub environment: String,
    /// Root path for the server
    pub root_path: String,
    /// Network configuration
    pub network: Network,
    /// Logging configuration
    pub logging: Logging,
    /// TLS/SSL certificate configuration
    pub certificate: Option<Certificate>,
    /// Compression configuration
    pub compression: Option<Compression>,
    /// Command and control configuration (raw JSON, parsed by product-os-command-control)
    pub command_control: Option<serde_json::Value>,
    /// Security configuration (raw JSON, parsed by product-os-security)
    pub security: Option<serde_json::Value>,
    /// OIDC configuration
    pub oidc: Option<serde_json::Value>,
    /// Data store configurations (raw JSON, parsed by product-os-store)
    pub store: Option<serde_json::Value>,
    /// Authentication configuration (raw JSON, parsed by product-os-authentication)
    pub authentication: Option<serde_json::Value>,
    /// Content server configuration (raw JSON, parsed by product-os-content-setup)
    pub content: Option<serde_json::Value>,
    /// Network proxy configuration (raw JSON, parsed by product-os-proxy)
    pub proxy: Option<serde_json::Value>,
    /// Browser configuration (raw JSON, parsed by product-os-browser)
    pub browser: Option<serde_json::Value>,
    /// Web crawler configuration (raw JSON, parsed by product-os-crawler)
    pub crawler: Option<serde_json::Value>,
    /// VPN configuration (raw JSON, parsed by product-os-vpn)
    pub vpn: Option<serde_json::Value>,
    /// API connector definitions (raw JSON)
    pub connectors: Option<BTreeMap<String, serde_json::Value>>,
}

impl Default for ServerConfig {
    fn default() -> Self {
        Self::new()
    }
}

impl ServerConfig {
    /// Create a new `ServerConfig` with default development settings
    #[must_use]
    pub fn new() -> Self {
        Self {
            environment: String::from("development"),
            root_path: String::from("/"),
            network: Network::default(),
            logging: Logging::default(),
            certificate: None,
            compression: Some(Compression {
                enable: false,
                gzip: true,
                deflate: false,
                brotli: true,
            }),
            command_control: None,
            security: None,
            oidc: None,
            store: None,
            authentication: None,
            content: None,
            proxy: None,
            browser: None,
            crawler: None,
            vpn: None,
            connectors: None,
        }
    }

    /// Get the environment name
    #[must_use]
    pub fn environment(&self) -> &str {
        &self.environment
    }

    /// Get the tracing log level from configuration
    ///
    /// Requires the `core` feature (which enables `tracing`).
    #[cfg(feature = "core")]
    #[must_use]
    pub fn log_level(&self) -> tracing::Level {
        match self.logging.level {
            LogLevel::WARN => tracing::Level::WARN,
            LogLevel::INFO => tracing::Level::INFO,
            LogLevel::DEBUG => tracing::Level::DEBUG,
            LogLevel::TRACE => tracing::Level::TRACE,
            LogLevel::ERROR | LogLevel::OFF => tracing::Level::ERROR,
        }
    }

    /// Get the configured hostname
    #[must_use]
    pub fn get_host(&self) -> String {
        self.network.host.clone()
    }

    /// Build the complete URL address from configuration
    ///
    /// # Panics
    ///
    /// Panics if the URL cannot be built. Use `try_url_address` for a non-panicking alternative.
    #[must_use]
    #[allow(clippy::panic)]
    #[deprecated(since = "0.0.53", note = "Use try_url_address which returns Result instead of panicking")]
    pub fn url_address(&self) -> String {
        self.try_url_address()
            .unwrap_or_else(|e| ::std::panic!("Failed to build URL from config: {}", e))
    }

    /// Build the complete URL address from configuration, returning an error if validation fails.
    ///
    /// # Errors
    ///
    /// Returns `ConfigurationError` if the host is empty or the protocol is empty.
    pub fn try_url_address(&self) -> crate::error::Result<String> {
        if self.network.host.is_empty() {
            return Err(crate::error::ProductOSServerError::ConfigurationError {
                component: "url_address".to_string(),
                message: "Host must not be empty".to_string(),
            });
        }
        if self.network.protocol.is_empty() {
            return Err(crate::error::ProductOSServerError::ConfigurationError {
                component: "url_address".to_string(),
                message: "Protocol must not be empty".to_string(),
            });
        }

        let root = self.root_path.strip_suffix('/').unwrap_or(&self.root_path);

        let url_string = alloc::format!(
            "{}://{}:{}{}",
            self.network.protocol,
            self.network.host,
            self.network.port,
            root,
        );

        Ok(url_string)
    }

    /// Get socket address for binding
    #[must_use]
    pub fn socket_address(&self, port: Option<u16>, default_all: bool) -> SocketAddr {
        let socket_port = port.unwrap_or(self.network.port);
        Self::get_socket_address(&self.network.host, socket_port, default_all)
    }

    /// Create socket address from components
    #[must_use]
    pub fn get_socket_address(host: &str, port: u16, default_all: bool) -> SocketAddr {
        product_os_utilities::ProductOSUtilities::get_socket_address(host, port, default_all)
    }

    /// Check if secure mode is enabled
    #[must_use]
    pub fn is_secure(&self) -> bool {
        self.network.secure
    }

    /// Check if insecure connections are allowed
    #[must_use]
    pub fn all_insecure(&self) -> bool {
        self.network.allow_insecure
    }

    /// Get insecure port number
    #[must_use]
    pub fn insecure_port(&self) -> u16 {
        self.network.insecure_port
    }

    /// Check if insecure connections should force redirect to secure
    #[must_use]
    pub fn insecure_force_secure(&self) -> bool {
        self.network.insecure_force_secure
    }

    /// Check if gzip compression is enabled
    #[must_use]
    pub fn is_compression_gzip(&self) -> bool {
        self.compression.as_ref().map_or(false, |c| c.gzip)
    }

    /// Check if deflate compression is enabled
    #[must_use]
    pub fn is_compression_deflate(&self) -> bool {
        self.compression.as_ref().map_or(false, |c| c.deflate)
    }

    /// Check if brotli compression is enabled
    #[must_use]
    pub fn is_compression_brotli(&self) -> bool {
        self.compression.as_ref().map_or(false, |c| c.brotli)
    }
}

impl ProductOSConfig for ServerConfig {
    const SECTION_KEY: &'static str = "server";

    fn validate(&self) -> Result<(), ConfigError> {
        let mut errors = Vec::new();
        if self.network.host.is_empty() && self.network.port == 0 {
            errors.push("network host and port must be configured".into());
        }
        if errors.is_empty() {
            Ok(())
        } else {
            Err(ConfigError::ValidationError(errors))
        }
    }
}


/// Network configuration for the server
#[derive(Clone, Debug, Deserialize, Serialize, Default)]
#[serde(rename_all = "camelCase")]
pub struct Network {
    /// Network protocol (http, https, etc.)
    pub protocol: String,
    /// Whether to use secure connections
    pub secure: bool,
    /// Server hostname
    pub host: String,
    /// Server port number
    pub port: u16,
    /// Whether to listen on all network interfaces
    pub listen_all_interfaces: bool,
    /// Whether to allow insecure connections
    pub allow_insecure: bool,
    /// Port for insecure connections
    pub insecure_port: u16,
    /// Whether to use a different port for insecure connections
    pub insecure_use_different_port: bool,
    /// Whether to force redirect from insecure to secure
    pub insecure_force_secure: bool,
    /// Whether to inject the client's socket address into request extensions
    /// (enables `ConnectInfo<SocketAddr>` extraction in handlers)
    #[serde(default)]
    pub connect_info: bool,
}

/// TLS/SSL certificate configuration
#[derive(Clone, Debug, Deserialize, Serialize, Default)]
#[serde(rename_all = "camelCase")]
pub struct Certificate {
    /// Certificate and key file paths
    pub files: Option<CertificateFiles>,
    /// Type of certificate (self-signed or CA-issued)
    pub file_kind: Option<CertificateFilesKind>,
    /// Certificate entries for generation
    pub entries: Option<Vec<BTreeMap<String, serde_json::Value>>>,
    /// Subject alternative names for the certificate
    pub names: Option<Vec<String>>,
    /// Certificate serial number
    pub serial: Option<u32>,
    /// Certificate validity period in days
    pub valid_for: Option<u32>,
}

/// Certificate file paths
#[derive(Clone, Debug, Deserialize, Serialize, Default)]
#[serde(rename_all = "camelCase")]
pub struct CertificateFiles {
    /// Path to certificate file
    pub cert_file: String,
    /// Path to private key file
    pub key_file: String,
}

/// Type of certificate
#[derive(Clone, Copy, Debug, Deserialize, Serialize, PartialEq, Eq, Default)]
#[serde(rename_all = "lowercase")]
pub enum CertificateFilesKind {
    /// Self-signed certificate
    #[default]
    SelfSigned,
    /// Certificate Authority issued certificate
    CA,
}

/// Compression configuration
#[derive(Clone, Debug, Deserialize, Serialize, Default)]
#[serde(rename_all = "camelCase")]
pub struct Compression {
    /// Enable compression
    pub enable: bool,
    /// Enable gzip compression
    pub gzip: bool,
    /// Enable deflate compression
    pub deflate: bool,
    /// Enable brotli compression
    pub brotli: bool,
}