web_server_abstraction/
config.rs

1//! Unified Configuration System
2//!
3//! This module provides a comprehensive configuration management system
4//! with support for multiple configuration sources, validation, and dynamic updates.
5
6use serde::{Deserialize, Serialize};
7use serde_yaml;
8use std::collections::HashMap;
9use std::path::Path;
10use std::sync::Arc;
11use thiserror::Error;
12use tokio::sync::RwLock;
13
14/// Configuration error types
15#[derive(Error, Debug)]
16pub enum ConfigError {
17    #[error("IO error: {0}")]
18    Io(#[from] std::io::Error),
19    #[error("YAML parsing error: {0}")]
20    Yaml(#[from] serde_yaml::Error),
21    #[error("Configuration validation error: {message}")]
22    Validation { message: String },
23    #[error("Key not found: {key}")]
24    KeyNotFound { key: String },
25    #[error("Type conversion error: {0}")]
26    TypeConversion(String),
27}
28
29/// Configuration value type
30#[derive(Debug, Clone, Serialize, Deserialize)]
31#[serde(untagged)]
32pub enum ConfigValue {
33    String(String),
34    Integer(i64),
35    Float(f64),
36    Boolean(bool),
37    Array(Vec<ConfigValue>),
38    Object(HashMap<String, ConfigValue>),
39}
40
41impl ConfigValue {
42    pub fn as_string(&self) -> Result<String, ConfigError> {
43        match self {
44            ConfigValue::String(s) => Ok(s.clone()),
45            _ => Err(ConfigError::TypeConversion("Expected string".to_string())),
46        }
47    }
48
49    pub fn as_bool(&self) -> Result<bool, ConfigError> {
50        match self {
51            ConfigValue::Boolean(b) => Ok(*b),
52            _ => Err(ConfigError::TypeConversion("Expected boolean".to_string())),
53        }
54    }
55
56    pub fn as_i64(&self) -> Result<i64, ConfigError> {
57        match self {
58            ConfigValue::Integer(i) => Ok(*i),
59            _ => Err(ConfigError::TypeConversion("Expected integer".to_string())),
60        }
61    }
62}
63
64/// Configuration manager
65pub struct ConfigManager {
66    data: Arc<RwLock<HashMap<String, ConfigValue>>>,
67}
68
69impl Default for ConfigManager {
70    fn default() -> Self {
71        Self::new()
72    }
73}
74
75impl ConfigManager {
76    pub fn new() -> Self {
77        Self {
78            data: Arc::new(RwLock::new(HashMap::new())),
79        }
80    }
81
82    pub async fn load_from_file<P: AsRef<Path>>(&self, path: P) -> Result<(), ConfigError> {
83        let content = tokio::fs::read_to_string(path).await?;
84        let yaml_value: serde_yaml::Value = serde_yaml::from_str(&content)?;
85
86        let mut data = self.data.write().await;
87        self.load_yaml_recursive(&yaml_value, "", &mut data)?;
88        Ok(())
89    }
90
91    fn load_yaml_recursive(
92        &self,
93        value: &serde_yaml::Value,
94        prefix: &str,
95        data: &mut HashMap<String, ConfigValue>,
96    ) -> Result<(), ConfigError> {
97        match value {
98            serde_yaml::Value::Mapping(map) => {
99                for (key, val) in map {
100                    let key_str = key.as_str().ok_or_else(|| ConfigError::Validation {
101                        message: "Non-string key in YAML".to_string(),
102                    })?;
103                    let full_key = if prefix.is_empty() {
104                        key_str.to_string()
105                    } else {
106                        format!("{}.{}", prefix, key_str)
107                    };
108
109                    self.load_yaml_recursive(val, &full_key, data)?;
110                }
111            }
112            _ => {
113                let config_value = self.yaml_to_config_value(value)?;
114                data.insert(prefix.to_string(), config_value);
115            }
116        }
117        Ok(())
118    }
119
120    fn yaml_to_config_value(&self, value: &serde_yaml::Value) -> Result<ConfigValue, ConfigError> {
121        match value {
122            serde_yaml::Value::String(s) => Ok(ConfigValue::String(s.clone())),
123            serde_yaml::Value::Number(n) => {
124                if let Some(i) = n.as_i64() {
125                    Ok(ConfigValue::Integer(i))
126                } else if let Some(f) = n.as_f64() {
127                    Ok(ConfigValue::Float(f))
128                } else {
129                    Err(ConfigError::TypeConversion("Invalid number".to_string()))
130                }
131            }
132            serde_yaml::Value::Bool(b) => Ok(ConfigValue::Boolean(*b)),
133            serde_yaml::Value::Sequence(seq) => {
134                let array: Result<Vec<_>, _> =
135                    seq.iter().map(|v| self.yaml_to_config_value(v)).collect();
136                Ok(ConfigValue::Array(array?))
137            }
138            serde_yaml::Value::Mapping(map) => {
139                let mut object = HashMap::new();
140                for (k, v) in map {
141                    let key = k
142                        .as_str()
143                        .ok_or_else(|| ConfigError::TypeConversion("Non-string key".to_string()))?;
144                    object.insert(key.to_string(), self.yaml_to_config_value(v)?);
145                }
146                Ok(ConfigValue::Object(object))
147            }
148            serde_yaml::Value::Null => Ok(ConfigValue::String("".to_string())),
149            serde_yaml::Value::Tagged(tagged) => {
150                // Handle tagged values by processing the inner value
151                self.yaml_to_config_value(&tagged.value)
152            }
153        }
154    }
155
156    pub async fn get(&self, key: &str) -> Result<ConfigValue, ConfigError> {
157        let data = self.data.read().await;
158        data.get(key)
159            .cloned()
160            .ok_or_else(|| ConfigError::KeyNotFound {
161                key: key.to_string(),
162            })
163    }
164
165    pub async fn set(&self, key: &str, value: ConfigValue) -> Result<(), ConfigError> {
166        let mut data = self.data.write().await;
167        data.insert(key.to_string(), value);
168        Ok(())
169    }
170
171    pub async fn get_value(&self, key: &str) -> Result<ConfigValue, ConfigError> {
172        self.get(key).await
173    }
174
175    pub async fn set_value(&self, key: &str, value: ConfigValue) -> Result<(), ConfigError> {
176        self.set(key, value).await
177    }
178
179    pub async fn watch(&self, _key: &str) -> Result<(), ConfigError> {
180        // Simple implementation - just return ok for now
181        Ok(())
182    }
183
184    pub async fn save_to_file<P: AsRef<Path>>(&self, _path: P) -> Result<(), ConfigError> {
185        // Simple implementation - just return ok for now
186        Ok(())
187    }
188}
189
190/// Adapter configuration
191#[derive(Debug, Clone, Serialize, Deserialize)]
192pub struct AdapterConfig {
193    pub name: String,
194    pub enabled: bool,
195    pub settings: HashMap<String, ConfigValue>,
196}
197
198/// Middleware configuration
199#[derive(Debug, Clone, Serialize, Deserialize)]
200pub struct MiddlewareConfig {
201    pub cors: CorsConfig,
202    pub compression: CompressionConfig,
203    pub rate_limiting: RateLimitingConfig,
204    pub security_headers: SecurityHeadersConfig,
205}
206
207/// CORS configuration
208#[derive(Debug, Clone, Serialize, Deserialize)]
209pub struct CorsConfig {
210    pub enabled: bool,
211    pub allowed_origins: Vec<String>,
212    pub allowed_methods: Vec<String>,
213    pub allowed_headers: Vec<String>,
214    pub credentials: bool,
215    pub max_age: u32,
216}
217
218impl Default for CorsConfig {
219    fn default() -> Self {
220        Self {
221            enabled: true,
222            allowed_origins: vec!["*".to_string()],
223            allowed_methods: vec![
224                "GET".to_string(),
225                "POST".to_string(),
226                "PUT".to_string(),
227                "DELETE".to_string(),
228            ],
229            allowed_headers: vec!["Content-Type".to_string(), "Authorization".to_string()],
230            credentials: false,
231            max_age: 3600,
232        }
233    }
234}
235
236/// Compression configuration
237#[derive(Debug, Clone, Serialize, Deserialize)]
238pub struct CompressionConfig {
239    pub enabled: bool,
240    pub algorithms: Vec<String>,
241    pub min_size: usize,
242}
243
244impl Default for CompressionConfig {
245    fn default() -> Self {
246        Self {
247            enabled: true,
248            algorithms: vec!["gzip".to_string(), "br".to_string()],
249            min_size: 1024,
250        }
251    }
252}
253
254/// Rate limiting configuration
255#[derive(Debug, Clone, Serialize, Deserialize)]
256pub struct RateLimitingConfig {
257    pub enabled: bool,
258    pub requests_per_minute: u64,
259    pub burst_size: u64,
260    pub window_size: u64,
261}
262
263impl Default for RateLimitingConfig {
264    fn default() -> Self {
265        Self {
266            enabled: true,
267            requests_per_minute: 1000,
268            burst_size: 100,
269            window_size: 60,
270        }
271    }
272}
273
274/// Security headers configuration
275#[derive(Debug, Clone, Serialize, Deserialize)]
276pub struct SecurityHeadersConfig {
277    pub enabled: bool,
278    pub content_security_policy: Option<String>,
279    pub x_frame_options: Option<String>,
280    pub x_content_type_options: bool,
281    pub strict_transport_security: Option<String>,
282}
283
284impl Default for SecurityHeadersConfig {
285    fn default() -> Self {
286        Self {
287            enabled: true,
288            content_security_policy: Some("default-src 'self'".to_string()),
289            x_frame_options: Some("DENY".to_string()),
290            x_content_type_options: true,
291            strict_transport_security: Some("max-age=31536000; includeSubDomains".to_string()),
292        }
293    }
294}
295
296/// Security configuration
297#[derive(Debug, Clone, Serialize, Deserialize)]
298pub struct SecurityConfig {
299    pub enable_csrf_protection: bool,
300    pub enable_rate_limiting: bool,
301    pub enable_input_validation: bool,
302    pub enable_request_logging: bool,
303    pub max_request_size: usize,
304    pub allowed_file_types: Vec<String>,
305    pub tls: TlsConfig,
306    pub rate_limit_per_minute: Option<u32>,
307}
308
309impl Default for SecurityConfig {
310    fn default() -> Self {
311        Self {
312            enable_csrf_protection: true,
313            enable_rate_limiting: true,
314            enable_input_validation: true,
315            enable_request_logging: true,
316            max_request_size: 10 * 1024 * 1024, // 10MB
317            allowed_file_types: vec!["jpg".to_string(), "png".to_string(), "pdf".to_string()],
318            tls: TlsConfig::default(),
319            rate_limit_per_minute: Some(60), // Default rate limit
320        }
321    }
322}
323
324/// Monitoring configuration
325#[derive(Debug, Clone, Serialize, Deserialize)]
326pub struct MonitoringConfig {
327    pub enable_metrics: bool,
328    pub enable_tracing: bool,
329    pub enable_health_checks: bool,
330    pub enable_alerts: bool,
331    pub metrics_endpoint: String,
332    pub health_endpoint: String,
333}
334
335impl Default for MonitoringConfig {
336    fn default() -> Self {
337        Self {
338            enable_metrics: true,
339            enable_tracing: true,
340            enable_health_checks: true,
341            enable_alerts: true,
342            metrics_endpoint: "/metrics".to_string(),
343            health_endpoint: "/health".to_string(),
344        }
345    }
346}
347
348/// TLS configuration
349#[derive(Debug, Clone, Serialize, Deserialize)]
350pub struct TlsConfig {
351    pub enabled: bool,
352    pub cert_file: String,
353    pub key_file: String,
354    pub ca_file: Option<String>,
355    pub verify_client: bool,
356}
357
358impl Default for TlsConfig {
359    fn default() -> Self {
360        Self {
361            enabled: false,
362            cert_file: "cert.pem".to_string(),
363            key_file: "key.pem".to_string(),
364            ca_file: None,
365            verify_client: false,
366        }
367    }
368}
369
370/// Feature flags configuration
371#[derive(Debug, Clone, Serialize, Deserialize)]
372pub struct FeatureFlags {
373    pub enable_caching: bool,
374    pub enable_compression: bool,
375    pub enable_websockets: bool,
376    pub enable_sse: bool,
377    pub enable_graphql: bool,
378}
379
380impl Default for FeatureFlags {
381    fn default() -> Self {
382        Self {
383            enable_caching: true,
384            enable_compression: true,
385            enable_websockets: false,
386            enable_sse: false,
387            enable_graphql: false,
388        }
389    }
390}
391
392/// Main web server configuration
393#[derive(Debug, Clone, Serialize, Deserialize)]
394pub struct WebServerConfig {
395    pub server: ServerConfig,
396    pub adapters: Vec<AdapterConfig>,
397    pub middleware: MiddlewareConfig,
398    pub security: SecurityConfig,
399    pub monitoring: MonitoringConfig,
400    pub tls: TlsConfig,
401    pub feature_flags: FeatureFlags,
402}
403
404/// Server configuration
405#[derive(Debug, Clone, Serialize, Deserialize)]
406pub struct ServerConfig {
407    pub host: String,
408    pub port: u16,
409    pub max_connections: usize,
410    pub timeout: u64,
411    pub keep_alive: bool,
412}
413
414impl Default for ServerConfig {
415    fn default() -> Self {
416        Self {
417            host: "127.0.0.1".to_string(),
418            port: 8080,
419            max_connections: 1000,
420            timeout: 30,
421            keep_alive: true,
422        }
423    }
424}
425
426impl Default for WebServerConfig {
427    fn default() -> Self {
428        Self {
429            server: ServerConfig::default(),
430            adapters: vec![],
431            middleware: MiddlewareConfig {
432                cors: CorsConfig::default(),
433                compression: CompressionConfig::default(),
434                rate_limiting: RateLimitingConfig::default(),
435                security_headers: SecurityHeadersConfig::default(),
436            },
437            security: SecurityConfig::default(),
438            monitoring: MonitoringConfig::default(),
439            tls: TlsConfig::default(),
440            feature_flags: FeatureFlags::default(),
441        }
442    }
443}
444
445/// Unified configuration manager
446pub struct UnifiedConfigManager {
447    manager: ConfigManager,
448    config: Arc<RwLock<WebServerConfig>>,
449}
450
451impl UnifiedConfigManager {
452    pub async fn new() -> Result<Self, ConfigError> {
453        let manager = ConfigManager::new();
454        let config = Arc::new(RwLock::new(WebServerConfig::default()));
455
456        Ok(Self { manager, config })
457    }
458
459    pub async fn load_from_file<P: AsRef<Path>>(&self, path: P) -> Result<(), ConfigError> {
460        self.manager.load_from_file(path).await?;
461        self.sync_config().await
462    }
463
464    async fn sync_config(&self) -> Result<(), ConfigError> {
465        // This is a simplified sync - in a real implementation you'd parse all the nested config
466        Ok(())
467    }
468
469    pub async fn get_server_config(&self) -> ServerConfig {
470        self.config.read().await.server.clone()
471    }
472
473    pub async fn get_adapters_config(&self) -> Result<Vec<AdapterConfig>, ConfigError> {
474        Ok(self.config.read().await.adapters.clone())
475    }
476
477    pub async fn get_middleware_config(&self) -> Result<MiddlewareConfig, ConfigError> {
478        Ok(self.config.read().await.middleware.clone())
479    }
480
481    pub async fn get_security_config(&self) -> Result<SecurityConfig, ConfigError> {
482        Ok(self.config.read().await.security.clone())
483    }
484
485    pub async fn get_monitoring_config(&self) -> Result<MonitoringConfig, ConfigError> {
486        Ok(self.config.read().await.monitoring.clone())
487    }
488
489    pub async fn get_feature_flags(&self) -> Result<FeatureFlags, ConfigError> {
490        Ok(self.config.read().await.feature_flags.clone())
491    }
492
493    pub async fn is_feature_enabled(&self, feature: &str) -> Result<bool, ConfigError> {
494        let flags = self.get_feature_flags().await?;
495        let enabled = match feature {
496            "caching" => flags.enable_caching,
497            "compression" => flags.enable_compression,
498            "websockets" => flags.enable_websockets,
499            "sse" => flags.enable_sse,
500            "graphql" => flags.enable_graphql,
501            _ => false,
502        };
503        Ok(enabled)
504    }
505
506    pub async fn set_feature(&self, key: &str, value: ConfigValue) -> Result<(), ConfigError> {
507        self.manager.set(key, value).await
508    }
509
510    pub async fn watch_config(&self, _key: &str) -> Result<(), ConfigError> {
511        self.manager.watch(_key).await
512    }
513
514    pub async fn save_config<P: AsRef<Path>>(&self, path: P) -> Result<(), ConfigError> {
515        self.manager.save_to_file(path).await
516    }
517}
518
519#[cfg(test)]
520mod tests {
521    use super::*;
522    use tokio;
523
524    #[tokio::test]
525    async fn test_config_manager_creation() {
526        let manager = ConfigManager::new();
527
528        // Test setting and getting values
529        let test_value = ConfigValue::String("test".to_string());
530        manager.set("test_key", test_value.clone()).await.unwrap();
531
532        let retrieved = manager.get("test_key").await.unwrap();
533        match (test_value, retrieved) {
534            (ConfigValue::String(expected), ConfigValue::String(actual)) => {
535                assert_eq!(expected, actual);
536            }
537            _ => panic!("Values don't match"),
538        }
539    }
540
541    #[tokio::test]
542    async fn test_unified_config_manager() {
543        let manager = UnifiedConfigManager::new().await.unwrap();
544
545        // Test getting default configs
546        let server_config = manager.get_server_config().await;
547        assert_eq!(server_config.host, "127.0.0.1");
548        assert_eq!(server_config.port, 8080);
549
550        let feature_flags = manager.get_feature_flags().await.unwrap();
551        assert!(feature_flags.enable_caching);
552        assert!(feature_flags.enable_compression);
553
554        // Test feature checking
555        let caching_enabled = manager.is_feature_enabled("caching").await.unwrap();
556        assert!(caching_enabled);
557
558        let unknown_feature = manager.is_feature_enabled("unknown").await.unwrap();
559        assert!(!unknown_feature);
560    }
561
562    #[tokio::test]
563    async fn test_config_value_conversions() {
564        let string_val = ConfigValue::String("test".to_string());
565        assert_eq!(string_val.as_string().unwrap(), "test");
566
567        let bool_val = ConfigValue::Boolean(true);
568        assert!(bool_val.as_bool().unwrap());
569
570        let int_val = ConfigValue::Integer(42);
571        assert_eq!(int_val.as_i64().unwrap(), 42);
572
573        // Test type conversion errors
574        assert!(string_val.as_bool().is_err());
575        assert!(bool_val.as_string().is_err());
576        assert!(int_val.as_string().is_err());
577    }
578}