faf_rust_sdk/
validator.rs1use crate::parser::FafFile;
4
5#[derive(Debug, Clone)]
7pub struct ValidationResult {
8 pub valid: bool,
10 pub errors: Vec<String>,
12 pub warnings: Vec<String>,
14 pub score: u8,
16}
17
18pub fn validate(faf: &FafFile) -> ValidationResult {
36 let mut errors = Vec::new();
37 let mut warnings = Vec::new();
38
39 if faf.data.faf_version.is_empty() {
41 errors.push("Missing faf_version".to_string());
42 }
43
44 if faf.data.project.name.is_empty() {
45 errors.push("Missing project.name".to_string());
46 }
47
48 if let Some(ic) = &faf.data.instant_context {
50 if ic.what_building.is_none() {
51 warnings.push("Missing instant_context.what_building".to_string());
52 }
53 if ic.tech_stack.is_none() {
54 warnings.push("Missing instant_context.tech_stack".to_string());
55 }
56 } else {
57 warnings.push("Missing instant_context section".to_string());
58 }
59
60 if faf.data.stack.is_none() {
61 warnings.push("Missing stack section".to_string());
62 }
63
64 if faf.data.human_context.is_none() {
65 warnings.push("Missing human_context section".to_string());
66 }
67
68 let score = calculate_score(faf);
70
71 ValidationResult {
72 valid: errors.is_empty(),
73 errors,
74 warnings,
75 score,
76 }
77}
78
79fn calculate_score(faf: &FafFile) -> u8 {
80 let mut score: u8 = 0;
81
82 if !faf.data.faf_version.is_empty() {
84 score += 10;
85 }
86 if !faf.data.project.name.is_empty() {
87 score += 10;
88 }
89 if faf.data.project.goal.is_some() {
90 score += 10;
91 }
92
93 if let Some(ic) = &faf.data.instant_context {
95 if ic.what_building.is_some() {
96 score += 10;
97 }
98 if ic.tech_stack.is_some() {
99 score += 10;
100 }
101 if !ic.key_files.is_empty() {
102 score += 10;
103 }
104 }
105
106 if faf.data.stack.is_some() {
108 score += 15;
109 }
110
111 if faf.data.human_context.is_some() {
113 score += 15;
114 }
115
116 if !faf.data.tags.is_empty() {
118 score += 5;
119 }
120 if faf.data.state.is_some() {
121 score += 5;
122 }
123
124 score.min(100)
125}
126
127#[cfg(test)]
128mod tests {
129 use super::*;
130 use crate::parse;
131
132 #[test]
133 fn test_validate_minimal() {
134 let content = r#"
135faf_version: 2.5.0
136project:
137 name: test
138"#;
139 let faf = parse(content).unwrap();
140 let result = validate(&faf);
141 assert!(result.valid);
142 assert!(result.score >= 20);
143 }
144
145 #[test]
146 fn test_validate_full() {
147 let content = r#"
148faf_version: 2.5.0
149project:
150 name: test
151 goal: Testing
152instant_context:
153 what_building: Test
154 tech_stack: Rust
155 key_files:
156 - main.rs
157stack:
158 backend: Rust
159human_context:
160 who: Developers
161tags:
162 - rust
163state:
164 phase: dev
165"#;
166 let faf = parse(content).unwrap();
167 let result = validate(&faf);
168 assert!(result.valid);
169 assert!(result.score >= 90);
170 }
171}