sara_core/validation/
report.rs1use std::time::Duration;
4
5use serde::Serialize;
6
7use crate::error::ValidationError;
8
9#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
11#[serde(rename_all = "lowercase")]
12pub enum Severity {
13 Error,
15 Warning,
17}
18
19#[derive(Debug, Clone, Serialize)]
21pub struct ValidationIssue {
22 pub severity: Severity,
24 pub error: ValidationError,
26}
27
28impl ValidationIssue {
29 pub fn error(error: ValidationError) -> Self {
31 Self {
32 severity: Severity::Error,
33 error,
34 }
35 }
36
37 pub fn warning(error: ValidationError) -> Self {
39 Self {
40 severity: Severity::Warning,
41 error,
42 }
43 }
44}
45
46#[derive(Debug, Clone, Serialize)]
48pub struct ValidationReport {
49 pub issues: Vec<ValidationIssue>,
51 pub items_checked: usize,
53 pub relationships_checked: usize,
55 #[serde(skip)]
57 pub duration: Duration,
58}
59
60impl ValidationReport {
61 pub fn new() -> Self {
63 Self {
64 issues: Vec::new(),
65 items_checked: 0,
66 relationships_checked: 0,
67 duration: Duration::ZERO,
68 }
69 }
70
71 pub fn is_valid(&self) -> bool {
73 !self.issues.iter().any(|i| i.severity == Severity::Error)
74 }
75
76 pub fn error_count(&self) -> usize {
78 self.issues
79 .iter()
80 .filter(|i| i.severity == Severity::Error)
81 .count()
82 }
83
84 pub fn warning_count(&self) -> usize {
86 self.issues
87 .iter()
88 .filter(|i| i.severity == Severity::Warning)
89 .count()
90 }
91
92 pub fn errors(&self) -> Vec<&ValidationError> {
94 self.issues
95 .iter()
96 .filter(|i| i.severity == Severity::Error)
97 .map(|i| &i.error)
98 .collect()
99 }
100
101 pub fn warnings(&self) -> Vec<&ValidationError> {
103 self.issues
104 .iter()
105 .filter(|i| i.severity == Severity::Warning)
106 .map(|i| &i.error)
107 .collect()
108 }
109
110 pub fn add_error(&mut self, error: ValidationError) {
112 self.issues.push(ValidationIssue::error(error));
113 }
114
115 pub fn add_warning(&mut self, error: ValidationError) {
117 self.issues.push(ValidationIssue::warning(error));
118 }
119
120 pub fn add_errors(&mut self, errors: impl IntoIterator<Item = ValidationError>) {
122 for error in errors {
123 self.add_error(error);
124 }
125 }
126
127 pub fn add_warnings(&mut self, errors: impl IntoIterator<Item = ValidationError>) {
129 for error in errors {
130 self.add_warning(error);
131 }
132 }
133}
134
135impl Default for ValidationReport {
136 fn default() -> Self {
137 Self::new()
138 }
139}
140
141#[derive(Debug, Default)]
143pub struct ValidationReportBuilder {
144 report: ValidationReport,
145}
146
147impl ValidationReportBuilder {
148 pub fn new() -> Self {
150 Self::default()
151 }
152
153 pub fn items_checked(mut self, count: usize) -> Self {
155 self.report.items_checked = count;
156 self
157 }
158
159 pub fn relationships_checked(mut self, count: usize) -> Self {
161 self.report.relationships_checked = count;
162 self
163 }
164
165 pub fn duration(mut self, duration: Duration) -> Self {
167 self.report.duration = duration;
168 self
169 }
170
171 pub fn error(mut self, error: ValidationError) -> Self {
173 self.report.add_error(error);
174 self
175 }
176
177 pub fn warning(mut self, error: ValidationError) -> Self {
179 self.report.add_warning(error);
180 self
181 }
182
183 pub fn errors(mut self, errors: impl IntoIterator<Item = ValidationError>) -> Self {
185 self.report.add_errors(errors);
186 self
187 }
188
189 pub fn warnings(mut self, errors: impl IntoIterator<Item = ValidationError>) -> Self {
191 self.report.add_warnings(errors);
192 self
193 }
194
195 pub fn build(self) -> ValidationReport {
197 self.report
198 }
199}
200
201#[cfg(test)]
202mod tests {
203 use super::*;
204 use crate::model::ItemId;
205
206 #[test]
207 fn test_empty_report_is_valid() {
208 let report = ValidationReport::new();
209 assert!(report.is_valid());
210 assert_eq!(report.error_count(), 0);
211 assert_eq!(report.warning_count(), 0);
212 }
213
214 #[test]
215 fn test_report_with_error() {
216 let mut report = ValidationReport::new();
217 report.add_error(ValidationError::BrokenReference {
218 from: ItemId::new_unchecked("A"),
219 to: ItemId::new_unchecked("B"),
220 location: None,
221 });
222
223 assert!(!report.is_valid());
224 assert_eq!(report.error_count(), 1);
225 assert_eq!(report.warning_count(), 0);
226 }
227
228 #[test]
229 fn test_report_with_warning() {
230 let mut report = ValidationReport::new();
231 report.add_warning(ValidationError::OrphanItem {
232 id: ItemId::new_unchecked("A"),
233 item_type: crate::model::ItemType::UseCase,
234 location: None,
235 });
236
237 assert!(report.is_valid());
238 assert_eq!(report.error_count(), 0);
239 assert_eq!(report.warning_count(), 1);
240 }
241
242 #[test]
243 fn test_builder() {
244 let report = ValidationReportBuilder::new()
245 .items_checked(10)
246 .relationships_checked(15)
247 .error(ValidationError::BrokenReference {
248 from: ItemId::new_unchecked("A"),
249 to: ItemId::new_unchecked("B"),
250 location: None,
251 })
252 .build();
253
254 assert_eq!(report.items_checked, 10);
255 assert_eq!(report.relationships_checked, 15);
256 assert_eq!(report.error_count(), 1);
257 }
258}