imferno_core/diagnostics/
rules.rs1use serde::{Deserialize, Serialize};
9use std::collections::HashMap;
10
11use crate::diagnostics::codes::ValidationCode;
12use crate::{Severity, ValidationReport};
13
14#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
19#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
20#[serde(rename_all = "lowercase")]
21pub enum RuleSeverity {
22 Off,
24 Info,
26 Warn,
28 Error,
30 Critical,
32}
33
34#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
46#[derive(Debug, Clone, Default, Serialize, Deserialize)]
47pub struct RulesConfig(HashMap<String, RuleSeverity>);
48
49impl RulesConfig {
50 pub fn set(&mut self, code: impl ValidationCode, severity: RuleSeverity) {
60 self.0.insert(code.code().to_string(), severity);
61 }
62
63 pub fn set_raw(&mut self, key: String, severity: RuleSeverity) {
65 self.0.insert(key, severity);
66 }
67
68 pub fn is_empty(&self) -> bool {
70 self.0.is_empty()
71 }
72
73 pub fn len(&self) -> usize {
75 self.0.len()
76 }
77
78 pub fn iter(&self) -> impl Iterator<Item = (&String, &RuleSeverity)> {
80 self.0.iter()
81 }
82}
83
84fn rule_matches(code: &str, key: &str) -> bool {
85 code == key || code.rsplit('/').next() == Some(key)
87}
88
89impl ValidationReport {
90 pub fn apply_rules(mut self, rules: &RulesConfig) -> Self {
98 if rules.is_empty() {
99 return self;
100 }
101
102 let all: Vec<_> = self
103 .critical
104 .drain(..)
105 .chain(self.errors.drain(..))
106 .chain(self.warnings.drain(..))
107 .chain(self.info.drain(..))
108 .collect();
109
110 for mut issue in all {
111 let override_sev = rules
112 .iter()
113 .find(|(k, _)| rule_matches(&issue.code, k))
114 .map(|(_, v)| v);
115
116 match override_sev {
117 Some(RuleSeverity::Off) => {} Some(RuleSeverity::Info) => {
119 issue.severity = Severity::Info;
120 self.info.push(issue);
121 }
122 Some(RuleSeverity::Warn) => {
123 issue.severity = Severity::Warning;
124 self.warnings.push(issue);
125 }
126 Some(RuleSeverity::Error) => {
127 issue.severity = Severity::Error;
128 self.errors.push(issue);
129 }
130 Some(RuleSeverity::Critical) => {
131 issue.severity = Severity::Critical;
132 self.critical.push(issue);
133 }
134 None => match issue.severity {
135 Severity::Critical => self.critical.push(issue),
136 Severity::Error => self.errors.push(issue),
137 Severity::Warning => self.warnings.push(issue),
138 Severity::Info => self.info.push(issue),
139 },
140 }
141 }
142
143 self.is_playable = self.critical.is_empty();
144 self.is_compliant = self.critical.is_empty() && self.errors.is_empty();
145 self
146 }
147}
148
149#[cfg(test)]
150mod tests {
151 use super::*;
152
153 #[test]
154 fn rules_config_accessors() {
155 let mut rules = RulesConfig::default();
156 assert!(rules.is_empty());
157 assert_eq!(rules.len(), 0);
158
159 rules.set(
160 crate::assetmap::codes::St2067_2_2020::FileNotFound,
161 RuleSeverity::Critical,
162 );
163 assert!(!rules.is_empty());
164 assert_eq!(rules.len(), 1);
165 assert_eq!(rules.iter().count(), 1);
166 }
167
168 #[test]
169 fn rules_config_serde_round_trip() {
170 let mut rules = RulesConfig::default();
171 rules.set(
172 crate::assetmap::codes::St2067_2_2020::FileNotFound,
173 RuleSeverity::Off,
174 );
175 let json = serde_json::to_string(&rules).unwrap();
176 let deserialized: RulesConfig = serde_json::from_str(&json).unwrap();
177 assert_eq!(deserialized.len(), 1);
178 }
179}