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 faf.data.instant_context.is_none() {
50 warnings.push("Missing instant_context section".to_string());
51 } else {
52 let ic = faf.data.instant_context.as_ref().unwrap();
53 if ic.what_building.is_none() {
54 warnings.push("Missing instant_context.what_building".to_string());
55 }
56 if ic.tech_stack.is_none() {
57 warnings.push("Missing instant_context.tech_stack".to_string());
58 }
59 }
60
61 if faf.data.stack.is_none() {
62 warnings.push("Missing stack section".to_string());
63 }
64
65 if faf.data.human_context.is_none() {
66 warnings.push("Missing human_context section".to_string());
67 }
68
69 let score = calculate_score(faf);
71
72 ValidationResult {
73 valid: errors.is_empty(),
74 errors,
75 warnings,
76 score,
77 }
78}
79
80fn calculate_score(faf: &FafFile) -> u8 {
81 let mut score: u8 = 0;
82
83 if !faf.data.faf_version.is_empty() {
85 score += 10;
86 }
87 if !faf.data.project.name.is_empty() {
88 score += 10;
89 }
90 if faf.data.project.goal.is_some() {
91 score += 10;
92 }
93
94 if let Some(ic) = &faf.data.instant_context {
96 if ic.what_building.is_some() {
97 score += 10;
98 }
99 if ic.tech_stack.is_some() {
100 score += 10;
101 }
102 if !ic.key_files.is_empty() {
103 score += 10;
104 }
105 }
106
107 if faf.data.stack.is_some() {
109 score += 15;
110 }
111
112 if faf.data.human_context.is_some() {
114 score += 15;
115 }
116
117 if !faf.data.tags.is_empty() {
119 score += 5;
120 }
121 if faf.data.state.is_some() {
122 score += 5;
123 }
124
125 score.min(100)
126}
127
128#[cfg(test)]
129mod tests {
130 use super::*;
131 use crate::parse;
132
133 #[test]
134 fn test_validate_minimal() {
135 let content = r#"
136faf_version: 2.5.0
137project:
138 name: test
139"#;
140 let faf = parse(content).unwrap();
141 let result = validate(&faf);
142 assert!(result.valid);
143 assert!(result.score >= 20);
144 }
145
146 #[test]
147 fn test_validate_full() {
148 let content = r#"
149faf_version: 2.5.0
150project:
151 name: test
152 goal: Testing
153instant_context:
154 what_building: Test
155 tech_stack: Rust
156 key_files:
157 - main.rs
158stack:
159 backend: Rust
160human_context:
161 who: Developers
162tags:
163 - rust
164state:
165 phase: dev
166"#;
167 let faf = parse(content).unwrap();
168 let result = validate(&faf);
169 assert!(result.valid);
170 assert!(result.score >= 90);
171 }
172}