llm_config_security/
validation.rs

1//! Generic validation framework
2
3use crate::errors::{SecurityError, SecurityResult};
4use std::collections::HashMap;
5
6/// Validation rule
7pub trait ValidationRule: Send + Sync {
8    /// Validate a value
9    fn validate(&self, value: &str) -> SecurityResult<()>;
10
11    /// Get the rule name
12    fn name(&self) -> &str;
13
14    /// Get the rule description
15    fn description(&self) -> &str;
16}
17
18/// Composite validator
19pub struct Validator {
20    rules: HashMap<String, Box<dyn ValidationRule>>,
21}
22
23impl Validator {
24    /// Create a new validator
25    pub fn new() -> Self {
26        Self {
27            rules: HashMap::new(),
28        }
29    }
30
31    /// Add a validation rule
32    pub fn add_rule(&mut self, name: String, rule: Box<dyn ValidationRule>) {
33        self.rules.insert(name, rule);
34    }
35
36    /// Remove a validation rule
37    pub fn remove_rule(&mut self, name: &str) {
38        self.rules.remove(name);
39    }
40
41    /// Validate a value against all rules
42    pub fn validate_all(&self, value: &str) -> SecurityResult<()> {
43        for (name, rule) in &self.rules {
44            rule.validate(value).map_err(|e| {
45                SecurityError::ValidationError(format!("Rule '{}' failed: {}", name, e))
46            })?;
47        }
48        Ok(())
49    }
50
51    /// Validate against specific rules
52    pub fn validate_with(&self, value: &str, rule_names: &[&str]) -> SecurityResult<()> {
53        for name in rule_names {
54            if let Some(rule) = self.rules.get(*name) {
55                rule.validate(value).map_err(|e| {
56                    SecurityError::ValidationError(format!("Rule '{}' failed: {}", name, e))
57                })?;
58            } else {
59                return Err(SecurityError::ValidationError(format!(
60                    "Rule '{}' not found",
61                    name
62                )));
63            }
64        }
65        Ok(())
66    }
67
68    /// Get all rule names
69    pub fn get_rule_names(&self) -> Vec<String> {
70        self.rules.keys().cloned().collect()
71    }
72}
73
74impl Default for Validator {
75    fn default() -> Self {
76        Self::new()
77    }
78}
79
80/// Length validation rule
81pub struct LengthRule {
82    min: usize,
83    max: usize,
84}
85
86impl LengthRule {
87    pub fn new(min: usize, max: usize) -> Self {
88        Self { min, max }
89    }
90}
91
92impl ValidationRule for LengthRule {
93    fn validate(&self, value: &str) -> SecurityResult<()> {
94        let len = value.len();
95        if len < self.min {
96            return Err(SecurityError::ValidationError(format!(
97                "Value too short (minimum {} characters)",
98                self.min
99            )));
100        }
101        if len > self.max {
102            return Err(SecurityError::ValidationError(format!(
103                "Value too long (maximum {} characters)",
104                self.max
105            )));
106        }
107        Ok(())
108    }
109
110    fn name(&self) -> &str {
111        "length"
112    }
113
114    fn description(&self) -> &str {
115        "Validates string length"
116    }
117}
118
119/// Regex validation rule
120pub struct RegexRule {
121    pattern: regex::Regex,
122    description_text: String,
123}
124
125impl RegexRule {
126    pub fn new(pattern: regex::Regex, description: String) -> Self {
127        Self {
128            pattern,
129            description_text: description,
130        }
131    }
132}
133
134impl ValidationRule for RegexRule {
135    fn validate(&self, value: &str) -> SecurityResult<()> {
136        if !self.pattern.is_match(value) {
137            return Err(SecurityError::ValidationError(format!(
138                "Value does not match required pattern: {}",
139                self.description_text
140            )));
141        }
142        Ok(())
143    }
144
145    fn name(&self) -> &str {
146        "regex"
147    }
148
149    fn description(&self) -> &str {
150        &self.description_text
151    }
152}
153
154/// Alphanumeric validation rule
155pub struct AlphanumericRule {
156    allow_spaces: bool,
157}
158
159impl AlphanumericRule {
160    pub fn new(allow_spaces: bool) -> Self {
161        Self { allow_spaces }
162    }
163}
164
165impl ValidationRule for AlphanumericRule {
166    fn validate(&self, value: &str) -> SecurityResult<()> {
167        for c in value.chars() {
168            if !c.is_alphanumeric() {
169                if self.allow_spaces && c.is_whitespace() {
170                    continue;
171                }
172                return Err(SecurityError::ValidationError(
173                    "Value must be alphanumeric".to_string(),
174                ));
175            }
176        }
177        Ok(())
178    }
179
180    fn name(&self) -> &str {
181        "alphanumeric"
182    }
183
184    fn description(&self) -> &str {
185        if self.allow_spaces {
186            "Validates alphanumeric characters with spaces"
187        } else {
188            "Validates alphanumeric characters"
189        }
190    }
191}
192
193/// Not empty validation rule
194pub struct NotEmptyRule;
195
196impl ValidationRule for NotEmptyRule {
197    fn validate(&self, value: &str) -> SecurityResult<()> {
198        if value.trim().is_empty() {
199            return Err(SecurityError::ValidationError(
200                "Value cannot be empty".to_string(),
201            ));
202        }
203        Ok(())
204    }
205
206    fn name(&self) -> &str {
207        "not_empty"
208    }
209
210    fn description(&self) -> &str {
211        "Validates that value is not empty"
212    }
213}
214
215/// Custom validation rule from closure
216pub struct CustomRule<F>
217where
218    F: Fn(&str) -> SecurityResult<()> + Send + Sync,
219{
220    validator: F,
221    rule_name: String,
222    description_text: String,
223}
224
225impl<F> CustomRule<F>
226where
227    F: Fn(&str) -> SecurityResult<()> + Send + Sync,
228{
229    pub fn new(validator: F, name: String, description: String) -> Self {
230        Self {
231            validator,
232            rule_name: name,
233            description_text: description,
234        }
235    }
236}
237
238impl<F> ValidationRule for CustomRule<F>
239where
240    F: Fn(&str) -> SecurityResult<()> + Send + Sync,
241{
242    fn validate(&self, value: &str) -> SecurityResult<()> {
243        (self.validator)(value)
244    }
245
246    fn name(&self) -> &str {
247        &self.rule_name
248    }
249
250    fn description(&self) -> &str {
251        &self.description_text
252    }
253}
254
255#[cfg(test)]
256mod tests {
257    use super::*;
258
259    #[test]
260    fn test_length_rule() {
261        let rule = LengthRule::new(5, 10);
262
263        assert!(rule.validate("hello").is_ok());
264        assert!(rule.validate("hello world").is_err()); // Too long
265        assert!(rule.validate("hi").is_err()); // Too short
266    }
267
268    #[test]
269    fn test_regex_rule() {
270        let pattern = regex::Regex::new(r"^[a-z]+$").unwrap();
271        let rule = RegexRule::new(pattern, "lowercase letters only".to_string());
272
273        assert!(rule.validate("hello").is_ok());
274        assert!(rule.validate("Hello").is_err());
275        assert!(rule.validate("123").is_err());
276    }
277
278    #[test]
279    fn test_alphanumeric_rule() {
280        let rule = AlphanumericRule::new(false);
281
282        assert!(rule.validate("abc123").is_ok());
283        assert!(rule.validate("abc 123").is_err());
284        assert!(rule.validate("abc-123").is_err());
285
286        let rule_with_spaces = AlphanumericRule::new(true);
287        assert!(rule_with_spaces.validate("abc 123").is_ok());
288    }
289
290    #[test]
291    fn test_not_empty_rule() {
292        let rule = NotEmptyRule;
293
294        assert!(rule.validate("hello").is_ok());
295        assert!(rule.validate("").is_err());
296        assert!(rule.validate("   ").is_err());
297    }
298
299    #[test]
300    fn test_validator_multiple_rules() {
301        let mut validator = Validator::new();
302
303        validator.add_rule("not_empty".to_string(), Box::new(NotEmptyRule));
304        validator.add_rule("length".to_string(), Box::new(LengthRule::new(3, 10)));
305
306        assert!(validator.validate_all("hello").is_ok());
307        assert!(validator.validate_all("").is_err());
308        assert!(validator.validate_all("this is too long").is_err());
309    }
310
311    #[test]
312    fn test_validator_specific_rules() {
313        let mut validator = Validator::new();
314
315        validator.add_rule("not_empty".to_string(), Box::new(NotEmptyRule));
316        validator.add_rule("length".to_string(), Box::new(LengthRule::new(3, 10)));
317        validator.add_rule(
318            "alphanumeric".to_string(),
319            Box::new(AlphanumericRule::new(false)),
320        );
321
322        // Validate with specific rules
323        assert!(validator
324            .validate_with("hello123", &["not_empty", "alphanumeric"])
325            .is_ok());
326
327        // Should fail alphanumeric but we're not checking it
328        assert!(validator.validate_with("hello!", &["not_empty"]).is_ok());
329
330        // Should fail when we check alphanumeric
331        assert!(validator
332            .validate_with("hello!", &["not_empty", "alphanumeric"])
333            .is_err());
334    }
335
336    #[test]
337    fn test_custom_rule() {
338        let rule = CustomRule::new(
339            |value| {
340                if value.starts_with("test_") {
341                    Ok(())
342                } else {
343                    Err(SecurityError::ValidationError(
344                        "Must start with test_".to_string(),
345                    ))
346                }
347            },
348            "starts_with_test".to_string(),
349            "Validates that value starts with test_".to_string(),
350        );
351
352        assert!(rule.validate("test_value").is_ok());
353        assert!(rule.validate("value").is_err());
354    }
355
356    #[test]
357    fn test_validator_rule_management() {
358        let mut validator = Validator::new();
359
360        validator.add_rule("rule1".to_string(), Box::new(NotEmptyRule));
361        assert_eq!(validator.get_rule_names().len(), 1);
362
363        validator.add_rule("rule2".to_string(), Box::new(NotEmptyRule));
364        assert_eq!(validator.get_rule_names().len(), 2);
365
366        validator.remove_rule("rule1");
367        assert_eq!(validator.get_rule_names().len(), 1);
368    }
369
370    #[test]
371    fn test_nonexistent_rule() {
372        let validator = Validator::new();
373
374        let result = validator.validate_with("value", &["nonexistent"]);
375        assert!(result.is_err());
376    }
377}