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, fmt, slice};
47
48use tracing::error;
49
50use crate::json;
51
52/// A `Verdict` is a standard [`Result`] with [`Warning`]s potentially issued for both the `Ok` and `Err` variants.
53pub type Verdict<T, K> = Result<Caveat<T, K>, Set<K>>;
54
55/// A value that may have associated [`Warning`]s.
56///
57/// Even though the value has been created there may be certain caveats you should be aware of before using it.
58#[derive(Debug)]
59pub struct Caveat<T, K: Kind> {
60    /// The value created by the function.
61    value: T,
62
63    /// A list of [`Warning`]s or caveats issued when creating the value.
64    warnings: Set<K>,
65}
66
67impl<T, K> Caveat<T, K>
68where
69    T: IntoCaveat,
70    K: Kind,
71{
72    /// The only way to create `Caveat<T>` is if `T` impls `IntoCaveat`.
73    pub(crate) fn new(value: T, warnings: Set<K>) -> Self {
74        Self { value, warnings }
75    }
76}
77
78impl<T, K> Caveat<T, K>
79where
80    K: Kind,
81{
82    /// Return the value and any [`Warning`]s stored in the `Caveat`.
83    pub fn into_parts(self) -> (T, Set<K>) {
84        let Self { value, warnings } = self;
85        (value, warnings)
86    }
87}
88
89impl<T, K> Caveat<T, K>
90where
91    K: Kind,
92{
93    /// Convert a `Caveat<T>` into a `T` by gathering it's [`Warning`]s.
94    pub(crate) fn gather_warnings_into<KA>(self, outer_warnings: &mut Set<KA>) -> T
95    where
96        K: Into<KA>,
97        KA: Kind,
98    {
99        let Self { value, warnings } = self;
100
101        outer_warnings.0.extend(warnings.0.into_iter().map(|warn| {
102            let Warning { kind, elem_id } = warn;
103            Warning {
104                kind: kind.into(),
105                elem_id,
106            }
107        }));
108
109        value
110    }
111
112    pub fn map<U, F: FnOnce(T) -> U>(self, op: F) -> Caveat<U, K> {
113        let Self { value, warnings } = self;
114        Caveat {
115            value: op(value),
116            warnings,
117        }
118    }
119}
120
121/// Gather warnings from an `Option<Caveat<T, K>>`.
122pub trait OptionCaveat<T, K>
123where
124    K: Kind,
125{
126    fn gather_warnings_into<KA>(self, outer_warnings: &mut Set<KA>) -> Option<T>
127    where
128        K: Into<KA>,
129        KA: Kind;
130}
131
132impl<T, K> OptionCaveat<T, K> for Option<Caveat<T, K>>
133where
134    K: Kind,
135{
136    /// Convert a `Option<Caveat<T>>` into a `Option<T>` by gathering it's [`Warning`]s.
137    fn gather_warnings_into<KA>(self, outer_warnings: &mut Set<KA>) -> Option<T>
138    where
139        K: Into<KA>,
140        KA: Kind,
141    {
142        self.map(|v| v.gather_warnings_into(outer_warnings))
143    }
144}
145/// Converts a value `T` into a `Caveat`.
146///
147/// Each module can use this to whitelist their types for conversion to `Caveat<T>`.
148pub trait IntoCaveat: Sized {
149    /// Any type can be converted to `Caveat<T>` only a list of [`Warning`]s is required.
150    fn into_caveat<K: Kind>(self, warnings: Set<K>) -> Caveat<Self, K>;
151}
152
153/// Allow unit to be converted into a `Caveat`.
154impl IntoCaveat for () {
155    fn into_caveat<K: Kind>(self, warnings: Set<K>) -> Caveat<Self, K> {
156        Caveat::new(self, warnings)
157    }
158}
159
160/// Allow `Cow<'a, str>` to be converted into a `Caveat`.
161impl IntoCaveat for Cow<'_, str> {
162    fn into_caveat<K: Kind>(self, warnings: Set<K>) -> Caveat<Self, K> {
163        Caveat::new(self, warnings)
164    }
165}
166
167/// Allow `Option<T: IntoCaveat>` to be converted into a `Caveat`.
168impl<T> IntoCaveat for Option<T>
169where
170    T: IntoCaveat,
171{
172    fn into_caveat<K: Kind>(self, warnings: Set<K>) -> Caveat<Self, K> {
173        Caveat::new(self, warnings)
174    }
175}
176
177/// `Verdict` specific extinsion methods for the `Result` type.
178pub trait VerdictExt<T, K: Kind> {
179    /// Converts from `Verdict<T, K>` to `Caveat<Option<T>, K>`.
180    ///
181    /// The `Ok` and `Err` variants are encoded as `Some(T)` and `None` respectively and the [`Warning`]s are retained.
182    fn ok_caveat(self) -> Caveat<Option<T>, K>;
183}
184
185impl<T, K: Kind> VerdictExt<T, K> for Verdict<T, K>
186where
187    T: IntoCaveat,
188{
189    fn ok_caveat(self) -> Caveat<Option<T>, K> {
190        match self {
191            Ok(v) => {
192                let (v, warnings) = v.into_parts();
193                Some(v).into_caveat(warnings)
194            }
195            Err(warnings) => None.into_caveat(warnings),
196        }
197    }
198}
199
200/// Convert a [`Kind`] into a [`Warning`] by supplying a [`json::Element`].
201pub trait IntoWarning<K: Kind> {
202    /// Convert a [`Kind`] into a [`Warning`] by supplying a [`json::Element`].
203    fn into_warning(self, elem: &json::Element<'_>) -> Warning<K>;
204}
205
206/// Implement `IntoWarning` for all implementors of `Kind`.
207impl<K> IntoWarning<K> for K
208where
209    K: Kind,
210{
211    /// Convert a [`Kind`] into a [`Warning`] by supplying a [`json::Element`].
212    fn into_warning(self, elem: &json::Element<'_>) -> Warning<K> {
213        Warning {
214            kind: self,
215            elem_id: elem.id(),
216        }
217    }
218}
219
220/// Implement a conversion from `warning::Set<A>` to `warning::Set<B>` so that the `Err` variant
221/// of a `Verdict<_, A>` can be converted using the `?` operator to `Verdict<_, B>`.
222///
223/// `warning::Set::into_set` is used to perform the conversion between set A and B.
224#[macro_export]
225#[doc(hidden)]
226macro_rules! from_warning_set_to {
227    ($kind_a:path => $kind_b:path) => {
228        impl From<$crate::warning::Set<$kind_a>> for $crate::warning::Set<$kind_b> {
229            fn from(set_a: warning::Set<$kind_a>) -> Self {
230                set_a.into_set()
231            }
232        }
233    };
234}
235
236/// Convert an `Option` into a `Verdict` ready to exit the fn.
237pub trait OptionExt<T, K>
238where
239    K: Kind,
240{
241    /// Convert an `Option` into a `Verdict` ready to exit the fn.
242    fn exit_with_warning<F>(self, warnings: Set<K>, f: F) -> Verdict<T, K>
243    where
244        F: FnOnce() -> Warning<K>;
245}
246
247impl<T, K> OptionExt<T, K> for Option<T>
248where
249    T: IntoCaveat,
250    K: Kind,
251{
252    fn exit_with_warning<F>(self, mut warnings: Set<K>, f: F) -> Verdict<T, K>
253    where
254        F: FnOnce() -> Warning<K>,
255    {
256        if let Some(v) = self {
257            Ok(v.into_caveat(warnings))
258        } else {
259            warnings.push(f());
260            Err(warnings)
261        }
262    }
263}
264
265/// Groups together a module's `Kind` and the associated [`json::Element`].
266///
267/// The [`json::Element`] is referenced by `ElemId`.
268#[derive(Debug)]
269pub struct Warning<K: Kind> {
270    /// The kind of warning.
271    pub kind: K,
272
273    /// The Id of the element that caused the [`Warning`].
274    pub elem_id: json::ElemId,
275}
276
277impl<K: Kind> Warning<K> {
278    pub fn id(&self) -> Cow<'static, str> {
279        self.kind.id()
280    }
281}
282
283impl<K: Kind> fmt::Display for Warning<K> {
284    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
285        write!(f, "{}", self.kind)
286    }
287}
288
289/// Each mod defines warnings for the type that it's trying to parse or lint from a [`json::Element`].
290///
291/// The `mod::Kind` should impl this trait to take part in the [`Warning`] system.
292pub trait Kind: Sized + fmt::Debug + fmt::Display {
293    /// Return the human readable identifier for the [`Warning`].
294    ///
295    /// This is used in the `auto_test` assertion system.
296    /// Changing these strings may require updating `output_price__cdr.json` files.
297    fn id(&self) -> Cow<'static, str>;
298}
299
300/// A set of [`Warning`]s transported through the system using a `Verdict` or `Caveat`.
301#[derive(Debug)]
302pub struct Set<K: Kind>(Vec<Warning<K>>);
303
304impl<K: Kind> Set<K> {
305    /// Create a new list of [`Warning`]s
306    pub(crate) fn new() -> Self {
307        Self(vec![])
308    }
309
310    /// Push a new [`Warning`] onto the list.
311    pub(crate) fn push(&mut self, warning: Warning<K>) {
312        self.0.push(warning);
313    }
314
315    /// Converts `Set<K>` into `Set<KB>` using the `impl From<K> for KB`.
316    ///
317    /// This is used by the `from_warning_set_to` macro.
318    pub(crate) fn into_set<KB>(self) -> Set<KB>
319    where
320        KB: Kind + From<K>,
321    {
322        let warnings = self
323            .0
324            .into_iter()
325            .map(|warn| {
326                let Warning { kind, elem_id } = warn;
327                Warning {
328                    kind: kind.into(),
329                    elem_id,
330                }
331            })
332            .collect();
333
334        Set(warnings)
335    }
336}
337
338impl<K: Kind> Set<K> {
339    /// Return true if the [`Warning`] list is empty.
340    pub fn is_empty(&self) -> bool {
341        self.0.is_empty()
342    }
343
344    /// Return true if the [`Warning`] list is empty.
345    pub fn len(&self) -> usize {
346        self.0.len()
347    }
348
349    /// Convert the set of [`Warning`]s into a `Report` ready for presentation.
350    pub fn into_report(self) -> Report<K> {
351        Report::new(self)
352    }
353}
354
355/// A collection of [`Warning`]s grouped by [`json::Element`].
356///
357/// Use the `Report::iter` to iterate over the [`json::Element`]s that have [`Warning`]s in order.
358#[derive(Debug)]
359pub struct Report<K>
360where
361    K: Kind,
362{
363    groups: Vec<Group<K>>,
364}
365
366/// A `Group` of `WarningKind`s that all share the same `ElemeId`.
367#[derive(Debug)]
368struct Group<K> {
369    /// The `ElemId` all [`Warning`]s share.
370    id: json::ElemId,
371
372    /// The group of `WarningKind`s.
373    warnings: Vec<K>,
374}
375
376impl<K> Report<K>
377where
378    K: Kind,
379{
380    /// Create a new `Report` ready to present each [`json::Element`] that has [`Warning`]s.
381    fn new(warnings: Set<K>) -> Self {
382        let Set(mut warnings) = warnings;
383        // sort the warnings list so that we know the warngs are grouped by `ElemId`.
384        warnings.sort_unstable_by(|a, b| a.elem_id.cmp(&b.elem_id));
385        let mut warnings = warnings.into_iter();
386
387        let Some(first) = warnings.next() else {
388            return Self { groups: vec![] };
389        };
390
391        // Insert the first warning into the resulting elements list.
392        // This is used as the first id to start grouping by.
393        let mut groups = vec![Group {
394            id: first.elem_id,
395            warnings: vec![first.kind],
396        }];
397
398        // Group the warnings by `ElemId`, the warnings list is already compacted so that the warnings
399        // Are sorted by `ElemId`. This loop simplify cuts the [`Warning`] list up into those groups.
400        for warn in warnings {
401            // Compare the current [`Warning`] with the next to detect the end of the current run/group.
402            if let Some(s) = groups.last_mut() {
403                if warn.elem_id == s.id {
404                    // The run continues, add the current `WarningKind` to the `Group`.
405                    s.warnings.push(warn.kind);
406                } else {
407                    // This is the end of the run/group. Create a new `Group`.
408                    groups.push(Group {
409                        id: warn.elem_id,
410                        warnings: vec![warn.kind],
411                    });
412                }
413            }
414        }
415
416        Self { groups }
417    }
418
419    /// Iterate over all [`json::Element`]s that have [`Warning`]s.
420    ///
421    /// The iteration happens in the order of the `Element::id`
422    pub fn iter<'a, 'bin>(&'a self, root: &'a json::Element<'bin>) -> ReportIter<'a, 'bin, K> {
423        ReportIter {
424            walker: json::walk::DepthFirst::new(root),
425            groups: self.groups.iter(),
426        }
427    }
428
429    /// Return true if there are no [`json::Element`]s with [`Warning`]s.
430    pub fn is_empty(&self) -> bool {
431        self.groups.is_empty()
432    }
433
434    /// Return the number of [`json::Element`]s with [`Warning`]s.
435    pub fn len(&self) -> usize {
436        self.groups.len()
437    }
438}
439
440/// An iterator the walks over all [`json::Element`]s and returns `Some` if the given
441/// [`json::Element`] has any [`Warning`]s.
442pub struct ReportIter<'a, 'bin, K: Kind> {
443    /// The [`json::Element`] tree walker.
444    walker: json::walk::DepthFirst<'a, 'bin>,
445
446    /// The iterator for all [`Warning`] `Group`s.
447    groups: slice::Iter<'a, Group<K>>,
448}
449
450impl<'a, 'bin, K: Kind> Iterator for ReportIter<'a, 'bin, K> {
451    type Item = ElementReport<'a, 'bin, K>;
452
453    fn next(&mut self) -> Option<Self::Item> {
454        let group = self.groups.next()?;
455
456        // Resolve the [`Warning`] `Group`'s `ElemId` to an [`json::Element`] in the given tree.
457        loop {
458            let Some(element) = self.walker.next() else {
459                error!("An Element with id: `{}` was not found", group.id);
460                return None;
461            };
462
463            if element.id() == group.id {
464                return Some(ElementReport {
465                    element,
466                    warnings: &group.warnings,
467                });
468            }
469        }
470    }
471}
472
473/// A report of all warnings for a given [`json::Element`].
474pub struct ElementReport<'a, 'bin, K: Kind> {
475    /// The [`json::Element`] that has [`Warning`]s.
476    pub element: &'a json::Element<'bin>,
477
478    /// The list of warnings for the [`json::Element`].
479    pub warnings: &'a [K],
480}
481
482impl<K: Kind> fmt::Debug for ElementReport<'_, '_, K> {
483    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
484        f.debug_struct("ElementReport")
485            .field("element", &self.element.path())
486            .field("warnings", &self.warnings)
487            .finish()
488    }
489}
490
491#[cfg(test)]
492mod test {
493    use std::{ops, slice};
494
495    use assert_matches::assert_matches;
496
497    use super::{Caveat, Kind, Set, Warning};
498
499    impl<K: Kind> Set<K> {
500        /// Return the inner storage.
501        pub fn into_vec(self) -> Vec<Warning<K>> {
502            self.0
503        }
504
505        /// Return the inner storgae as a slice.
506        pub fn as_slice(&self) -> &[Warning<K>] {
507            self.0.as_slice()
508        }
509
510        /// Return an immutable iterator over the slice.
511        pub fn iter(&self) -> slice::Iter<'_, Warning<K>> {
512            self.0.iter()
513        }
514    }
515
516    impl<K: Kind> ops::Deref for Set<K> {
517        type Target = [Warning<K>];
518
519        fn deref(&self) -> &[Warning<K>] {
520            self.as_slice()
521        }
522    }
523
524    impl<K: Kind> IntoIterator for Set<K> {
525        type Item = Warning<K>;
526
527        type IntoIter = <Vec<Self::Item> as IntoIterator>::IntoIter;
528
529        fn into_iter(self) -> Self::IntoIter {
530            self.0.into_iter()
531        }
532    }
533
534    impl<'a, K: Kind> IntoIterator for &'a Set<K> {
535        type Item = &'a Warning<K>;
536
537        type IntoIter = slice::Iter<'a, Warning<K>>;
538
539        fn into_iter(self) -> Self::IntoIter {
540            self.0.iter()
541        }
542    }
543
544    impl<T, K> Caveat<T, K>
545    where
546        K: Kind,
547    {
548        /// Return the value and assert there are no [`Warning`]s.
549        pub fn unwrap(self) -> T {
550            let Self { value, warnings } = self;
551            assert_matches!(warnings.into_vec().as_slice(), []);
552            value
553        }
554    }
555}
556
557#[cfg(test)]
558mod test_report {
559    use std::fmt;
560
561    use assert_matches::assert_matches;
562
563    use crate::{json, test, warning::ElementReport};
564
565    use super::{Kind, Report, Set, Warning};
566
567    #[derive(Debug)]
568    enum WarningKind {
569        Root,
570        One,
571        OneAgain,
572        Three,
573    }
574
575    impl Kind for WarningKind {
576        fn id(&self) -> std::borrow::Cow<'static, str> {
577            match self {
578                WarningKind::Root => "root".into(),
579                WarningKind::One => "one".into(),
580                WarningKind::OneAgain => "one_again".into(),
581                WarningKind::Three => "three".into(),
582            }
583        }
584    }
585
586    impl fmt::Display for WarningKind {
587        fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
588            match self {
589                WarningKind::Root => write!(f, "NopeRoot"),
590                WarningKind::One => write!(f, "NopeOne"),
591                WarningKind::OneAgain => write!(f, "NopeOneAgain"),
592                WarningKind::Three => write!(f, "NopeThree"),
593            }
594        }
595    }
596
597    #[test]
598    fn should_group_warnings() {
599        const JSON: &str = r#"{
600    "field_one#": "one",
601    "field_two": "two",
602    "field_three": "three"
603}"#;
604
605        test::setup();
606
607        let elem = parse(JSON);
608        let mut warnings = Set::<WarningKind>::new();
609
610        // Push warnings into the set out of order.
611        // They should be sorted by `ElemId`.
612        warnings.push(Warning {
613            kind: WarningKind::Root,
614            elem_id: 0.into(),
615        });
616        warnings.push(Warning {
617            kind: WarningKind::Three,
618            elem_id: 3.into(),
619        });
620        warnings.push(Warning {
621            kind: WarningKind::One,
622            elem_id: 1.into(),
623        });
624        warnings.push(Warning {
625            kind: WarningKind::OneAgain,
626            elem_id: 1.into(),
627        });
628
629        let report = Report::new(warnings);
630        let mut iter = report.iter(&elem);
631        let ElementReport { element, warnings } = iter.next().unwrap();
632
633        assert!(
634            element.value().is_object(),
635            "The root object should be reported first"
636        );
637        assert_eq!(
638            element.id(),
639            json::ElemId::from(0),
640            "The root object should be reported first"
641        );
642        assert_matches!(warnings, [WarningKind::Root]);
643
644        let ElementReport { element, warnings } = iter.next().unwrap();
645
646        assert_eq!(element.value().as_raw_str().unwrap().as_raw(), "one");
647        assert_eq!(element.id(), json::ElemId::from(1));
648        assert_matches!(
649            warnings,
650            [WarningKind::One, WarningKind::OneAgain],
651            "[`json::Element`] 1 should have two warnings"
652        );
653
654        // [`json::Element`] 2 has no [`Warning`]s so we expect [`json::Element`] 3 next
655        let ElementReport { element, warnings } = iter.next().unwrap();
656
657        assert_eq!(element.value().as_raw_str().unwrap().as_raw(), "three");
658        assert_eq!(element.id(), json::ElemId::from(3));
659        assert_matches!(warnings, [WarningKind::Three]);
660    }
661
662    fn parse(json: &str) -> json::Element<'_> {
663        json::parse(json).unwrap()
664    }
665}