aaai_core/audit/
engine.rs1use crate::audit::warning;
4use crate::config::definition::AuditDefinition;
5use crate::diff::entry::{DiffEntry, DiffType};
6use super::result::{AuditResult, AuditStatus, FileAuditResult};
7use super::strategy;
8
9pub struct AuditEngine;
10
11#[derive(Debug, Clone, Default)]
13pub struct AuditOptions {
14 pub suppress_warnings: Vec<String>,
16}
17
18impl AuditEngine {
19 pub fn evaluate(diffs: &[DiffEntry], definition: &AuditDefinition) -> AuditResult {
21 Self::evaluate_with_options(diffs, definition, &AuditOptions::default())
22 }
23
24 pub fn evaluate_with_options(
26 diffs: &[DiffEntry],
27 definition: &AuditDefinition,
28 options: &AuditOptions,
29 ) -> AuditResult {
30 let mut results = Vec::new();
31
32 for diff in diffs {
33 let mut result = judge(diff, definition);
34 if !options.suppress_warnings.is_empty() {
36 result.warnings.retain(|w| {
37 !options.suppress_warnings.iter().any(|s| s == w.kind())
38 });
39 }
40 results.push(result);
41 }
42
43 AuditResult::new(results)
44 }
45}
46
47fn judge(diff: &DiffEntry, definition: &AuditDefinition) -> FileAuditResult {
48 if diff.diff_type.is_error() {
50 return FileAuditResult {
51 diff: diff.clone(),
52 entry: None,
53 status: AuditStatus::Error,
54 detail: diff.error_detail.clone().or_else(|| {
55 Some("File could not be read or compared.".into())
56 }),
57 warnings: Vec::new(),
58 };
59 }
60
61 if diff.diff_type == DiffType::Unchanged {
63 return FileAuditResult {
64 diff: diff.clone(),
65 entry: definition.find_entry(&diff.path).cloned(),
66 status: AuditStatus::Ok,
67 detail: None,
68 warnings: Vec::new(),
69 };
70 }
71
72 let entry = match definition.find_entry(&diff.path) {
74 Some(e) => e,
75 None => {
76 return FileAuditResult {
77 diff: diff.clone(),
78 entry: None,
79 status: AuditStatus::Pending,
80 detail: Some("No audit rule defined for this path.".into()),
81 warnings: Vec::new(),
82 };
83 }
84 };
85
86 if !entry.enabled {
88 return FileAuditResult {
89 diff: diff.clone(),
90 entry: Some(entry.clone()),
91 status: AuditStatus::Ignored,
92 detail: Some("Entry is disabled.".into()),
93 warnings: Vec::new(),
94 };
95 }
96
97 if entry.reason.trim().is_empty() {
99 return FileAuditResult {
100 diff: diff.clone(),
101 entry: Some(entry.clone()),
102 status: AuditStatus::Pending,
103 detail: Some("Entry exists but has no reason — human approval required.".into()),
104 warnings: Vec::new(),
105 };
106 }
107
108 if entry.diff_type != diff.diff_type {
110 return FileAuditResult {
111 diff: diff.clone(),
112 entry: Some(entry.clone()),
113 status: AuditStatus::Failed,
114 detail: Some(format!(
115 "Expected diff type {:?} but found {:?}.",
116 entry.diff_type, diff.diff_type
117 )),
118 warnings: Vec::new(),
119 };
120 }
121
122 match strategy::evaluate(&entry.strategy, diff) {
124 Ok(()) => {
125 let warns = warning::collect(diff, entry);
126 FileAuditResult {
127 diff: diff.clone(),
128 entry: Some(entry.clone()),
129 status: AuditStatus::Ok,
130 detail: None,
131 warnings: warns,
132 }
133 }
134 Err(msg) => FileAuditResult {
135 diff: diff.clone(),
136 entry: Some(entry.clone()),
137 status: AuditStatus::Failed,
138 detail: Some(msg),
139 warnings: Vec::new(),
140 },
141 }
142}