zenith_core/validate/check/
policy.rs1use crate::ast::policy::{DiagnosticPolicy, PolicyVerb};
25use crate::diag_catalog;
26use crate::diagnostics::{Diagnostic, Severity};
27
28pub fn apply_policy(diagnostics: Vec<Diagnostic>, policy: &DiagnosticPolicy) -> Vec<Diagnostic> {
35 if policy.entries.is_empty() {
37 return diagnostics;
38 }
39
40 let mut out: Vec<Diagnostic> = Vec::with_capacity(diagnostics.len());
41 for mut diag in diagnostics {
42 match policy.verb_for(&diag.code, diag.subject_id.as_deref()) {
43 None => out.push(diag),
44 Some(verb) => match verb {
45 PolicyVerb::Allow => {
46 match diag.severity {
48 Severity::Error => out.push(diag),
49 Severity::Warning | Severity::Advisory => {
50 }
52 }
53 }
54 PolicyVerb::Deny => {
55 match diag.severity {
57 Severity::Error => {}
58 Severity::Warning | Severity::Advisory => {
59 diag.severity = Severity::Error;
60 }
61 }
62 out.push(diag);
63 }
64 PolicyVerb::Warn => {
65 match diag.severity {
67 Severity::Error => {}
68 Severity::Warning | Severity::Advisory => {
69 diag.severity = Severity::Warning;
70 }
71 }
72 out.push(diag);
73 }
74 },
75 }
76 }
77 out
78}
79
80pub(super) fn check_policy_entries(policy: &DiagnosticPolicy, diagnostics: &mut Vec<Diagnostic>) {
91 for entry in &policy.entries {
92 match diag_catalog::lookup(&entry.code) {
93 None => {
94 diagnostics.push(Diagnostic::warning(
95 "policy.unknown_code",
96 format!(
97 "diagnostics policy names '{}', which is not a diagnostic code this \
98 engine emits; the entry has no effect",
99 entry.code
100 ),
101 entry.source_span,
102 Some(entry.code.clone()),
103 ));
104 }
105 Some(catalog_entry) => {
106 if !catalog_entry.is_governable() {
107 let verb_name = match entry.verb {
110 PolicyVerb::Allow => "allow",
111 PolicyVerb::Warn => "warn",
112 PolicyVerb::Deny => continue,
114 };
115 diagnostics.push(Diagnostic::warning(
116 "policy.ineffective_on_error",
117 format!(
118 "diagnostics policy `{verb_name} \"{}\"` has no effect: '{}' is an \
119 integrity Error and Error severity cannot be suppressed or \
120 weakened",
121 entry.code, entry.code
122 ),
123 entry.source_span,
124 Some(entry.code.clone()),
125 ));
126 }
127 }
128 }
129 }
130}
131
132#[cfg(test)]
133mod tests {
134 use super::*;
135 use crate::ast::policy::PolicyEntry;
136
137 fn policy(entries: Vec<(PolicyVerb, &str)>) -> DiagnosticPolicy {
138 DiagnosticPolicy {
139 entries: entries
140 .into_iter()
141 .map(|(verb, code)| PolicyEntry {
142 verb,
143 code: code.to_owned(),
144 subjects: Vec::new(),
145 source_span: None,
146 })
147 .collect(),
148 }
149 }
150
151 fn scoped_policy(entries: Vec<(PolicyVerb, &str, Vec<&str>)>) -> DiagnosticPolicy {
152 DiagnosticPolicy {
153 entries: entries
154 .into_iter()
155 .map(|(verb, code, subjects)| PolicyEntry {
156 verb,
157 code: code.to_owned(),
158 subjects: subjects.into_iter().map(str::to_owned).collect(),
159 source_span: None,
160 })
161 .collect(),
162 }
163 }
164
165 fn diag(code: &str, severity: Severity) -> Diagnostic {
166 Diagnostic::new(code, severity, "msg", None, None)
167 }
168
169 fn subject_diag(code: &str, severity: Severity, subject: &str) -> Diagnostic {
170 Diagnostic::new(code, severity, "msg", None, Some(subject.to_owned()))
171 }
172
173 #[test]
174 fn empty_policy_is_identity() {
175 let input = vec![diag("layout.off_canvas", Severity::Advisory)];
176 let out = apply_policy(input.clone(), &DiagnosticPolicy::default());
177 assert_eq!(out, input);
178 }
179
180 #[test]
181 fn allow_drops_advisory_but_not_error() {
182 let input = vec![
183 diag("layout.off_canvas", Severity::Advisory),
184 diag("id.duplicate", Severity::Error),
185 ];
186 let p = policy(vec![
187 (PolicyVerb::Allow, "layout.off_canvas"),
188 (PolicyVerb::Allow, "id.duplicate"),
189 ]);
190 let out = apply_policy(input, &p);
191 assert_eq!(out.len(), 1);
193 assert_eq!(out[0].code, "id.duplicate");
194 assert_eq!(out[0].severity, Severity::Error);
195 }
196
197 #[test]
198 fn deny_elevates_to_error() {
199 let input = vec![diag("token.unused", Severity::Advisory)];
200 let p = policy(vec![(PolicyVerb::Deny, "token.unused")]);
201 let out = apply_policy(input, &p);
202 assert_eq!(out.len(), 1);
203 assert_eq!(out[0].severity, Severity::Error);
204 }
205
206 #[test]
207 fn warn_forces_warning_and_last_wins_over_deny() {
208 let input = vec![diag("node.unknown_property", Severity::Warning)];
209 let p = policy(vec![
211 (PolicyVerb::Deny, "node.unknown_property"),
212 (PolicyVerb::Warn, "node.unknown_property"),
213 ]);
214 let out = apply_policy(input, &p);
215 assert_eq!(out[0].severity, Severity::Warning);
216 }
217
218 #[test]
219 fn scoped_allow_drops_only_matching_subject() {
220 let input = vec![
221 subject_diag("layout.off_canvas", Severity::Advisory, "bg.glow"),
222 subject_diag("layout.off_canvas", Severity::Advisory, "shape.1"),
223 ];
224 let p = scoped_policy(vec![(
225 PolicyVerb::Allow,
226 "layout.off_canvas",
227 vec!["bg.glow"],
228 )]);
229 let out = apply_policy(input, &p);
230 assert_eq!(out.len(), 1);
231 assert_eq!(out[0].subject_id.as_deref(), Some("shape.1"));
232 }
233
234 #[test]
235 fn unscoped_later_entry_overrides_scoped_entry_for_same_subject() {
236 let input = vec![subject_diag(
237 "layout.off_canvas",
238 Severity::Advisory,
239 "bg.glow",
240 )];
241 let p = DiagnosticPolicy {
242 entries: vec![
243 PolicyEntry {
244 verb: PolicyVerb::Deny,
245 code: "layout.off_canvas".to_owned(),
246 subjects: vec!["bg.glow".to_owned()],
247 source_span: None,
248 },
249 PolicyEntry {
250 verb: PolicyVerb::Warn,
251 code: "layout.off_canvas".to_owned(),
252 subjects: Vec::new(),
253 source_span: None,
254 },
255 ],
256 };
257 let out = apply_policy(input, &p);
258 assert_eq!(out[0].severity, Severity::Warning);
259 }
260
261 #[test]
262 fn unknown_code_is_flagged() {
263 let p = policy(vec![(PolicyVerb::Allow, "not.a_real_code")]);
264 let mut out = Vec::new();
265 check_policy_entries(&p, &mut out);
266 assert_eq!(out.len(), 1);
267 assert_eq!(out[0].code, "policy.unknown_code");
268 }
269
270 #[test]
271 fn allow_on_error_code_is_flagged_but_deny_is_silent() {
272 let p = policy(vec![
273 (PolicyVerb::Allow, "id.duplicate"),
274 (PolicyVerb::Deny, "id.duplicate"),
275 ]);
276 let mut out = Vec::new();
277 check_policy_entries(&p, &mut out);
278 assert_eq!(out.len(), 1);
280 assert_eq!(out[0].code, "policy.ineffective_on_error");
281 }
282}