automapper_validation/validator/
report.rs1use serde::{Deserialize, Serialize};
4
5use super::issue::{Severity, ValidationCategory, ValidationIssue};
6use super::level::ValidationLevel;
7
8#[derive(Debug, Clone, Serialize, Deserialize)]
13pub struct ValidationReport {
14 pub message_type: String,
16
17 pub pruefidentifikator: Option<String>,
19
20 pub format_version: Option<String>,
22
23 pub level: ValidationLevel,
25
26 pub issues: Vec<ValidationIssue>,
28}
29
30impl ValidationReport {
31 pub fn new(message_type: impl Into<String>, level: ValidationLevel) -> Self {
33 Self {
34 message_type: message_type.into(),
35 pruefidentifikator: None,
36 format_version: None,
37 level,
38 issues: Vec::new(),
39 }
40 }
41
42 pub fn with_pruefidentifikator(mut self, pid: impl Into<String>) -> Self {
44 self.pruefidentifikator = Some(pid.into());
45 self
46 }
47
48 pub fn with_format_version(mut self, fv: impl Into<String>) -> Self {
50 self.format_version = Some(fv.into());
51 self
52 }
53
54 pub fn add_issue(&mut self, issue: ValidationIssue) {
56 self.issues.push(issue);
57 }
58
59 pub fn add_issues(&mut self, issues: impl IntoIterator<Item = ValidationIssue>) {
61 self.issues.extend(issues);
62 }
63
64 pub fn is_valid(&self) -> bool {
66 !self.issues.iter().any(|i| i.severity == Severity::Error)
67 }
68
69 pub fn error_count(&self) -> usize {
71 self.issues
72 .iter()
73 .filter(|i| i.severity == Severity::Error)
74 .count()
75 }
76
77 pub fn warning_count(&self) -> usize {
79 self.issues
80 .iter()
81 .filter(|i| i.severity == Severity::Warning)
82 .count()
83 }
84
85 pub fn errors(&self) -> impl Iterator<Item = &ValidationIssue> {
87 self.issues.iter().filter(|i| i.severity == Severity::Error)
88 }
89
90 pub fn warnings(&self) -> impl Iterator<Item = &ValidationIssue> {
92 self.issues
93 .iter()
94 .filter(|i| i.severity == Severity::Warning)
95 }
96
97 pub fn infos(&self) -> impl Iterator<Item = &ValidationIssue> {
99 self.issues.iter().filter(|i| i.severity == Severity::Info)
100 }
101
102 pub fn by_category(
104 &self,
105 category: ValidationCategory,
106 ) -> impl Iterator<Item = &ValidationIssue> {
107 self.issues.iter().filter(move |i| i.category() == category)
108 }
109
110 pub fn total_issues(&self) -> usize {
112 self.issues.len()
113 }
114
115 pub fn enrich_bo4e_paths(&mut self, resolver: impl Fn(&str, Option<&str>) -> Option<String>) {
122 for issue in &mut self.issues {
123 if let Some(ref edifact_path) = issue.field_path {
124 let hint = issue.expected_value.as_deref().or(issue.rule.as_deref());
127 issue.bo4e_path = resolver(edifact_path, hint);
128 }
129 }
130 }
131}
132
133#[cfg(test)]
134mod tests {
135 use super::*;
136 use crate::IssueKind;
137
138 fn make_error() -> ValidationIssue {
141 ValidationIssue::new(
142 Severity::Error,
143 IssueKind::MissingRequiredField {
144 field_name: "test".into(),
145 },
146 )
147 }
148
149 fn make_warning() -> ValidationIssue {
151 ValidationIssue::new(
152 Severity::Warning,
153 IssueKind::UntSegmentCountMismatch {
154 declared: 1,
155 actual: 2,
156 },
157 )
158 }
159
160 fn make_info() -> ValidationIssue {
162 ValidationIssue::new(
163 Severity::Info,
164 IssueKind::CodeNotAllowedForPid {
165 value: "X".into(),
166 allowed: vec![],
167 },
168 )
169 }
170
171 #[test]
172 fn test_empty_report_is_valid() {
173 let report = ValidationReport::new("UTILMD", ValidationLevel::Full);
174 assert!(report.is_valid());
175 assert_eq!(report.error_count(), 0);
176 assert_eq!(report.warning_count(), 0);
177 assert_eq!(report.total_issues(), 0);
178 }
179
180 #[test]
181 fn test_report_with_errors_is_invalid() {
182 let mut report = ValidationReport::new("UTILMD", ValidationLevel::Full);
183 report.add_issue(make_error());
184
185 assert!(!report.is_valid());
186 assert_eq!(report.error_count(), 1);
187 }
188
189 #[test]
190 fn test_report_with_only_warnings_is_valid() {
191 let mut report = ValidationReport::new("UTILMD", ValidationLevel::Full);
192 report.add_issue(make_warning());
193
194 assert!(report.is_valid());
195 assert_eq!(report.warning_count(), 1);
196 assert_eq!(report.error_count(), 0);
197 }
198
199 #[test]
200 fn test_report_mixed_issues() {
201 let mut report = ValidationReport::new("UTILMD", ValidationLevel::Full)
202 .with_pruefidentifikator("11001")
203 .with_format_version("FV2510");
204
205 report.add_issue(make_error());
206 report.add_issue(make_error());
207 report.add_issue(make_warning());
208 report.add_issue(make_info());
209
210 assert!(!report.is_valid());
211 assert_eq!(report.error_count(), 2);
212 assert_eq!(report.warning_count(), 1);
213 assert_eq!(report.total_issues(), 4);
214 assert_eq!(report.errors().count(), 2);
215 assert_eq!(report.warnings().count(), 1);
216 assert_eq!(report.infos().count(), 1);
217 }
218
219 #[test]
220 fn test_report_by_category() {
221 let mut report = ValidationReport::new("UTILMD", ValidationLevel::Full);
222 report.add_issue(make_error());
223 report.add_issue(make_warning());
224
225 assert_eq!(report.by_category(ValidationCategory::Ahb).count(), 1);
226 assert_eq!(report.by_category(ValidationCategory::Structure).count(), 1);
227 assert_eq!(report.by_category(ValidationCategory::Format).count(), 0);
228 }
229
230 #[test]
231 fn test_report_add_issues() {
232 let mut report = ValidationReport::new("UTILMD", ValidationLevel::Full);
233 let issues = vec![make_error(), make_warning()];
234 report.add_issues(issues);
235
236 assert_eq!(report.total_issues(), 2);
237 }
238
239 #[test]
240 fn test_enrich_bo4e_paths() {
241 let mut report = ValidationReport::new("UTILMD", ValidationLevel::Full);
242 report.add_issue(make_error().with_field_path("SG4/SG5/LOC/C517/3225"));
243 report.add_issue(make_warning().with_field_path("SG2/NAD/3035"));
244 report.add_issue(make_error());
246
247 report.enrich_bo4e_paths(|path, _hint| match path {
248 "SG4/SG5/LOC/C517/3225" => Some("stammdaten.Marktlokation.marktlokationsId".into()),
249 "SG2/NAD/3035" => Some("stammdaten.Marktteilnehmer".into()),
250 _ => None,
251 });
252
253 assert_eq!(
254 report.issues[0].bo4e_path.as_deref(),
255 Some("stammdaten.Marktlokation.marktlokationsId")
256 );
257 assert_eq!(
258 report.issues[1].bo4e_path.as_deref(),
259 Some("stammdaten.Marktteilnehmer")
260 );
261 assert!(report.issues[2].bo4e_path.is_none());
263 }
264
265 #[test]
266 fn test_report_serialization() {
267 let mut report = ValidationReport::new("UTILMD", ValidationLevel::Conditions)
268 .with_pruefidentifikator("11001")
269 .with_format_version("FV2510");
270 report.add_issue(make_error());
271
272 let json = serde_json::to_string_pretty(&report).unwrap();
273 assert!(json.contains("UTILMD"));
274 assert!(json.contains("11001"));
275 assert!(json.contains("missingRequiredField"));
278
279 let deserialized: ValidationReport = serde_json::from_str(&json).unwrap();
280 assert_eq!(deserialized.message_type, "UTILMD");
281 assert_eq!(deserialized.pruefidentifikator.as_deref(), Some("11001"));
282 assert_eq!(deserialized.total_issues(), 1);
283 assert_eq!(deserialized.issues[0].code(), "AHB001");
284 }
285}