Skip to main content

nodex_core/rules/
mod.rs

1pub mod freshness;
2pub mod naming;
3pub mod schema;
4
5use crate::config::Config;
6use crate::model::Graph;
7
8/// Severity of a rule violation.
9#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize)]
10#[serde(rename_all = "snake_case")]
11pub enum Severity {
12    Error,
13    Warning,
14}
15
16/// A single rule violation.
17#[derive(Debug, Clone, serde::Serialize)]
18pub struct Violation {
19    pub rule_id: String,
20    pub severity: Severity,
21    pub node_id: Option<String>,
22    pub path: Option<String>,
23    pub message: String,
24}
25
26/// Trait for validation rules.
27pub trait Rule: Send + Sync {
28    fn id(&self) -> &str;
29    fn severity(&self) -> Severity;
30    fn check(&self, graph: &Graph, config: &Config) -> Vec<Violation>;
31}
32
33/// Run all built-in rules and return violations.
34pub fn check_all(graph: &Graph, config: &Config) -> Vec<Violation> {
35    let rules: Vec<Box<dyn Rule>> = vec![
36        // Schema family — required-field presence + declarative type,
37        // enum, and cross-field constraints driven by nodex.toml.
38        Box::new(schema::RequiredFieldRule),
39        Box::new(schema::FieldTypeRule),
40        Box::new(schema::FieldEnumRule),
41        Box::new(schema::CrossFieldRule),
42        // Freshness family.
43        Box::new(freshness::StaleReviewRule),
44        // Naming family.
45        Box::new(naming::FilenamePatternRule),
46        Box::new(naming::SequentialNumberingRule),
47        Box::new(naming::UniqueNumberingRule),
48    ];
49
50    let mut violations: Vec<Violation> = rules
51        .iter()
52        .flat_map(|rule| rule.check(graph, config))
53        .collect();
54
55    violations.sort_by(|a, b| {
56        a.rule_id
57            .cmp(&b.rule_id)
58            .then_with(|| a.node_id.cmp(&b.node_id))
59    });
60
61    violations
62}