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