Skip to main content

fraiseql_server/operational/
config.rs

1//! Configuration validation
2//!
3//! Validates server configuration at startup
4
5use serde::{Deserialize, Serialize};
6
7/// Server configuration
8#[derive(Debug, Clone, Serialize, Deserialize)]
9pub struct ServerConfig {
10    /// Server port
11    pub port:                 u16,
12    /// Server host
13    pub host:                 String,
14    /// Database URL
15    pub database_url:         String,
16    /// Log level
17    pub log_level:            String,
18    /// Request timeout in seconds
19    pub request_timeout_secs: u32,
20}
21
22/// Configuration validation result
23#[derive(Debug, Clone)]
24pub struct ValidationResult {
25    /// Is valid
26    pub valid:  bool,
27    /// Errors
28    pub errors: Vec<String>,
29}
30
31impl ValidationResult {
32    /// Create valid result
33    pub fn valid() -> Self {
34        Self {
35            valid:  true,
36            errors: Vec::new(),
37        }
38    }
39
40    /// Create invalid result with errors
41    pub fn invalid(errors: Vec<String>) -> Self {
42        Self {
43            valid: false,
44            errors,
45        }
46    }
47
48    /// Add an error
49    pub fn with_error(mut self, error: String) -> Self {
50        self.valid = false;
51        self.errors.push(error);
52        self
53    }
54}
55
56/// Validate server configuration
57pub fn validate_config(config: &ServerConfig) -> ValidationResult {
58    let mut errors = Vec::new();
59
60    // Validate port
61    if config.port == 0 {
62        errors.push("Port must not be zero".to_string());
63    }
64
65    // Validate host
66    if config.host.is_empty() {
67        errors.push("Host must not be empty".to_string());
68    }
69
70    // Validate database URL
71    if config.database_url.is_empty() {
72        errors.push("Database URL must not be empty".to_string());
73    }
74
75    // Validate log level
76    let valid_levels = ["debug", "info", "warn", "error"];
77    if !valid_levels.contains(&config.log_level.as_str()) {
78        errors.push(format!(
79            "Invalid log level: {}. Must be one of: {:?}",
80            config.log_level, valid_levels
81        ));
82    }
83
84    // Validate request timeout
85    if config.request_timeout_secs == 0 {
86        errors.push("Request timeout must be greater than zero".to_string());
87    }
88
89    if errors.is_empty() {
90        ValidationResult::valid()
91    } else {
92        ValidationResult::invalid(errors)
93    }
94}
95
96#[cfg(test)]
97mod tests {
98    use super::*;
99
100    #[test]
101    fn test_valid_config() {
102        let config = ServerConfig {
103            port:                 8080,
104            host:                 "0.0.0.0".to_string(),
105            database_url:         "postgres://localhost/db".to_string(),
106            log_level:            "info".to_string(),
107            request_timeout_secs: 30,
108        };
109
110        let result = validate_config(&config);
111        assert!(result.valid);
112        assert!(result.errors.is_empty());
113    }
114
115    #[test]
116    fn test_invalid_config() {
117        let config = ServerConfig {
118            port:                 0,
119            host:                 "".to_string(),
120            database_url:         "".to_string(),
121            log_level:            "invalid".to_string(),
122            request_timeout_secs: 0,
123        };
124
125        let result = validate_config(&config);
126        assert!(!result.valid);
127        assert!(!result.errors.is_empty());
128    }
129}