amaters_server/
config.rs

1//! Server configuration module
2//!
3//! This module handles configuration loading from multiple sources:
4//! 1. Default values
5//! 2. TOML configuration file
6//! 3. Environment variables
7//! 4. CLI arguments (highest priority)
8
9use serde::{Deserialize, Serialize};
10use std::net::SocketAddr;
11use std::path::{Path, PathBuf};
12use std::time::Duration;
13use thiserror::Error;
14
15/// Configuration errors
16#[derive(Error, Debug)]
17pub enum ConfigError {
18    #[error("Failed to read configuration file: {0}")]
19    ReadFile(#[from] std::io::Error),
20
21    #[error("Failed to parse TOML: {0}")]
22    ParseToml(#[from] toml::de::Error),
23
24    #[error("Validation error: {0}")]
25    Validation(String),
26
27    #[error("Invalid socket address: {0}")]
28    InvalidAddress(#[from] std::net::AddrParseError),
29}
30
31pub type ConfigResult<T> = Result<T, ConfigError>;
32
33/// Main server configuration
34#[derive(Debug, Clone, Serialize, Deserialize)]
35pub struct ServerConfig {
36    /// Server settings
37    pub server: ServerSettings,
38
39    /// Storage settings
40    pub storage: StorageSettings,
41
42    /// Network settings
43    pub network: NetworkSettings,
44
45    /// Cluster settings (optional)
46    #[serde(default)]
47    pub cluster: Option<ClusterSettings>,
48
49    /// Logging settings
50    pub logging: LoggingSettings,
51
52    /// Metrics settings
53    pub metrics: MetricsSettings,
54
55    /// Authentication settings
56    #[serde(default)]
57    pub auth: AuthSettings,
58
59    /// Authorization settings
60    #[serde(default)]
61    pub authz: AuthorizationSettings,
62}
63
64/// Server-specific settings
65#[derive(Debug, Clone, Serialize, Deserialize)]
66pub struct ServerSettings {
67    /// Server bind address
68    pub bind_address: String,
69
70    /// Data directory
71    pub data_dir: PathBuf,
72
73    /// PID file location (for stop/status commands)
74    #[serde(default = "default_pid_file")]
75    pub pid_file: PathBuf,
76
77    /// Maximum number of concurrent connections
78    #[serde(default = "default_max_connections")]
79    pub max_connections: usize,
80
81    /// Shutdown timeout
82    #[serde(default = "default_shutdown_timeout")]
83    pub shutdown_timeout_secs: u64,
84}
85
86/// Storage engine settings
87#[derive(Debug, Clone, Serialize, Deserialize)]
88pub struct StorageSettings {
89    /// Storage engine type (memory, lsm)
90    #[serde(default = "default_storage_engine")]
91    pub engine: String,
92
93    /// Write-ahead log settings
94    #[serde(default)]
95    pub wal: WalSettings,
96
97    /// Memtable size in MB
98    #[serde(default = "default_memtable_size")]
99    pub memtable_size_mb: usize,
100
101    /// Block cache size in MB
102    #[serde(default = "default_block_cache_size")]
103    pub block_cache_size_mb: usize,
104
105    /// Compaction settings
106    #[serde(default)]
107    pub compaction: CompactionSettings,
108}
109
110/// Write-ahead log settings
111#[derive(Debug, Clone, Serialize, Deserialize)]
112pub struct WalSettings {
113    /// Enable WAL
114    #[serde(default = "default_true")]
115    pub enabled: bool,
116
117    /// WAL directory (relative to data_dir)
118    #[serde(default = "default_wal_dir")]
119    pub dir: PathBuf,
120
121    /// WAL segment size in MB
122    #[serde(default = "default_wal_segment_size")]
123    pub segment_size_mb: usize,
124
125    /// Sync mode (always, interval, none)
126    #[serde(default = "default_sync_mode")]
127    pub sync_mode: String,
128}
129
130/// Compaction settings
131#[derive(Debug, Clone, Serialize, Deserialize)]
132pub struct CompactionSettings {
133    /// Compaction strategy (leveled, tiered, universal)
134    #[serde(default = "default_compaction_strategy")]
135    pub strategy: String,
136
137    /// Number of levels
138    #[serde(default = "default_num_levels")]
139    pub num_levels: usize,
140
141    /// Level size multiplier
142    #[serde(default = "default_level_multiplier")]
143    pub level_multiplier: usize,
144
145    /// Maximum number of concurrent compactions
146    #[serde(default = "default_max_compactions")]
147    pub max_concurrent: usize,
148}
149
150/// Network settings
151#[derive(Debug, Clone, Serialize, Deserialize)]
152pub struct NetworkSettings {
153    /// Enable TLS
154    #[serde(default = "default_false")]
155    pub tls_enabled: bool,
156
157    /// TLS certificate file
158    pub tls_cert: Option<PathBuf>,
159
160    /// TLS key file
161    pub tls_key: Option<PathBuf>,
162
163    /// TLS CA file (for mTLS)
164    pub tls_ca: Option<PathBuf>,
165
166    /// Require client certificates (mTLS)
167    #[serde(default = "default_false")]
168    pub require_client_cert: bool,
169
170    /// Connection timeout in seconds
171    #[serde(default = "default_connection_timeout")]
172    pub connection_timeout_secs: u64,
173
174    /// Keep-alive interval in seconds
175    #[serde(default = "default_keepalive_interval")]
176    pub keepalive_interval_secs: u64,
177}
178
179/// Cluster settings (Raft consensus)
180#[derive(Debug, Clone, Serialize, Deserialize)]
181pub struct ClusterSettings {
182    /// Enable clustering
183    #[serde(default = "default_true")]
184    pub enabled: bool,
185
186    /// Node ID (must be unique in cluster)
187    pub node_id: u64,
188
189    /// Cluster peers (node_id:address)
190    pub peers: Vec<String>,
191
192    /// Raft heartbeat interval in milliseconds
193    #[serde(default = "default_heartbeat_interval")]
194    pub heartbeat_interval_ms: u64,
195
196    /// Raft election timeout in milliseconds
197    #[serde(default = "default_election_timeout")]
198    pub election_timeout_ms: u64,
199}
200
201/// Logging settings
202#[derive(Debug, Clone, Serialize, Deserialize)]
203pub struct LoggingSettings {
204    /// Log level (trace, debug, info, warn, error)
205    #[serde(default = "default_log_level")]
206    pub level: String,
207
208    /// Log format (json, pretty, compact)
209    #[serde(default = "default_log_format")]
210    pub format: String,
211
212    /// Log to file
213    #[serde(default = "default_false")]
214    pub file_enabled: bool,
215
216    /// Log file path
217    pub file_path: Option<PathBuf>,
218
219    /// Log rotation settings
220    #[serde(default)]
221    pub rotation: LogRotationSettings,
222}
223
224/// Log rotation settings
225#[derive(Debug, Clone, Serialize, Deserialize)]
226pub struct LogRotationSettings {
227    /// Enable rotation
228    #[serde(default = "default_true")]
229    pub enabled: bool,
230
231    /// Max file size in MB
232    #[serde(default = "default_log_max_size")]
233    pub max_size_mb: usize,
234
235    /// Max number of backup files
236    #[serde(default = "default_log_max_backups")]
237    pub max_backups: usize,
238}
239
240/// Metrics settings
241#[derive(Debug, Clone, Serialize, Deserialize)]
242pub struct MetricsSettings {
243    /// Enable metrics collection
244    #[serde(default = "default_true")]
245    pub enabled: bool,
246
247    /// Metrics bind address
248    #[serde(default = "default_metrics_address")]
249    pub bind_address: String,
250
251    /// Metrics export interval in seconds
252    #[serde(default = "default_metrics_interval")]
253    pub export_interval_secs: u64,
254}
255
256/// Authentication settings
257#[derive(Debug, Clone, Serialize, Deserialize)]
258pub struct AuthSettings {
259    /// Enable authentication
260    #[serde(default = "default_false")]
261    pub enabled: bool,
262
263    /// Allowed authentication methods
264    #[serde(default = "default_auth_methods")]
265    pub methods: Vec<String>,
266
267    /// mTLS settings
268    #[serde(default)]
269    pub mtls: MtlsSettings,
270
271    /// JWT settings
272    #[serde(default)]
273    pub jwt: JwtSettings,
274
275    /// API key settings
276    #[serde(default)]
277    pub api_key: ApiKeySettings,
278
279    /// Reject unauthenticated requests
280    #[serde(default = "default_true")]
281    pub reject_unauthenticated: bool,
282}
283
284/// mTLS authentication settings
285#[derive(Debug, Clone, Serialize, Deserialize)]
286pub struct MtlsSettings {
287    /// Enable mTLS authentication
288    #[serde(default = "default_false")]
289    pub enabled: bool,
290
291    /// Trusted CA certificates directory
292    pub ca_certs_dir: Option<PathBuf>,
293
294    /// Certificate revocation list (CRL) path
295    pub crl_path: Option<PathBuf>,
296
297    /// Verify client certificate CN matches user identity
298    #[serde(default = "default_true")]
299    pub verify_cn: bool,
300
301    /// Allowed certificate organizations (empty = allow all)
302    #[serde(default)]
303    pub allowed_organizations: Vec<String>,
304}
305
306/// JWT authentication settings
307#[derive(Debug, Clone, Serialize, Deserialize)]
308pub struct JwtSettings {
309    /// Enable JWT authentication
310    #[serde(default = "default_false")]
311    pub enabled: bool,
312
313    /// JWT secret key (for HS256)
314    pub secret: Option<String>,
315
316    /// JWT public key path (for RS256)
317    pub public_key_path: Option<PathBuf>,
318
319    /// JWT algorithm (HS256, RS256)
320    #[serde(default = "default_jwt_algorithm")]
321    pub algorithm: String,
322
323    /// Token expiration time in seconds
324    #[serde(default = "default_jwt_expiration")]
325    pub expiration_secs: u64,
326
327    /// Issuer to verify
328    pub issuer: Option<String>,
329
330    /// Audience to verify
331    pub audience: Option<String>,
332}
333
334/// API key authentication settings
335#[derive(Debug, Clone, Serialize, Deserialize)]
336pub struct ApiKeySettings {
337    /// Enable API key authentication
338    #[serde(default = "default_false")]
339    pub enabled: bool,
340
341    /// API keys file path (JSON format)
342    pub keys_file: Option<PathBuf>,
343
344    /// API key header name
345    #[serde(default = "default_api_key_header")]
346    pub header_name: String,
347
348    /// Hash API keys for storage
349    #[serde(default = "default_true")]
350    pub hash_keys: bool,
351}
352
353/// Authorization settings
354#[derive(Debug, Clone, Serialize, Deserialize)]
355pub struct AuthorizationSettings {
356    /// Enable authorization
357    #[serde(default = "default_true")]
358    pub enabled: bool,
359
360    /// Default role for authenticated users
361    #[serde(default = "default_user_role")]
362    pub default_role: String,
363
364    /// Role definitions file (JSON/TOML)
365    pub roles_file: Option<PathBuf>,
366
367    /// Policy definitions file (JSON/TOML)
368    pub policies_file: Option<PathBuf>,
369
370    /// Enable collection-level permissions
371    #[serde(default = "default_true")]
372    pub collection_permissions: bool,
373
374    /// Default permission mode (deny-by-default or allow-by-default)
375    #[serde(default = "default_permission_mode")]
376    pub default_mode: String,
377
378    /// Enable audit logging for authorization decisions
379    #[serde(default = "default_true")]
380    pub audit_enabled: bool,
381
382    /// Audit log file path
383    pub audit_log_path: Option<PathBuf>,
384}
385
386// Default value functions
387fn default_pid_file() -> PathBuf {
388    PathBuf::from("/var/run/amaters-server.pid")
389}
390
391fn default_max_connections() -> usize {
392    1000
393}
394
395fn default_shutdown_timeout() -> u64 {
396    30
397}
398
399fn default_storage_engine() -> String {
400    "lsm".to_string()
401}
402
403fn default_memtable_size() -> usize {
404    64
405}
406
407fn default_block_cache_size() -> usize {
408    256
409}
410
411fn default_wal_dir() -> PathBuf {
412    PathBuf::from("wal")
413}
414
415fn default_wal_segment_size() -> usize {
416    64
417}
418
419fn default_sync_mode() -> String {
420    "interval".to_string()
421}
422
423fn default_compaction_strategy() -> String {
424    "leveled".to_string()
425}
426
427fn default_num_levels() -> usize {
428    7
429}
430
431fn default_level_multiplier() -> usize {
432    10
433}
434
435fn default_max_compactions() -> usize {
436    4
437}
438
439fn default_connection_timeout() -> u64 {
440    30
441}
442
443fn default_keepalive_interval() -> u64 {
444    60
445}
446
447fn default_heartbeat_interval() -> u64 {
448    100
449}
450
451fn default_election_timeout() -> u64 {
452    300
453}
454
455fn default_log_level() -> String {
456    "info".to_string()
457}
458
459fn default_log_format() -> String {
460    "pretty".to_string()
461}
462
463fn default_log_max_size() -> usize {
464    100
465}
466
467fn default_log_max_backups() -> usize {
468    10
469}
470
471fn default_metrics_address() -> String {
472    "127.0.0.1:9090".to_string()
473}
474
475fn default_metrics_interval() -> u64 {
476    60
477}
478
479fn default_true() -> bool {
480    true
481}
482
483fn default_false() -> bool {
484    false
485}
486
487fn default_auth_methods() -> Vec<String> {
488    vec!["mtls".to_string()]
489}
490
491fn default_jwt_algorithm() -> String {
492    "HS256".to_string()
493}
494
495fn default_jwt_expiration() -> u64 {
496    3600 // 1 hour
497}
498
499fn default_api_key_header() -> String {
500    "X-API-Key".to_string()
501}
502
503fn default_user_role() -> String {
504    "user".to_string()
505}
506
507fn default_permission_mode() -> String {
508    "deny-by-default".to_string()
509}
510
511impl Default for ServerConfig {
512    fn default() -> Self {
513        Self {
514            server: ServerSettings {
515                bind_address: "0.0.0.0:7878".to_string(),
516                data_dir: PathBuf::from("./data"),
517                pid_file: default_pid_file(),
518                max_connections: default_max_connections(),
519                shutdown_timeout_secs: default_shutdown_timeout(),
520            },
521            storage: StorageSettings {
522                engine: default_storage_engine(),
523                wal: WalSettings::default(),
524                memtable_size_mb: default_memtable_size(),
525                block_cache_size_mb: default_block_cache_size(),
526                compaction: CompactionSettings::default(),
527            },
528            network: NetworkSettings {
529                tls_enabled: false,
530                tls_cert: None,
531                tls_key: None,
532                tls_ca: None,
533                require_client_cert: false,
534                connection_timeout_secs: default_connection_timeout(),
535                keepalive_interval_secs: default_keepalive_interval(),
536            },
537            cluster: None,
538            logging: LoggingSettings {
539                level: default_log_level(),
540                format: default_log_format(),
541                file_enabled: false,
542                file_path: None,
543                rotation: LogRotationSettings::default(),
544            },
545            metrics: MetricsSettings {
546                enabled: true,
547                bind_address: default_metrics_address(),
548                export_interval_secs: default_metrics_interval(),
549            },
550            auth: AuthSettings::default(),
551            authz: AuthorizationSettings::default(),
552        }
553    }
554}
555
556impl Default for WalSettings {
557    fn default() -> Self {
558        Self {
559            enabled: true,
560            dir: default_wal_dir(),
561            segment_size_mb: default_wal_segment_size(),
562            sync_mode: default_sync_mode(),
563        }
564    }
565}
566
567impl Default for CompactionSettings {
568    fn default() -> Self {
569        Self {
570            strategy: default_compaction_strategy(),
571            num_levels: default_num_levels(),
572            level_multiplier: default_level_multiplier(),
573            max_concurrent: default_max_compactions(),
574        }
575    }
576}
577
578impl Default for LogRotationSettings {
579    fn default() -> Self {
580        Self {
581            enabled: true,
582            max_size_mb: default_log_max_size(),
583            max_backups: default_log_max_backups(),
584        }
585    }
586}
587
588impl Default for AuthSettings {
589    fn default() -> Self {
590        Self {
591            enabled: false,
592            methods: default_auth_methods(),
593            mtls: MtlsSettings::default(),
594            jwt: JwtSettings::default(),
595            api_key: ApiKeySettings::default(),
596            reject_unauthenticated: true,
597        }
598    }
599}
600
601impl Default for MtlsSettings {
602    fn default() -> Self {
603        Self {
604            enabled: false,
605            ca_certs_dir: None,
606            crl_path: None,
607            verify_cn: true,
608            allowed_organizations: Vec::new(),
609        }
610    }
611}
612
613impl Default for JwtSettings {
614    fn default() -> Self {
615        Self {
616            enabled: false,
617            secret: None,
618            public_key_path: None,
619            algorithm: default_jwt_algorithm(),
620            expiration_secs: default_jwt_expiration(),
621            issuer: None,
622            audience: None,
623        }
624    }
625}
626
627impl Default for ApiKeySettings {
628    fn default() -> Self {
629        Self {
630            enabled: false,
631            keys_file: None,
632            header_name: default_api_key_header(),
633            hash_keys: true,
634        }
635    }
636}
637
638impl Default for AuthorizationSettings {
639    fn default() -> Self {
640        Self {
641            enabled: true,
642            default_role: default_user_role(),
643            roles_file: None,
644            policies_file: None,
645            collection_permissions: true,
646            default_mode: default_permission_mode(),
647            audit_enabled: true,
648            audit_log_path: None,
649        }
650    }
651}
652
653impl ServerConfig {
654    /// Load configuration from TOML file
655    pub fn from_file(path: impl AsRef<Path>) -> ConfigResult<Self> {
656        let contents = std::fs::read_to_string(path)?;
657        let config: ServerConfig = toml::from_str(&contents)?;
658        config.validate()?;
659        Ok(config)
660    }
661
662    /// Load configuration with environment variable overrides
663    pub fn from_file_with_env(path: impl AsRef<Path>) -> ConfigResult<Self> {
664        let mut config = Self::from_file(path)?;
665        config.apply_env_overrides();
666        config.validate()?;
667        Ok(config)
668    }
669
670    /// Apply environment variable overrides
671    pub fn apply_env_overrides(&mut self) {
672        if let Ok(bind) = std::env::var("AMATERS_BIND_ADDRESS") {
673            self.server.bind_address = bind;
674        }
675        if let Ok(data_dir) = std::env::var("AMATERS_DATA_DIR") {
676            self.server.data_dir = PathBuf::from(data_dir);
677        }
678        if let Ok(log_level) = std::env::var("AMATERS_LOG_LEVEL") {
679            self.logging.level = log_level;
680        }
681        if let Ok(tls_enabled) = std::env::var("AMATERS_TLS_ENABLED") {
682            self.network.tls_enabled = tls_enabled.parse().unwrap_or(false);
683        }
684    }
685
686    /// Validate configuration
687    pub fn validate(&self) -> ConfigResult<()> {
688        // Validate bind address
689        let _: SocketAddr = self
690            .server
691            .bind_address
692            .parse()
693            .map_err(|e| ConfigError::Validation(format!("Invalid bind address: {}", e)))?;
694
695        // Validate data directory is not empty
696        if self.server.data_dir.as_os_str().is_empty() {
697            return Err(ConfigError::Validation(
698                "Data directory cannot be empty".to_string(),
699            ));
700        }
701
702        // Validate storage engine
703        match self.storage.engine.as_str() {
704            "memory" | "lsm" => {}
705            other => {
706                return Err(ConfigError::Validation(format!(
707                    "Invalid storage engine: {}. Must be 'memory' or 'lsm'",
708                    other
709                )));
710            }
711        }
712
713        // Validate TLS configuration
714        if self.network.tls_enabled {
715            if self.network.tls_cert.is_none() {
716                return Err(ConfigError::Validation(
717                    "TLS enabled but no certificate file specified".to_string(),
718                ));
719            }
720            if self.network.tls_key.is_none() {
721                return Err(ConfigError::Validation(
722                    "TLS enabled but no key file specified".to_string(),
723                ));
724            }
725            if self.network.require_client_cert && self.network.tls_ca.is_none() {
726                return Err(ConfigError::Validation(
727                    "Client certificate required but no CA file specified".to_string(),
728                ));
729            }
730        }
731
732        // Validate cluster configuration
733        if let Some(ref cluster) = self.cluster {
734            if cluster.enabled && cluster.peers.is_empty() {
735                return Err(ConfigError::Validation(
736                    "Cluster enabled but no peers specified".to_string(),
737                ));
738            }
739        }
740
741        // Validate log level
742        match self.logging.level.to_lowercase().as_str() {
743            "trace" | "debug" | "info" | "warn" | "error" => {}
744            other => {
745                return Err(ConfigError::Validation(format!(
746                    "Invalid log level: {}. Must be one of: trace, debug, info, warn, error",
747                    other
748                )));
749            }
750        }
751
752        // Validate metrics address
753        let _: SocketAddr = self
754            .metrics
755            .bind_address
756            .parse()
757            .map_err(|e| ConfigError::Validation(format!("Invalid metrics address: {}", e)))?;
758
759        // Validate authentication settings
760        if self.auth.enabled {
761            // Validate at least one auth method is enabled
762            let has_enabled_method = (self.auth.mtls.enabled
763                && self.auth.methods.contains(&"mtls".to_string()))
764                || (self.auth.jwt.enabled && self.auth.methods.contains(&"jwt".to_string()))
765                || (self.auth.api_key.enabled
766                    && self.auth.methods.contains(&"api_key".to_string()));
767
768            if !has_enabled_method {
769                return Err(ConfigError::Validation(
770                    "Authentication enabled but no valid auth methods configured".to_string(),
771                ));
772            }
773
774            // Validate JWT settings
775            if self.auth.jwt.enabled {
776                match self.auth.jwt.algorithm.as_str() {
777                    "HS256" => {
778                        if self.auth.jwt.secret.is_none() {
779                            return Err(ConfigError::Validation(
780                                "JWT HS256 enabled but no secret key provided".to_string(),
781                            ));
782                        }
783                    }
784                    "RS256" => {
785                        if self.auth.jwt.public_key_path.is_none() {
786                            return Err(ConfigError::Validation(
787                                "JWT RS256 enabled but no public key path provided".to_string(),
788                            ));
789                        }
790                    }
791                    other => {
792                        return Err(ConfigError::Validation(format!(
793                            "Invalid JWT algorithm: {}. Supported: HS256, RS256",
794                            other
795                        )));
796                    }
797                }
798            }
799
800            // Validate API key settings
801            if self.auth.api_key.enabled && self.auth.api_key.keys_file.is_none() {
802                return Err(ConfigError::Validation(
803                    "API key auth enabled but no keys file specified".to_string(),
804                ));
805            }
806
807            // Validate mTLS settings
808            if self.auth.mtls.enabled && self.auth.mtls.ca_certs_dir.is_none() {
809                return Err(ConfigError::Validation(
810                    "mTLS enabled but no CA certificates directory specified".to_string(),
811                ));
812            }
813        }
814
815        // Validate authorization settings
816        if self.authz.enabled {
817            match self.authz.default_mode.as_str() {
818                "deny-by-default" | "allow-by-default" => {}
819                other => {
820                    return Err(ConfigError::Validation(format!(
821                        "Invalid authorization default mode: {}. Must be 'deny-by-default' or 'allow-by-default'",
822                        other
823                    )));
824                }
825            }
826        }
827
828        Ok(())
829    }
830
831    /// Get shutdown timeout as Duration
832    pub fn shutdown_timeout(&self) -> Duration {
833        Duration::from_secs(self.server.shutdown_timeout_secs)
834    }
835
836    /// Get connection timeout as Duration
837    pub fn connection_timeout(&self) -> Duration {
838        Duration::from_secs(self.network.connection_timeout_secs)
839    }
840
841    /// Get keepalive interval as Duration
842    pub fn keepalive_interval(&self) -> Duration {
843        Duration::from_secs(self.network.keepalive_interval_secs)
844    }
845
846    /// Save configuration to TOML file
847    pub fn save_to_file(&self, path: impl AsRef<Path>) -> ConfigResult<()> {
848        let contents = toml::to_string_pretty(self)
849            .map_err(|e| ConfigError::Validation(format!("Failed to serialize config: {}", e)))?;
850        std::fs::write(path, contents)?;
851        Ok(())
852    }
853
854    /// Generate example configuration file
855    pub fn example() -> Self {
856        Self::default()
857    }
858}
859
860#[cfg(test)]
861mod tests {
862    use super::*;
863    use std::env;
864
865    #[test]
866    fn test_default_config() {
867        let config = ServerConfig::default();
868        assert_eq!(config.server.bind_address, "0.0.0.0:7878");
869        assert_eq!(config.storage.engine, "lsm");
870        assert_eq!(config.logging.level, "info");
871    }
872
873    #[test]
874    fn test_config_validation() {
875        let config = ServerConfig::default();
876        assert!(config.validate().is_ok());
877    }
878
879    #[test]
880    fn test_invalid_bind_address() {
881        let mut config = ServerConfig::default();
882        config.server.bind_address = "invalid".to_string();
883        assert!(config.validate().is_err());
884    }
885
886    #[test]
887    fn test_invalid_storage_engine() {
888        let mut config = ServerConfig::default();
889        config.storage.engine = "invalid".to_string();
890        assert!(config.validate().is_err());
891    }
892
893    #[test]
894    fn test_tls_validation() {
895        let mut config = ServerConfig::default();
896        config.network.tls_enabled = true;
897        assert!(config.validate().is_err()); // No cert/key specified
898    }
899
900    #[test]
901    fn test_env_overrides() {
902        unsafe {
903            env::set_var("AMATERS_BIND_ADDRESS", "127.0.0.1:9999");
904            env::set_var("AMATERS_LOG_LEVEL", "debug");
905        }
906
907        let mut config = ServerConfig::default();
908        config.apply_env_overrides();
909
910        assert_eq!(config.server.bind_address, "127.0.0.1:9999");
911        assert_eq!(config.logging.level, "debug");
912
913        unsafe {
914            env::remove_var("AMATERS_BIND_ADDRESS");
915            env::remove_var("AMATERS_LOG_LEVEL");
916        }
917    }
918
919    #[test]
920    fn test_save_and_load() {
921        let temp_dir = env::temp_dir();
922        let config_path = temp_dir.join("test_config.toml");
923
924        let config = ServerConfig::default();
925        config
926            .save_to_file(&config_path)
927            .expect("Failed to save config");
928
929        let loaded = ServerConfig::from_file(&config_path).expect("Failed to load config");
930        assert_eq!(config.server.bind_address, loaded.server.bind_address);
931
932        std::fs::remove_file(&config_path).ok();
933    }
934}