1pub mod freshness;
2pub mod naming;
3pub mod schema;
4
5use crate::config::Config;
6use crate::model::Graph;
7
8#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize)]
10#[serde(rename_all = "snake_case")]
11pub enum Severity {
12 Error,
13 Warning,
14}
15
16#[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
26pub 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
33pub fn check_all(graph: &Graph, config: &Config) -> Vec<Violation> {
35 let rules: Vec<Box<dyn Rule>> = vec![
36 Box::new(schema::RequiredFieldRule),
39 Box::new(schema::FieldTypeRule),
40 Box::new(schema::FieldEnumRule),
41 Box::new(schema::CrossFieldRule),
42 Box::new(freshness::StaleReviewRule),
44 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}