Skip to main content

auth_framework/server/core/
common_config.rs

1//! Common Configuration Framework
2//!
3//! This module provides shared configuration patterns and utilities
4//! to eliminate duplication across server modules.
5
6use crate::errors::Result;
7use serde::{Deserialize, Serialize};
8use std::collections::HashMap;
9use std::time::Duration;
10
11/// Base configuration trait that all server configs must implement.
12pub trait ServerConfig {
13    /// Validate the configuration.
14    ///
15    /// Implementors **must** override this method to perform meaningful validation.
16    /// The default returns `Ok(())` only for backward compatibility with existing
17    /// implementors; new types should always provide a real check.
18    fn validate(&self) -> Result<()> {
19        Ok(())
20    }
21
22    /// Get configuration name for logging/debugging
23    fn config_name(&self) -> &'static str;
24
25    /// Check if configuration is enabled
26    fn is_enabled(&self) -> bool {
27        true
28    }
29}
30
31/// Common timeout configuration used across modules
32#[derive(Debug, Clone, Serialize, Deserialize)]
33pub struct TimeoutConfig {
34    /// Connection timeout
35    pub connect_timeout: Duration,
36    /// Read timeout
37    pub read_timeout: Duration,
38    /// Write timeout
39    pub write_timeout: Duration,
40}
41
42impl Default for TimeoutConfig {
43    fn default() -> Self {
44        Self {
45            connect_timeout: Duration::from_secs(30),
46            read_timeout: Duration::from_secs(30),
47            write_timeout: Duration::from_secs(30),
48        }
49    }
50}
51
52/// Common security configuration patterns
53#[derive(Debug, Clone, Serialize, Deserialize)]
54pub struct SecurityConfig {
55    /// Enable TLS
56    pub enable_tls: bool,
57    /// Minimum TLS version
58    pub min_tls_version: String,
59    /// Allowed cipher suites
60    pub cipher_suites: Vec<String>,
61    /// Certificate validation mode
62    pub cert_validation: CertificateValidation,
63    /// Whether to verify certificates (legacy compatibility)
64    pub verify_certificates: bool,
65    /// Accept invalid TLS certificates on outbound connections.
66    /// Default: `false`. Only enable for development/testing — never in production.
67    #[serde(default)]
68    pub accept_invalid_certs: bool,
69}
70
71impl Default for SecurityConfig {
72    fn default() -> Self {
73        Self {
74            enable_tls: true,
75            min_tls_version: "1.2".to_string(),
76            cipher_suites: vec![
77                "TLS_AES_256_GCM_SHA384".to_string(),
78                "TLS_CHACHA20_POLY1305_SHA256".to_string(),
79                "TLS_AES_128_GCM_SHA256".to_string(),
80            ],
81            cert_validation: CertificateValidation::Full,
82            verify_certificates: true,
83            accept_invalid_certs: false,
84        }
85    }
86}
87
88/// Certificate validation modes
89#[derive(Debug, Clone, Serialize, Deserialize)]
90pub enum CertificateValidation {
91    /// Full certificate chain validation
92    Full,
93    /// Skip hostname verification
94    SkipHostname,
95    /// Skip all certificate validation (insecure)
96    None,
97}
98
99/// Common endpoint configuration
100#[derive(Debug, Clone, Serialize, Deserialize)]
101pub struct EndpointConfig {
102    /// Base URL
103    pub base_url: String,
104    /// API version
105    pub api_version: Option<String>,
106    /// Custom headers
107    pub headers: HashMap<String, String>,
108    /// Timeout configuration
109    pub timeout: TimeoutConfig,
110    /// Security configuration
111    pub security: SecurityConfig,
112}
113
114impl EndpointConfig {
115    /// Create a new endpoint config with defaults
116    pub fn new(base_url: impl Into<String>) -> Self {
117        Self {
118            base_url: base_url.into(),
119            api_version: None,
120            headers: HashMap::new(),
121            timeout: TimeoutConfig::default(),
122            security: SecurityConfig::default(),
123        }
124    }
125
126    /// Add a custom header
127    pub fn with_header(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
128        self.headers.insert(key.into(), value.into());
129        self
130    }
131
132    /// Set API version
133    pub fn with_api_version(mut self, version: impl Into<String>) -> Self {
134        self.api_version = Some(version.into());
135        self
136    }
137}
138
139/// Common retry configuration
140#[derive(Debug, Clone, Serialize, Deserialize)]
141pub struct RetryConfig {
142    /// Maximum retry attempts
143    pub max_attempts: u32,
144    /// Initial delay between retries
145    pub initial_delay: Duration,
146    /// Maximum delay between retries
147    pub max_delay: Duration,
148    /// Backoff multiplier
149    pub backoff_multiplier: f64,
150    /// Jitter factor (0.0 to 1.0)
151    pub jitter_factor: f64,
152}
153
154impl Default for RetryConfig {
155    fn default() -> Self {
156        Self {
157            max_attempts: 3,
158            initial_delay: Duration::from_millis(100),
159            max_delay: Duration::from_secs(30),
160            backoff_multiplier: 2.0,
161            jitter_factor: 0.1,
162        }
163    }
164}
165
166/// Common logging configuration
167#[derive(Debug, Clone, Serialize, Deserialize)]
168pub struct LoggingConfig {
169    /// Enable debug logging
170    pub debug: bool,
171    /// Log request/response bodies
172    pub log_bodies: bool,
173    /// Log sensitive fields (tokens, keys)
174    pub log_sensitive: bool,
175    /// Maximum log message size
176    pub max_log_size: usize,
177}
178
179impl Default for LoggingConfig {
180    fn default() -> Self {
181        Self {
182            debug: false,
183            log_bodies: false,
184            log_sensitive: false,
185            max_log_size: 4096,
186        }
187    }
188}
189
190/// Configuration validation utilities
191pub mod validation {
192    use super::*;
193
194    /// Validate URL format
195    pub fn validate_url(url: &str) -> Result<()> {
196        if url.is_empty() {
197            return Err(crate::errors::AuthError::config(
198                "URL cannot be empty".to_string(),
199            ));
200        }
201
202        if !url.starts_with("http://") && !url.starts_with("https://") {
203            return Err(crate::errors::AuthError::config(format!(
204                "Invalid URL format: {}",
205                url
206            )));
207        }
208
209        Ok(())
210    }
211
212    /// Validate duration is positive
213    pub fn validate_positive_duration(duration: &Duration, field_name: &str) -> Result<()> {
214        if duration.is_zero() {
215            return Err(crate::errors::AuthError::config(format!(
216                "{} must be greater than zero",
217                field_name
218            )));
219        }
220        Ok(())
221    }
222
223    /// Validate port number
224    pub fn validate_port(port: u16) -> Result<()> {
225        if port == 0 {
226            return Err(crate::errors::AuthError::config(
227                "Port cannot be zero".to_string(),
228            ));
229        }
230        Ok(())
231    }
232
233    /// Validate required field is not empty
234    pub fn validate_required_field(value: &str, field_name: &str) -> Result<()> {
235        if value.trim().is_empty() {
236            return Err(crate::errors::AuthError::config(format!(
237                "{} is required and cannot be empty",
238                field_name
239            )));
240        }
241        Ok(())
242    }
243}
244
245#[cfg(test)]
246mod tests {
247    use super::*;
248
249    #[test]
250    fn test_timeout_config_default() {
251        let tc = TimeoutConfig::default();
252        assert_eq!(tc.connect_timeout, Duration::from_secs(30));
253        assert_eq!(tc.read_timeout, Duration::from_secs(30));
254    }
255
256    #[test]
257    fn test_security_config_default() {
258        let sc = SecurityConfig::default();
259        assert!(sc.enable_tls);
260        assert!(sc.verify_certificates);
261    }
262
263    #[test]
264    fn test_retry_config_default() {
265        let rc = RetryConfig::default();
266        assert!(rc.max_attempts > 0);
267    }
268
269    #[test]
270    fn test_logging_config_default() {
271        let lc = LoggingConfig::default();
272        assert!(!lc.debug);
273        assert!(!lc.log_bodies);
274        assert!(!lc.log_sensitive);
275        assert!(lc.max_log_size > 0);
276    }
277
278    #[test]
279    fn test_endpoint_config_new() {
280        let ec = EndpointConfig::new("https://api.example.com");
281        assert_eq!(ec.base_url, "https://api.example.com");
282        assert!(ec.api_version.is_none());
283    }
284
285    #[test]
286    fn test_endpoint_config_with_header() {
287        let ec = EndpointConfig::new("https://api.example.com")
288            .with_header("Authorization", "Bearer xxx");
289        assert_eq!(ec.headers.get("Authorization").unwrap(), "Bearer xxx");
290    }
291
292    #[test]
293    fn test_endpoint_config_with_api_version() {
294        let ec = EndpointConfig::new("https://api.example.com").with_api_version("2024-01-01");
295        assert_eq!(ec.api_version.as_deref(), Some("2024-01-01"));
296    }
297
298    #[test]
299    fn test_validate_url_valid() {
300        assert!(validation::validate_url("https://example.com").is_ok());
301    }
302
303    #[test]
304    fn test_validate_url_empty() {
305        assert!(validation::validate_url("").is_err());
306    }
307
308    #[test]
309    fn test_validate_url_no_scheme() {
310        assert!(validation::validate_url("example.com").is_err());
311    }
312
313    #[test]
314    fn test_validate_positive_duration() {
315        assert!(validation::validate_positive_duration(&Duration::from_secs(1), "timeout").is_ok());
316    }
317
318    #[test]
319    fn test_validate_zero_duration() {
320        assert!(validation::validate_positive_duration(&Duration::ZERO, "timeout").is_err());
321    }
322
323    #[test]
324    fn test_validate_port() {
325        assert!(validation::validate_port(8080).is_ok());
326        assert!(validation::validate_port(0).is_err());
327    }
328
329    #[test]
330    fn test_validate_required_field() {
331        assert!(validation::validate_required_field("value", "name").is_ok());
332        assert!(validation::validate_required_field("", "name").is_err());
333        assert!(validation::validate_required_field("  ", "name").is_err());
334    }
335
336    #[test]
337    fn test_security_config_accept_invalid_certs_defaults_false() {
338        let sc = SecurityConfig::default();
339        assert!(
340            !sc.accept_invalid_certs,
341            "accept_invalid_certs must default to false"
342        );
343    }
344
345    #[test]
346    fn test_security_config_accept_invalid_certs_deserialization_default() {
347        // A JSON object missing accept_invalid_certs should deserialize to false
348        let json = r#"{"enable_tls":true,"min_tls_version":"1.2","cipher_suites":[],"cert_validation":"Full","verify_certificates":true}"#;
349        let sc: SecurityConfig = serde_json::from_str(json).unwrap();
350        assert!(!sc.accept_invalid_certs);
351    }
352}