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