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;
26
27#[derive(Debug, Clone, Default)]
29pub struct AuditOptions {
30 pub suppress_warnings: Vec<String>,
32}
33
34impl AuditEngine {
35 pub fn evaluate(diffs: &[DiffEntry], definition: &AuditDefinition) -> AuditResult {
40 Self::evaluate_with_options(diffs, definition, &AuditOptions::default())
41 }
42
43 pub fn evaluate_with_options(
48 diffs: &[DiffEntry],
49 definition: &AuditDefinition,
50 options: &AuditOptions,
51 ) -> AuditResult {
52 let mut results = Vec::new();
53
54 for diff in diffs {
55 let mut result = judge(diff, definition);
56 if !options.suppress_warnings.is_empty() {
58 result.warnings.retain(|w| {
59 !options.suppress_warnings.iter().any(|s| s == w.kind())
60 });
61 }
62 results.push(result);
63 }
64
65 AuditResult::new(results)
66 }
67}
68
69fn judge(diff: &DiffEntry, definition: &AuditDefinition) -> FileAuditResult {
70 if diff.diff_type.is_error() {
72 return FileAuditResult {
73 diff: diff.clone(),
74 entry: None,
75 status: AuditStatus::Error,
76 detail: diff.error_detail.clone().or_else(|| {
77 Some("File could not be read or compared.".into())
78 }),
79 warnings: Vec::new(),
80 };
81 }
82
83 if diff.diff_type == DiffType::Unchanged {
85 return FileAuditResult {
86 diff: diff.clone(),
87 entry: definition.find_entry(&diff.path).cloned(),
88 status: AuditStatus::Ok,
89 detail: None,
90 warnings: Vec::new(),
91 };
92 }
93
94 let entry = match definition.find_entry(&diff.path) {
96 Some(e) => e,
97 None => {
98 return FileAuditResult {
99 diff: diff.clone(),
100 entry: None,
101 status: AuditStatus::Pending,
102 detail: Some("No audit rule defined for this path.".into()),
103 warnings: Vec::new(),
104 };
105 }
106 };
107
108 if !entry.enabled {
110 return FileAuditResult {
111 diff: diff.clone(),
112 entry: Some(entry.clone()),
113 status: AuditStatus::Ignored,
114 detail: Some("Entry is disabled.".into()),
115 warnings: Vec::new(),
116 };
117 }
118
119 if entry.reason.trim().is_empty() {
121 return FileAuditResult {
122 diff: diff.clone(),
123 entry: Some(entry.clone()),
124 status: AuditStatus::Pending,
125 detail: Some("Entry exists but has no reason — human approval required.".into()),
126 warnings: Vec::new(),
127 };
128 }
129
130 if entry.diff_type != diff.diff_type {
132 return FileAuditResult {
133 diff: diff.clone(),
134 entry: Some(entry.clone()),
135 status: AuditStatus::Failed,
136 detail: Some(format!(
137 "Expected diff type {:?} but found {:?}.",
138 entry.diff_type, diff.diff_type
139 )),
140 warnings: Vec::new(),
141 };
142 }
143
144 match strategy::evaluate(&entry.strategy, diff) {
146 Ok(()) => {
147 let warns = warning::collect(diff, entry);
148 FileAuditResult {
149 diff: diff.clone(),
150 entry: Some(entry.clone()),
151 status: AuditStatus::Ok,
152 detail: None,
153 warnings: warns,
154 }
155 }
156 Err(msg) => FileAuditResult {
157 diff: diff.clone(),
158 entry: Some(entry.clone()),
159 status: AuditStatus::Failed,
160 detail: Some(msg),
161 warnings: Vec::new(),
162 },
163 }
164}