Skip to main content

product_os_server/
config.rs

1//! Server configuration types
2//!
3//! Network, certificate, and compression configuration for Product OS Server.
4
5use serde::{Deserialize, Serialize};
6use core::default::Default;
7use std::prelude::v1::*;
8use std::collections::BTreeMap;
9
10use product_os_configuration::{ProductOSConfig, ConfigError};
11#[cfg(feature = "core")]
12use product_os_configuration::logging::LogLevel;
13use product_os_configuration::logging::Logging;
14use product_os_net::SocketAddr;
15
16/// Main configuration structure for Product OS Server
17///
18/// This structure contains all configuration options for running a Product OS server
19/// instance, including network settings, security, authentication, data stores, and more.
20///
21/// Individual domain crates own their own config types. This struct composes them
22/// for the server's top-level configuration.
23#[derive(Clone, Debug, Deserialize, Serialize)]
24#[serde(rename_all = "camelCase")]
25pub struct ServerConfig {
26    /// Environment name (e.g., "development", "production")
27    pub environment: String,
28    /// Root path for the server
29    pub root_path: String,
30    /// Network configuration
31    pub network: Network,
32    /// Logging configuration
33    pub logging: Logging,
34    /// TLS/SSL certificate configuration
35    pub certificate: Option<Certificate>,
36    /// Compression configuration
37    pub compression: Option<Compression>,
38    /// Command and control configuration (raw JSON, parsed by product-os-command-control)
39    pub command_control: Option<serde_json::Value>,
40    /// Security configuration (raw JSON, parsed by product-os-security)
41    pub security: Option<serde_json::Value>,
42    /// OIDC configuration
43    pub oidc: Option<serde_json::Value>,
44    /// Data store configurations (raw JSON, parsed by product-os-store)
45    pub store: Option<serde_json::Value>,
46    /// Authentication configuration (raw JSON, parsed by product-os-authentication)
47    pub authentication: Option<serde_json::Value>,
48    /// Content server configuration (raw JSON, parsed by product-os-content-setup)
49    pub content: Option<serde_json::Value>,
50    /// Network proxy configuration (raw JSON, parsed by product-os-proxy)
51    pub proxy: Option<serde_json::Value>,
52    /// Browser configuration (raw JSON, parsed by product-os-browser)
53    pub browser: Option<serde_json::Value>,
54    /// Web crawler configuration (raw JSON, parsed by product-os-crawler)
55    pub crawler: Option<serde_json::Value>,
56    /// VPN configuration (raw JSON, parsed by product-os-vpn)
57    pub vpn: Option<serde_json::Value>,
58    /// API connector definitions (raw JSON)
59    pub connectors: Option<BTreeMap<String, serde_json::Value>>,
60}
61
62impl Default for ServerConfig {
63    fn default() -> Self {
64        Self::new()
65    }
66}
67
68impl ServerConfig {
69    /// Create a new `ServerConfig` with default development settings
70    #[must_use]
71    pub fn new() -> Self {
72        Self {
73            environment: String::from("development"),
74            root_path: String::from("/"),
75            network: Network::default(),
76            logging: Logging::default(),
77            certificate: None,
78            compression: Some(Compression {
79                enable: false,
80                gzip: true,
81                deflate: false,
82                brotli: true,
83            }),
84            command_control: None,
85            security: None,
86            oidc: None,
87            store: None,
88            authentication: None,
89            content: None,
90            proxy: None,
91            browser: None,
92            crawler: None,
93            vpn: None,
94            connectors: None,
95        }
96    }
97
98    /// Get the environment name
99    #[must_use]
100    pub fn environment(&self) -> &str {
101        &self.environment
102    }
103
104    /// Get the tracing log level from configuration
105    ///
106    /// Requires the `core` feature (which enables `tracing`).
107    #[cfg(feature = "core")]
108    #[must_use]
109    pub fn log_level(&self) -> tracing::Level {
110        match self.logging.level {
111            LogLevel::WARN => tracing::Level::WARN,
112            LogLevel::INFO => tracing::Level::INFO,
113            LogLevel::DEBUG => tracing::Level::DEBUG,
114            LogLevel::TRACE => tracing::Level::TRACE,
115            LogLevel::ERROR | LogLevel::OFF => tracing::Level::ERROR,
116        }
117    }
118
119    /// Get the configured hostname
120    #[must_use]
121    pub fn get_host(&self) -> String {
122        self.network.host.clone()
123    }
124
125    /// Build the complete URL address from configuration
126    ///
127    /// # Panics
128    ///
129    /// Panics if the URL cannot be parsed. Use `try_url_address` for a non-panicking alternative.
130    #[must_use]
131    #[deprecated(since = "0.0.53", note = "Use try_url_address which returns Result instead of panicking")]
132    pub fn url_address(&self) -> url::Url {
133        self.try_url_address()
134            .unwrap_or_else(|e| panic!("Failed to build URL from config: {}", e))
135    }
136
137    /// Build the complete URL address from configuration, returning an error if parsing fails.
138    ///
139    /// # Errors
140    ///
141    /// Returns `ConfigurationError` if the URL cannot be parsed from the configured
142    /// protocol, host, port, and root path.
143    pub fn try_url_address(&self) -> crate::error::Result<url::Url> {
144        let mut url_string = self.network.protocol.clone();
145        url_string.push_str("://");
146        url_string.push_str(&self.network.host);
147        url_string.push(':');
148        url_string.push_str(&self.network.port.to_string());
149        let root = self.root_path.strip_suffix('/').unwrap_or(&self.root_path);
150        url_string.push_str(root);
151
152        url::Url::parse(&url_string).map_err(|e| crate::error::ProductOSServerError::ConfigurationError {
153            component: "url_address".to_string(),
154            message: format!("Failed to parse URL '{}': {}", url_string, e),
155        })
156    }
157
158    /// Get socket address for binding
159    #[must_use]
160    pub fn socket_address(&self, port: Option<u16>, default_all: bool) -> SocketAddr {
161        let socket_port = port.unwrap_or(self.network.port);
162        Self::get_socket_address(&self.network.host, socket_port, default_all)
163    }
164
165    /// Create socket address from components
166    #[must_use]
167    pub fn get_socket_address(host: &str, port: u16, default_all: bool) -> SocketAddr {
168        product_os_utilities::ProductOSUtilities::get_socket_address(host, port, default_all)
169    }
170
171    /// Check if secure mode is enabled
172    #[must_use]
173    pub fn is_secure(&self) -> bool {
174        self.network.secure
175    }
176
177    /// Check if insecure connections are allowed
178    #[must_use]
179    pub fn all_insecure(&self) -> bool {
180        self.network.allow_insecure
181    }
182
183    /// Get insecure port number
184    #[must_use]
185    pub fn insecure_port(&self) -> u16 {
186        self.network.insecure_port
187    }
188
189    /// Check if insecure connections should force redirect to secure
190    #[must_use]
191    pub fn insecure_force_secure(&self) -> bool {
192        self.network.insecure_force_secure
193    }
194
195    /// Check if gzip compression is enabled
196    #[must_use]
197    pub fn is_compression_gzip(&self) -> bool {
198        self.compression.as_ref().map_or(false, |c| c.gzip)
199    }
200
201    /// Check if deflate compression is enabled
202    #[must_use]
203    pub fn is_compression_deflate(&self) -> bool {
204        self.compression.as_ref().map_or(false, |c| c.deflate)
205    }
206
207    /// Check if brotli compression is enabled
208    #[must_use]
209    pub fn is_compression_brotli(&self) -> bool {
210        self.compression.as_ref().map_or(false, |c| c.brotli)
211    }
212}
213
214impl ProductOSConfig for ServerConfig {
215    const SECTION_KEY: &'static str = "server";
216
217    fn validate(&self) -> Result<(), ConfigError> {
218        let mut errors = Vec::new();
219        if self.network.host.is_empty() && self.network.port == 0 {
220            errors.push("network host and port must be configured".into());
221        }
222        if errors.is_empty() {
223            Ok(())
224        } else {
225            Err(ConfigError::ValidationError(errors))
226        }
227    }
228}
229
230
231/// Network configuration for the server
232#[derive(Clone, Debug, Deserialize, Serialize, Default)]
233#[serde(rename_all = "camelCase")]
234pub struct Network {
235    /// Network protocol (http, https, etc.)
236    pub protocol: String,
237    /// Whether to use secure connections
238    pub secure: bool,
239    /// Server hostname
240    pub host: String,
241    /// Server port number
242    pub port: u16,
243    /// Whether to listen on all network interfaces
244    pub listen_all_interfaces: bool,
245    /// Whether to allow insecure connections
246    pub allow_insecure: bool,
247    /// Port for insecure connections
248    pub insecure_port: u16,
249    /// Whether to use a different port for insecure connections
250    pub insecure_use_different_port: bool,
251    /// Whether to force redirect from insecure to secure
252    pub insecure_force_secure: bool,
253}
254
255/// TLS/SSL certificate configuration
256#[derive(Clone, Debug, Deserialize, Serialize, Default)]
257#[serde(rename_all = "camelCase")]
258pub struct Certificate {
259    /// Certificate and key file paths
260    pub files: Option<CertificateFiles>,
261    /// Type of certificate (self-signed or CA-issued)
262    pub file_kind: Option<CertificateFilesKind>,
263    /// Certificate entries for generation
264    pub entries: Option<Vec<BTreeMap<String, serde_json::Value>>>,
265    /// Subject alternative names for the certificate
266    pub names: Option<Vec<String>>,
267    /// Certificate serial number
268    pub serial: Option<u32>,
269    /// Certificate validity period in days
270    pub valid_for: Option<u32>,
271}
272
273/// Certificate file paths
274#[derive(Clone, Debug, Deserialize, Serialize, Default)]
275#[serde(rename_all = "camelCase")]
276pub struct CertificateFiles {
277    /// Path to certificate file
278    pub cert_file: String,
279    /// Path to private key file
280    pub key_file: String,
281}
282
283/// Type of certificate
284#[derive(Clone, Copy, Debug, Deserialize, Serialize, PartialEq, Eq, Default)]
285#[serde(rename_all = "lowercase")]
286pub enum CertificateFilesKind {
287    /// Self-signed certificate
288    #[default]
289    SelfSigned,
290    /// Certificate Authority issued certificate
291    CA,
292}
293
294/// Compression configuration
295#[derive(Clone, Debug, Deserialize, Serialize, Default)]
296#[serde(rename_all = "camelCase")]
297pub struct Compression {
298    /// Enable compression
299    pub enable: bool,
300    /// Enable gzip compression
301    pub gzip: bool,
302    /// Enable deflate compression
303    pub deflate: bool,
304    /// Enable brotli compression
305    pub brotli: bool,
306}