1use std::{borrow::Cow, fmt, slice};
47
48use tracing::error;
49
50use crate::json;
51
52#[doc(hidden)]
54#[macro_export]
55macro_rules! into_caveat {
56 ($kind:ident<$life:lifetime>) => {
57 impl<$life> $crate::IntoCaveat for $kind<$life> {
58 fn into_caveat<K: $crate::warning::Kind>(
59 self,
60 warnings: $crate::warning::Set<K>,
61 ) -> $crate::Caveat<Self, K> {
62 $crate::Caveat::new(self, warnings)
63 }
64 }
65 };
66 ($kind:path) => {
67 impl $crate::IntoCaveat for $kind {
68 fn into_caveat<K: $crate::warning::Kind>(
69 self,
70 warnings: $crate::warning::Set<K>,
71 ) -> $crate::Caveat<Self, K> {
72 $crate::Caveat::new(self, warnings)
73 }
74 }
75 };
76}
77
78macro_rules! into_caveat_all {
80 ($($kind:ty),+) => {
81 $(impl IntoCaveat for $kind {
82 fn into_caveat<K: Kind>(
83 self,
84 warnings: Set<K>,
85 ) -> $crate::Caveat<Self, K> {
86 $crate::Caveat::new(self, warnings)
87 }
88 })+
89 };
90}
91
92pub type Verdict<T, K> = Result<Caveat<T, K>, Set<K>>;
94
95#[derive(Debug)]
99pub struct Caveat<T, K: Kind> {
100 value: T,
102
103 warnings: Set<K>,
105}
106
107impl<T, K> Caveat<T, K>
108where
109 T: IntoCaveat,
110 K: Kind,
111{
112 pub(crate) fn new(value: T, warnings: Set<K>) -> Self {
114 Self { value, warnings }
115 }
116}
117
118impl<T, K> Caveat<T, K>
119where
120 K: Kind,
121{
122 pub fn into_parts(self) -> (T, Set<K>) {
124 let Self { value, warnings } = self;
125 (value, warnings)
126 }
127
128 pub fn ignore_warnings(self) -> T {
130 self.value
131 }
132
133 pub fn map<U, F: FnOnce(T) -> U>(self, op: F) -> Caveat<U, K> {
134 let Self { value, warnings } = self;
135 Caveat {
136 value: op(value),
137 warnings,
138 }
139 }
140}
141
142pub(crate) trait GatherWarnings<T, K>
144where
145 K: Kind,
146{
147 type Output;
148
149 fn gather_warnings_into<KA>(self, warnings: &mut Set<KA>) -> Self::Output
151 where
152 K: Into<KA>,
153 KA: Kind;
154}
155
156impl<T, K> GatherWarnings<T, K> for Caveat<T, K>
158where
159 K: Kind,
160{
161 type Output = T;
162
163 fn gather_warnings_into<KA>(self, warnings: &mut Set<KA>) -> Self::Output
165 where
166 K: Into<KA>,
167 KA: Kind,
168 {
169 let Self {
170 value,
171 warnings: inner_warnings,
172 } = self;
173
174 warnings.0.extend(inner_warnings.0.into_iter().map(|warn| {
175 let Warning { kind, elem_id } = warn;
176 Warning {
177 kind: kind.into(),
178 elem_id,
179 }
180 }));
181
182 value
183 }
184}
185
186impl<T, K> GatherWarnings<T, K> for Option<Caveat<T, K>>
188where
189 K: Kind,
190{
191 type Output = Option<T>;
192
193 fn gather_warnings_into<KA>(self, warnings: &mut Set<KA>) -> Self::Output
195 where
196 K: Into<KA>,
197 KA: Kind,
198 {
199 match self {
200 Some(cv) => Some(cv.gather_warnings_into(warnings)),
201 None => None,
202 }
203 }
204}
205
206impl<T, K, E> GatherWarnings<T, K> for Result<Caveat<T, K>, E>
208where
209 K: Kind,
210 E: std::error::Error,
211{
212 type Output = Result<T, E>;
213
214 fn gather_warnings_into<KA>(self, warnings: &mut Set<KA>) -> Self::Output
216 where
217 K: Into<KA>,
218 KA: Kind,
219 {
220 match self {
221 Ok(cv) => Ok(cv.gather_warnings_into(warnings)),
222 Err(err) => Err(err),
223 }
224 }
225}
226
227impl<T, K> GatherWarnings<T, K> for Vec<Caveat<T, K>>
229where
230 K: Kind,
231{
232 type Output = Vec<T>;
233
234 fn gather_warnings_into<KA>(self, warnings: &mut Set<KA>) -> Self::Output
236 where
237 K: Into<KA>,
238 KA: Kind,
239 {
240 self.into_iter()
241 .map(|cv| cv.gather_warnings_into(warnings))
242 .collect()
243 }
244}
245
246pub trait IntoCaveat: Sized {
250 fn into_caveat<K: Kind>(self, warnings: Set<K>) -> Caveat<Self, K>;
252}
253
254into_caveat_all!(
255 (),
256 bool,
257 char,
258 u8,
259 u16,
260 u32,
261 u64,
262 u128,
263 i8,
264 i16,
265 i32,
266 i64,
267 i128
268);
269
270impl IntoCaveat for Cow<'_, str> {
272 fn into_caveat<K: Kind>(self, warnings: Set<K>) -> Caveat<Self, K> {
273 Caveat::new(self, warnings)
274 }
275}
276
277impl<T> IntoCaveat for Option<T>
279where
280 T: IntoCaveat,
281{
282 fn into_caveat<K: Kind>(self, warnings: Set<K>) -> Caveat<Self, K> {
283 Caveat::new(self, warnings)
284 }
285}
286
287pub trait VerdictExt<T, K: Kind> {
289 fn ok_caveat(self) -> Caveat<Option<T>, K>;
293}
294
295impl<T, K: Kind> VerdictExt<T, K> for Verdict<T, K>
296where
297 T: IntoCaveat,
298{
299 fn ok_caveat(self) -> Caveat<Option<T>, K> {
300 match self {
301 Ok(v) => {
302 let (v, warnings) = v.into_parts();
303 Some(v).into_caveat(warnings)
304 }
305 Err(warnings) => None.into_caveat(warnings),
306 }
307 }
308}
309
310#[macro_export]
315#[doc(hidden)]
316macro_rules! from_warning_set_to {
317 ($kind_a:path => $kind_b:path) => {
318 impl From<$crate::warning::Set<$kind_a>> for $crate::warning::Set<$kind_b> {
319 fn from(set_a: warning::Set<$kind_a>) -> Self {
320 set_a.into_set()
321 }
322 }
323 };
324}
325
326pub trait OptionExt<T, K>
328where
329 K: Kind,
330{
331 fn exit_with_warning<F>(self, warnings: Set<K>, f: F) -> Verdict<T, K>
333 where
334 F: FnOnce() -> Warning<K>;
335}
336
337impl<T, K> OptionExt<T, K> for Option<T>
338where
339 T: IntoCaveat,
340 K: Kind,
341{
342 fn exit_with_warning<F>(self, mut warnings: Set<K>, f: F) -> Verdict<T, K>
343 where
344 F: FnOnce() -> Warning<K>,
345 {
346 if let Some(v) = self {
347 Ok(v.into_caveat(warnings))
348 } else {
349 warnings.push(f());
350 Err(warnings)
351 }
352 }
353}
354
355#[derive(Debug)]
359pub struct Warning<K: Kind> {
360 kind: K,
362
363 elem_id: Option<json::ElemId>,
365}
366
367impl<K: Kind> Warning<K> {
368 pub(crate) const fn with_elem(kind: K, elem: &json::Element<'_>) -> Warning<K> {
370 Warning {
371 kind,
372 elem_id: Some(elem.id()),
373 }
374 }
375
376 pub(crate) const fn only_kind(kind: K) -> Warning<K> {
378 Warning {
379 kind,
380 elem_id: None,
381 }
382 }
383
384 pub fn id(&self) -> Cow<'static, str> {
385 self.kind.id()
386 }
387
388 pub fn kind(&self) -> &K {
389 &self.kind
390 }
391
392 pub fn into_kind(self) -> K {
394 self.kind
395 }
396}
397
398impl<K: Kind> fmt::Display for Warning<K> {
399 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
400 write!(f, "{}", self.kind)
401 }
402}
403
404pub trait Kind: Sized + fmt::Debug + fmt::Display {
408 fn id(&self) -> Cow<'static, str>;
413}
414
415#[derive(Debug)]
417pub struct Set<K: Kind>(Vec<Warning<K>>);
418
419impl<K: Kind> Set<K> {
420 pub(crate) fn new() -> Self {
422 Self(vec![])
423 }
424
425 fn push(&mut self, warning: Warning<K>) {
427 self.0.push(warning);
428 }
429
430 pub(crate) fn with_elem(&mut self, kind: K, elem: &json::Element<'_>) {
432 self.push(Warning::with_elem(kind, elem));
433 }
434
435 pub(crate) fn only_kind(&mut self, kind: K) {
437 self.push(Warning::only_kind(kind));
438 }
439
440 pub(crate) fn into_set<KB>(self) -> Set<KB>
444 where
445 KB: Kind + From<K>,
446 {
447 let warnings = self
448 .0
449 .into_iter()
450 .map(|warn| {
451 let Warning { kind, elem_id } = warn;
452 Warning {
453 kind: kind.into(),
454 elem_id,
455 }
456 })
457 .collect();
458
459 Set(warnings)
460 }
461
462 pub(crate) fn into_parts_vec(self) -> Vec<(K, Option<json::ElemId>)> {
463 self.0
464 .into_iter()
465 .map(|Warning { kind, elem_id }| (kind, elem_id))
466 .collect()
467 }
468
469 #[expect(
473 dead_code,
474 reason = "This should only be used in tests and the `mod price`"
475 )]
476 pub(crate) fn to_kind_vec(&self) -> Vec<&K> {
477 self.0.iter().map(Warning::kind).collect()
478 }
479
480 pub fn is_empty(&self) -> bool {
482 self.0.is_empty()
483 }
484
485 pub fn len(&self) -> usize {
487 self.0.len()
488 }
489
490 pub fn into_report(self) -> Report<K> {
492 Report::new(self)
493 }
494}
495
496#[derive(Debug)]
500pub struct Report<K>
501where
502 K: Kind,
503{
504 groups: Vec<Group<K>>,
505}
506
507#[derive(Debug)]
509struct Group<K> {
510 id: Option<json::ElemId>,
512
513 warning_kinds: Vec<K>,
515}
516
517impl<K> Report<K>
518where
519 K: Kind,
520{
521 fn new(warnings: Set<K>) -> Self {
523 let Set(mut warnings) = warnings;
524 warnings.sort_unstable_by(|a, b| a.elem_id.cmp(&b.elem_id));
526 let mut warnings = warnings.into_iter();
527
528 let Some(first) = warnings.next() else {
529 return Self { groups: vec![] };
530 };
531
532 let mut groups = vec![Group {
535 id: first.elem_id,
536 warning_kinds: vec![first.kind],
537 }];
538
539 for warn in warnings {
542 if let Some(s) = groups.last_mut() {
544 if warn.elem_id == s.id {
545 s.warning_kinds.push(warn.kind);
547 } else {
548 groups.push(Group {
550 id: warn.elem_id,
551 warning_kinds: vec![warn.kind],
552 });
553 }
554 }
555 }
556
557 Self { groups }
558 }
559
560 pub fn iter<'a, 'bin>(&'a self, root: &'a json::Element<'bin>) -> ReportIter<'a, 'bin, K> {
564 ReportIter {
565 walker: json::walk::DepthFirst::new(root),
566 groups: self.groups.iter(),
567 }
568 }
569
570 pub fn is_empty(&self) -> bool {
572 self.groups.is_empty()
573 }
574
575 pub fn len(&self) -> usize {
577 self.groups.len()
578 }
579}
580
581pub struct ReportIter<'a, 'bin, K: Kind> {
584 walker: json::walk::DepthFirst<'a, 'bin>,
586
587 groups: slice::Iter<'a, Group<K>>,
589}
590
591impl<'a, 'bin, K: Kind> Iterator for ReportIter<'a, 'bin, K> {
592 type Item = ElementReport<'a, 'bin, K>;
593
594 fn next(&mut self) -> Option<Self::Item> {
595 let group = self.groups.next()?;
596
597 loop {
599 let Some(element) = self.walker.next() else {
600 if let Some(id) = group.id {
601 error!("An Element with id: `{id}` was not found");
602 } else {
603 error!("An Element without an id was not found");
604 }
605 return None;
606 };
607
608 if let Some(id) = group.id {
609 if element.id() == id {
610 return Some(ElementReport {
611 element,
612 warnings: &group.warning_kinds,
613 });
614 }
615 }
616 }
617 }
618}
619
620pub struct ElementReport<'a, 'bin, K: Kind> {
622 pub element: &'a json::Element<'bin>,
624
625 pub warnings: &'a [K],
627}
628
629impl<K: Kind> fmt::Debug for ElementReport<'_, '_, K> {
630 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
631 f.debug_struct("ElementReport")
632 .field("element", &self.element.path())
633 .field("warnings", &self.warnings)
634 .finish()
635 }
636}
637
638#[cfg(test)]
639mod test {
640 use std::{ops, slice};
641
642 use assert_matches::assert_matches;
643
644 use crate::json;
645
646 use super::{Caveat, Kind, Set, Warning};
647 impl<K: Kind> Warning<K> {
648 pub(crate) fn elem_id(&self) -> Option<json::ElemId> {
649 self.elem_id
650 }
651 }
652
653 impl<K: Kind> Set<K> {
654 pub fn into_vec(self) -> Vec<Warning<K>> {
656 self.0
657 }
658
659 pub fn into_kind_vec(self) -> Vec<K> {
663 self.0.into_iter().map(Warning::into_kind).collect()
664 }
665
666 pub fn as_slice(&self) -> &[Warning<K>] {
668 self.0.as_slice()
669 }
670
671 pub fn iter(&self) -> slice::Iter<'_, Warning<K>> {
673 self.0.iter()
674 }
675 }
676
677 impl<K: Kind> ops::Deref for Set<K> {
678 type Target = [Warning<K>];
679
680 fn deref(&self) -> &[Warning<K>] {
681 self.as_slice()
682 }
683 }
684
685 impl<K: Kind> IntoIterator for Set<K> {
686 type Item = Warning<K>;
687
688 type IntoIter = <Vec<Self::Item> as IntoIterator>::IntoIter;
689
690 fn into_iter(self) -> Self::IntoIter {
691 self.0.into_iter()
692 }
693 }
694
695 impl<'a, K: Kind> IntoIterator for &'a Set<K> {
696 type Item = &'a Warning<K>;
697
698 type IntoIter = slice::Iter<'a, Warning<K>>;
699
700 fn into_iter(self) -> Self::IntoIter {
701 self.0.iter()
702 }
703 }
704
705 impl<T, K> Caveat<T, K>
706 where
707 K: Kind,
708 {
709 pub fn unwrap(self) -> T {
711 let Self { value, warnings } = self;
712 assert_matches!(warnings.into_vec().as_slice(), []);
713 value
714 }
715 }
716}
717
718#[cfg(test)]
719mod test_report {
720 use std::fmt;
721
722 use assert_matches::assert_matches;
723
724 use crate::{json, test, warning::ElementReport};
725
726 use super::{Kind, Report, Set, Warning};
727
728 #[derive(Debug)]
729 enum WarningKind {
730 Root,
731 One,
732 OneAgain,
733 Three,
734 }
735
736 impl Kind for WarningKind {
737 fn id(&self) -> std::borrow::Cow<'static, str> {
738 match self {
739 WarningKind::Root => "root".into(),
740 WarningKind::One => "one".into(),
741 WarningKind::OneAgain => "one_again".into(),
742 WarningKind::Three => "three".into(),
743 }
744 }
745 }
746
747 impl fmt::Display for WarningKind {
748 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
749 match self {
750 WarningKind::Root => write!(f, "NopeRoot"),
751 WarningKind::One => write!(f, "NopeOne"),
752 WarningKind::OneAgain => write!(f, "NopeOneAgain"),
753 WarningKind::Three => write!(f, "NopeThree"),
754 }
755 }
756 }
757
758 #[test]
759 fn should_group_warnings() {
760 const JSON: &str = r#"{
761 "field_one#": "one",
762 "field_two": "two",
763 "field_three": "three"
764}"#;
765
766 test::setup();
767
768 let elem = parse(JSON);
769 let mut warnings = Set::<WarningKind>::new();
770
771 warnings.push(Warning {
774 kind: WarningKind::Root,
775 elem_id: Some(Into::into(0)),
776 });
777 warnings.push(Warning {
778 kind: WarningKind::Three,
779 elem_id: Some(Into::into(3)),
780 });
781 warnings.push(Warning {
782 kind: WarningKind::One,
783 elem_id: Some(Into::into(1)),
784 });
785 warnings.push(Warning {
786 kind: WarningKind::OneAgain,
787 elem_id: Some(Into::into(1)),
788 });
789
790 let report = Report::new(warnings);
791 let mut iter = report.iter(&elem);
792 let ElementReport { element, warnings } = iter.next().unwrap();
793
794 assert!(
795 element.value().is_object(),
796 "The root object should be reported first"
797 );
798 assert_eq!(
799 element.id(),
800 json::ElemId::from(0),
801 "The root object should be reported first"
802 );
803 assert_matches!(warnings, [WarningKind::Root]);
804
805 let ElementReport { element, warnings } = iter.next().unwrap();
806
807 assert_eq!(element.value().as_raw_str().unwrap().as_raw(), "one");
808 assert_eq!(element.id(), json::ElemId::from(1));
809 assert_matches!(
810 warnings,
811 [WarningKind::One, WarningKind::OneAgain],
812 "[`json::Element`] 1 should have two warnings"
813 );
814
815 let ElementReport { element, warnings } = iter.next().unwrap();
817
818 assert_eq!(element.value().as_raw_str().unwrap().as_raw(), "three");
819 assert_eq!(element.id(), json::ElemId::from(3));
820 assert_matches!(warnings, [WarningKind::Three]);
821 }
822
823 fn parse(json: &str) -> json::Element<'_> {
824 json::parse(json).unwrap()
825 }
826}