kora_lib/validator/
cache_validator.rs1use deadpool_redis::Runtime;
2use redis::AsyncCommands;
3
4use crate::config::UsageLimitConfig;
5
6pub struct CacheValidator {}
7
8impl CacheValidator {
9 async fn test_redis_connection(cache_url: &str) -> Result<(), String> {
11 let cfg = deadpool_redis::Config::from_url(cache_url);
12 let pool = cfg
13 .create_pool(Some(Runtime::Tokio1))
14 .map_err(|e| format!("Failed to create Redis pool: {e}"))?;
15
16 let mut conn = pool.get().await.map_err(|e| format!("Failed to connect to Redis: {e}"))?;
17
18 let _: Option<String> = conn
19 .get("__config_validator_test__")
20 .await
21 .map_err(|e| format!("Redis connection test failed: {e}"))?;
22
23 drop(conn);
24 drop(pool);
25
26 Ok(())
27 }
28
29 pub async fn validate(usage_config: &UsageLimitConfig) -> (Vec<String>, Vec<String>) {
30 let mut errors = Vec::new();
31 let mut warnings = Vec::new();
32
33 if !usage_config.enabled {
35 return (errors, warnings);
36 }
37
38 match &usage_config.cache_url {
40 None => {
41 if !usage_config.fallback_if_unavailable {
42 errors.push(
43 "Usage limiting enabled without cache_url and fallback disabled - service will fail"
44 .to_string(),
45 );
46 } else {
47 warnings.push(
48 "Usage limiting enabled without cache_url - fallback mode will disable limits"
49 .to_string(),
50 );
51 }
52 }
53 Some(cache_url) => {
54 if !cache_url.starts_with("redis://") && !cache_url.starts_with("rediss://") {
56 errors.push(format!(
57 "Invalid cache_url format: '{cache_url}' - must start with redis:// or rediss://"
58 ));
59 }
60 }
61 }
62
63 if !usage_config.fallback_if_unavailable {
65 warnings.push(
66 "Usage limit fallback disabled - service will fail if cache becomes unavailable"
67 .to_string(),
68 );
69 }
70
71 if let Some(cache_url) = &usage_config.cache_url {
73 if cache_url.starts_with("redis://") || cache_url.starts_with("rediss://") {
74 match Self::test_redis_connection(cache_url).await {
75 Ok(_) => {}
76 Err(e) => {
77 if usage_config.fallback_if_unavailable {
78 warnings.push(format!(
79 "Usage limit Redis connection failed (fallback enabled): {e}"
80 ));
81 } else {
82 errors.push(format!(
83 "Usage limit Redis connection failed (fallback disabled): {e}"
84 ));
85 }
86 }
87 };
88 }
89 }
90
91 (errors, warnings)
92 }
93}
94
95#[cfg(test)]
96mod tests {
97 use super::*;
98 use crate::tests::config_mock::ConfigMockBuilder;
99 use serial_test::serial;
100
101 #[tokio::test]
102 #[serial]
103 async fn test_validate_usage_limit_disabled() {
104 let config = ConfigMockBuilder::new().with_usage_limit_enabled(false).build();
105
106 let (errors, warnings) = CacheValidator::validate(&config.kora.usage_limit).await;
107
108 assert!(errors.is_empty());
109 assert!(warnings.is_empty());
110 }
111
112 #[tokio::test]
113 #[serial]
114 async fn test_validate_usage_limit_enabled_no_cache_url_fallback_enabled() {
115 let config = ConfigMockBuilder::new()
116 .with_usage_limit_enabled(true)
117 .with_usage_limit_cache_url(None)
118 .with_usage_limit_fallback(true)
119 .build();
120
121 let (errors, warnings) = CacheValidator::validate(&config.kora.usage_limit).await;
122
123 assert!(errors.is_empty());
124 assert!(warnings.iter().any(|w| w.contains(
125 "Usage limiting enabled without cache_url - fallback mode will disable limits"
126 )));
127 }
128
129 #[tokio::test]
130 #[serial]
131 async fn test_validate_usage_limit_enabled_no_cache_url_fallback_disabled() {
132 let config = ConfigMockBuilder::new()
133 .with_usage_limit_enabled(true)
134 .with_usage_limit_cache_url(None)
135 .with_usage_limit_fallback(false)
136 .build();
137
138 let (errors, warnings) = CacheValidator::validate(&config.kora.usage_limit).await;
139
140 assert!(errors.iter().any(|e| e.contains(
142 "Usage limiting enabled without cache_url and fallback disabled - service will fail"
143 )));
144 assert!(warnings.iter().any(|w| w.contains(
145 "Usage limit fallback disabled - service will fail if cache becomes unavailable"
146 )));
147 }
148
149 #[tokio::test]
150 #[serial]
151 async fn test_validate_usage_limit_invalid_cache_url_format() {
152 let config = ConfigMockBuilder::new()
153 .with_usage_limit_enabled(true)
154 .with_usage_limit_cache_url(Some("invalid://localhost:6379".to_string()))
155 .with_usage_limit_fallback(true)
156 .build();
157
158 let (errors, warnings) = CacheValidator::validate(&config.kora.usage_limit).await;
159
160 assert!(errors.iter().any(|e| e.contains("Invalid cache_url format")
162 && e.contains("must start with redis:// or rediss://")));
163 assert!(!warnings.iter().any(|w| w.contains(
165 "Usage limit fallback disabled - service will fail if cache becomes unavailable"
166 )));
167 }
168
169 #[tokio::test]
170 #[serial]
171 async fn test_validate_usage_limit_fallback_disabled_warning() {
172 let config = ConfigMockBuilder::new()
173 .with_usage_limit_enabled(true)
174 .with_usage_limit_cache_url(Some("redis://localhost:6379".to_string()))
175 .with_usage_limit_fallback(false)
176 .build();
177
178 let (errors, warnings) = CacheValidator::validate(&config.kora.usage_limit).await;
179
180 assert!(errors
182 .iter()
183 .any(|e| e.contains("Usage limit Redis connection failed (fallback disabled)")));
184 assert!(warnings.iter().any(|w| w.contains(
185 "Usage limit fallback disabled - service will fail if cache becomes unavailable"
186 )));
187 }
188
189 #[tokio::test]
190 #[serial]
191 async fn test_validate_usage_limit_valid_redis_url() {
192 let config = ConfigMockBuilder::new()
193 .with_usage_limit_enabled(true)
194 .with_usage_limit_cache_url(Some("redis://localhost:6379".to_string()))
195 .with_usage_limit_fallback(true)
196 .build();
197
198 let (errors, warnings) = CacheValidator::validate(&config.kora.usage_limit).await;
199
200 assert!(errors.is_empty());
202 assert!(warnings
203 .iter()
204 .any(|w| w.contains("Usage limit Redis connection failed (fallback enabled)")));
205 }
206}