nntp_proxy/config/
validation.rs

1//! Configuration validation
2//!
3//! This module provides validation logic for the configuration to ensure
4//! all settings are valid before the proxy starts.
5
6use anyhow::Result;
7use std::time::Duration;
8
9use super::types::{Config, Server};
10use crate::constants::pool::{MAX_RECOMMENDED_KEEPALIVE_SECS, MIN_RECOMMENDED_KEEPALIVE_SECS};
11
12const MIN_RECOMMENDED_KEEPALIVE: Duration = Duration::from_secs(MIN_RECOMMENDED_KEEPALIVE_SECS);
13const MAX_RECOMMENDED_KEEPALIVE: Duration = Duration::from_secs(MAX_RECOMMENDED_KEEPALIVE_SECS);
14
15impl Config {
16    /// Validate configuration for correctness
17    ///
18    /// Most validations are now enforced by type system (NonZero types, validated strings, etc.)
19    /// This checks remaining semantic constraints:
20    /// - At least one server configured
21    /// - Keep-alive intervals are in recommended ranges
22    pub fn validate(&self) -> Result<()> {
23        if self.servers.is_empty() {
24            return Err(anyhow::anyhow!(
25                "Configuration must have at least one server"
26            ));
27        }
28
29        for server in &self.servers {
30            validate_server(server)?;
31        }
32
33        Ok(())
34    }
35}
36
37/// Validate a single server configuration
38fn validate_server(server: &Server) -> Result<()> {
39    // Name, host, port, max_connections validations now enforced by types:
40    // - HostName/ServerName cannot be empty (validated at construction)
41    // - Port cannot be 0 (NonZeroU16)
42    // - max_connections cannot be 0 (NonZeroUsize via MaxConnections)
43
44    // Warn if connection_keepalive is outside recommended range
45    if let Some(keepalive) = server.connection_keepalive {
46        if keepalive < MIN_RECOMMENDED_KEEPALIVE {
47            tracing::warn!(
48                "Server '{}' has connection_keepalive set to {:?} (< {:?}). \
49                 This may cause excessive health check traffic and connection churn. \
50                 Consider using at least {:?} or None to disable.",
51                server.name.as_str(),
52                keepalive,
53                MIN_RECOMMENDED_KEEPALIVE,
54                MIN_RECOMMENDED_KEEPALIVE
55            );
56        } else if keepalive > MAX_RECOMMENDED_KEEPALIVE {
57            tracing::warn!(
58                "Server '{}' has connection_keepalive set to {:?} (> {:?} / 5 minutes). \
59                 This may not detect stale connections quickly enough. Consider a lower value.",
60                server.name.as_str(),
61                keepalive,
62                MAX_RECOMMENDED_KEEPALIVE
63            );
64        }
65    }
66
67    Ok(())
68}
69
70#[cfg(test)]
71mod tests {
72    use super::*;
73
74    fn create_test_server(name: &str, keepalive: Option<Duration>) -> Server {
75        let mut builder = Server::builder("localhost", 119).name(name);
76
77        if let Some(ka) = keepalive {
78            builder = builder.connection_keepalive(ka);
79        }
80
81        builder.build().unwrap()
82    }
83
84    #[test]
85    fn test_validate_empty_config_fails() {
86        let config = Config {
87            servers: vec![],
88            ..Default::default()
89        };
90        assert!(config.validate().is_err());
91    }
92
93    #[test]
94    fn test_validate_single_server_succeeds() {
95        let config = Config {
96            servers: vec![create_test_server("test", None)],
97            ..Default::default()
98        };
99        assert!(config.validate().is_ok());
100    }
101
102    #[test]
103    fn test_validate_multiple_servers_succeeds() {
104        let config = Config {
105            servers: vec![
106                create_test_server("server1", None),
107                create_test_server("server2", None),
108            ],
109            ..Default::default()
110        };
111        assert!(config.validate().is_ok());
112    }
113
114    #[test]
115    fn test_validate_server_with_recommended_keepalive() {
116        let server = create_test_server("test", Some(Duration::from_secs(60)));
117        assert!(validate_server(&server).is_ok());
118    }
119
120    #[test]
121    fn test_validate_server_with_low_keepalive_warns() {
122        // This should warn but not fail
123        let server = create_test_server("test", Some(Duration::from_secs(5)));
124        assert!(validate_server(&server).is_ok());
125    }
126
127    #[test]
128    fn test_validate_server_with_high_keepalive_warns() {
129        // This should warn but not fail
130        let server = create_test_server("test", Some(Duration::from_secs(600)));
131        assert!(validate_server(&server).is_ok());
132    }
133
134    #[test]
135    fn test_validate_server_with_no_keepalive() {
136        let server = create_test_server("test", None);
137        assert!(validate_server(&server).is_ok());
138    }
139
140    #[test]
141    fn test_validate_server_at_min_boundary() {
142        let server = create_test_server("test", Some(MIN_RECOMMENDED_KEEPALIVE));
143        assert!(validate_server(&server).is_ok());
144    }
145
146    #[test]
147    fn test_validate_server_at_max_boundary() {
148        let server = create_test_server("test", Some(MAX_RECOMMENDED_KEEPALIVE));
149        assert!(validate_server(&server).is_ok());
150    }
151}