ocpi_tariffs/
warning.rs

1//! These types are the basis for writing functions that can emit a set of [`Warning`]s based on the value they are trying to create.
2//!
3//! The aim is for functions to be as resilient as possible while creating the value and emit commentary on their progress in the form of a growing set of [`Warning`]s.
4//!
5//! The caller of the function can use the set of [`Warning`]s to decide whether the operation was a success or failure and whether the value can be used or needs to be modified.
6//!  
7//! A concrete example is the conversion of a JSON [`json::Element`] into a `country::Code`. The [`json::Element`] may be the incorrect type and so the function issues a [`Warning`] and exits as it cannot continue with the given data. The signature of this fn is something like:
8//!
9//! ```rust ignore
10//! // file: country.rs
11//!  
12//! pub enum Warning {
13//!     InvalidType,
14//!     ...
15//! }
16//!
17//! pub enum Expect {
18//!     Alpha2,
19//!     Alpha3
20//! }
21//!
22//! pub enum Code {
23//!     fn from_json_element(json: json::Element, expect: Expect) -> Verdict<Code, Warning> {
24//!         ...
25//!     }
26//! }
27//! ```
28//!
29//! A [`Verdict`] is a [`Result`] where both the `Ok` and `Err` variants return a potential set of [`Warning`]s.
30//! The `Ok` variant is `Caveat<T>`, where a [`Caveat`] contains a value but potentially contains cautionary details to be taken into account when using the value.
31//! Hence, the name.
32//!
33//! The `Err` variant is `Warnings<K>`, a collection of [`Warning`]s. A [`Warning`] can be converted into an `Error` by the caller. A `Caveat<T>` is more completely described as `Caveat<T, K>` where the `Caveat` contains a value `T` and a set of `Warnings<K>`.
34//!
35//! All of this is to say that a resilient function can always return [`Warning`]s and the caller can gather them
36//! together into a new set or fail.
37//!
38//! Returning to the example of the [`country::Code`](crate::country::Code), if the [`json::Element`] is the expected string type, then processing continues.
39//! The string may contain control chars or escape chars and both these cases will emit a [`Warning`].
40//! The string may be made up of three chars when two were expected.
41//! This is the interesting case, as some [`country::Code`](crate::country::Code) fields are `alpha-3` where others are `alpha-2`.
42//! Processing can still continue, as an `alpha-3` code can be converted to an `alpha-2` simply, while emitting a [`Warning`].
43//!
44//! The caller can decide whether this is acceptable or not.
45
46use std::{borrow::Cow, collections::BTreeMap, fmt, iter, ops::Deref, vec};
47
48use tracing::error;
49
50use crate::json;
51
52/// Implement `IntoCaveat` for the given type so that it can take part in the `Warning` system.
53#[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:ty) => {
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
78/// Implement `IntoCaveat` for the given type so that it can take part in the `Warning` system.
79#[doc(hidden)]
80#[macro_export]
81macro_rules! into_caveat_all {
82    ($($kind:ty),+) => {
83        $(impl $crate::IntoCaveat for $kind {
84            fn into_caveat<K: $crate::warning::Kind>(
85                self,
86                warnings: $crate::warning::Set<K>,
87            ) -> $crate::Caveat<Self, K> {
88                $crate::Caveat::new(self, warnings)
89            }
90        })+
91    };
92}
93
94/// A `Verdict` is a standard [`Result`] with [`Warning`]s potentially issued for both the `Ok` and `Err` variants.
95pub type Verdict<T, K> = Result<Caveat<T, K>, Set<K>>;
96
97/// A value that may have associated [`Warning`]s.
98///
99/// Even though the value has been created there may be certain caveats you should be aware of before using it.
100#[derive(Debug)]
101pub struct Caveat<T, K: Kind> {
102    /// The value created by the function.
103    value: T,
104
105    /// A list of [`Warning`]s or caveats issued when creating the value.
106    warnings: Set<K>,
107}
108
109impl<T, K> Caveat<T, K>
110where
111    T: IntoCaveat,
112    K: Kind,
113{
114    /// The only way to create `Caveat<T>` is if `T` impls `IntoCaveat`.
115    pub(crate) fn new(value: T, warnings: Set<K>) -> Self {
116        Self { value, warnings }
117    }
118}
119
120/// A Caveat is simply a value with associated warnings, so providing an `impl Deref` makes sense for the interface.
121///
122/// > The same advice applies to both deref traits. In general, deref traits
123/// > **should** be implemented if:
124/// >
125/// > 1. a value of the type transparently behaves like a value of the target
126/// >    type;
127/// > 1. the implementation of the deref function is cheap; and
128/// > 1. users of the type will not be surprised by any deref coercion behavior.
129///
130/// See: <https://doc.rust-lang.org/std/ops/trait.Deref.html#when-to-implement-deref-or-derefmut>
131impl<T, K> Deref for Caveat<T, K>
132where
133    K: Kind,
134{
135    type Target = T;
136
137    fn deref(&self) -> &T {
138        &self.value
139    }
140}
141
142impl<T, K> Caveat<T, K>
143where
144    K: Kind,
145{
146    /// Return the value and any [`Warning`]s stored in the `Caveat`.
147    pub fn into_parts(self) -> (T, Set<K>) {
148        let Self { value, warnings } = self;
149        (value, warnings)
150    }
151
152    /// Return the value and drop any warnings contained within.
153    pub fn ignore_warnings(self) -> T {
154        self.value
155    }
156
157    /// Map the value to another target type while retaining the warnings about the source type.
158    pub fn map<U, F: FnOnce(T) -> U>(self, op: F) -> Caveat<U, K> {
159        let Self { value, warnings } = self;
160        Caveat {
161            value: op(value),
162            warnings,
163        }
164    }
165}
166
167/// Convert a `Caveat`-like type into a `T` by gathering up it's [`Warning`]s.
168///
169/// Gathering warnings into a parent `warning::Set` move's the responsibility of alerting the
170/// caller to the existence of those warnings to the owner of the set.
171pub(crate) trait GatherWarnings<T, K>
172where
173    K: Kind,
174{
175    /// The output type of after all the warnings have been gathered.
176    type Output;
177
178    /// Convert a `Caveat`-like type into a `T` by gathering up it's [`Warning`]s.
179    #[must_use = "If you want to ignore the value use `let _ =`"]
180    fn gather_warnings_into<KA>(self, warnings: &mut Set<KA>) -> Self::Output
181    where
182        K: Into<KA>,
183        KA: Kind;
184}
185
186/// Convert a `Caveat<T>` into `T` by gathering up it's `Warning`s.
187impl<T, K> GatherWarnings<T, K> for Caveat<T, K>
188where
189    K: Kind,
190{
191    type Output = T;
192
193    /// Convert a `Caveat<T>` into `T` by gathering up it's `Warning`s.
194    fn gather_warnings_into<KA>(self, warnings: &mut Set<KA>) -> Self::Output
195    where
196        K: Into<KA>,
197        KA: Kind,
198    {
199        let Self {
200            value,
201            warnings: inner_warnings,
202        } = self;
203
204        warnings.extend(inner_warnings);
205
206        value
207    }
208}
209
210/// Convert a `Option<Caveat<T>>` into `Option<T>` by gathering up it's `Warning`s.
211impl<T, K> GatherWarnings<T, K> for Option<Caveat<T, K>>
212where
213    K: Kind,
214{
215    type Output = Option<T>;
216
217    /// Convert a `Caveat` related to type `T` into a `T` by gathering it's [`Warning`]s.
218    fn gather_warnings_into<KA>(self, warnings: &mut Set<KA>) -> Self::Output
219    where
220        K: Into<KA>,
221        KA: Kind,
222    {
223        match self {
224            Some(cv) => Some(cv.gather_warnings_into(warnings)),
225            None => None,
226        }
227    }
228}
229
230/// Convert a `Result<Caveat<T>>` into `Result<T>` by gathering up it's `Warning`s.
231impl<T, K, E> GatherWarnings<T, K> for Result<Caveat<T, K>, E>
232where
233    K: Kind,
234    E: std::error::Error,
235{
236    type Output = Result<T, E>;
237
238    /// Convert a `Caveat` related to type `T` into a `T` by gathering it's [`Warning`]s.
239    fn gather_warnings_into<KA>(self, warnings: &mut Set<KA>) -> Self::Output
240    where
241        K: Into<KA>,
242        KA: Kind,
243    {
244        match self {
245            Ok(cv) => Ok(cv.gather_warnings_into(warnings)),
246            Err(err) => Err(err),
247        }
248    }
249}
250
251/// Convert a `Result<Caveat<T>>` into `Result<T>` by gathering up it's `Warning`s.
252impl<T, K> GatherWarnings<T, K> for Verdict<T, K>
253where
254    K: Kind,
255{
256    type Output = Option<T>;
257
258    /// Convert a `Verdict` into an `Option` by collecting `Warnings` from the `Ok` and `Err` variants
259    /// and mapping `Ok` to `Some` and `Err` to `None`.
260    fn gather_warnings_into<KA>(self, warnings: &mut Set<KA>) -> Self::Output
261    where
262        K: Into<KA>,
263        KA: Kind,
264    {
265        match self {
266            Ok(cv) => Some(cv.gather_warnings_into(warnings)),
267            Err(inner_warnings) => {
268                warnings.extend(inner_warnings);
269                None
270            }
271        }
272    }
273}
274
275/// Convert a `Vec<Caveat<T>>` into `Vec<T>` by gathering up each elements `Warning`s.
276impl<T, K> GatherWarnings<T, K> for Vec<Caveat<T, K>>
277where
278    K: Kind,
279{
280    type Output = Vec<T>;
281
282    /// Convert a `Caveat` related to type `T` into a `T` by gathering it's [`Warning`]s.
283    fn gather_warnings_into<KA>(self, warnings: &mut Set<KA>) -> Self::Output
284    where
285        K: Into<KA>,
286        KA: Kind,
287    {
288        self.into_iter()
289            .map(|cv| cv.gather_warnings_into(warnings))
290            .collect()
291    }
292}
293
294/// Converts a value `T` into a `Caveat`.
295///
296/// Each module can use this to whitelist their types for conversion to `Caveat<T>`.
297pub trait IntoCaveat: Sized {
298    /// Any type can be converted to `Caveat<T>` only a list of [`Warning`]s is required.
299    fn into_caveat<K: Kind>(self, warnings: Set<K>) -> Caveat<Self, K>;
300}
301
302macro_rules! peel {
303    ($name:ident, $($other:ident,)*) => (into_caveat_tuple! { $($other,)* })
304}
305
306macro_rules! into_caveat_tuple {
307    () => ();
308    ( $($name:ident,)+ ) => (
309        impl<$($name),+> $crate::IntoCaveat for ($($name,)+) {
310            fn into_caveat<K: $crate::warning::Kind>(
311                self,
312                warnings: $crate::warning::Set<K>,
313            ) -> $crate::Caveat<Self, K> {
314                $crate::Caveat::new(self, warnings)
315            }
316        }
317        peel! { $($name,)+ }
318    )
319}
320
321into_caveat_tuple! { E, D, C, B, A, Z, Y, X, W, V, U, T, }
322
323into_caveat_all!(
324    (),
325    bool,
326    char,
327    u8,
328    u16,
329    u32,
330    u64,
331    u128,
332    i8,
333    i16,
334    i32,
335    i64,
336    i128
337);
338
339/// Allow `Cow<'a, str>` to be converted into a `Caveat`.
340impl IntoCaveat for Cow<'_, str> {
341    fn into_caveat<K: Kind>(self, warnings: Set<K>) -> Caveat<Self, K> {
342        Caveat::new(self, warnings)
343    }
344}
345
346/// Allow `Option<T: IntoCaveat>` to be converted into a `Caveat`.
347impl<T> IntoCaveat for Option<T>
348where
349    T: IntoCaveat,
350{
351    fn into_caveat<K: Kind>(self, warnings: Set<K>) -> Caveat<Self, K> {
352        Caveat::new(self, warnings)
353    }
354}
355
356/// Allow `Vec<T: IntoCaveat>` to be converted into a `Caveat`.
357impl<T> IntoCaveat for Vec<T>
358where
359    T: IntoCaveat,
360{
361    fn into_caveat<K: Kind>(self, warnings: Set<K>) -> Caveat<Self, K> {
362        Caveat::new(self, warnings)
363    }
364}
365
366/// `Verdict` specific extension methods for the `Result` type.
367pub trait VerdictExt<T, K: Kind> {
368    /// Maps a `Verdict<T, E>` to `Verdict<U, E>` by applying a function to a
369    /// contained [`Ok`] value, leaving an [`Err`] value untouched.
370    fn map_caveat<F, U>(self, op: F) -> Verdict<U, K>
371    where
372        F: FnOnce(T) -> U;
373}
374
375impl<T, K: Kind> VerdictExt<T, K> for Verdict<T, K>
376where
377    T: IntoCaveat,
378{
379    fn map_caveat<F, U>(self, op: F) -> Verdict<U, K>
380    where
381        F: FnOnce(T) -> U,
382    {
383        match self {
384            Ok(c) => Ok(c.map(op)),
385            Err(w) => Err(w),
386        }
387    }
388}
389
390/// Implement a conversion from `warning::Set<A>` to `warning::Set<B>` so that the `Err` variant
391/// of a `Verdict<_, A>` can be converted using the `?` operator to `Verdict<_, B>`.
392///
393/// `warning::Set::into_set` is used to perform the conversion between set `A` and `B`.
394#[macro_export]
395#[doc(hidden)]
396macro_rules! from_warning_set_to {
397    ($kind_a:path => $kind_b:path) => {
398        impl From<$crate::warning::Set<$kind_a>> for $crate::warning::Set<$kind_b> {
399            fn from(set_a: warning::Set<$kind_a>) -> Self {
400                set_a.into_set()
401            }
402        }
403    };
404}
405
406/// Convert an `Option` into a `Verdict` ready to exit the fn.
407pub trait OptionExt<T, K>
408where
409    K: Kind,
410{
411    /// Convert an `Option` into a `Verdict` ready to exit the fn.
412    fn exit_with_warning<F>(self, warnings: Set<K>, f: F) -> Verdict<T, K>
413    where
414        F: FnOnce() -> Warning<K>;
415}
416
417impl<T, K> OptionExt<T, K> for Option<T>
418where
419    T: IntoCaveat,
420    K: Kind,
421{
422    fn exit_with_warning<F>(self, mut warnings: Set<K>, f: F) -> Verdict<T, K>
423    where
424        F: FnOnce() -> Warning<K>,
425    {
426        if let Some(v) = self {
427            Ok(v.into_caveat(warnings))
428        } else {
429            warnings.push(f());
430            Err(warnings)
431        }
432    }
433}
434
435/// Groups together a module's `Kind` and the associated [`json::Element`].
436///
437/// The [`json::Element`] is referenced by `ElemId`.
438#[derive(Debug)]
439pub struct Warning<K: Kind> {
440    /// The `Kind` of warning.
441    kind: K,
442
443    /// The Id of the element that caused the [`Warning`].
444    elem_id: json::ElemId,
445}
446
447/// A Display object for writing a set of warnings.
448///
449/// The warnings set is formatted as a tree with element paths on the first level
450/// and a list of warning ids on the second.
451///
452/// ```shell
453/// $path.to.json.[0].field:
454///   - list_of_warning_ids
455///   - next_warning_id
456///
457/// $next.path.to.[1].json.field
458///   - list_of_warning_ids
459/// ```
460pub struct SetWriter<'caller, 'buf, K: Kind> {
461    /// The [`json::Element`] that has [`Warning`]s.
462    root_elem: &'caller json::Element<'buf>,
463
464    /// The list of warnings for the [`json::Element`].
465    warnings: &'caller Set<K>,
466
467    /// The indent to prefix to each warning id.
468    indent: &'caller str,
469}
470
471impl<'caller, 'buf, K: Kind> SetWriter<'caller, 'buf, K> {
472    /// Create a new `SetWriter` with a default warning id indent of `"  - "`.
473    pub fn new(root_elem: &'caller json::Element<'buf>, warnings: &'caller Set<K>) -> Self {
474        Self {
475            root_elem,
476            warnings,
477            indent: "  - ",
478        }
479    }
480
481    /// Create a new `SetWriter` with a custom warning id indent.
482    pub fn with_indent(
483        root_elem: &'caller json::Element<'buf>,
484        warnings: &'caller Set<K>,
485        indent: &'caller str,
486    ) -> Self {
487        Self {
488            root_elem,
489            warnings,
490            indent,
491        }
492    }
493}
494
495impl<K: Kind> fmt::Debug for SetWriter<'_, '_, K> {
496    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
497        fmt::Display::fmt(self, f)
498    }
499}
500
501impl<K: Kind> fmt::Display for SetWriter<'_, '_, K> {
502    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
503        let mut iter = self.warnings.group_by_elem(self.root_elem);
504
505        {
506            // Write the first group without an empty line prefix.
507            let Some(Group { element, warnings }) = iter.next() else {
508                return Ok(());
509            };
510
511            writeln!(f, "{}", element.path())?;
512
513            for warning in warnings {
514                write!(f, "{}{}", self.indent, warning)?;
515            }
516        }
517
518        // Write the rest of the Groups with am empty line padding.
519        for Group { element, warnings } in iter {
520            writeln!(f, "\n{}", element.path())?;
521
522            for warning in warnings {
523                write!(f, "{}{}", self.indent, warning)?;
524            }
525        }
526
527        Ok(())
528    }
529}
530
531impl<K: Kind> Warning<K> {
532    /// Create a Warning from a `Kind`.
533    ///
534    /// The `Kind` is typically defined in a domain module.
535    pub(crate) const fn with_elem(kind: K, elem: &json::Element<'_>) -> Warning<K> {
536        Warning {
537            kind,
538            elem_id: elem.id(),
539        }
540    }
541
542    /// Return the warning `Kind`'s id.
543    pub fn id(&self) -> Cow<'static, str> {
544        self.kind.id()
545    }
546
547    /// Return the `Kind` as a reference.
548    pub fn kind(&self) -> &K {
549        &self.kind
550    }
551
552    /// Consume the Warning and return the `Kind` of warning.
553    pub fn into_kind(self) -> K {
554        self.kind
555    }
556}
557
558impl<K: Kind> fmt::Display for Warning<K> {
559    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
560        write!(f, "{}", self.kind)
561    }
562}
563
564/// Each mod defines warnings for the type that it's trying to parse or lint from a [`json::Element`].
565///
566/// The `WarningKind` in the mod should impl this trait to take part in the [`Warning`] system.
567pub trait Kind: Sized + fmt::Debug + fmt::Display {
568    /// Return the human readable identifier for the [`Warning`].
569    ///
570    /// This is used in the `auto_test` assertion system.
571    /// Changing these strings may require updating `output_price__cdr.json` files.
572    fn id(&self) -> Cow<'static, str>;
573}
574
575/// A set of [`Warning`]s transported through the system using a `Verdict` or `Caveat`.
576#[derive(Debug)]
577pub struct Set<K: Kind>(Vec<Warning<K>>);
578
579impl<K: Kind> Set<K> {
580    /// Create a new list of [`Warning`]s
581    pub(crate) fn new() -> Self {
582        Self(vec![])
583    }
584
585    pub(crate) fn from_vec(warnings: Vec<Warning<K>>) -> Self {
586        Self(warnings)
587    }
588
589    /// Push a new [`Warning`] onto the list.
590    fn push(&mut self, warning: Warning<K>) {
591        self.0.push(warning);
592    }
593
594    /// Create and push a Warning from a kind defined in a domain module and as associated `json::Element`.
595    pub(crate) fn with_elem(&mut self, kind: K, elem: &json::Element<'_>) {
596        self.push(Warning::with_elem(kind, elem));
597    }
598
599    /// Converts `Set<K>` into `Set<KB>` using the `impl From<K> for KB`.
600    ///
601    /// This is used by the `from_warning_set_to` macro.
602    pub(crate) fn into_set<KB>(self) -> Set<KB>
603    where
604        KB: Kind + From<K>,
605    {
606        let warnings = self
607            .0
608            .into_iter()
609            .map(|warn| {
610                let Warning { kind, elem_id } = warn;
611                Warning {
612                    kind: kind.into(),
613                    elem_id,
614                }
615            })
616            .collect();
617
618        Set(warnings)
619    }
620
621    /// Converts `Set<K>` into `Set<KU>` using the `impl From<K> for KU`.
622    ///
623    /// This is used by the `from_warning_set_to` macro.
624    pub(crate) fn map_warning<KU>(self) -> Set<KU>
625    where
626        KU: Kind + From<K>,
627    {
628        let warnings = self
629            .0
630            .into_iter()
631            .map(|warn| {
632                let Warning { kind, elem_id } = warn;
633                Warning {
634                    kind: kind.into(),
635                    elem_id,
636                }
637            })
638            .collect();
639
640        Set(warnings)
641    }
642
643    /// Extend this set with the warnings of another.
644    ///
645    /// The other set's warnings will be converted if necessary.
646    pub(crate) fn extend<KA>(&mut self, warnings: Set<KA>)
647    where
648        KA: Into<K> + Kind,
649    {
650        self.0.extend(warnings.0.into_iter().map(|warn| {
651            let Warning { kind, elem_id } = warn;
652            Warning {
653                kind: kind.into(),
654                elem_id,
655            }
656        }));
657    }
658
659    /// Return true if the [`Warning`] set is empty.
660    pub fn is_empty(&self) -> bool {
661        self.0.is_empty()
662    }
663
664    /// Return the amount of [`Warning`]s in this set.
665    pub fn len(&self) -> usize {
666        self.0.len()
667    }
668
669    /// Return an iterator of [`Warning`]s grouped by [`json::Element`].
670    ///
671    /// The iterator emits [`Group`]s that borrows the warnings.
672    pub fn group_by_elem<'caller: 'buf, 'buf>(
673        &'caller self,
674        root: &'caller json::Element<'buf>,
675    ) -> GroupByElem<'caller, 'buf, K> {
676        let mut warnings = self.0.iter().collect::<Vec<_>>();
677        warnings.sort_unstable_by_key(|warning| warning.elem_id);
678
679        GroupByElem {
680            walker: json::walk::DepthFirst::new(root),
681            warnings: warnings.into_iter().peekable(),
682        }
683    }
684
685    /// Return an iterator of [`Warning`]s grouped by [`json::Element`].
686    ///
687    /// The iterator emits [`IntoGroup`]s that owns the warnings.
688    pub fn into_group_by_elem<'caller, 'buf>(
689        self,
690        root: &'caller json::Element<'buf>,
691    ) -> IntoGroupByElem<'caller, 'buf, K> {
692        let Self(mut warnings) = self;
693        warnings.sort_unstable_by_key(|warning| warning.elem_id);
694
695        IntoGroupByElem {
696            walker: json::walk::DepthFirst::new(root),
697            warnings: warnings.into_iter().peekable(),
698        }
699    }
700}
701
702/// An iterator of owned warning [`Kind`]s grouped by [`json::Element`].
703pub struct IntoGroupByElem<'caller, 'buf, K>
704where
705    K: Kind,
706{
707    /// The [`json::Element`] tree walker.
708    walker: json::walk::DepthFirst<'caller, 'buf>,
709
710    /// The iterator over every [`Warning`].
711    warnings: iter::Peekable<vec::IntoIter<Warning<K>>>,
712}
713
714impl<K> IntoGroupByElem<'_, '_, K>
715where
716    K: Kind,
717{
718    /// Return a map of [`json::Element`] paths to a list of [`Kind`]s.
719    pub fn into_kind_map(self) -> BTreeMap<String, Vec<K>> {
720        self.map(IntoGroup::into_kinds).collect()
721    }
722
723    /// Return a map of [`json::Element`] paths to a list of [`Warning`] ids as Strings.
724    ///
725    /// This is designed to be used to print out maps of warnings associated with elements.
726    /// You can use the debug alternate format `{:#?}` to print the map 'pretty' over multiple lines
727    /// with indentation.
728    ///
729    /// Note: This representation is also valid JSON and can be copied directly to
730    /// a test expectation file.
731    pub fn into_id_map(self) -> BTreeMap<String, Vec<String>> {
732        self.map(IntoGroup::into_str_ids).collect()
733    }
734
735    /// Return a map of [`json::Element`] paths to a list of [`Warning`] messages as Strings.
736    ///
737    /// This is designed to be used to print out maps of warnings associated with elements.
738    /// You can use the debug alternate format `{:#?}` to print the map 'pretty' over multiple lines
739    /// with indentation.
740    pub fn into_msg_map(self) -> BTreeMap<String, Vec<String>> {
741        self.map(IntoGroup::into_str_msgs).collect()
742    }
743}
744
745/// A group of warning `Kind`s associated with an `Element`.
746///
747/// This group is emitted from the `IntoGroupByElem` iterator.
748/// The warning `Kind`s are owned and so can be moved to another location.
749#[derive(Debug)]
750pub struct IntoGroup<'caller, 'buf, K> {
751    /// The [`json::Element`] that has [`Warning`]s.
752    pub element: &'caller json::Element<'buf>,
753
754    /// The list of warnings for the [`json::Element`].
755    pub warnings: Vec<K>,
756}
757
758impl<K> IntoGroup<'_, '_, K>
759where
760    K: Kind,
761{
762    /// Convert the Group into String versions of its Element path.
763    ///
764    /// The first tuple field is the [`Element`](json::Element) path as a String.
765    /// The second tuple field is a list of [`Warning`](Kind)s.
766    pub fn into_kinds(self) -> (String, Vec<K>) {
767        let Self { element, warnings } = self;
768        (element.path().to_string(), warnings)
769    }
770
771    /// Convert the Group into String versions of its parts.
772    ///
773    /// The first tuple field is the [`Element`](json::Element) path as a String.
774    /// The second tuple field is a list of [`Warning`](Kind) ids.
775    pub fn into_str_ids(self) -> (String, Vec<String>) {
776        let Self { element, warnings } = self;
777        (
778            element.path().to_string(),
779            warnings.iter().map(|kind| kind.id().to_string()).collect(),
780        )
781    }
782
783    /// Convert the Group into String versions of its parts.
784    ///
785    /// The first tuple field is the [`Element`](json::Element) path as a String.
786    /// The second tuple field is a list of [`Warning`](Kind) messages.
787    pub fn into_str_msgs(self) -> (String, Vec<String>) {
788        let Self { element, warnings } = self;
789        (
790            element.path().to_string(),
791            warnings.iter().map(ToString::to_string).collect(),
792        )
793    }
794}
795
796impl<'caller, 'buf, K: Kind> Iterator for IntoGroupByElem<'caller, 'buf, K> {
797    type Item = IntoGroup<'caller, 'buf, K>;
798
799    fn next(&mut self) -> Option<Self::Item> {
800        // The warnings are sorted and grouped by consecutive and compacted ids.
801        let warning = self.warnings.next()?;
802
803        // Search for the element associated with warning.
804        let element = loop {
805            let Some(element) = self.walker.next() else {
806                error!("An Element with id: `{}` was not found", warning.elem_id);
807                return None;
808            };
809
810            if element.id() < warning.elem_id {
811                // This element does not have any warnings continue the search for the
812                // element associated with the warning.
813                continue;
814            }
815
816            if element.id() > warning.elem_id {
817                debug_assert!(
818                    element.id() <= warning.elem_id,
819                    "The elements or the warnings are not sorted."
820                );
821                return None;
822            }
823
824            // We found the element for the first warning in the group.
825            break element;
826        };
827
828        // Insert the first warning into the `grouped` list.
829        let mut warnings_grouped = vec![warning.into_kind()];
830
831        // Collect all warnings in the group.
832        loop {
833            let warning = self.warnings.next_if(|w| w.elem_id == element.id());
834            let Some(warning) = warning else {
835                break;
836            };
837
838            warnings_grouped.push(warning.into_kind());
839        }
840
841        Some(IntoGroup {
842            element,
843            warnings: warnings_grouped,
844        })
845    }
846}
847
848/// An iterator of borrowed warning [`Kind`]s grouped by [`json::Element`].
849pub struct GroupByElem<'caller, 'buf, K>
850where
851    K: Kind,
852{
853    /// The [`json::Element`] tree walker.
854    walker: json::walk::DepthFirst<'caller, 'buf>,
855
856    /// The iterator over every [`Warning`].
857    warnings: iter::Peekable<vec::IntoIter<&'caller Warning<K>>>,
858}
859
860impl<K> GroupByElem<'_, '_, K>
861where
862    K: Kind,
863{
864    /// Return a map of [`json::Element`] paths to a list of [`Warning`] ids as Strings.
865    ///
866    /// This is designed to be used to print out maps of warnings associated with elements.
867    /// You can use the debug alternate format `{:#?}` to print the map 'pretty' over multiple lines
868    /// with indentation.
869    ///
870    /// Note: This representation is also valid JSON and can be copied directly to
871    /// a test expectation file.
872    pub fn into_id_map(self) -> BTreeMap<String, Vec<String>> {
873        self.map(Group::into_str_ids).collect()
874    }
875
876    /// Return a map of [`json::Element`] paths to a list of [`Warning`] messages as Strings.
877    ///
878    /// This is designed to be used to print out maps of warnings associated with elements.
879    /// You can use the debug alternate format `{:#?}` to print the map 'pretty' over multiple lines
880    /// with indentation.
881    pub fn into_msg_map(self) -> BTreeMap<String, Vec<String>> {
882        self.map(Group::into_str_msgs).collect()
883    }
884}
885
886/// A group of warning `Kind`s associated with an `Element`.
887///
888/// This group is emitted from the `GroupByElem` iterator.
889/// The warning `Kind`s are borrowed so the source Set does not need to be consumed/moved.
890#[derive(Debug)]
891pub struct Group<'caller, 'buf, K> {
892    /// The [`json::Element`] that has [`Warning`]s.
893    pub element: &'caller json::Element<'buf>,
894
895    /// The list of warnings for the [`json::Element`].
896    pub warnings: Vec<&'caller K>,
897}
898
899impl<K> Group<'_, '_, K>
900where
901    K: Kind,
902{
903    /// Convert the Group into String versions of its parts.
904    ///
905    /// The first tuple field is the [`Element`](json::Element) path as a String.
906    /// The second tuple field is a list of [`Warning`](Kind) ids.
907    pub fn into_str_ids(self) -> (String, Vec<String>) {
908        let Self { element, warnings } = self;
909        (
910            element.path().to_string(),
911            warnings.iter().map(|kind| kind.id().to_string()).collect(),
912        )
913    }
914
915    /// Convert the Group into String versions of its parts.
916    ///
917    /// The first tuple field is the [`Element`](json::Element) path as a String.
918    /// The second tuple field is a list of [`Warning`](Kind) messages.
919    pub fn into_str_msgs(self) -> (String, Vec<String>) {
920        let Self { element, warnings } = self;
921        (
922            element.path().to_string(),
923            warnings.iter().map(ToString::to_string).collect(),
924        )
925    }
926}
927
928impl<'caller, 'buf, K: Kind> Iterator for GroupByElem<'caller, 'buf, K> {
929    type Item = Group<'caller, 'buf, K>;
930
931    fn next(&mut self) -> Option<Self::Item> {
932        // The warnings are sorted and grouped by consecutive and compacted ids.
933        let warning = self.warnings.next()?;
934
935        // Search for the element associated with warning.
936        let element = loop {
937            let Some(element) = self.walker.next() else {
938                error!("An Element with id: `{}` was not found", warning.elem_id);
939                return None;
940            };
941
942            if element.id() < warning.elem_id {
943                // This element does not have any warnings continue the search for the
944                // element associated with the warning.
945                continue;
946            }
947
948            if element.id() > warning.elem_id {
949                debug_assert!(
950                    element.id() <= warning.elem_id,
951                    "The elements or the warnings are not sorted."
952                );
953                return None;
954            }
955
956            // We found the element for the first warning in the group.
957            break element;
958        };
959
960        // Insert the first warning into the `grouped` list.
961        let mut warnings_grouped = vec![warning.kind()];
962
963        // Collect all warnings in the group.
964        loop {
965            let warning = self.warnings.next_if(|w| w.elem_id == element.id());
966            let Some(warning) = warning else {
967                break;
968            };
969
970            warnings_grouped.push(warning.kind());
971        }
972
973        Some(Group {
974            element,
975            warnings: warnings_grouped,
976        })
977    }
978}
979
980#[cfg(test)]
981pub(crate) mod test {
982    use std::{
983        borrow::Cow,
984        collections::{BTreeMap, BTreeSet},
985        ops, slice,
986    };
987
988    use assert_matches::assert_matches;
989
990    use crate::{
991        json,
992        test::{ExpectValue, Expectation},
993    };
994
995    use super::{Caveat, Group, Kind, Set, Warning};
996
997    impl<K: Kind> Set<K> {
998        /// Return the inner storage.
999        pub fn into_vec(self) -> Vec<Warning<K>> {
1000            self.0
1001        }
1002
1003        pub fn into_parts_vec(self) -> Vec<(K, json::ElemId)> {
1004            self.0
1005                .into_iter()
1006                .map(|Warning { kind, elem_id }| (kind, elem_id))
1007                .collect()
1008        }
1009
1010        /// Return the inner kinds.
1011        ///
1012        /// This should only be used in tests and the `mod price`.
1013        pub fn into_kind_vec(self) -> Vec<K> {
1014            self.0.into_iter().map(Warning::into_kind).collect()
1015        }
1016
1017        /// Return the inner storage as a slice.
1018        pub fn as_slice(&self) -> &[Warning<K>] {
1019            self.0.as_slice()
1020        }
1021
1022        /// Return an immutable iterator over the slice.
1023        pub fn iter(&self) -> slice::Iter<'_, Warning<K>> {
1024            self.0.iter()
1025        }
1026    }
1027
1028    impl<K: Kind> ops::Deref for Set<K> {
1029        type Target = [Warning<K>];
1030
1031        fn deref(&self) -> &[Warning<K>] {
1032            self.as_slice()
1033        }
1034    }
1035
1036    impl<K: Kind> IntoIterator for Set<K> {
1037        type Item = Warning<K>;
1038
1039        type IntoIter = <Vec<Self::Item> as IntoIterator>::IntoIter;
1040
1041        fn into_iter(self) -> Self::IntoIter {
1042            self.0.into_iter()
1043        }
1044    }
1045
1046    impl<'a, K: Kind> IntoIterator for &'a Set<K> {
1047        type Item = &'a Warning<K>;
1048
1049        type IntoIter = slice::Iter<'a, Warning<K>>;
1050
1051        fn into_iter(self) -> Self::IntoIter {
1052            self.0.iter()
1053        }
1054    }
1055
1056    impl<T, K> Caveat<T, K>
1057    where
1058        K: Kind,
1059    {
1060        /// Return the value and assert there are no [`Warning`]s.
1061        pub fn unwrap(self) -> T {
1062            let Self { value, warnings } = self;
1063            assert_matches!(warnings.into_vec().as_slice(), []);
1064            value
1065        }
1066    }
1067
1068    /// Assert that the warnings given are expected.
1069    ///
1070    /// Panic with print out of the warnings and the expectations if any warnings were unexpected.
1071    pub(crate) fn assert_warnings<K>(
1072        expect_file_name: &str,
1073        root: &json::Element<'_>,
1074        warnings: &Set<K>,
1075        expected: Expectation<BTreeMap<String, Vec<String>>>,
1076    ) where
1077        K: Kind,
1078    {
1079        let Expectation::Present(ExpectValue::Some(expected)) = expected else {
1080            assert!(
1081                warnings.is_empty(),
1082                "There is no `warnings` field in the `{expect_file_name}` file but the tariff has warnings;\n{:?}",
1083                warnings.group_by_elem(root).into_id_map()
1084            );
1085            return;
1086        };
1087
1088        {
1089            // Assert that the `expect` file doesn't have extraneous entries.
1090            let warnings_grouped = warnings
1091                .group_by_elem(root)
1092                .map(|Group { element, warnings }| (element.path().to_string(), warnings))
1093                .collect::<BTreeMap<_, _>>();
1094
1095            let mut elems_in_expect_without_warning = vec![];
1096
1097            for elem_path in expected.keys() {
1098                if !warnings_grouped.contains_key(elem_path) {
1099                    elems_in_expect_without_warning.push(elem_path);
1100                }
1101            }
1102
1103            assert!(elems_in_expect_without_warning.is_empty(),
1104                "The expect file `{expect_file_name}` has entries for elements that have no warnings:\n\
1105                {elems_in_expect_without_warning:#?}"
1106            );
1107        }
1108
1109        // The elements that have warnings but have no entry for the elements path in the `expect` file.
1110        let mut elems_missing_from_expect = vec![];
1111        // The element that have warnings and an entry in the `expect` file, but the list of expected warnings
1112        // is not equal to the list of actual warnings.
1113        let mut unequal_warnings = vec![];
1114
1115        for group in warnings.group_by_elem(root) {
1116            let path_str = group.element.path().to_string();
1117            let Some(warnings_expected) = expected.get(&*path_str) else {
1118                elems_missing_from_expect.push(group);
1119                continue;
1120            };
1121
1122            // Make two sets of actual and expected warnings.
1123            let warnings_expected = warnings_expected
1124                .iter()
1125                .map(|s| Cow::Borrowed(&**s))
1126                .collect::<BTreeSet<_>>();
1127            let warnings = group
1128                .warnings
1129                .iter()
1130                .map(|w| w.id())
1131                .collect::<BTreeSet<_>>();
1132
1133            if warnings_expected != warnings {
1134                unequal_warnings.push(group);
1135            }
1136        }
1137
1138        if !elems_missing_from_expect.is_empty() || !unequal_warnings.is_empty() {
1139            let missing = elems_missing_from_expect
1140                .into_iter()
1141                .map(Group::into_str_ids)
1142                .collect::<BTreeMap<_, _>>();
1143
1144            let unequal = unequal_warnings
1145                .into_iter()
1146                .map(Group::into_str_ids)
1147                .collect::<BTreeMap<_, _>>();
1148
1149            match (!missing.is_empty(), !unequal.is_empty()) {
1150                (true, true) => panic!(
1151                    "Elements with warnings but are not defined in the `{expect_file_name}` file:\n{missing:#?}\n\
1152                    Elements that are in the `{expect_file_name}` file but the warnings list is not correct.\n\
1153                    The warnings reported are: \n{unequal:#?}"                  
1154                ),
1155                (true, false) => panic!("Elements with warnings but are not defined in the `{expect_file_name}` file:\n{missing:#?}"),
1156                (false, true) => panic!(
1157                    "Elements that are in the `{expect_file_name}` file but the warnings list is not correct.\n\
1158                    The warnings reported are: \n{unequal:#?}"
1159                ),
1160                (false, false) => (),
1161            }
1162        }
1163    }
1164}
1165
1166#[cfg(test)]
1167mod test_group_by_elem {
1168    use std::fmt;
1169
1170    use assert_matches::assert_matches;
1171
1172    use crate::{json, test};
1173
1174    use super::{Group, IntoGroup, Kind, Set, Warning};
1175
1176    const JSON: &str = r#"{
1177    "field_one#": "one",
1178    "field_two": "two",
1179    "field_three": "three"
1180}"#;
1181
1182    #[derive(Debug)]
1183    enum WarningKind {
1184        Root,
1185        One,
1186        OneAgain,
1187        Three,
1188    }
1189
1190    impl Kind for WarningKind {
1191        fn id(&self) -> std::borrow::Cow<'static, str> {
1192            match self {
1193                WarningKind::Root => "root".into(),
1194                WarningKind::One => "one".into(),
1195                WarningKind::OneAgain => "one_again".into(),
1196                WarningKind::Three => "three".into(),
1197            }
1198        }
1199    }
1200
1201    impl fmt::Display for WarningKind {
1202        fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1203            match self {
1204                WarningKind::Root => write!(f, "NopeRoot"),
1205                WarningKind::One => write!(f, "NopeOne"),
1206                WarningKind::OneAgain => write!(f, "NopeOneAgain"),
1207                WarningKind::Three => write!(f, "NopeThree"),
1208            }
1209        }
1210    }
1211
1212    #[test]
1213    fn should_group_by_elem() {
1214        test::setup();
1215
1216        let elem = parse(JSON);
1217        let mut warnings = Set::<WarningKind>::new();
1218
1219        // Push warnings into the set out of order.
1220        // They should be sorted by `ElemId`.
1221        warnings.push(Warning {
1222            kind: WarningKind::Root,
1223            elem_id: json::ElemId::from(0),
1224        });
1225        warnings.push(Warning {
1226            kind: WarningKind::One,
1227            elem_id: json::ElemId::from(1),
1228        });
1229        warnings.push(Warning {
1230            kind: WarningKind::Three,
1231            elem_id: json::ElemId::from(3),
1232        });
1233        warnings.push(Warning {
1234            kind: WarningKind::OneAgain,
1235            elem_id: json::ElemId::from(1),
1236        });
1237
1238        let mut iter = warnings.group_by_elem(&elem);
1239
1240        {
1241            let Group { element, warnings } = iter.next().unwrap();
1242
1243            assert!(
1244                element.value().is_object(),
1245                "The root object should be emitted first"
1246            );
1247            assert_eq!(
1248                element.id(),
1249                json::ElemId::from(0),
1250                "The root object should be emitted first"
1251            );
1252            assert_matches!(warnings.as_slice(), [WarningKind::Root]);
1253        }
1254
1255        {
1256            let Group { element, warnings } = iter.next().unwrap();
1257
1258            assert_eq!(element.value().as_raw_str().unwrap().as_raw(), "one");
1259            assert_eq!(element.id(), json::ElemId::from(1));
1260            assert_matches!(
1261                warnings.as_slice(),
1262                [WarningKind::One, WarningKind::OneAgain],
1263                "[`json::Element`] 1 should have two warnings"
1264            );
1265        }
1266
1267        {
1268            // [`json::Element`] 2 has no [`Warning`]s so we expect [`json::Element`] 3 next
1269            let Group { element, warnings } = iter.next().unwrap();
1270
1271            assert_eq!(element.value().as_raw_str().unwrap().as_raw(), "three");
1272            assert_eq!(element.id(), json::ElemId::from(3));
1273            assert_matches!(warnings.as_slice(), [WarningKind::Three]);
1274        }
1275    }
1276
1277    #[test]
1278    fn should_into_group_by_elem() {
1279        test::setup();
1280
1281        let elem = parse(JSON);
1282        let mut warnings = Set::<WarningKind>::new();
1283
1284        // Push warnings into the set out of order.
1285        // They should be sorted by `ElemId`.
1286        warnings.push(Warning {
1287            kind: WarningKind::Root,
1288            elem_id: json::ElemId::from(0),
1289        });
1290        warnings.push(Warning {
1291            kind: WarningKind::Three,
1292            elem_id: json::ElemId::from(3),
1293        });
1294        warnings.push(Warning {
1295            kind: WarningKind::One,
1296            elem_id: json::ElemId::from(1),
1297        });
1298        warnings.push(Warning {
1299            kind: WarningKind::OneAgain,
1300            elem_id: json::ElemId::from(1),
1301        });
1302
1303        let mut iter = warnings.into_group_by_elem(&elem);
1304
1305        {
1306            let IntoGroup { element, warnings } = iter.next().unwrap();
1307
1308            assert!(
1309                element.value().is_object(),
1310                "The root object should be emitted first"
1311            );
1312            assert_eq!(
1313                element.id(),
1314                json::ElemId::from(0),
1315                "The root object should be emitted first"
1316            );
1317            assert_matches!(warnings.as_slice(), [WarningKind::Root]);
1318        }
1319
1320        {
1321            let IntoGroup { element, warnings } = iter.next().unwrap();
1322
1323            assert_eq!(element.value().as_raw_str().unwrap().as_raw(), "one");
1324            assert_eq!(element.id(), json::ElemId::from(1));
1325            assert_matches!(
1326                warnings.as_slice(),
1327                [WarningKind::One, WarningKind::OneAgain],
1328                "[`json::Element`] 1 should have two warnings"
1329            );
1330        }
1331
1332        {
1333            // [`json::Element`] 2 has no [`Warning`]s so we expect [`json::Element`] 3 next
1334            let IntoGroup { element, warnings } = iter.next().unwrap();
1335
1336            assert_eq!(element.value().as_raw_str().unwrap().as_raw(), "three");
1337            assert_eq!(element.id(), json::ElemId::from(3));
1338            assert_matches!(warnings.as_slice(), [WarningKind::Three]);
1339        }
1340    }
1341
1342    fn parse(json: &str) -> json::Element<'_> {
1343        json::parse(json).unwrap()
1344    }
1345}