Skip to main content

roder_roadmap/
validator.rs

1use std::collections::HashSet;
2
3use crate::parser::path_diagnostic;
4use crate::{Diagnostic, DiagnosticSeverity, Document, ValidationResult};
5
6pub fn validate_document(document: &Document) -> ValidationResult {
7    let mut diagnostics = Vec::new();
8    if let Some(diagnostic) = path_diagnostic(&document.path) {
9        diagnostics.push(diagnostic);
10    }
11    require(
12        &mut diagnostics,
13        document,
14        !document.title.trim().is_empty(),
15        None,
16        "missing title heading",
17    );
18    require(
19        &mut diagnostics,
20        document,
21        !document.goal.trim().is_empty(),
22        None,
23        "missing **Goal:** field",
24    );
25    require(
26        &mut diagnostics,
27        document,
28        !document.architecture.trim().is_empty(),
29        None,
30        "missing **Architecture:** field",
31    );
32    require(
33        &mut diagnostics,
34        document,
35        !document.owned_paths.is_empty(),
36        None,
37        "missing owned paths",
38    );
39    require(
40        &mut diagnostics,
41        document,
42        !document.tasks.is_empty(),
43        None,
44        "missing task checklist items",
45    );
46    require(
47        &mut diagnostics,
48        document,
49        document
50            .tasks
51            .iter()
52            .any(|task| !task.run_blocks.is_empty()),
53        None,
54        "missing Run block",
55    );
56    require(
57        &mut diagnostics,
58        document,
59        !document.acceptance.is_empty(),
60        None,
61        "missing acceptance checklist",
62    );
63
64    let mut seen = HashSet::new();
65    for task in &document.tasks {
66        if !seen.insert(task.id.clone()) {
67            diagnostics.push(Diagnostic {
68                path: document.path.clone(),
69                line: Some(task.line),
70                severity: DiagnosticSeverity::Error,
71                message: format!("duplicate task id: {}", task.id),
72            });
73        }
74    }
75
76    ValidationResult {
77        document_id: document.id.clone(),
78        diagnostics,
79    }
80}
81
82fn require(
83    diagnostics: &mut Vec<Diagnostic>,
84    document: &Document,
85    ok: bool,
86    line: Option<usize>,
87    message: &str,
88) {
89    if !ok {
90        diagnostics.push(Diagnostic {
91            path: document.path.clone(),
92            line,
93            severity: DiagnosticSeverity::Error,
94            message: message.to_string(),
95        });
96    }
97}