automapper_validation/
display.rs1mod format_impl;
9mod narrator;
10mod views;
11
12pub use narrator::TechnicalNarrator;
13pub use views::{Bo4eView, EdifactView};
14
15pub mod format {
17 pub use super::format_impl::{lines, table};
18}
19
20use crate::{SegmentPosition, Severity, ValidationCategory, ValidationIssue};
21
22pub trait IssueView {
24 fn name(&self) -> &str;
26 fn location(&self, issue: &ValidationIssue) -> Option<String>;
28}
29
30pub trait IssueNarrator {
36 fn describe(&self, issue: &ValidationIssue, location: Option<&str>) -> String;
37}
38
39#[derive(Debug, Clone)]
41pub struct IssueDisplay<'a> {
42 pub severity: Severity,
43 pub category: ValidationCategory,
44 pub code: &'static str,
45 pub text: String,
47 pub location: Option<String>,
49 pub segment_position: Option<SegmentPosition>,
50 pub rule: Option<&'a str>,
51 pub actual_value: Option<&'a str>,
52 pub expected_value: Option<&'a str>,
53}
54
55impl<'a> IssueDisplay<'a> {
56 pub fn new(
58 issue: &'a ValidationIssue,
59 view: &dyn IssueView,
60 narrator: &dyn IssueNarrator,
61 ) -> Self {
62 let location = view.location(issue);
63 Self {
64 severity: issue.severity,
65 category: issue.category(),
66 code: issue.code(),
67 text: narrator.describe(issue, location.as_deref()),
68 location,
69 segment_position: issue.segment_position,
70 rule: issue.rule.as_deref(),
71 actual_value: issue.actual_value.as_deref(),
72 expected_value: issue.expected_value.as_deref(),
73 }
74 }
75}
76
77pub fn report_to_json(
84 report: &crate::ValidationReport,
85 view: &dyn IssueView,
86 narrator: &dyn IssueNarrator,
87) -> serde_json::Value {
88 let mut value = serde_json::to_value(report).unwrap_or(serde_json::Value::Null);
89 if let Some(issues) = value.get_mut("issues").and_then(|i| i.as_array_mut()) {
90 for (json, issue) in issues.iter_mut().zip(report.issues.iter()) {
91 if let Some(obj) = json.as_object_mut() {
92 let location = view.location(issue);
93 obj.insert(
94 "message".into(),
95 serde_json::Value::String(narrator.describe(issue, location.as_deref())),
96 );
97 obj.insert(
98 "code".into(),
99 serde_json::Value::String(issue.code().into()),
100 );
101 obj.insert(
102 "category".into(),
103 serde_json::to_value(issue.category()).unwrap_or(serde_json::Value::Null),
104 );
105 }
106 }
107 }
108 value
109}
110
111#[cfg(test)]
112mod tests {
113 use super::*;
114 use crate::{IssueKind, ValidationLevel, ValidationReport};
115
116 #[test]
121 fn report_to_json_carries_message_code_and_category_for_every_issue() {
122 let mut report = ValidationReport::new("UTILMD", ValidationLevel::Full);
123 report.add_issue(
124 ValidationIssue::new(
125 Severity::Error,
126 IssueKind::MissingRequiredField {
127 field_name: "LOC+Z16".into(),
128 },
129 )
130 .with_field_path("SG4/SG5/LOC/C517/3225"),
131 );
132 report.add_issue(ValidationIssue::new(
133 Severity::Warning,
134 IssueKind::CodeNotAllowedForPid {
135 value: "Z99".into(),
136 allowed: vec!["Z01".into(), "Z02".into()],
137 },
138 ));
139
140 let json = report_to_json(&report, &EdifactView, &TechnicalNarrator);
141 let issues = json["issues"].as_array().expect("issues array");
142 assert_eq!(issues.len(), 2);
143
144 for issue in issues {
145 assert!(issue["message"].is_string(), "{issue}");
146 assert!(issue["code"].is_string(), "{issue}");
147 assert!(issue["category"].is_string(), "{issue}");
148 assert!(issue["kind"].is_object(), "{issue}");
149 }
150
151 let with_path = &issues[0];
152 assert_eq!(with_path["field_path"], "SG4/SG5/LOC/C517/3225");
153 assert_eq!(with_path["severity"], "Error");
154 assert_eq!(with_path["category"], "Ahb");
155 assert_eq!(with_path["code"], "AHB001");
156 assert_eq!(
157 with_path["message"],
158 "Required field 'LOC+Z16' at SG4/SG5/LOC/C517/3225 is missing"
159 );
160 assert_eq!(with_path["kind"]["type"], "missingRequiredField");
161
162 let without_path = &issues[1];
163 assert_eq!(without_path["field_path"], serde_json::Value::Null);
164 assert!(without_path["message"].is_string());
165 assert!(without_path["code"].is_string());
166 assert!(without_path["category"].is_string());
167 assert_eq!(without_path["severity"], "Warning");
168 assert_eq!(without_path["category"], "Code");
169 assert_eq!(without_path["code"], "COD002");
170 assert_eq!(without_path["kind"]["type"], "codeNotAllowedForPid");
171 }
172}