Skip to main content

litellm_rs/config/validation/
monitoring_validators.rs

1//! Monitoring configuration validators
2//!
3//! This module provides validation implementations for monitoring-related
4//! configuration structures including MonitoringConfig, MetricsConfig,
5//! TracingConfig, and HealthConfig.
6
7use super::trait_def::Validate;
8use crate::config::models::*;
9use tracing::debug;
10
11impl Validate for MonitoringConfig {
12    fn validate(&self) -> Result<(), String> {
13        debug!("Validating monitoring configuration");
14
15        self.metrics.validate()?;
16        self.tracing.validate()?;
17        self.health.validate()?;
18
19        Ok(())
20    }
21}
22
23impl Validate for MetricsConfig {
24    fn validate(&self) -> Result<(), String> {
25        if self.enabled && self.port == 0 {
26            return Err("Metrics port must be greater than 0 when metrics are enabled".to_string());
27        }
28
29        if self.path.is_empty() {
30            return Err("Metrics path cannot be empty".to_string());
31        }
32
33        if !self.path.starts_with('/') {
34            return Err("Metrics path must start with '/'".to_string());
35        }
36
37        Ok(())
38    }
39}
40
41impl Validate for TracingConfig {
42    fn validate(&self) -> Result<(), String> {
43        if self.enabled && self.endpoint.is_none() {
44            return Err("Tracing endpoint must be specified when tracing is enabled".to_string());
45        }
46
47        if self.service_name.is_empty() {
48            return Err("Service name cannot be empty".to_string());
49        }
50
51        Ok(())
52    }
53}
54
55impl Validate for HealthConfig {
56    fn validate(&self) -> Result<(), String> {
57        if self.path.is_empty() {
58            return Err("Health check path cannot be empty".to_string());
59        }
60
61        if !self.path.starts_with('/') {
62            return Err("Health check path must start with '/'".to_string());
63        }
64
65        Ok(())
66    }
67}
68
69#[cfg(test)]
70mod tests {
71    use super::super::trait_def::Validate;
72    use super::*;
73
74    // Helper to call the Validate trait method explicitly
75    fn validate_config<T: Validate>(config: &T) -> Result<(), String> {
76        Validate::validate(config)
77    }
78
79    // ==================== MetricsConfig Validation Tests ====================
80
81    #[test]
82    fn test_metrics_config_valid() {
83        let config = MetricsConfig {
84            enabled: true,
85            port: 9090,
86            path: "/metrics".to_string(),
87        };
88        assert!(validate_config(&config).is_ok());
89    }
90
91    #[test]
92    fn test_metrics_config_disabled_with_zero_port() {
93        let config = MetricsConfig {
94            enabled: false,
95            port: 0,
96            path: "/metrics".to_string(),
97        };
98        assert!(validate_config(&config).is_ok());
99    }
100
101    #[test]
102    fn test_metrics_config_enabled_with_zero_port() {
103        let config = MetricsConfig {
104            enabled: true,
105            port: 0,
106            path: "/metrics".to_string(),
107        };
108        let result = validate_config(&config);
109        assert!(result.is_err());
110        assert!(result.unwrap_err().contains("port must be greater than 0"));
111    }
112
113    #[test]
114    fn test_metrics_config_empty_path() {
115        let config = MetricsConfig {
116            enabled: true,
117            port: 9090,
118            path: "".to_string(),
119        };
120        let result = validate_config(&config);
121        assert!(result.is_err());
122        assert!(result.unwrap_err().contains("path cannot be empty"));
123    }
124
125    #[test]
126    fn test_metrics_config_path_without_leading_slash() {
127        let config = MetricsConfig {
128            enabled: true,
129            port: 9090,
130            path: "metrics".to_string(),
131        };
132        let result = validate_config(&config);
133        assert!(result.is_err());
134        assert!(result.unwrap_err().contains("must start with '/'"));
135    }
136
137    #[test]
138    fn test_metrics_config_custom_path() {
139        let config = MetricsConfig {
140            enabled: true,
141            port: 9090,
142            path: "/custom/metrics/path".to_string(),
143        };
144        assert!(validate_config(&config).is_ok());
145    }
146
147    // ==================== TracingConfig Validation Tests ====================
148
149    #[test]
150    fn test_tracing_config_valid() {
151        let config = TracingConfig {
152            enabled: true,
153            endpoint: Some("http://localhost:4317".to_string()),
154            service_name: "gateway".to_string(),
155        };
156        assert!(validate_config(&config).is_ok());
157    }
158
159    #[test]
160    fn test_tracing_config_disabled_no_endpoint() {
161        let config = TracingConfig {
162            enabled: false,
163            endpoint: None,
164            service_name: "gateway".to_string(),
165        };
166        assert!(validate_config(&config).is_ok());
167    }
168
169    #[test]
170    fn test_tracing_config_enabled_no_endpoint() {
171        let config = TracingConfig {
172            enabled: true,
173            endpoint: None,
174            service_name: "gateway".to_string(),
175        };
176        let result = validate_config(&config);
177        assert!(result.is_err());
178        assert!(result.unwrap_err().contains("endpoint must be specified"));
179    }
180
181    #[test]
182    fn test_tracing_config_empty_service_name() {
183        let config = TracingConfig {
184            enabled: false,
185            endpoint: None,
186            service_name: "".to_string(),
187        };
188        let result = validate_config(&config);
189        assert!(result.is_err());
190        assert!(result.unwrap_err().contains("Service name cannot be empty"));
191    }
192
193    // ==================== HealthConfig Validation Tests ====================
194
195    #[test]
196    fn test_health_config_valid() {
197        let config = HealthConfig {
198            path: "/health".to_string(),
199            ..Default::default()
200        };
201        assert!(validate_config(&config).is_ok());
202    }
203
204    #[test]
205    fn test_health_config_empty_path() {
206        let config = HealthConfig {
207            path: "".to_string(),
208            ..Default::default()
209        };
210        let result = validate_config(&config);
211        assert!(result.is_err());
212        assert!(result.unwrap_err().contains("path cannot be empty"));
213    }
214
215    #[test]
216    fn test_health_config_path_without_leading_slash() {
217        let config = HealthConfig {
218            path: "health".to_string(),
219            ..Default::default()
220        };
221        let result = validate_config(&config);
222        assert!(result.is_err());
223        assert!(result.unwrap_err().contains("must start with '/'"));
224    }
225
226    #[test]
227    fn test_health_config_custom_path() {
228        let config = HealthConfig {
229            path: "/api/v1/health".to_string(),
230            ..Default::default()
231        };
232        assert!(validate_config(&config).is_ok());
233    }
234
235    // ==================== MonitoringConfig Validation Tests ====================
236
237    #[test]
238    fn test_monitoring_config_valid() {
239        let config = MonitoringConfig::default();
240        assert!(validate_config(&config).is_ok());
241    }
242
243    #[test]
244    fn test_monitoring_config_with_invalid_metrics() {
245        let mut config = MonitoringConfig::default();
246        config.metrics.enabled = true;
247        config.metrics.port = 0;
248
249        let result = validate_config(&config);
250        assert!(result.is_err());
251    }
252
253    #[test]
254    fn test_monitoring_config_with_invalid_tracing() {
255        let mut config = MonitoringConfig::default();
256        config.tracing.enabled = true;
257        config.tracing.endpoint = None;
258
259        let result = validate_config(&config);
260        assert!(result.is_err());
261    }
262
263    #[test]
264    fn test_monitoring_config_with_invalid_health() {
265        let mut config = MonitoringConfig::default();
266        config.health.path = "".to_string();
267
268        let result = validate_config(&config);
269        assert!(result.is_err());
270    }
271}