llm_config_security/
validation.rs1use crate::errors::{SecurityError, SecurityResult};
4use std::collections::HashMap;
5
6pub trait ValidationRule: Send + Sync {
8 fn validate(&self, value: &str) -> SecurityResult<()>;
10
11 fn name(&self) -> &str;
13
14 fn description(&self) -> &str;
16}
17
18pub struct Validator {
20 rules: HashMap<String, Box<dyn ValidationRule>>,
21}
22
23impl Validator {
24 pub fn new() -> Self {
26 Self {
27 rules: HashMap::new(),
28 }
29 }
30
31 pub fn add_rule(&mut self, name: String, rule: Box<dyn ValidationRule>) {
33 self.rules.insert(name, rule);
34 }
35
36 pub fn remove_rule(&mut self, name: &str) {
38 self.rules.remove(name);
39 }
40
41 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 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 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
80pub 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
119pub 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
154pub 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
193pub 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
215pub 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()); assert!(rule.validate("hi").is_err()); }
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 assert!(validator
324 .validate_with("hello123", &["not_empty", "alphanumeric"])
325 .is_ok());
326
327 assert!(validator.validate_with("hello!", &["not_empty"]).is_ok());
329
330 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}