oxi_store/
settings_validation.rs1use crate::settings::Settings;
7
8#[derive(Debug)]
10pub struct ValidationReport {
11 pub errors: Vec<ValidationError>,
12 pub warnings: Vec<ValidationWarning>,
13}
14
15#[derive(Debug, Clone)]
16pub struct ValidationError {
17 pub field: String,
18 pub message: String,
19}
20
21#[derive(Debug, Clone)]
22pub struct ValidationWarning {
23 pub field: String,
24 pub message: String,
25}
26
27impl ValidationReport {
28 pub fn is_valid(&self) -> bool {
30 self.errors.is_empty()
31 }
32}
33
34impl Settings {
35 pub fn validate(&self) -> ValidationReport {
39 let mut report = ValidationReport {
40 errors: Vec::new(),
41 warnings: Vec::new(),
42 };
43
44 if let Some(temp) = self.default_temperature {
46 if !(0.0..=2.0).contains(&temp) {
47 report.errors.push(ValidationError {
48 field: "default_temperature".to_string(),
49 message: format!(
50 "Temperature must be between 0.0 and 2.0 (current: {})",
51 temp
52 ),
53 });
54 }
55 }
56 if let Some(temp) = self.temperature {
58 if !(0.0..=2.0).contains(&temp) {
59 report.errors.push(ValidationError {
60 field: "temperature".to_string(),
61 message: format!(
62 "Temperature must be between 0.0 and 2.0 (current: {})",
63 temp
64 ),
65 });
66 }
67 }
68
69 if let Some(tokens) = self.max_response_tokens {
71 if tokens == 0 {
72 report.errors.push(ValidationError {
73 field: "max_response_tokens".to_string(),
74 message: "Must be at least 1 (current: 0)".to_string(),
75 });
76 } else if tokens > 128_000 {
77 report.warnings.push(ValidationWarning {
78 field: "max_response_tokens".to_string(),
79 message: format!(
80 "Value exceeds 128,000. Most models may not support this (current: {})",
81 tokens
82 ),
83 });
84 }
85 }
86 if let Some(tokens) = self.max_tokens {
88 if tokens == 0 {
89 report.errors.push(ValidationError {
90 field: "max_tokens".to_string(),
91 message: "Must be at least 1 (current: 0)".to_string(),
92 });
93 } else if tokens as usize > 128_000 {
94 report.warnings.push(ValidationWarning {
95 field: "max_tokens".to_string(),
96 message: format!(
97 "Value exceeds 128,000. Most models may not support this (current: {})",
98 tokens
99 ),
100 });
101 }
102 }
103
104 if self.tool_timeout_seconds == 0 {
106 report.errors.push(ValidationError {
107 field: "tool_timeout_seconds".to_string(),
108 message: "Must be at least 1 second (current: 0)".to_string(),
109 });
110 }
111
112 if self.session_history_size == 0 {
121 report.errors.push(ValidationError {
122 field: "session_history_size".to_string(),
123 message: "Must be at least 1 (current: 0)".to_string(),
124 });
125 }
126
127 report
128 }
129}
130
131#[cfg(test)]
132mod tests {
133 use super::*;
134 use crate::settings::ThinkingLevel;
135
136 #[test]
138 fn test_default_settings_are_valid() {
139 let settings = Settings::default();
140 let report = settings.validate();
141 assert!(report.is_valid(), "default settings should be valid");
142 assert!(
143 report.warnings.is_empty(),
144 "default settings should have no warnings"
145 );
146 }
147
148 #[test]
151 fn test_temperature_in_range() {
152 let mut settings = Settings::default();
153 settings.default_temperature = Some(1.5);
154 let report = settings.validate();
155 assert!(report.is_valid());
156 }
157
158 #[test]
159 fn test_temperature_at_boundaries() {
160 let mut settings = Settings::default();
161 settings.default_temperature = Some(0.0);
162 assert!(settings.validate().is_valid());
163
164 settings.default_temperature = Some(2.0);
165 assert!(settings.validate().is_valid());
166 }
167
168 #[test]
169 fn test_temperature_below_range() {
170 let mut settings = Settings::default();
171 settings.default_temperature = Some(-0.1);
172 let report = settings.validate();
173 assert!(!report.is_valid());
174 assert_eq!(report.errors.len(), 1);
175 assert_eq!(report.errors[0].field, "default_temperature");
176 }
177
178 #[test]
179 fn test_temperature_above_range() {
180 let mut settings = Settings::default();
181 settings.default_temperature = Some(2.5);
182 let report = settings.validate();
183 assert!(!report.is_valid());
184 assert_eq!(report.errors.len(), 1);
185 assert_eq!(report.errors[0].field, "default_temperature");
186 }
187
188 #[test]
191 fn test_legacy_temperature_out_of_range() {
192 let mut settings = Settings::default();
193 settings.temperature = Some(3.0);
194 let report = settings.validate();
195 assert!(!report.is_valid());
196 assert_eq!(report.errors[0].field, "temperature");
197 }
198
199 #[test]
202 fn test_max_response_tokens_zero_is_error() {
203 let mut settings = Settings::default();
204 settings.max_response_tokens = Some(0);
205 let report = settings.validate();
206 assert!(!report.is_valid());
207 assert_eq!(report.errors[0].field, "max_response_tokens");
208 }
209
210 #[test]
211 fn test_max_response_tokens_above_128k_is_warning() {
212 let mut settings = Settings::default();
213 settings.max_response_tokens = Some(200_000);
214 let report = settings.validate();
215 assert!(report.is_valid(), "warning should not block");
216 assert_eq!(report.warnings.len(), 1);
217 assert_eq!(report.warnings[0].field, "max_response_tokens");
218 }
219
220 #[test]
221 fn test_max_response_tokens_normal() {
222 let mut settings = Settings::default();
223 settings.max_response_tokens = Some(4096);
224 let report = settings.validate();
225 assert!(report.is_valid());
226 assert!(report.warnings.is_empty());
227 }
228
229 #[test]
232 fn test_max_tokens_zero_is_error() {
233 let mut settings = Settings::default();
234 settings.max_tokens = Some(0);
235 let report = settings.validate();
236 assert!(!report.is_valid());
237 assert_eq!(report.errors[0].field, "max_tokens");
238 }
239
240 #[test]
241 fn test_max_tokens_above_128k_is_warning() {
242 let mut settings = Settings::default();
243 settings.max_tokens = Some(200_000);
244 let report = settings.validate();
245 assert!(report.is_valid());
246 assert_eq!(report.warnings.len(), 1);
247 }
248
249 #[test]
252 fn test_tool_timeout_zero_is_error() {
253 let mut settings = Settings::default();
254 settings.tool_timeout_seconds = 0;
255 let report = settings.validate();
256 assert!(!report.is_valid());
257 assert_eq!(report.errors[0].field, "tool_timeout_seconds");
258 }
259
260 #[test]
261 fn test_tool_timeout_positive_is_ok() {
262 let mut settings = Settings::default();
263 settings.tool_timeout_seconds = 60;
264 assert!(settings.validate().is_valid());
265 }
266
267 #[test]
270 fn test_model_without_slash_is_ok() {
271 let mut settings = Settings::default();
272 settings.default_model = Some("claude-3".to_string());
273 let report = settings.validate();
274 assert!(report.is_valid());
275 assert!(report.warnings.is_empty());
276 }
277
278 #[test]
279 fn test_model_none_produces_no_warning() {
280 let settings = Settings::default();
281 let report = settings.validate();
282 assert!(report.warnings.is_empty());
283 }
284
285 #[test]
288 fn test_session_history_size_zero_is_error() {
289 let mut settings = Settings::default();
290 settings.session_history_size = 0;
291 let report = settings.validate();
292 assert!(!report.is_valid());
293 assert_eq!(report.errors[0].field, "session_history_size");
294 }
295
296 #[test]
297 fn test_session_history_size_positive_is_ok() {
298 let mut settings = Settings::default();
299 settings.session_history_size = 50;
300 assert!(settings.validate().is_valid());
301 }
302
303 #[test]
306 fn test_multiple_errors_and_warnings() {
307 let mut settings = Settings::default();
308 settings.default_temperature = Some(5.0);
309 settings.tool_timeout_seconds = 0;
310 settings.default_model = Some("badmodel".to_string());
311 settings.session_history_size = 0;
312
313 let report = settings.validate();
314 assert!(!report.is_valid());
315 assert_eq!(report.errors.len(), 3);
317 assert_eq!(report.warnings.len(), 0);
319 }
320
321 #[test]
324 fn test_report_is_valid_with_warnings_only() {
325 let report = ValidationReport {
326 errors: vec![],
327 warnings: vec![ValidationWarning {
328 field: "x".to_string(),
329 message: "soft warning".to_string(),
330 }],
331 };
332 assert!(report.is_valid());
333 }
334
335 #[test]
336 fn test_report_is_invalid_with_errors() {
337 let report = ValidationReport {
338 errors: vec![ValidationError {
339 field: "x".to_string(),
340 message: "hard error".to_string(),
341 }],
342 warnings: vec![],
343 };
344 assert!(!report.is_valid());
345 }
346}