1use std::{borrow::Cow, fmt, slice};
47
48use tracing::error;
49
50use crate::json;
51
52pub type Verdict<T, K> = Result<Caveat<T, K>, Set<K>>;
54
55#[derive(Debug)]
59pub struct Caveat<T, K: Kind> {
60 value: T,
62
63 warnings: Set<K>,
65}
66
67impl<T, K> Caveat<T, K>
68where
69 T: IntoCaveat,
70 K: Kind,
71{
72 pub(crate) fn new(value: T, warnings: Set<K>) -> Self {
74 Self { value, warnings }
75 }
76}
77
78impl<T, K> Caveat<T, K>
79where
80 K: Kind,
81{
82 pub fn into_parts(self) -> (T, Set<K>) {
84 let Self { value, warnings } = self;
85 (value, warnings)
86 }
87}
88
89impl<T, K> Caveat<T, K>
90where
91 K: Kind,
92{
93 pub(crate) fn gather_warnings_into<KA>(self, outer_warnings: &mut Set<KA>) -> T
95 where
96 K: Into<KA>,
97 KA: Kind,
98 {
99 let Self { value, warnings } = self;
100
101 outer_warnings.0.extend(warnings.0.into_iter().map(|warn| {
102 let Warning { kind, elem_id } = warn;
103 Warning {
104 kind: kind.into(),
105 elem_id,
106 }
107 }));
108
109 value
110 }
111
112 pub fn map<U, F: FnOnce(T) -> U>(self, op: F) -> Caveat<U, K> {
113 let Self { value, warnings } = self;
114 Caveat {
115 value: op(value),
116 warnings,
117 }
118 }
119}
120
121pub trait OptionCaveat<T, K>
123where
124 K: Kind,
125{
126 fn gather_warnings_into<KA>(self, outer_warnings: &mut Set<KA>) -> Option<T>
127 where
128 K: Into<KA>,
129 KA: Kind;
130}
131
132impl<T, K> OptionCaveat<T, K> for Option<Caveat<T, K>>
133where
134 K: Kind,
135{
136 fn gather_warnings_into<KA>(self, outer_warnings: &mut Set<KA>) -> Option<T>
138 where
139 K: Into<KA>,
140 KA: Kind,
141 {
142 self.map(|v| v.gather_warnings_into(outer_warnings))
143 }
144}
145pub trait IntoCaveat: Sized {
149 fn into_caveat<K: Kind>(self, warnings: Set<K>) -> Caveat<Self, K>;
151}
152
153impl IntoCaveat for () {
155 fn into_caveat<K: Kind>(self, warnings: Set<K>) -> Caveat<Self, K> {
156 Caveat::new(self, warnings)
157 }
158}
159
160impl IntoCaveat for Cow<'_, str> {
162 fn into_caveat<K: Kind>(self, warnings: Set<K>) -> Caveat<Self, K> {
163 Caveat::new(self, warnings)
164 }
165}
166
167impl<T> IntoCaveat for Option<T>
169where
170 T: IntoCaveat,
171{
172 fn into_caveat<K: Kind>(self, warnings: Set<K>) -> Caveat<Self, K> {
173 Caveat::new(self, warnings)
174 }
175}
176
177pub trait VerdictExt<T, K: Kind> {
179 fn ok_caveat(self) -> Caveat<Option<T>, K>;
183}
184
185impl<T, K: Kind> VerdictExt<T, K> for Verdict<T, K>
186where
187 T: IntoCaveat,
188{
189 fn ok_caveat(self) -> Caveat<Option<T>, K> {
190 match self {
191 Ok(v) => {
192 let (v, warnings) = v.into_parts();
193 Some(v).into_caveat(warnings)
194 }
195 Err(warnings) => None.into_caveat(warnings),
196 }
197 }
198}
199
200pub trait IntoWarning<K: Kind> {
202 fn into_warning(self, elem: &json::Element<'_>) -> Warning<K>;
204}
205
206impl<K> IntoWarning<K> for K
208where
209 K: Kind,
210{
211 fn into_warning(self, elem: &json::Element<'_>) -> Warning<K> {
213 Warning {
214 kind: self,
215 elem_id: elem.id(),
216 }
217 }
218}
219
220#[macro_export]
225#[doc(hidden)]
226macro_rules! from_warning_set_to {
227 ($kind_a:path => $kind_b:path) => {
228 impl From<$crate::warning::Set<$kind_a>> for $crate::warning::Set<$kind_b> {
229 fn from(set_a: warning::Set<$kind_a>) -> Self {
230 set_a.into_set()
231 }
232 }
233 };
234}
235
236pub trait OptionExt<T, K>
238where
239 K: Kind,
240{
241 fn exit_with_warning<F>(self, warnings: Set<K>, f: F) -> Verdict<T, K>
243 where
244 F: FnOnce() -> Warning<K>;
245}
246
247impl<T, K> OptionExt<T, K> for Option<T>
248where
249 T: IntoCaveat,
250 K: Kind,
251{
252 fn exit_with_warning<F>(self, mut warnings: Set<K>, f: F) -> Verdict<T, K>
253 where
254 F: FnOnce() -> Warning<K>,
255 {
256 if let Some(v) = self {
257 Ok(v.into_caveat(warnings))
258 } else {
259 warnings.push(f());
260 Err(warnings)
261 }
262 }
263}
264
265#[derive(Debug)]
269pub struct Warning<K: Kind> {
270 pub kind: K,
272
273 pub elem_id: json::ElemId,
275}
276
277impl<K: Kind> Warning<K> {
278 pub fn id(&self) -> Cow<'static, str> {
279 self.kind.id()
280 }
281}
282
283impl<K: Kind> fmt::Display for Warning<K> {
284 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
285 write!(f, "{}", self.kind)
286 }
287}
288
289pub trait Kind: Sized + fmt::Debug + fmt::Display {
293 fn id(&self) -> Cow<'static, str>;
298}
299
300#[derive(Debug)]
302pub struct Set<K: Kind>(Vec<Warning<K>>);
303
304impl<K: Kind> Set<K> {
305 pub(crate) fn new() -> Self {
307 Self(vec![])
308 }
309
310 pub(crate) fn push(&mut self, warning: Warning<K>) {
312 self.0.push(warning);
313 }
314
315 pub(crate) fn into_set<KB>(self) -> Set<KB>
319 where
320 KB: Kind + From<K>,
321 {
322 let warnings = self
323 .0
324 .into_iter()
325 .map(|warn| {
326 let Warning { kind, elem_id } = warn;
327 Warning {
328 kind: kind.into(),
329 elem_id,
330 }
331 })
332 .collect();
333
334 Set(warnings)
335 }
336}
337
338impl<K: Kind> Set<K> {
339 pub fn is_empty(&self) -> bool {
341 self.0.is_empty()
342 }
343
344 pub fn len(&self) -> usize {
346 self.0.len()
347 }
348
349 pub fn into_report(self) -> Report<K> {
351 Report::new(self)
352 }
353}
354
355#[derive(Debug)]
359pub struct Report<K>
360where
361 K: Kind,
362{
363 groups: Vec<Group<K>>,
364}
365
366#[derive(Debug)]
368struct Group<K> {
369 id: json::ElemId,
371
372 warnings: Vec<K>,
374}
375
376impl<K> Report<K>
377where
378 K: Kind,
379{
380 fn new(warnings: Set<K>) -> Self {
382 let Set(mut warnings) = warnings;
383 warnings.sort_unstable_by(|a, b| a.elem_id.cmp(&b.elem_id));
385 let mut warnings = warnings.into_iter();
386
387 let Some(first) = warnings.next() else {
388 return Self { groups: vec![] };
389 };
390
391 let mut groups = vec![Group {
394 id: first.elem_id,
395 warnings: vec![first.kind],
396 }];
397
398 for warn in warnings {
401 if let Some(s) = groups.last_mut() {
403 if warn.elem_id == s.id {
404 s.warnings.push(warn.kind);
406 } else {
407 groups.push(Group {
409 id: warn.elem_id,
410 warnings: vec![warn.kind],
411 });
412 }
413 }
414 }
415
416 Self { groups }
417 }
418
419 pub fn iter<'a, 'bin>(&'a self, root: &'a json::Element<'bin>) -> ReportIter<'a, 'bin, K> {
423 ReportIter {
424 walker: json::walk::DepthFirst::new(root),
425 groups: self.groups.iter(),
426 }
427 }
428
429 pub fn is_empty(&self) -> bool {
431 self.groups.is_empty()
432 }
433
434 pub fn len(&self) -> usize {
436 self.groups.len()
437 }
438}
439
440pub struct ReportIter<'a, 'bin, K: Kind> {
443 walker: json::walk::DepthFirst<'a, 'bin>,
445
446 groups: slice::Iter<'a, Group<K>>,
448}
449
450impl<'a, 'bin, K: Kind> Iterator for ReportIter<'a, 'bin, K> {
451 type Item = ElementReport<'a, 'bin, K>;
452
453 fn next(&mut self) -> Option<Self::Item> {
454 let group = self.groups.next()?;
455
456 loop {
458 let Some(element) = self.walker.next() else {
459 error!("An Element with id: `{}` was not found", group.id);
460 return None;
461 };
462
463 if element.id() == group.id {
464 return Some(ElementReport {
465 element,
466 warnings: &group.warnings,
467 });
468 }
469 }
470 }
471}
472
473pub struct ElementReport<'a, 'bin, K: Kind> {
475 pub element: &'a json::Element<'bin>,
477
478 pub warnings: &'a [K],
480}
481
482impl<K: Kind> fmt::Debug for ElementReport<'_, '_, K> {
483 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
484 f.debug_struct("ElementReport")
485 .field("element", &self.element.path())
486 .field("warnings", &self.warnings)
487 .finish()
488 }
489}
490
491#[cfg(test)]
492mod test {
493 use std::{ops, slice};
494
495 use assert_matches::assert_matches;
496
497 use super::{Caveat, Kind, Set, Warning};
498
499 impl<K: Kind> Set<K> {
500 pub fn into_vec(self) -> Vec<Warning<K>> {
502 self.0
503 }
504
505 pub fn as_slice(&self) -> &[Warning<K>] {
507 self.0.as_slice()
508 }
509
510 pub fn iter(&self) -> slice::Iter<'_, Warning<K>> {
512 self.0.iter()
513 }
514 }
515
516 impl<K: Kind> ops::Deref for Set<K> {
517 type Target = [Warning<K>];
518
519 fn deref(&self) -> &[Warning<K>] {
520 self.as_slice()
521 }
522 }
523
524 impl<K: Kind> IntoIterator for Set<K> {
525 type Item = Warning<K>;
526
527 type IntoIter = <Vec<Self::Item> as IntoIterator>::IntoIter;
528
529 fn into_iter(self) -> Self::IntoIter {
530 self.0.into_iter()
531 }
532 }
533
534 impl<'a, K: Kind> IntoIterator for &'a Set<K> {
535 type Item = &'a Warning<K>;
536
537 type IntoIter = slice::Iter<'a, Warning<K>>;
538
539 fn into_iter(self) -> Self::IntoIter {
540 self.0.iter()
541 }
542 }
543
544 impl<T, K> Caveat<T, K>
545 where
546 K: Kind,
547 {
548 pub fn unwrap(self) -> T {
550 let Self { value, warnings } = self;
551 assert_matches!(warnings.into_vec().as_slice(), []);
552 value
553 }
554 }
555}
556
557#[cfg(test)]
558mod test_report {
559 use std::fmt;
560
561 use assert_matches::assert_matches;
562
563 use crate::{json, test, warning::ElementReport};
564
565 use super::{Kind, Report, Set, Warning};
566
567 #[derive(Debug)]
568 enum WarningKind {
569 Root,
570 One,
571 OneAgain,
572 Three,
573 }
574
575 impl Kind for WarningKind {
576 fn id(&self) -> std::borrow::Cow<'static, str> {
577 match self {
578 WarningKind::Root => "root".into(),
579 WarningKind::One => "one".into(),
580 WarningKind::OneAgain => "one_again".into(),
581 WarningKind::Three => "three".into(),
582 }
583 }
584 }
585
586 impl fmt::Display for WarningKind {
587 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
588 match self {
589 WarningKind::Root => write!(f, "NopeRoot"),
590 WarningKind::One => write!(f, "NopeOne"),
591 WarningKind::OneAgain => write!(f, "NopeOneAgain"),
592 WarningKind::Three => write!(f, "NopeThree"),
593 }
594 }
595 }
596
597 #[test]
598 fn should_group_warnings() {
599 const JSON: &str = r#"{
600 "field_one#": "one",
601 "field_two": "two",
602 "field_three": "three"
603}"#;
604
605 test::setup();
606
607 let elem = parse(JSON);
608 let mut warnings = Set::<WarningKind>::new();
609
610 warnings.push(Warning {
613 kind: WarningKind::Root,
614 elem_id: 0.into(),
615 });
616 warnings.push(Warning {
617 kind: WarningKind::Three,
618 elem_id: 3.into(),
619 });
620 warnings.push(Warning {
621 kind: WarningKind::One,
622 elem_id: 1.into(),
623 });
624 warnings.push(Warning {
625 kind: WarningKind::OneAgain,
626 elem_id: 1.into(),
627 });
628
629 let report = Report::new(warnings);
630 let mut iter = report.iter(&elem);
631 let ElementReport { element, warnings } = iter.next().unwrap();
632
633 assert!(
634 element.value().is_object(),
635 "The root object should be reported first"
636 );
637 assert_eq!(
638 element.id(),
639 json::ElemId::from(0),
640 "The root object should be reported first"
641 );
642 assert_matches!(warnings, [WarningKind::Root]);
643
644 let ElementReport { element, warnings } = iter.next().unwrap();
645
646 assert_eq!(element.value().as_raw_str().unwrap().as_raw(), "one");
647 assert_eq!(element.id(), json::ElemId::from(1));
648 assert_matches!(
649 warnings,
650 [WarningKind::One, WarningKind::OneAgain],
651 "[`json::Element`] 1 should have two warnings"
652 );
653
654 let ElementReport { element, warnings } = iter.next().unwrap();
656
657 assert_eq!(element.value().as_raw_str().unwrap().as_raw(), "three");
658 assert_eq!(element.id(), json::ElemId::from(3));
659 assert_matches!(warnings, [WarningKind::Three]);
660 }
661
662 fn parse(json: &str) -> json::Element<'_> {
663 json::parse(json).unwrap()
664 }
665}