ricecoder_storage/markdown_config/
validation.rs

1//! Validation functions for markdown configuration
2
3use crate::markdown_config::error::{MarkdownConfigError, MarkdownConfigResult};
4use crate::markdown_config::types::{AgentConfig, CommandConfig, ModeConfig, Parameter};
5
6/// Validation result containing all errors found
7#[derive(Debug, Clone, PartialEq, Eq)]
8pub struct ValidationResult {
9    /// List of validation errors found
10    pub errors: Vec<String>,
11}
12
13impl ValidationResult {
14    /// Create a new validation result
15    pub fn new() -> Self {
16        Self {
17            errors: Vec::new(),
18        }
19    }
20
21    /// Add an error to the validation result
22    pub fn add_error(&mut self, error: impl Into<String>) {
23        self.errors.push(error.into());
24    }
25
26    /// Check if validation passed (no errors)
27    pub fn is_valid(&self) -> bool {
28        self.errors.is_empty()
29    }
30
31    /// Get the number of errors
32    pub fn error_count(&self) -> usize {
33        self.errors.len()
34    }
35
36    /// Convert to a MarkdownConfigError if there are errors
37    pub fn to_error(self) -> Option<MarkdownConfigError> {
38        if self.errors.is_empty() {
39            None
40        } else {
41            let message = self.errors.join("; ");
42            Some(MarkdownConfigError::validation_error(message))
43        }
44    }
45}
46
47impl Default for ValidationResult {
48    fn default() -> Self {
49        Self::new()
50    }
51}
52
53// ============ Agent Validation ============
54
55/// Validate an agent configuration
56///
57/// Checks all required fields and validates field values.
58/// Returns a ValidationResult containing all errors found.
59pub fn validate_agent_config(config: &AgentConfig) -> ValidationResult {
60    let mut result = ValidationResult::new();
61
62    // Check required fields
63    if config.name.is_empty() {
64        result.add_error("Agent name is required and cannot be empty");
65    }
66
67    if config.prompt.is_empty() {
68        result.add_error("Agent prompt is required and cannot be empty");
69    }
70
71    // Validate optional fields
72    if let Some(temp) = config.temperature {
73        if !(0.0..=2.0).contains(&temp) {
74            result.add_error(format!(
75                "Agent temperature must be between 0.0 and 2.0, got {}",
76                temp
77            ));
78        }
79    }
80
81    if let Some(tokens) = config.max_tokens {
82        if tokens == 0 {
83            result.add_error("Agent max_tokens must be greater than 0");
84        }
85    }
86
87    result
88}
89
90/// Validate an agent configuration and return an error if invalid
91pub fn validate_agent_config_strict(config: &AgentConfig) -> MarkdownConfigResult<()> {
92    let result = validate_agent_config(config);
93    result.to_error().map_or(Ok(()), Err)
94}
95
96// ============ Mode Validation ============
97
98/// Validate a mode configuration
99///
100/// Checks all required fields and validates field values.
101/// Returns a ValidationResult containing all errors found.
102pub fn validate_mode_config(config: &ModeConfig) -> ValidationResult {
103    let mut result = ValidationResult::new();
104
105    // Check required fields
106    if config.name.is_empty() {
107        result.add_error("Mode name is required and cannot be empty");
108    }
109
110    if config.prompt.is_empty() {
111        result.add_error("Mode prompt is required and cannot be empty");
112    }
113
114    result
115}
116
117/// Validate a mode configuration and return an error if invalid
118pub fn validate_mode_config_strict(config: &ModeConfig) -> MarkdownConfigResult<()> {
119    let result = validate_mode_config(config);
120    result.to_error().map_or(Ok(()), Err)
121}
122
123// ============ Command Validation ============
124
125/// Validate a command configuration
126///
127/// Checks all required fields and validates field values.
128/// Returns a ValidationResult containing all errors found.
129pub fn validate_command_config(config: &CommandConfig) -> ValidationResult {
130    let mut result = ValidationResult::new();
131
132    // Check required fields
133    if config.name.is_empty() {
134        result.add_error("Command name is required and cannot be empty");
135    }
136
137    if config.template.is_empty() {
138        result.add_error("Command template is required and cannot be empty");
139    }
140
141    // Validate parameters
142    for (idx, param) in config.parameters.iter().enumerate() {
143        let param_errors = validate_parameter(param);
144        for error in param_errors.errors {
145            result.add_error(format!("Parameter[{}]: {}", idx, error));
146        }
147    }
148
149    result
150}
151
152/// Validate a command configuration and return an error if invalid
153pub fn validate_command_config_strict(config: &CommandConfig) -> MarkdownConfigResult<()> {
154    let result = validate_command_config(config);
155    result.to_error().map_or(Ok(()), Err)
156}
157
158// ============ Parameter Validation ============
159
160/// Validate a parameter configuration
161///
162/// Checks all required fields and validates field values.
163/// Returns a ValidationResult containing all errors found.
164pub fn validate_parameter(param: &Parameter) -> ValidationResult {
165    let mut result = ValidationResult::new();
166
167    // Check required fields
168    if param.name.is_empty() {
169        result.add_error("Parameter name is required and cannot be empty");
170    }
171
172    result
173}
174
175/// Validate a parameter configuration and return an error if invalid
176pub fn validate_parameter_strict(param: &Parameter) -> MarkdownConfigResult<()> {
177    let result = validate_parameter(param);
178    result.to_error().map_or(Ok(()), Err)
179}
180
181#[cfg(test)]
182mod tests {
183    use super::*;
184
185    fn create_test_agent(name: &str) -> AgentConfig {
186        AgentConfig {
187            name: name.to_string(),
188            description: Some("Test agent".to_string()),
189            prompt: "You are a helpful assistant".to_string(),
190            model: Some("gpt-4".to_string()),
191            temperature: Some(0.7),
192            max_tokens: Some(2000),
193            tools: vec![],
194        }
195    }
196
197    fn create_test_mode(name: &str) -> ModeConfig {
198        ModeConfig {
199            name: name.to_string(),
200            description: Some("Test mode".to_string()),
201            prompt: "Focus on the task".to_string(),
202            keybinding: Some("C-f".to_string()),
203            enabled: true,
204        }
205    }
206
207    fn create_test_command(name: &str) -> CommandConfig {
208        CommandConfig {
209            name: name.to_string(),
210            description: Some("Test command".to_string()),
211            template: "echo {{message}}".to_string(),
212            parameters: vec![],
213            keybinding: Some("C-t".to_string()),
214        }
215    }
216
217    // ============ ValidationResult Tests ============
218
219    #[test]
220    fn test_validation_result_new() {
221        let result = ValidationResult::new();
222        assert!(result.is_valid());
223        assert_eq!(result.error_count(), 0);
224    }
225
226    #[test]
227    fn test_validation_result_add_error() {
228        let mut result = ValidationResult::new();
229        result.add_error("Test error");
230        assert!(!result.is_valid());
231        assert_eq!(result.error_count(), 1);
232    }
233
234    #[test]
235    fn test_validation_result_multiple_errors() {
236        let mut result = ValidationResult::new();
237        result.add_error("Error 1");
238        result.add_error("Error 2");
239        result.add_error("Error 3");
240        assert!(!result.is_valid());
241        assert_eq!(result.error_count(), 3);
242    }
243
244    #[test]
245    fn test_validation_result_to_error_valid() {
246        let result = ValidationResult::new();
247        assert!(result.to_error().is_none());
248    }
249
250    #[test]
251    fn test_validation_result_to_error_invalid() {
252        let mut result = ValidationResult::new();
253        result.add_error("Test error");
254        assert!(result.to_error().is_some());
255    }
256
257    // ============ Agent Validation Tests ============
258
259    #[test]
260    fn test_validate_agent_config_valid() {
261        let agent = create_test_agent("test-agent");
262        let result = validate_agent_config(&agent);
263        assert!(result.is_valid());
264    }
265
266    #[test]
267    fn test_validate_agent_config_empty_name() {
268        let mut agent = create_test_agent("test");
269        agent.name = String::new();
270        let result = validate_agent_config(&agent);
271        assert!(!result.is_valid());
272        assert!(result.errors.iter().any(|e| e.contains("name")));
273    }
274
275    #[test]
276    fn test_validate_agent_config_empty_prompt() {
277        let mut agent = create_test_agent("test");
278        agent.prompt = String::new();
279        let result = validate_agent_config(&agent);
280        assert!(!result.is_valid());
281        assert!(result.errors.iter().any(|e| e.contains("prompt")));
282    }
283
284    #[test]
285    fn test_validate_agent_config_invalid_temperature_too_high() {
286        let mut agent = create_test_agent("test");
287        agent.temperature = Some(3.0);
288        let result = validate_agent_config(&agent);
289        assert!(!result.is_valid());
290        assert!(result.errors.iter().any(|e| e.contains("temperature")));
291    }
292
293    #[test]
294    fn test_validate_agent_config_invalid_temperature_negative() {
295        let mut agent = create_test_agent("test");
296        agent.temperature = Some(-0.5);
297        let result = validate_agent_config(&agent);
298        assert!(!result.is_valid());
299        assert!(result.errors.iter().any(|e| e.contains("temperature")));
300    }
301
302    #[test]
303    fn test_validate_agent_config_invalid_max_tokens() {
304        let mut agent = create_test_agent("test");
305        agent.max_tokens = Some(0);
306        let result = validate_agent_config(&agent);
307        assert!(!result.is_valid());
308        assert!(result.errors.iter().any(|e| e.contains("max_tokens")));
309    }
310
311    #[test]
312    fn test_validate_agent_config_multiple_errors() {
313        let mut agent = create_test_agent("test");
314        agent.name = String::new();
315        agent.prompt = String::new();
316        agent.temperature = Some(3.0);
317        let result = validate_agent_config(&agent);
318        assert!(!result.is_valid());
319        assert_eq!(result.error_count(), 3);
320    }
321
322    #[test]
323    fn test_validate_agent_config_strict_valid() {
324        let agent = create_test_agent("test");
325        assert!(validate_agent_config_strict(&agent).is_ok());
326    }
327
328    #[test]
329    fn test_validate_agent_config_strict_invalid() {
330        let mut agent = create_test_agent("test");
331        agent.name = String::new();
332        assert!(validate_agent_config_strict(&agent).is_err());
333    }
334
335    // ============ Mode Validation Tests ============
336
337    #[test]
338    fn test_validate_mode_config_valid() {
339        let mode = create_test_mode("test-mode");
340        let result = validate_mode_config(&mode);
341        assert!(result.is_valid());
342    }
343
344    #[test]
345    fn test_validate_mode_config_empty_name() {
346        let mut mode = create_test_mode("test");
347        mode.name = String::new();
348        let result = validate_mode_config(&mode);
349        assert!(!result.is_valid());
350        assert!(result.errors.iter().any(|e| e.contains("name")));
351    }
352
353    #[test]
354    fn test_validate_mode_config_empty_prompt() {
355        let mut mode = create_test_mode("test");
356        mode.prompt = String::new();
357        let result = validate_mode_config(&mode);
358        assert!(!result.is_valid());
359        assert!(result.errors.iter().any(|e| e.contains("prompt")));
360    }
361
362    #[test]
363    fn test_validate_mode_config_strict_valid() {
364        let mode = create_test_mode("test");
365        assert!(validate_mode_config_strict(&mode).is_ok());
366    }
367
368    #[test]
369    fn test_validate_mode_config_strict_invalid() {
370        let mut mode = create_test_mode("test");
371        mode.name = String::new();
372        assert!(validate_mode_config_strict(&mode).is_err());
373    }
374
375    // ============ Command Validation Tests ============
376
377    #[test]
378    fn test_validate_command_config_valid() {
379        let command = create_test_command("test-command");
380        let result = validate_command_config(&command);
381        assert!(result.is_valid());
382    }
383
384    #[test]
385    fn test_validate_command_config_empty_name() {
386        let mut command = create_test_command("test");
387        command.name = String::new();
388        let result = validate_command_config(&command);
389        assert!(!result.is_valid());
390        assert!(result.errors.iter().any(|e| e.contains("name")));
391    }
392
393    #[test]
394    fn test_validate_command_config_empty_template() {
395        let mut command = create_test_command("test");
396        command.template = String::new();
397        let result = validate_command_config(&command);
398        assert!(!result.is_valid());
399        assert!(result.errors.iter().any(|e| e.contains("template")));
400    }
401
402    #[test]
403    fn test_validate_command_config_with_parameters() {
404        let mut command = create_test_command("test");
405        command.parameters = vec![
406            Parameter {
407                name: "param1".to_string(),
408                description: Some("First parameter".to_string()),
409                required: true,
410                default: None,
411            },
412            Parameter {
413                name: "param2".to_string(),
414                description: None,
415                required: false,
416                default: Some("default".to_string()),
417            },
418        ];
419        let result = validate_command_config(&command);
420        assert!(result.is_valid());
421    }
422
423    #[test]
424    fn test_validate_command_config_invalid_parameter() {
425        let mut command = create_test_command("test");
426        command.parameters = vec![Parameter {
427            name: String::new(),
428            description: None,
429            required: false,
430            default: None,
431        }];
432        let result = validate_command_config(&command);
433        assert!(!result.is_valid());
434        assert!(result.errors.iter().any(|e| e.contains("Parameter")));
435    }
436
437    #[test]
438    fn test_validate_command_config_strict_valid() {
439        let command = create_test_command("test");
440        assert!(validate_command_config_strict(&command).is_ok());
441    }
442
443    #[test]
444    fn test_validate_command_config_strict_invalid() {
445        let mut command = create_test_command("test");
446        command.name = String::new();
447        assert!(validate_command_config_strict(&command).is_err());
448    }
449
450    // ============ Parameter Validation Tests ============
451
452    #[test]
453    fn test_validate_parameter_valid() {
454        let param = Parameter {
455            name: "test-param".to_string(),
456            description: Some("Test parameter".to_string()),
457            required: true,
458            default: None,
459        };
460        let result = validate_parameter(&param);
461        assert!(result.is_valid());
462    }
463
464    #[test]
465    fn test_validate_parameter_empty_name() {
466        let param = Parameter {
467            name: String::new(),
468            description: None,
469            required: false,
470            default: None,
471        };
472        let result = validate_parameter(&param);
473        assert!(!result.is_valid());
474        assert!(result.errors.iter().any(|e| e.contains("name")));
475    }
476
477    #[test]
478    fn test_validate_parameter_strict_valid() {
479        let param = Parameter {
480            name: "test".to_string(),
481            description: None,
482            required: false,
483            default: None,
484        };
485        assert!(validate_parameter_strict(&param).is_ok());
486    }
487
488    #[test]
489    fn test_validate_parameter_strict_invalid() {
490        let param = Parameter {
491            name: String::new(),
492            description: None,
493            required: false,
494            default: None,
495        };
496        assert!(validate_parameter_strict(&param).is_err());
497    }
498}