tldr_core/patterns/
validation.rs1use super::signals::PatternSignals;
11use crate::types::ValidationPattern;
12
13pub fn signals_to_pattern(
15 signals: &PatternSignals,
16 evidence_limit: usize,
17) -> Option<ValidationPattern> {
18 let validation = &signals.validation;
19
20 if !validation.has_signals() {
21 return None;
22 }
23
24 let confidence = validation.calculate_confidence();
25
26 let mut frameworks = Vec::new();
28 if !validation.pydantic_models.is_empty() {
29 frameworks.push("pydantic".to_string());
30 }
31 if !validation.zod_schemas.is_empty() {
32 frameworks.push("zod".to_string());
33 }
34 for (name, _) in &validation.other_validators {
35 frameworks.push(name.clone());
36 }
37 frameworks.sort();
38 frameworks.dedup();
39
40 let mut patterns = Vec::new();
42 if !validation.pydantic_models.is_empty() || !validation.zod_schemas.is_empty() {
43 patterns.push("schema_validation".to_string());
44 }
45 if !validation.guard_clauses.is_empty() {
46 patterns.push("guard_clauses".to_string());
47 }
48 if !validation.assert_statements.is_empty() {
49 patterns.push("assertions".to_string());
50 }
51 if !validation.type_checks.is_empty() {
52 patterns.push("type_checking".to_string());
53 }
54
55 let mut evidence = Vec::new();
57 evidence.extend(
58 validation
59 .pydantic_models
60 .iter()
61 .take(evidence_limit)
62 .cloned(),
63 );
64 evidence.extend(validation.zod_schemas.iter().take(evidence_limit).cloned());
65 evidence.extend(
66 validation
67 .guard_clauses
68 .iter()
69 .take(evidence_limit)
70 .cloned(),
71 );
72 evidence.extend(
73 validation
74 .assert_statements
75 .iter()
76 .take(evidence_limit)
77 .cloned(),
78 );
79 evidence.extend(validation.type_checks.iter().take(evidence_limit).cloned());
80 evidence.truncate(evidence_limit);
81
82 Some(ValidationPattern {
83 confidence,
84 frameworks,
85 patterns,
86 evidence,
87 })
88}
89
90#[cfg(test)]
91mod tests {
92 use super::*;
93 use crate::types::Evidence;
94
95 #[test]
96 fn test_no_signals_returns_none() {
97 let signals = PatternSignals::default();
98 assert!(signals_to_pattern(&signals, 3).is_none());
99 }
100
101 #[test]
102 fn test_pydantic_detected() {
103 let mut signals = PatternSignals::default();
104 signals.validation.pydantic_models.push(Evidence::new(
105 "models.py",
106 5,
107 "class User(BaseModel):",
108 ));
109
110 let pattern = signals_to_pattern(&signals, 3).unwrap();
111 assert!(pattern.confidence >= 0.5);
112 assert!(pattern.frameworks.contains(&"pydantic".to_string()));
113 assert!(pattern.patterns.contains(&"schema_validation".to_string()));
114 }
115
116 #[test]
117 fn test_zod_detected() {
118 let mut signals = PatternSignals::default();
119 signals.validation.zod_schemas.push(Evidence::new(
120 "schema.ts",
121 10,
122 "const userSchema = z.object({ name: z.string() })",
123 ));
124
125 let pattern = signals_to_pattern(&signals, 3).unwrap();
126 assert!(pattern.frameworks.contains(&"zod".to_string()));
127 }
128
129 #[test]
130 fn test_assert_pattern_detected() {
131 let mut signals = PatternSignals::default();
132 signals.validation.assert_statements.push(Evidence::new(
133 "utils.py",
134 15,
135 "assert x > 0, 'x must be positive'",
136 ));
137
138 let pattern = signals_to_pattern(&signals, 3).unwrap();
139 assert!(pattern.patterns.contains(&"assertions".to_string()));
140 }
141}