1use crate::ion_path::IonPath;
2use std::fmt;
3use std::fmt::Formatter;
4use thiserror::Error;
5
6#[derive(Debug, Clone, Error)]
12pub struct Violation {
13 constraint: String, code: ViolationCode, message: String, ion_path: IonPath, violations: Vec<Violation>,
18}
19
20impl Violation {
21 pub fn new<A: AsRef<str>, B: AsRef<str>>(
22 constraint: A,
23 code: ViolationCode,
24 message: B,
25 ion_path: &mut IonPath,
26 ) -> Self {
27 Self {
28 constraint: constraint.as_ref().to_owned(),
29 code,
30 message: message.as_ref().to_owned(),
31 ion_path: ion_path.to_owned(),
32 violations: Vec::new(),
33 }
34 }
35
36 pub fn with_violations<A: AsRef<str>, B: AsRef<str>>(
37 constraint: A,
38 code: ViolationCode,
39 message: B,
40 ion_path: &mut IonPath,
41 violations: Vec<Violation>,
42 ) -> Self {
43 Self {
44 constraint: constraint.as_ref().to_owned(),
45 code,
46 message: message.as_ref().to_owned(),
47 ion_path: ion_path.to_owned(),
48 violations,
49 }
50 }
51
52 pub fn ion_path(&self) -> &IonPath {
53 &self.ion_path
54 }
55
56 pub fn message(&self) -> &String {
57 &self.message
58 }
59
60 pub fn code(&self) -> &ViolationCode {
61 &self.code
62 }
63
64 pub fn flattened_violations(&self) -> Vec<&Violation> {
66 let mut flattened_violations = Vec::new();
67 self.flatten_violations(&mut flattened_violations);
68 flattened_violations
69 }
70
71 fn flatten_violations<'a>(&'a self, flattened: &mut Vec<&'a Violation>) {
72 if self.violations.is_empty() {
73 flattened.push(self);
74 }
75 for violation in &self.violations {
76 if violation.violations.is_empty() {
77 flattened.push(violation);
78 } else {
79 violation.flatten_violations(flattened)
80 }
81 }
82 }
83
84 pub fn violations(&self) -> impl Iterator<Item = &Violation> {
85 self.violations.iter()
86 }
87}
88
89impl fmt::Display for Violation {
90 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
91 f.write_str(self.message.as_str())?;
92
93 let mut stack = vec![];
94 let mut violations_iter = self.violations.iter();
95 let mut violation = violations_iter.next();
96
97 let mut indent = " ".to_string();
98
99 while let Some(v) = violation {
100 f.write_fmt(format_args!("\n{}- {}", &indent, v.message))?;
101
102 if !v.violations.is_empty() {
103 stack.push(violations_iter);
104 violations_iter = v.violations.iter();
105 indent.push_str(" ");
106 }
107 violation = violations_iter.next();
108 while violation.is_none() && !stack.is_empty() {
109 violations_iter = stack.pop().unwrap();
110 indent.truncate(indent.len() - 2);
111 violation = violations_iter.next();
112 }
113 }
114 Ok(())
115 }
116}
117
118#[derive(Debug, Clone, PartialEq, Eq, Hash)]
120#[non_exhaustive]
121pub enum ViolationCode {
122 AllTypesNotMatched,
123 AnnotationMismatched,
124 ElementMismatched, ElementNotDistinct, FieldNamesMismatched, FieldNamesNotDistinct, FieldsNotMatched,
129 InvalidIeee754Float, InvalidLength, InvalidNull, InvalidOpenContent, InvalidValue, MissingAnnotation, MissingValue, MoreThanOneTypeMatched,
137 NoTypesMatched,
138 RegexMismatched, TypeConstraintsUnsatisfied,
140 TypeMatched,
141 TypeMismatched,
142 UnexpectedAnnotation, }
144
145impl fmt::Display for ViolationCode {
146 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
147 write!(
148 f,
149 "{}",
150 match self {
151 ViolationCode::AllTypesNotMatched => "all_types_not_matched",
152 ViolationCode::AnnotationMismatched => "annotation_mismatched",
153 ViolationCode::ElementMismatched => "element_mismatched",
154 ViolationCode::ElementNotDistinct => "element_not_distinct",
155 ViolationCode::FieldNamesMismatched => "field_names_mismatched",
156 ViolationCode::FieldNamesNotDistinct => "field_names_not_distinct",
157 ViolationCode::FieldsNotMatched => "fields_not_matched",
158 ViolationCode::InvalidIeee754Float => "invalid_ieee754_float",
159 ViolationCode::InvalidLength => "invalid_length",
160 ViolationCode::InvalidNull => "invalid_null",
161 ViolationCode::InvalidOpenContent => "invalid_open_content",
162 ViolationCode::InvalidValue => "invalid_value",
163 ViolationCode::MissingAnnotation => "missing_annotation",
164 ViolationCode::MissingValue => "missing_value",
165 ViolationCode::MoreThanOneTypeMatched => "more_than_one_type_matched",
166 ViolationCode::NoTypesMatched => "no_types_matched",
167 ViolationCode::RegexMismatched => "regex_mismatched",
168 ViolationCode::TypeConstraintsUnsatisfied => "type_constraints_unsatisfied",
169 ViolationCode::TypeMatched => "type_matched",
170 ViolationCode::TypeMismatched => "type_mismatched",
171 ViolationCode::UnexpectedAnnotation => "unexpected_annotation",
172 }
173 )
174 }
175}
176
177#[macro_export]
178macro_rules! assert_equivalent_violations {
181 ($left:expr, $right:expr $(,)?) => {
182 let mut left_strings: Vec<String> = $left
183 .flattened_violations()
184 .into_iter()
185 .map(|x| format!("{:?}", x))
186 .collect();
187 left_strings.sort();
188 let mut right_strings: Vec<String> = $right
189 .flattened_violations()
190 .into_iter()
191 .map(|x| format!("{:?}", x))
192 .collect();
193 right_strings.sort();
194 assert_eq!(left_strings, right_strings);
195 };
196 ($left:expr, $right:expr, $($arg:tt)+) => {
197 let mut left_strings: Vec<String> = $left
198 .flattened_violations()
199 .into_iter()
200 .map(|x| format!("{:?}", x))
201 .collect();
202 left_strings.sort();
203 let mut right_strings: Vec<String> = $right
204 .flattened_violations()
205 .into_iter()
206 .map(|x| format!("{:?}", x))
207 .collect();
208 right_strings.sort();
209 assert_eq!(left_strings, right_strings, $($arg)+);
210 };
211}
212
213#[macro_export]
216macro_rules! assert_non_equivalent_violations {
217 ($left:expr, $right:expr $(,)?) => {
218 let mut left_strings: Vec<String> = $left
219 .flattened_violations()
220 .into_iter()
221 .map(|x| format!("{:?}", x))
222 .collect();
223 left_strings.sort();
224 let mut right_strings: Vec<String> = $right
225 .flattened_violations()
226 .into_iter()
227 .map(|x| format!("{:?}", x))
228 .collect();
229 right_strings.sort();
230 assert_ne!(left_strings, right_strings);
231 };
232 ($left:expr, $right:expr, $($arg:tt)+) => {
233 let mut left_strings: Vec<String> = $left
234 .flattened_violations()
235 .into_iter()
236 .map(|x| format!("{:?}", x))
237 .collect();
238 left_strings.sort();
239 let mut right_strings: Vec<String> = $right
240 .flattened_violations()
241 .into_iter()
242 .map(|x| format!("{:?}", x))
243 .collect();
244 right_strings.sort();
245 assert_ne!(left_strings, right_strings, $($arg)+);
246 };
247}
248
249#[cfg(test)]
250mod violation_tests {
251 use crate::ion_path::{IonPath, IonPathElement};
252 use crate::violation::{Violation, ViolationCode};
253 use rstest::rstest;
254
255 #[rstest(violation1, violation2,
256 case::unordered_violations(Violation::with_violations(
257 "type_constraint",
258 ViolationCode::TypeMismatched,
259 "type mismatched",
260 &mut IonPath::default(),
261 vec![
262 Violation::new(
263 "regex",
264 ViolationCode::RegexMismatched,
265 "regex mismatched",
266 &mut IonPath::default(),
267 ),
268 Violation::new(
269 "container_length",
270 ViolationCode::InvalidLength,
271 "invalid length",
272 &mut IonPath::default(),
273 ),
274 ],
275 ), Violation::with_violations(
276 "type_constraint",
277 ViolationCode::TypeMismatched,
278 "type mismatched",
279 &mut IonPath::default(),
280 vec![
281 Violation::new(
282 "container_length",
283 ViolationCode::InvalidLength,
284 "invalid length",
285 &mut IonPath::default(),
286 ),
287 Violation::new(
288 "regex",
289 ViolationCode::RegexMismatched,
290 "regex mismatched",
291 &mut IonPath::default(),
292 ),
293 ],
294 )
295 ),
296 case::nested_violations(
297 Violation::with_violations(
298 "type_constraint",
299 ViolationCode::TypeMismatched,
300 "type mismatched",
301 &mut IonPath::default(),
302 vec![
303 Violation::with_violations(
304 "regex",
305 ViolationCode::RegexMismatched,
306 "regex mismatched",
307 &mut IonPath::default(),
308 vec![
309 Violation::new(
310 "container_length",
311 ViolationCode::InvalidLength,
312 "invalid length",
313 &mut IonPath::default(),
314 ),
315 Violation::new(
316 "codepoint_length",
317 ViolationCode::InvalidLength,
318 "invalid length",
319 &mut IonPath::default(),
320 )
321 ]
322 )
323 ],
324 ),
325 Violation::with_violations(
326 "type_constraint",
327 ViolationCode::TypeMismatched,
328 "type mismatched",
329 &mut IonPath::default(),
330 vec![
331 Violation::with_violations(
332 "regex",
333 ViolationCode::RegexMismatched,
334 "regex mismatched",
335 &mut IonPath::default(),
336 vec![
337 Violation::new(
338 "codepoint_length",
339 ViolationCode::InvalidLength,
340 "invalid length",
341 &mut IonPath::default(),
342 ),
343 Violation::new(
344 "container_length",
345 ViolationCode::InvalidLength,
346 "invalid length",
347 &mut IonPath::default(),
348 )
349 ]
350 )
351 ],
352 )
353 ),
354 case::empty_violations(
355 Violation::with_violations(
356 "type_constraint",
357 ViolationCode::TypeMismatched,
358 "type mismatched",
359 &mut IonPath::default(),
360 vec![],
361 ),
362 Violation::with_violations(
363 "type_constraint",
364 ViolationCode::TypeMismatched,
365 "type mismatched",
366 &mut IonPath::default(),
367 vec![],
368 )
369 ),
370 case::multiple_violations_from_one_constraint(
371 Violation::with_violations(
372 "type_constraint",
373 ViolationCode::TypeMismatched,
374 "type mismatched",
375 &mut IonPath::default(),
376 vec![
377 Violation::new(
378 "element",
379 ViolationCode::ElementMismatched,
380 "element mismatched",
381 &mut IonPath::default(),
382 ),
383 Violation::new(
384 "element",
385 ViolationCode::ElementNotDistinct,
386 "element not distinct",
387 &mut IonPath::default(),
388 ),
389 ],
390 ),
391 Violation::with_violations(
392 "type_constraint",
393 ViolationCode::TypeMismatched,
394 "type mismatched",
395 &mut IonPath::default(),
396 vec![
397 Violation::new(
398 "element",
399 ViolationCode::ElementNotDistinct,
400 "element not distinct",
401 &mut IonPath::default(),
402 ),
403 Violation::new(
404 "element",
405 ViolationCode::ElementMismatched,
406 "element mismatched",
407 &mut IonPath::default(),
408 ),
409 ],
410 ),
411 )
412 )]
413 fn violation_equivalence(violation1: Violation, violation2: Violation) {
414 assert_equivalent_violations!(violation1, violation2);
415 }
416
417 #[rstest(violation1, violation2,
418 case::different_violations(
419 Violation::with_violations(
420 "type_constraint",
421 ViolationCode::TypeMismatched,
422 "type mismatched",
423 &mut IonPath::default(),
424 vec![
425 Violation::new(
426 "regex",
427 ViolationCode::RegexMismatched,
428 "regex mismatched",
429 &mut IonPath::default(),
430 ),
431 Violation::new(
432 "container_length",
433 ViolationCode::InvalidLength,
434 "invalid length",
435 &mut IonPath::default(),
436 ),
437 ],
438 ), Violation::with_violations(
439 "type_constraint",
440 ViolationCode::TypeMismatched,
441 "type mismatched",
442 &mut IonPath::default(),
443 vec![
444 Violation::new(
445 "container_length",
446 ViolationCode::InvalidLength,
447 "invalid length",
448 &mut IonPath::default(),
449 ),
450 ],
451 )
452 ),
453 case::different_constraints(
454 Violation::new(
455 "type_constraint",
456 ViolationCode::TypeMismatched,
457 "type mismatched",
458 &mut IonPath::default(),
459 ),
460 Violation::new(
461 "regex",
462 ViolationCode::TypeMismatched,
463 "type mismatched",
464 &mut IonPath::default(),
465 )
466 ),
467 case::different_violation_code(
468 Violation::new(
469 "type_constraint",
470 ViolationCode::TypeMismatched,
471 "type mismatched",
472 &mut IonPath::default(),
473 ),
474 Violation::new(
475 "type_constraint",
476 ViolationCode::RegexMismatched,
477 "type mismatched",
478 &mut IonPath::default(),
479 )
480 ),
481 case::different_violation_message(
482 Violation::new(
483 "type_constraint",
484 ViolationCode::TypeMismatched,
485 "regex mismatched",
486 &mut IonPath::default(),
487 ),
488 Violation::new(
489 "type_constraint",
490 ViolationCode::TypeMismatched,
491 "type mismatched",
492 &mut IonPath::default(),
493 )
494 ),
495 case::different_ion_path(
496 Violation::new(
497 "type_constraint",
498 ViolationCode::TypeMismatched,
499 "type mismatched",
500 &mut IonPath::new(vec![IonPathElement::Index(2)]),
501 ),
502 Violation::new(
503 "type_constraint",
504 ViolationCode::TypeMismatched,
505 "type mismatched",
506 &mut IonPath::default(),
507 )
508 ))]
509 fn non_equivalent_violations(violation1: Violation, violation2: Violation) {
510 assert_non_equivalent_violations!(violation1, violation2);
511 }
512}