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]
214
215macro_rules! assert_non_equivalent_violations {
218 ($left:expr, $right:expr $(,)?) => {
219 let mut left_strings: Vec<String> = $left
220 .flattened_violations()
221 .into_iter()
222 .map(|x| format!("{:?}", x))
223 .collect();
224 left_strings.sort();
225 let mut right_strings: Vec<String> = $right
226 .flattened_violations()
227 .into_iter()
228 .map(|x| format!("{:?}", x))
229 .collect();
230 right_strings.sort();
231 assert_ne!(left_strings, right_strings);
232 };
233 ($left:expr, $right:expr, $($arg:tt)+) => {
234 let mut left_strings: Vec<String> = $left
235 .flattened_violations()
236 .into_iter()
237 .map(|x| format!("{:?}", x))
238 .collect();
239 left_strings.sort();
240 let mut right_strings: Vec<String> = $right
241 .flattened_violations()
242 .into_iter()
243 .map(|x| format!("{:?}", x))
244 .collect();
245 right_strings.sort();
246 assert_ne!(left_strings, right_strings, $($arg)+);
247 };
248}
249
250#[cfg(test)]
251mod violation_tests {
252 use crate::ion_path::{IonPath, IonPathElement};
253 use crate::violation::{Violation, ViolationCode};
254 use rstest::rstest;
255
256 #[rstest(violation1, violation2,
257 case::unordered_violations(Violation::with_violations(
258 "type_constraint",
259 ViolationCode::TypeMismatched,
260 "type mismatched",
261 &mut IonPath::default(),
262 vec![
263 Violation::new(
264 "regex",
265 ViolationCode::RegexMismatched,
266 "regex mismatched",
267 &mut IonPath::default(),
268 ),
269 Violation::new(
270 "container_length",
271 ViolationCode::InvalidLength,
272 "invalid length",
273 &mut IonPath::default(),
274 ),
275 ],
276 ), Violation::with_violations(
277 "type_constraint",
278 ViolationCode::TypeMismatched,
279 "type mismatched",
280 &mut IonPath::default(),
281 vec![
282 Violation::new(
283 "container_length",
284 ViolationCode::InvalidLength,
285 "invalid length",
286 &mut IonPath::default(),
287 ),
288 Violation::new(
289 "regex",
290 ViolationCode::RegexMismatched,
291 "regex mismatched",
292 &mut IonPath::default(),
293 ),
294 ],
295 )
296 ),
297 case::nested_violations(
298 Violation::with_violations(
299 "type_constraint",
300 ViolationCode::TypeMismatched,
301 "type mismatched",
302 &mut IonPath::default(),
303 vec![
304 Violation::with_violations(
305 "regex",
306 ViolationCode::RegexMismatched,
307 "regex mismatched",
308 &mut IonPath::default(),
309 vec![
310 Violation::new(
311 "container_length",
312 ViolationCode::InvalidLength,
313 "invalid length",
314 &mut IonPath::default(),
315 ),
316 Violation::new(
317 "codepoint_length",
318 ViolationCode::InvalidLength,
319 "invalid length",
320 &mut IonPath::default(),
321 )
322 ]
323 )
324 ],
325 ),
326 Violation::with_violations(
327 "type_constraint",
328 ViolationCode::TypeMismatched,
329 "type mismatched",
330 &mut IonPath::default(),
331 vec![
332 Violation::with_violations(
333 "regex",
334 ViolationCode::RegexMismatched,
335 "regex mismatched",
336 &mut IonPath::default(),
337 vec![
338 Violation::new(
339 "codepoint_length",
340 ViolationCode::InvalidLength,
341 "invalid length",
342 &mut IonPath::default(),
343 ),
344 Violation::new(
345 "container_length",
346 ViolationCode::InvalidLength,
347 "invalid length",
348 &mut IonPath::default(),
349 )
350 ]
351 )
352 ],
353 )
354 ),
355 case::empty_violations(
356 Violation::with_violations(
357 "type_constraint",
358 ViolationCode::TypeMismatched,
359 "type mismatched",
360 &mut IonPath::default(),
361 vec![],
362 ),
363 Violation::with_violations(
364 "type_constraint",
365 ViolationCode::TypeMismatched,
366 "type mismatched",
367 &mut IonPath::default(),
368 vec![],
369 )
370 ),
371 case::multiple_violations_from_one_constraint(
372 Violation::with_violations(
373 "type_constraint",
374 ViolationCode::TypeMismatched,
375 "type mismatched",
376 &mut IonPath::default(),
377 vec![
378 Violation::new(
379 "element",
380 ViolationCode::ElementMismatched,
381 "element mismatched",
382 &mut IonPath::default(),
383 ),
384 Violation::new(
385 "element",
386 ViolationCode::ElementNotDistinct,
387 "element not distinct",
388 &mut IonPath::default(),
389 ),
390 ],
391 ),
392 Violation::with_violations(
393 "type_constraint",
394 ViolationCode::TypeMismatched,
395 "type mismatched",
396 &mut IonPath::default(),
397 vec![
398 Violation::new(
399 "element",
400 ViolationCode::ElementNotDistinct,
401 "element not distinct",
402 &mut IonPath::default(),
403 ),
404 Violation::new(
405 "element",
406 ViolationCode::ElementMismatched,
407 "element mismatched",
408 &mut IonPath::default(),
409 ),
410 ],
411 ),
412 )
413 )]
414 fn violation_equivalence(violation1: Violation, violation2: Violation) {
415 assert_equivalent_violations!(violation1, violation2);
416 }
417
418 #[rstest(violation1, violation2,
419 case::different_violations(
420 Violation::with_violations(
421 "type_constraint",
422 ViolationCode::TypeMismatched,
423 "type mismatched",
424 &mut IonPath::default(),
425 vec![
426 Violation::new(
427 "regex",
428 ViolationCode::RegexMismatched,
429 "regex mismatched",
430 &mut IonPath::default(),
431 ),
432 Violation::new(
433 "container_length",
434 ViolationCode::InvalidLength,
435 "invalid length",
436 &mut IonPath::default(),
437 ),
438 ],
439 ), Violation::with_violations(
440 "type_constraint",
441 ViolationCode::TypeMismatched,
442 "type mismatched",
443 &mut IonPath::default(),
444 vec![
445 Violation::new(
446 "container_length",
447 ViolationCode::InvalidLength,
448 "invalid length",
449 &mut IonPath::default(),
450 ),
451 ],
452 )
453 ),
454 case::different_constraints(
455 Violation::new(
456 "type_constraint",
457 ViolationCode::TypeMismatched,
458 "type mismatched",
459 &mut IonPath::default(),
460 ),
461 Violation::new(
462 "regex",
463 ViolationCode::TypeMismatched,
464 "type mismatched",
465 &mut IonPath::default(),
466 )
467 ),
468 case::different_violation_code(
469 Violation::new(
470 "type_constraint",
471 ViolationCode::TypeMismatched,
472 "type mismatched",
473 &mut IonPath::default(),
474 ),
475 Violation::new(
476 "type_constraint",
477 ViolationCode::RegexMismatched,
478 "type mismatched",
479 &mut IonPath::default(),
480 )
481 ),
482 case::different_violation_message(
483 Violation::new(
484 "type_constraint",
485 ViolationCode::TypeMismatched,
486 "regex mismatched",
487 &mut IonPath::default(),
488 ),
489 Violation::new(
490 "type_constraint",
491 ViolationCode::TypeMismatched,
492 "type mismatched",
493 &mut IonPath::default(),
494 )
495 ),
496 case::different_ion_path(
497 Violation::new(
498 "type_constraint",
499 ViolationCode::TypeMismatched,
500 "type mismatched",
501 &mut IonPath::new(vec![IonPathElement::Index(2)]),
502 ),
503 Violation::new(
504 "type_constraint",
505 ViolationCode::TypeMismatched,
506 "type mismatched",
507 &mut IonPath::default(),
508 )
509 ))]
510 fn non_equivalent_violations(violation1: Violation, violation2: Violation) {
511 assert_non_equivalent_violations!(violation1, violation2);
512 }
513}