Skip to main content

aaai_core/templates/
library.rs

1//! Built-in rule templates for common audit patterns.
2//!
3//! A [`RuleTemplate`] carries a display name, description, and a factory that
4//! produces a pre-filled [`AuditStrategy`] ready to insert into an inspector
5//! or a snap-generated entry.
6
7use crate::config::definition::{AuditStrategy, LineAction, LineRule, RegexTarget};
8use crate::diff::entry::DiffType;
9
10/// A named, reusable audit-strategy pattern.
11#[derive(Debug, Clone)]
12pub struct RuleTemplate {
13    pub id:          &'static str,
14    pub name:        &'static str,
15    pub name_ja:     &'static str,
16    pub description: &'static str,
17    pub diff_type:   DiffType,
18    /// Produce a strategy instance for this template.
19    pub strategy:    fn() -> AuditStrategy,
20}
21
22/// The built-in template library.
23pub static TEMPLATES: &[RuleTemplate] = &[
24    RuleTemplate {
25        id:          "version_bump",
26        name:        "Version number update",
27        name_ja:     "バージョン番号の更新",
28        description: "A semver-like version string changed in a modified file (Regex).",
29        diff_type:   DiffType::Modified,
30        strategy:    || AuditStrategy::Regex {
31            pattern: r"^\d+\.\d+\.\d+".to_string(),
32            target:  RegexTarget::AddedLines,
33        },
34    },
35    RuleTemplate {
36        id:          "port_change",
37        name:        "Port number change",
38        name_ja:     "ポート番号の変更",
39        description: "A `port = N` line changed to `port = M` (LineMatch template — fill in actual values).",
40        diff_type:   DiffType::Modified,
41        strategy:    || AuditStrategy::LineMatch {
42            rules: vec![
43                LineRule { action: LineAction::Removed, line: "port = ".to_string() },
44                LineRule { action: LineAction::Added,   line: "port = ".to_string() },
45            ],
46        },
47    },
48    RuleTemplate {
49        id:          "file_added_any",
50        name:        "File added (content not inspected)",
51        name_ja:     "ファイルの追加(内容確認なし)",
52        description: "A new file was intentionally added; no content check required.",
53        diff_type:   DiffType::Added,
54        strategy:    || AuditStrategy::None,
55    },
56    RuleTemplate {
57        id:          "file_removed_any",
58        name:        "File removed (content not inspected)",
59        name_ja:     "ファイルの削除(内容確認なし)",
60        description: "A file was intentionally deleted; no content check required.",
61        diff_type:   DiffType::Removed,
62        strategy:    || AuditStrategy::None,
63    },
64    RuleTemplate {
65        id:          "config_line_change",
66        name:        "Config key=value change",
67        name_ja:     "設定値の変更",
68        description: "A `key = value` pair changed (LineMatch template — fill in key and values).",
69        diff_type:   DiffType::Modified,
70        strategy:    || AuditStrategy::LineMatch {
71            rules: vec![
72                LineRule { action: LineAction::Removed, line: "key = old_value".to_string() },
73                LineRule { action: LineAction::Added,   line: "key = new_value".to_string() },
74            ],
75        },
76    },
77    RuleTemplate {
78        id:          "date_string_change",
79        name:        "Date string update",
80        name_ja:     "日付文字列の更新",
81        description: "A date like 2025-01-15 changed; validates ISO-8601 format (Regex).",
82        diff_type:   DiffType::Modified,
83        strategy:    || AuditStrategy::Regex {
84            pattern: r"^\d{4}-\d{2}-\d{2}".to_string(),
85            target:  RegexTarget::AddedLines,
86        },
87    },
88    RuleTemplate {
89        id:          "exact_binary",
90        name:        "Binary / generated file (checksum)",
91        name_ja:     "バイナリ・生成ファイル(チェックサム)",
92        description: "Verify a binary or generated file by its SHA-256 digest (fill in hash).",
93        diff_type:   DiffType::Modified,
94        strategy:    || AuditStrategy::Checksum {
95            expected_sha256: String::new(),
96        },
97    },
98    RuleTemplate {
99        id:          "feature_flag_toggle",
100        name:        "Feature flag toggle",
101        name_ja:     "フィーチャーフラグの切り替え",
102        description: "A boolean flag changed from false to true or vice versa (Regex).",
103        diff_type:   DiffType::Modified,
104        strategy:    || AuditStrategy::Regex {
105            pattern: r"^(true|false)$".to_string(),
106            target:  RegexTarget::AddedLines,
107        },
108    },
109];
110
111/// Find a template by id.
112pub fn find(id: &str) -> Option<&'static RuleTemplate> {
113    TEMPLATES.iter().find(|t| t.id == id)
114}
115
116#[cfg(test)]
117mod tests {
118    use super::*;
119
120    #[test]
121    fn all_templates_produce_valid_strategies() {
122        for tmpl in TEMPLATES {
123            let strat = (tmpl.strategy)();
124            // None and LineMatch with placeholder are valid (or expected to need filling)
125            // Just ensure no panics
126            let _ = strat.label();
127        }
128    }
129
130    #[test]
131    fn find_by_id_works() {
132        assert!(find("version_bump").is_some());
133        assert!(find("nonexistent").is_none());
134    }
135
136    #[test]
137    fn no_duplicate_ids() {
138        let mut ids: Vec<&str> = TEMPLATES.iter().map(|t| t.id).collect();
139        let original_len = ids.len();
140        ids.dedup();
141        assert_eq!(ids.len(), original_len, "duplicate template IDs found");
142    }
143}