Skip to main content

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<W>`, 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, W>` where the `Caveat` contains a value `T` and a set of `Warnings<W>`.
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
46#[cfg(test)]
47pub(crate) mod test;
48
49#[cfg(test)]
50mod test_assert_warnings;
51
52#[cfg(test)]
53mod test_group_by_elem;
54
55use std::{
56    borrow::Cow,
57    collections::{btree_map, BTreeMap, HashSet},
58    convert::Infallible,
59    fmt,
60    ops::Deref,
61    vec,
62};
63
64use tracing::{debug, info};
65
66use crate::{json, schema::Integrity};
67
68#[doc(hidden)]
69#[macro_export]
70macro_rules! from_warning_all {
71    ($($source_kind:path => $target_kind:ident::$target_variant:ident),+) => {
72        $(
73            /// Convert from `Warning` A to B.
74            impl From<$source_kind> for $target_kind {
75                fn from(warning: $source_kind) -> Self {
76                    $target_kind::$target_variant(warning)
77                }
78            }
79
80            /// Implement a conversion from `warning::Set<A>` to `warning::Set<B>` so that the `Err` variant
81            /// of a `Verdict<_, A>` can be converted using the `?` operator to `Verdict<_, B>`.
82            ///
83            /// `warning::Set::into_other` is used to perform the conversion between set `A` and `B`.
84            impl From<$crate::warning::ErrorSet<$source_kind>> for $crate::warning::ErrorSet<$target_kind> {
85                fn from(set_a: $crate::warning::ErrorSet<$source_kind>) -> Self {
86                    set_a.into_other()
87                }
88            }
89
90            /// Implement a conversion from `warning::SetDeferred<A>` to `warning::SetDeferred<B>` so that the `Err` variant
91            /// of a `VerdictDeferred<_, A>` can be converted using the `?` operator to `VerdictDeferred<_, B>`.
92            ///
93            /// `warning::SetDeferred::into_other` is used to perform the conversion between set `A` and `B`.
94            impl From<$crate::warning::ErrorSetDeferred<$source_kind>> for $crate::warning::ErrorSetDeferred<$target_kind> {
95                fn from(set_a: $crate::warning::ErrorSetDeferred<$source_kind>) -> Self {
96                    set_a.into_other()
97                }
98            }
99        )+
100    };
101}
102
103#[derive(Clone, PartialOrd, Ord, PartialEq, Eq, Hash)]
104pub struct Id(Cow<'static, str>);
105
106impl Id {
107    /// Create an `Id` from a `'static str`.
108    pub(crate) const fn from_static(s: &'static str) -> Self {
109        Self(Cow::Borrowed(s))
110    }
111
112    /// Create an `Id` from a `String`.
113    pub(crate) const fn from_string(s: String) -> Self {
114        Self(Cow::Owned(s))
115    }
116
117    /// Return the contained `str`.
118    pub fn as_str(&self) -> &str {
119        &self.0
120    }
121}
122
123impl fmt::Debug for Id {
124    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
125        fmt::Debug::fmt(&self.0, f)
126    }
127}
128
129impl fmt::Display for Id {
130    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
131        fmt::Display::fmt(&self.0, f)
132    }
133}
134
135/// A `Verdict` is a standard [`Result`] with [`Warning`]s potentially issued for both the `Ok` and `Err` variants.
136pub type Verdict<T, W> = Result<Caveat<T, W>, ErrorSet<W>>;
137
138/// A `VerdictDeferred` is a standard [`Result`] with [`Warning`]s potentially issued for both the `Ok` and `Err` variants.
139///
140/// This verdict is considered deferred as the warnings still need to be associated with a [`json::Element`].
141///
142/// NOTE: The deferred types are used to avoid passing [`json::Element`] references
143/// to functions just to create [`Warning`]s.
144pub(crate) type VerdictDeferred<T, W> = Result<CaveatDeferred<T, W>, ErrorSetDeferred<W>>;
145
146/// A value that may have associated [`Warning`]s.
147///
148/// This caveat is considered deferred as the warning still need to be associated with
149/// a [`json::Element`] to become [`Warning`]s.
150///
151/// Even though the value has been created there may be certain caveats you should be aware of before using it.
152///
153/// NOTE: The deferred types are used to avoid passing [`json::Element`] references
154/// to functions just to create [`Warning`]s.
155#[derive(Debug)]
156pub struct CaveatDeferred<T, W: Warning> {
157    /// The value created by the function.
158    value: T,
159
160    /// A list of [`Warning`]s or caveats issued when creating the value.
161    warnings: SetDeferred<W>,
162}
163
164/// A deferred Caveat is simply a value with associated [`Warning`]s that still need to be associated
165/// with a [`json::Element`].
166///
167/// Providing an `impl Deref` makes sense for given that it's an annotated value.
168///
169/// > The same advice applies to both `deref` traits. In general, `deref` traits
170/// > **should** be implemented if:
171/// >
172/// > 1. a value of the type transparently behaves like a value of the target
173/// >    type;
174/// > 1. the implementation of the `deref` function is cheap; and
175/// > 1. users of the type will not be surprised by any `deref` coercion behavior.
176///
177/// See: <https://doc.rust-lang.org/std/ops/trait.Deref.html#when-to-implement-deref-or-derefmut>.
178impl<T, W> Deref for CaveatDeferred<T, W>
179where
180    W: Warning,
181{
182    type Target = T;
183
184    fn deref(&self) -> &T {
185        &self.value
186    }
187}
188
189impl<T, W> CaveatDeferred<T, W>
190where
191    W: Warning,
192{
193    /// The only way to create `CaveatDeferred<T>` is if `T` impls `IntoCaveatDeferred`.
194    pub(crate) fn new(value: T, warnings: SetDeferred<W>) -> Self {
195        Self { value, warnings }
196    }
197
198    /// Return the value and any [`Warning`]s stored in the `CaveatDeferred`.
199    pub fn into_parts(self) -> (T, SetDeferred<W>) {
200        let Self { value, warnings } = self;
201        (value, warnings)
202    }
203
204    /// Return the value and drop any warnings contained within.
205    pub fn ignore_warnings(self) -> T {
206        self.value
207    }
208}
209
210/// A value that may have associated [`Warning`]s.
211///
212/// Even though the value has been created there may be certain caveats you should be aware of before using it.
213#[derive(Debug)]
214pub struct Caveat<T, W: Warning> {
215    /// The value created by the function.
216    value: T,
217
218    /// A list of [`Warning`]s or caveats issued when creating the value.
219    warnings: Set<W>,
220}
221
222/// A Caveat is simply a value with associated warnings.
223/// Providing an `impl Deref` makes sense for given that it's an annotated value.
224///
225/// > The same advice applies to both `deref` traits. In general, `deref` traits
226/// > **should** be implemented if:
227/// >
228/// > 1. a value of the type transparently behaves like a value of the target
229/// >    type;
230/// > 1. the implementation of the `deref` function is cheap; and
231/// > 1. users of the type will not be surprised by any `deref` coercion behavior.
232///
233/// See: <https://doc.rust-lang.org/std/ops/trait.Deref.html#when-to-implement-deref-or-derefmut>.
234impl<T, W> Deref for Caveat<T, W>
235where
236    W: Warning,
237{
238    type Target = T;
239
240    fn deref(&self) -> &T {
241        &self.value
242    }
243}
244
245impl<T, W> Caveat<T, W>
246where
247    W: Warning,
248{
249    /// The only way to create `Caveat<T>` is if `T` impls `IntoCaveat`.
250    pub(crate) fn new(value: T, warnings: Set<W>) -> Self {
251        Self { value, warnings }
252    }
253
254    /// Return a ref to the warning Set.
255    pub fn warnings(&self) -> &Set<W> {
256        &self.warnings
257    }
258
259    /// Return the value and any [`Warning`]s stored in the `Caveat`.
260    pub fn into_parts(self) -> (T, Set<W>) {
261        let Self { value, warnings } = self;
262        (value, warnings)
263    }
264
265    /// Return the value and drop any warnings contained within.
266    pub fn ignore_warnings(self) -> T {
267        self.value
268    }
269
270    /// Map the value to another target type while retaining the warnings about the source type.
271    pub fn map<U, F: FnOnce(T) -> U>(self, op: F) -> Caveat<U, W> {
272        let Self { value, warnings } = self;
273        Caveat {
274            value: op(value),
275            warnings,
276        }
277    }
278}
279
280/// Convert a `Caveat`-like type into a `T` by gathering up its [`Warning`]s.
281///
282/// Gathering warnings into a parent `warning::Set` move's the responsibility of alerting the
283/// caller to the existence of those warnings to the owner of the set.
284pub trait GatherWarnings<T, W>
285where
286    W: Warning,
287{
288    /// The output type of after all the warnings have been gathered.
289    type Output;
290
291    /// Convert a `Caveat`-like type into a `T` by gathering up its [`Warning`]s.
292    #[must_use = "If you want to ignore the value use `let _ =`"]
293    fn gather_warnings_into<WA>(self, warnings: &mut Set<WA>) -> Self::Output
294    where
295        W: Into<WA>,
296        WA: Warning;
297}
298
299/// Convert a `Caveat<T>` into `T` by gathering up its `Warning`s.
300impl<T, W> GatherWarnings<T, W> for Caveat<T, W>
301where
302    W: Warning,
303{
304    type Output = T;
305
306    /// Convert a `Caveat<T>` into `T` by gathering up its `Warning`s.
307    fn gather_warnings_into<WA>(self, warnings: &mut Set<WA>) -> Self::Output
308    where
309        W: Into<WA>,
310        WA: Warning,
311    {
312        let Self {
313            value,
314            warnings: inner_warnings,
315        } = self;
316
317        let Set(inner_warnings) = inner_warnings;
318        let inner_warnings = inner_warnings
319            .into_iter()
320            .map(|(elem_id, group)| (elem_id, group.into_other()));
321
322        warnings.extend(inner_warnings);
323
324        value
325    }
326}
327
328/// Convert a `Option<Caveat<T>>` into `Option<T>` by gathering up its `Warning`s.
329impl<T, W> GatherWarnings<T, W> for Option<Caveat<T, W>>
330where
331    W: Warning,
332{
333    type Output = Option<T>;
334
335    /// Convert a `Caveat` related to type `T` into a `T` by gathering its [`Warning`]s.
336    fn gather_warnings_into<WA>(self, warnings: &mut Set<WA>) -> Self::Output
337    where
338        W: Into<WA>,
339        WA: Warning,
340    {
341        match self {
342            Some(cv) => Some(cv.gather_warnings_into(warnings)),
343            None => None,
344        }
345    }
346}
347
348/// Convert a `Result<Caveat<T>>` into `Result<T>` by gathering up its `Warning`s.
349impl<T, W, E> GatherWarnings<T, W> for Result<Caveat<T, W>, E>
350where
351    W: Warning,
352    E: std::error::Error,
353{
354    type Output = Result<T, E>;
355
356    /// Convert a `Caveat` related to type `T` into a `T` by gathering its [`Warning`]s.
357    fn gather_warnings_into<WA>(self, warnings: &mut Set<WA>) -> Self::Output
358    where
359        W: Into<WA>,
360        WA: Warning,
361    {
362        match self {
363            Ok(cv) => Ok(cv.gather_warnings_into(warnings)),
364            Err(err) => Err(err),
365        }
366    }
367}
368
369/// Convert a `Result<Caveat<T>>` into `Result<T>` by gathering up its `Warning`s.
370impl<T, W> GatherWarnings<T, W> for Verdict<T, W>
371where
372    W: Warning,
373{
374    type Output = Result<T, ErrorSet<W>>;
375
376    /// Convert a `Verdict` into an `Option` by collecting `Warnings` from the `Ok` and `Err` variants
377    /// and mapping `Ok` to `Some` and `Err` to `None`.
378    fn gather_warnings_into<WA>(self, warnings: &mut Set<WA>) -> Self::Output
379    where
380        W: Into<WA>,
381        WA: Warning,
382    {
383        match self {
384            Ok(cv) => Ok(cv.gather_warnings_into(warnings)),
385            Err(err_set) => Err(err_set),
386        }
387    }
388}
389
390/// Convert a `Result` that contains an `ErrorSet` into a `T` by gathering up its [`Warning`]s.
391///
392/// Gathering warnings into a parent `warning::Set` move's the responsibility of alerting the
393/// caller to the existence of those warnings to the owner of the set.
394#[allow(
395    dead_code,
396    reason = "Kept for the tariff linter being rebuilt on the schema IR; see `docs/lint-catalogue.md`"
397)]
398pub(crate) trait DeescalateError<T, W>
399where
400    W: Warning,
401{
402    /// Convert a `Caveat`-like type into a `T` by gathering up its [`Warning`]s.
403    #[must_use = "If you want to ignore the value use `let _ =`"]
404    fn deescalate_error_into<WA>(self, warnings: &mut Set<WA>) -> Option<T>
405    where
406        W: Into<WA>,
407        WA: Warning;
408}
409
410/// Convert a `Result<Caveat<T>>` into `Option<T>` by deescalating its [`Error`] and gathering up its [`Warning`]s.
411impl<T, W> DeescalateError<T, W> for Verdict<T, W>
412where
413    W: Warning,
414{
415    /// Convert a `Verdict` into an `Option` by collecting `Warnings` from the `Ok` and `Err` variants
416    /// and mapping `Ok` to `Some` and `Err` to `None`.
417    fn deescalate_error_into<WA>(self, warnings: &mut Set<WA>) -> Option<T>
418    where
419        W: Into<WA>,
420        WA: Warning,
421    {
422        match self {
423            Ok(cv) => Some(cv.gather_warnings_into(warnings)),
424            Err(err_set) => {
425                warnings.deescalate_error(err_set.into_other());
426                None
427            }
428        }
429    }
430}
431
432/// Convert a `Result<T>` into `Option<T>` by deescalating its [`Error`] and gathering up its [`Warning`]s.
433impl<T, W> DeescalateError<T, W> for Result<T, ErrorSet<W>>
434where
435    W: Warning,
436{
437    /// Convert a `Verdict` into an `Option` by collecting `Warnings` from the `Ok` and `Err` variants
438    /// and mapping `Ok` to `Some` and `Err` to `None`.
439    fn deescalate_error_into<WA>(self, warnings: &mut Set<WA>) -> Option<T>
440    where
441        W: Into<WA>,
442        WA: Warning,
443    {
444        match self {
445            Ok(cv) => Some(cv),
446            Err(err_set) => {
447                warnings.deescalate_error(err_set.into_other());
448                None
449            }
450        }
451    }
452}
453
454/// Convert a `Vec<Caveat<T>>` into `Vec<T>` by gathering up each elements `Warning`s.
455impl<T, W> GatherWarnings<T, W> for Vec<Caveat<T, W>>
456where
457    W: Warning,
458{
459    type Output = Vec<T>;
460
461    /// Convert a `Caveat` related to type `T` into a `T` by gathering its [`Warning`]s.
462    fn gather_warnings_into<WA>(self, warnings: &mut Set<WA>) -> Self::Output
463    where
464        W: Into<WA>,
465        WA: Warning,
466    {
467        self.into_iter()
468            .map(|cv| cv.gather_warnings_into(warnings))
469            .collect()
470    }
471}
472
473/// Convert a `Caveat`-like type into a `T` by gathering up its [`Warning`]s.
474///
475/// Gathering [`Warning`]s into a parent `warning::SetDeferred` move's the responsibility of alerting the
476/// caller to the existence of those [`Warning`]s to the owner of the set.
477pub(crate) trait GatherDeferredWarnings<T, W>
478where
479    W: Warning,
480{
481    /// The output type of after all the warnings have been gathered.
482    type Output;
483
484    /// Convert a `Caveat`-like type into a `T` by gathering up its [`Warning`]s.
485    #[must_use = "If you want to ignore the value use `let _ =`"]
486    fn gather_deferred_warnings_into<WA>(self, warnings: &mut SetDeferred<WA>) -> Self::Output
487    where
488        W: Into<WA>,
489        WA: Warning;
490}
491
492/// Convert a `CaveatDeferred<T>` into `T` by gathering up its [`Warning`]s.
493impl<T, W> GatherDeferredWarnings<T, W> for CaveatDeferred<T, W>
494where
495    W: Warning,
496{
497    type Output = T;
498
499    /// Convert a `Caveat<T>` into `T` by gathering up its `Warning`s.
500    fn gather_deferred_warnings_into<WA>(self, warnings: &mut SetDeferred<WA>) -> Self::Output
501    where
502        W: Into<WA>,
503        WA: Warning,
504    {
505        let Self {
506            value,
507            warnings: inner_warnings,
508        } = self;
509
510        warnings.extend(inner_warnings);
511
512        value
513    }
514}
515
516/// Convert a `Option<CaveatDeferred<T>>` into `Option<T>` by gathering up its warning `Warning`s.
517impl<T, W> GatherDeferredWarnings<T, W> for Option<CaveatDeferred<T, W>>
518where
519    W: Warning,
520{
521    type Output = Option<T>;
522
523    /// Convert a `Caveat` related to type `T` into a `T` by gathering its [`Warning`]s.
524    fn gather_deferred_warnings_into<WA>(self, warnings: &mut SetDeferred<WA>) -> Self::Output
525    where
526        W: Into<WA>,
527        WA: Warning,
528    {
529        match self {
530            Some(cv) => Some(cv.gather_deferred_warnings_into(warnings)),
531            None => None,
532        }
533    }
534}
535
536/// Convert a `Result<CaveatDeferred<T>>` into `Result<T>` by gathering up its [`Warning`]s.
537impl<T, W, E> GatherDeferredWarnings<T, W> for Result<CaveatDeferred<T, W>, E>
538where
539    W: Warning,
540    E: std::error::Error,
541{
542    type Output = Result<T, E>;
543
544    /// Convert a `Caveat` related to type `T` into a `T` by gathering its [`Warning`]s.
545    fn gather_deferred_warnings_into<WA>(self, warnings: &mut SetDeferred<WA>) -> Self::Output
546    where
547        W: Into<WA>,
548        WA: Warning,
549    {
550        match self {
551            Ok(cv) => Ok(cv.gather_deferred_warnings_into(warnings)),
552            Err(err) => Err(err),
553        }
554    }
555}
556
557/// Convert a `Result<CaveatDeferred<T>>` into `Result<T>` by gathering up its [`Warning`]s.
558impl<T, W> GatherDeferredWarnings<T, W> for VerdictDeferred<T, W>
559where
560    W: Warning,
561{
562    type Output = Result<T, ErrorSetDeferred<W>>;
563
564    /// Convert a `VerdictDeferred` into an `Option` by collecting [`Warning`]s from the `Ok` and `Err` variants
565    /// and mapping `Ok` to `Some` and `Err` to `None`.
566    fn gather_deferred_warnings_into<WA>(self, warnings: &mut SetDeferred<WA>) -> Self::Output
567    where
568        W: Into<WA>,
569        WA: Warning,
570    {
571        match self {
572            Ok(cv) => Ok(cv.gather_deferred_warnings_into(warnings)),
573            Err(err_set) => Err(err_set),
574        }
575    }
576}
577
578/// Convert a `Vec<CaveatDeferred<T>>` into `Vec<T>` by gathering up each elements [`Warning`]s.
579impl<T, W> GatherDeferredWarnings<T, W> for Vec<CaveatDeferred<T, W>>
580where
581    W: Warning,
582{
583    type Output = Vec<T>;
584
585    /// Convert a `CaveatDeferred` related to type `T` into a `T` by gathering its [`Warning`]s.
586    fn gather_deferred_warnings_into<WA>(self, warnings: &mut SetDeferred<WA>) -> Self::Output
587    where
588        W: Into<WA>,
589        WA: Warning,
590    {
591        self.into_iter()
592            .map(|cv| cv.gather_deferred_warnings_into(warnings))
593            .collect()
594    }
595}
596
597/// Converts a value `T` into a `Caveat`.
598///
599/// Each module can use this to whitelist their types for conversion to `Caveat<T>`.
600pub trait IntoCaveat: Sized {
601    /// Any type can be converted to `Caveat<T>` by supplying a list of [`Warning`]s.
602    fn into_caveat<W: Warning>(self, warnings: Set<W>) -> Caveat<Self, W>;
603
604    /// If a `FromSchema` is infallible a `Caveat` can be created using this method.
605    fn into_infallible_caveat(self) -> Caveat<Self, Infallible> {
606        self.into_caveat(Set::new())
607    }
608}
609
610/// Converts a value `T` into a `CaveatDeferred`.
611///
612/// Each module can use this to whitelist their types for conversion to `CaveatDeferred<T>`.
613pub(crate) trait IntoCaveatDeferred: Sized {
614    /// Any type can be converted to `CaveatDeferred<T>` by supplying a list of [`Warning`]s.
615    fn into_caveat_deferred<W: Warning>(self, warnings: SetDeferred<W>) -> CaveatDeferred<Self, W>;
616}
617
618/// Allow all types to be converted into `Caveat<T>`.
619impl<T> IntoCaveat for T {
620    fn into_caveat<W: Warning>(self, warnings: Set<W>) -> Caveat<Self, W> {
621        Caveat::new(self, warnings)
622    }
623}
624
625/// Allow `Vec<T: IntoCaveat>` to be converted into a `CaveatDeferred`.
626impl<T> IntoCaveatDeferred for T {
627    fn into_caveat_deferred<W: Warning>(self, warnings: SetDeferred<W>) -> CaveatDeferred<Self, W> {
628        CaveatDeferred::new(self, warnings)
629    }
630}
631
632/// `Verdict` specific extension methods for the `Result` type.
633pub trait VerdictExt<T, W: Warning> {
634    /// Maps a `Verdict<T, E>` to `Verdict<U, E>` by applying a function to a
635    /// contained [`Ok`] value, leaving an [`Err`] value untouched.
636    fn map_caveat<F, U>(self, op: F) -> Verdict<U, W>
637    where
638        F: FnOnce(T) -> U;
639
640    /// Discard all warnings in the `Err` variant and keep only the warning that caused the error.
641    fn only_error(self) -> Result<Caveat<T, W>, Error<W>>;
642}
643
644/// Used to log the contents of various `Verdict` impls.
645#[expect(dead_code, reason = "for debugging")]
646pub(crate) trait VerdictTrace<T, W: Warning> {
647    /// Log the contents as `info` level.
648    fn info_verdict(self, msg: &'static str) -> Self;
649
650    /// Log the contents as `debug` level.
651    fn debug_verdict(self, msg: &'static str) -> Self;
652}
653
654/// Used to log the contents of various `Result` impls.
655#[expect(dead_code, reason = "for debugging")]
656pub(crate) trait ResultTrace<T, W: Warning> {
657    /// Log the contents as `info` level.
658    fn info_result(self, msg: &'static str) -> Self;
659
660    /// Log the contents as `debug` level.
661    fn debug_result(self, msg: &'static str) -> Self;
662}
663
664impl<T, W: Warning> VerdictExt<T, W> for Verdict<T, W> {
665    fn map_caveat<F, U>(self, op: F) -> Verdict<U, W>
666    where
667        F: FnOnce(T) -> U,
668    {
669        match self {
670            Ok(c) => Ok(c.map(op)),
671            Err(w) => Err(w),
672        }
673    }
674
675    fn only_error(self) -> Result<Caveat<T, W>, Error<W>> {
676        match self {
677            Ok(c) => Ok(c),
678            Err(err_set) => {
679                let ErrorSet { error, warnings: _ } = err_set;
680                Err(*error)
681            }
682        }
683    }
684}
685
686/// Consume a [`Verdict`] whose warning type is uninhabited.
687///
688/// A `FromSchema` impl that can never emit a warning uses [`Infallible`] as its warning
689/// (typically the enum-to-enum lowerings). Such a verdict can neither error nor carry
690/// warnings, so a fallible caller can extract the value directly without threading an
691/// `Infallible`-to-`W` conversion through `?`/`gather_warnings_into`.
692pub(crate) trait IntoInfallible<T> {
693    /// The built value; there is no error path and no warning to gather.
694    fn into_infallible(self) -> T;
695}
696
697impl<T> IntoInfallible<T> for Verdict<T, Infallible> {
698    fn into_infallible(self) -> T {
699        match self {
700            Ok(caveat) => caveat.into_parts().0,
701            // `ErrorSet<Infallible>` cannot be constructed (its warning is uninhabited), so
702            // this arm is unreachable. The `Box` around the error hides that uninhabitedness
703            // from an empty `match`, so state it explicitly.
704            Err(_) => unreachable!("a `Verdict` with an `Infallible` warning cannot be an error"),
705        }
706    }
707}
708
709impl<T, W: Warning> VerdictTrace<T, W> for Verdict<T, W>
710where
711    T: fmt::Debug,
712{
713    fn info_verdict(self, msg: &'static str) -> Self {
714        match self {
715            Ok(c) => {
716                info!("{msg}: {c:#?}");
717                Ok(c)
718            }
719            Err(err_set) => {
720                info!("{msg}: {err_set:#?}");
721                Err(err_set)
722            }
723        }
724    }
725
726    fn debug_verdict(self, msg: &'static str) -> Self {
727        match self {
728            Ok(c) => {
729                debug!("{msg}: {c:#?}");
730                Ok(c)
731            }
732            Err(err_set) => {
733                debug!("{msg}: {err_set:#?}");
734                Err(err_set)
735            }
736        }
737    }
738}
739
740impl<T, W: Warning> ResultTrace<T, W> for Result<T, ErrorSet<W>>
741where
742    T: fmt::Debug,
743{
744    fn info_result(self, msg: &'static str) -> Self {
745        match self {
746            Ok(c) => {
747                info!("{msg}: {c:#?}");
748                Ok(c)
749            }
750            Err(err_set) => {
751                info!("{msg}: {err_set:#?}");
752                Err(err_set)
753            }
754        }
755    }
756
757    fn debug_result(self, msg: &'static str) -> Self {
758        match self {
759            Ok(c) => {
760                debug!("{msg}: {c:#?}");
761                Ok(c)
762            }
763            Err(err_set) => {
764                debug!("{msg}: {err_set:#?}");
765                Err(err_set)
766            }
767        }
768    }
769}
770
771/// The warning that caused an operation to fail.
772///
773/// The [`Warning`] is referred to by the [`json::Element`]s path as a `String`.
774#[derive(Debug)]
775pub struct Error<W: Warning> {
776    /// The `Warning` of warning.
777    warning: W,
778
779    /// The path of the element that caused the [`Warning`].
780    element: Element,
781}
782
783impl<W: Warning> Error<W> {
784    /// Return reference to the `Warning`.
785    pub fn warning(&self) -> &W {
786        &self.warning
787    }
788
789    /// Consume the `Error` and return the `Warning`.
790    pub fn into_warning(self) -> W {
791        self.warning
792    }
793
794    /// Return a reference to the [`Element`] that caused the [`Warning`].
795    pub fn element(&self) -> &Element {
796        &self.element
797    }
798
799    /// Return the constituent parts.
800    pub fn parts(&self) -> (&W, &Element) {
801        (&self.warning, &self.element)
802    }
803
804    /// Consume the `Cause` and return the constituent parts.
805    pub fn into_parts(self) -> (W, Element) {
806        let Self { warning, element } = self;
807        (warning, element)
808    }
809
810    /// Converts `Error<W>` into `Error<WA>` using the `impl Into<WA> for W`.
811    ///
812    /// This is used by the [`from_warning_all`] macro.
813    fn into_other<WA>(self) -> Error<WA>
814    where
815        W: Into<WA>,
816        WA: Warning,
817    {
818        let Self { warning, element } = self;
819        Error {
820            warning: warning.into(),
821            element,
822        }
823    }
824}
825
826impl<W: Warning> std::error::Error for Error<W> {}
827
828impl<W: Warning> fmt::Display for Error<W> {
829    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
830        write!(
831            f,
832            "A warning for element at `{}` was upgraded to an `error`: {}",
833            self.element.path, self.warning
834        )
835    }
836}
837
838/// Associate a [`json::Element`] with a set of [`Warning`]s contained by a [`VerdictDeferred`].
839pub trait WithElement<T, W: Warning> {
840    type Output;
841
842    fn with_element(self, element: &json::Element<'_>) -> Self::Output;
843}
844
845impl<T, W: Warning> WithElement<T, W> for CaveatDeferred<T, W> {
846    type Output = Caveat<T, W>;
847
848    /// Associate a [`json::Element`] with a set of [`Warning`]s.
849    fn with_element(self, element: &json::Element<'_>) -> Self::Output {
850        let CaveatDeferred { value, warnings } = self;
851        let SetDeferred(warnings) = warnings;
852        let warnings = if warnings.is_empty() {
853            BTreeMap::new()
854        } else {
855            let warnings = Group {
856                element: Element::from_json(element),
857                warnings,
858            };
859            BTreeMap::from([(element.id(), warnings)])
860        };
861
862        Caveat {
863            value,
864            warnings: Set(warnings),
865        }
866    }
867}
868
869impl<T, W: Warning> WithElement<T, W> for VerdictDeferred<T, W> {
870    type Output = Verdict<T, W>;
871
872    /// Associate a [`json::Element`] with a set of [`Warning`]s.
873    fn with_element(self, element: &json::Element<'_>) -> Self::Output {
874        match self {
875            Ok(v) => Ok(v.with_element(element)),
876            Err(set) => {
877                let ErrorSetDeferred { error, warnings } = set;
878                // An `ErrorSetDeferred` should have at least one warning in it.
879                let warnings = Group {
880                    element: Element::from_json(element),
881                    warnings,
882                };
883                let warnings = BTreeMap::from([(element.id(), warnings)]);
884                Err(ErrorSet {
885                    error: Box::new(Error {
886                        warning: error,
887                        element: Element::from_json(element),
888                    }),
889                    warnings,
890                })
891            }
892        }
893    }
894}
895
896/// A representation of a JSON element that satisfies the needs of most consumers of a [`Warning`].
897///
898/// This representation avoids the complexity of needing to provide a `'buf` lifetime to the [`json::Element`].
899/// This would complicate all warnings types with that lifetime.
900///
901/// A consumer of warnings wants to group them by [`json::ElemId`] using `Warning::group_by_elem` and then
902/// display or report the warnings by path.
903///
904/// The linter report also wants to highlight the source JSON that a warning refers too.
905#[derive(Clone, Debug, PartialEq, Eq)]
906pub struct Element {
907    /// The Id of the element that caused the [`Warning`].
908    ///
909    /// This is used for sorting warnings and can be used to retrieve the [`json::Element`] object.
910    pub id: json::ElemId,
911
912    /// The `Span` that delimits the [`json::Element`].
913    pub span: json::Span,
914
915    /// The elements path.
916    ///
917    /// Most consumers of warnings just want this data.
918    pub path: json::Path,
919
920    /// The location (line, column) of the beginning of this element in the JSON file.
921    pub location: json::Location,
922}
923
924impl Element {
925    /// Create an owned `Element` from a `json::Element<'buf'>`.
926    pub(crate) fn from_json(element: &json::Element<'_>) -> Element {
927        Self {
928            id: element.id(),
929            span: element.span(),
930            path: element.path(),
931            location: element.location(),
932        }
933    }
934}
935
936/// A Display object for writing a set of warnings.
937///
938/// The warnings set is formatted as a tree with element paths on the first level
939/// and a list of warning ids on the second.
940///
941/// ```shell
942/// $.path.to.json[0].field:
943///   - list_of_warning_ids
944///   - next_warning_id
945///
946/// $.next.path.to[1].json.field
947///   - list_of_warning_ids
948/// ```
949pub struct SetWriter<'caller, W: Warning> {
950    /// The list of warnings for the [`json::Element`].
951    warnings: &'caller Set<W>,
952
953    /// The indent to prefix to each warning id.
954    indent: &'caller str,
955}
956
957impl<'caller, W: Warning> SetWriter<'caller, W> {
958    /// Create a new `SetWriter` with a default warning id indent of `"  - "`.
959    pub fn new(warnings: &'caller Set<W>) -> Self {
960        Self {
961            warnings,
962            indent: "  - ",
963        }
964    }
965
966    /// Create a new `SetWriter` with a custom warning id indent.
967    pub fn with_indent(warnings: &'caller Set<W>, indent: &'caller str) -> Self {
968        Self { warnings, indent }
969    }
970}
971
972impl<W: Warning> fmt::Debug for SetWriter<'_, W> {
973    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
974        fmt::Display::fmt(self, f)
975    }
976}
977
978impl<W: Warning> fmt::Display for SetWriter<'_, W> {
979    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
980        let mut iter = self.warnings.iter();
981
982        {
983            // Write the first group without an empty line prefix.
984            let Some((element, warnings)) = iter.next().map(|g| g.to_parts()) else {
985                return Ok(());
986            };
987
988            writeln!(f, "{}", element.path)?;
989
990            for warning in warnings {
991                write!(f, "{}{}", self.indent, warning)?;
992            }
993        }
994
995        // Write the rest of the Groups with am empty line padding.
996        for (element, warnings) in iter.map(|g| g.to_parts()) {
997            writeln!(f, "\n{}", element.path)?;
998
999            for warning in warnings {
1000                write!(f, "{}{}", self.indent, warning)?;
1001            }
1002        }
1003
1004        Ok(())
1005    }
1006}
1007
1008/// Each mod defines warnings for the type that it's trying to parse or lint from a [`json::Element`].
1009///
1010/// The `Warning` in the mod should impl this trait to take part in the [`Warning`] system.
1011pub trait Warning: Sized + fmt::Debug + fmt::Display + Send + Sync {
1012    /// Return the human readable identifier for the [`Warning`].
1013    ///
1014    /// This is used in the `auto_test` assertion system.
1015    /// Changing these strings may require updating `output_price__cdr.json` files.
1016    fn id(&self) -> Id;
1017
1018    /// Whether this is the [`Rejected`] marker a feature emits when it rejects the schema IR
1019    /// for the object it is building (see [`Set::ok_or_bail`]).
1020    ///
1021    /// The precise structural cause (a missing or wrong-typed field) was already reported by
1022    /// the schema walk, so a consumer can drop these follow-on markers via
1023    /// [`Set::remove_rejected`]. Other Warnings return `false`.
1024    fn is_rejected(&self) -> bool {
1025        false
1026    }
1027}
1028
1029/// A content-free marker seeded into any [`Warning`] (via `From<Rejected>`) when a feature
1030/// rejects the schema IR for the object it is building, because a required field is
1031/// [`Integrity::Missing`]/[`Integrity::Err`].
1032///
1033/// A feature accepts a `Versioned` object and rejects it based on the warnings the schema walk
1034/// already reported, so this marker carries no detail of its own. Unlike the earlier iteration
1035/// it is not unanchored: [`Set::ok_or_bail`] anchors it to the field's [`Element`] taken from
1036/// the `Integrity` variant. It is easily filtered via [`Warning::is_rejected`].
1037#[derive(Clone, Copy, Debug)]
1038pub struct Rejected;
1039
1040/// A `FromSchema` implementation that can never emit a warning uses [`std::convert::Infallible`]
1041/// as its `Warning` type. The type is uninhabited, so every method is unreachable.
1042impl Warning for Infallible {
1043    fn id(&self) -> Id {
1044        match *self {}
1045    }
1046}
1047
1048/// A transparent container that stores the source line where the `Warning` occurred in a test build.
1049#[derive(Debug)]
1050struct Source<W: Warning> {
1051    #[cfg(test)]
1052    /// The line in the source code where this `Warning` occurred.
1053    location: &'static std::panic::Location<'static>,
1054
1055    /// The warning.
1056    warning: W,
1057}
1058
1059impl<W: Warning> Source<W> {
1060    #[track_caller]
1061    /// Create a new `Source` object.
1062    fn new(warning: W) -> Self {
1063        #[cfg(test)]
1064        {
1065            Self {
1066                location: std::panic::Location::caller(),
1067                warning,
1068            }
1069        }
1070
1071        #[expect(
1072            clippy::cfg_not_test,
1073            reason = "This is code that is designed for use in tests"
1074        )]
1075        #[cfg(not(test))]
1076        {
1077            Self { warning }
1078        }
1079    }
1080
1081    /// Discard the debug info and return the inner `Warning`.
1082    fn into_warning(self) -> W {
1083        self.warning
1084    }
1085
1086    /// Convert the inner `Warning` into another type of `Warning`.
1087    fn into_other<WA>(self) -> Source<WA>
1088    where
1089        W: Into<WA>,
1090        WA: Warning,
1091    {
1092        self.map(Into::into)
1093    }
1094
1095    /// Convert the inner `Warning` into another type of `Warning`.
1096    fn map<F, WA>(self, mut f: F) -> Source<WA>
1097    where
1098        F: FnMut(W) -> WA,
1099        WA: Warning,
1100    {
1101        #[cfg(test)]
1102        {
1103            let Self {
1104                location: source,
1105                warning,
1106            } = self;
1107            Source {
1108                location: source,
1109                warning: f(warning),
1110            }
1111        }
1112
1113        #[expect(
1114            clippy::cfg_not_test,
1115            reason = "This is code that is designed for use in tests"
1116        )]
1117        #[cfg(not(test))]
1118        {
1119            let Self { warning } = self;
1120            Source {
1121                warning: f(warning),
1122            }
1123        }
1124    }
1125}
1126
1127impl<W: Warning> Deref for Source<W> {
1128    type Target = W;
1129
1130    fn deref(&self) -> &Self::Target {
1131        &self.warning
1132    }
1133}
1134
1135/// A set of [`Warning`]s transported through the system using a `VerdictDeferred` or `CaveatDeferred`.
1136///
1137///
1138/// This set is considered deferred as the [`Warning`]s need to be associated with a [`json::Element`]
1139/// to become [`Warning`]s.
1140///
1141/// NOTE: The deferred types are used to avoid passing [`json::Element`] references
1142/// to functions just to create [`Warning`]s.
1143#[derive(Debug)]
1144pub struct SetDeferred<W: Warning>(Vec<Source<W>>);
1145
1146impl<W: Warning> SetDeferred<W> {
1147    /// Create a new set of [`Warning`]s.
1148    pub(crate) fn new() -> Self {
1149        Self(Vec::new())
1150    }
1151
1152    /// Create and add a [`Warning`] to the set while consuming the set into a [`VerdictDeferred`].
1153    ///
1154    /// This is designed for use as the last [`Warning`] of a function. The function should exit with the `Err` returned.
1155    #[track_caller]
1156    pub(crate) fn bail<T>(self, warning: W) -> VerdictDeferred<T, W> {
1157        let Self(warnings) = self;
1158        Err(ErrorSetDeferred {
1159            error: warning,
1160            warnings,
1161        })
1162    }
1163
1164    /// Add a single warning to the set.
1165    #[track_caller]
1166    pub(crate) fn insert(&mut self, warning: W) {
1167        self.0.push(Source::new(warning));
1168    }
1169
1170    /// Extend this set with the warnings of another.
1171    ///
1172    /// The other set's warnings will be converted if necessary.
1173    fn extend<WA>(&mut self, warnings: SetDeferred<WA>)
1174    where
1175        WA: Into<W> + Warning,
1176    {
1177        let SetDeferred(warnings) = warnings;
1178        self.0.extend(warnings.into_iter().map(Source::into_other));
1179    }
1180}
1181
1182/// A set of [`Warning`]s and a [`Warning`] that caused an operation to fail to be represented as an [`Error`].
1183///
1184/// This set is transported through the system using a [`VerdictDeferred`]s `Err` variant.
1185///
1186/// This set is considered deferred as the [`Warning`]s need to be associated with a [`json::Element`]
1187/// to become [`Warning`]s.
1188///
1189/// NOTE: The deferred types are used to avoid passing [`json::Element`] references
1190/// to functions just to create [`Warning`]s.
1191#[derive(Debug)]
1192pub struct ErrorSetDeferred<W: Warning> {
1193    /// The `Warning` that caused a function to halt.
1194    error: W,
1195
1196    /// The `Warning`s collected up to the halting point.
1197    warnings: Vec<Source<W>>,
1198}
1199
1200impl<W: Warning> ErrorSetDeferred<W> {
1201    /// Create a new set of [`Warning`]s.
1202    pub(crate) fn with_warn(warning: W) -> Self {
1203        Self {
1204            warnings: Vec::new(),
1205            error: warning,
1206        }
1207    }
1208
1209    /// Converts `ErrorSetDeferred<W>` into `ErrorSetDeferred<WA>` using the `impl Into<WA> for W`.
1210    ///
1211    /// This is used by the [`from_warning_all`] macro.
1212    pub(crate) fn into_other<WA>(self) -> ErrorSetDeferred<WA>
1213    where
1214        W: Into<WA>,
1215        WA: Warning,
1216    {
1217        let Self { error, warnings } = self;
1218        let warnings = warnings.into_iter().map(Source::into_other).collect();
1219        ErrorSetDeferred {
1220            error: error.into(),
1221            warnings,
1222        }
1223    }
1224}
1225
1226/// A set of [`Warning`]s transported through the system using a [`Caveat`].
1227#[derive(Debug, Default)]
1228pub struct Set<W: Warning>(BTreeMap<json::ElemId, Group<W>>);
1229
1230impl<W: Warning> Set<W> {
1231    /// Create a new set of [`Warning`]s.
1232    pub fn new() -> Self {
1233        Self(BTreeMap::new())
1234    }
1235
1236    /// Unpack the inner map.
1237    #[allow(
1238        dead_code,
1239        reason = "Kept for the tariff linter being rebuilt on the schema IR; see `docs/lint-catalogue.md`"
1240    )]
1241    pub(crate) fn into_inner(self) -> BTreeMap<json::ElemId, Group<W>> {
1242        self.0
1243    }
1244
1245    /// Insert a [`Warning`] defined in a domain module and it's associated [`json::Element`].
1246    #[track_caller]
1247    pub fn insert(&mut self, element: &json::Element<'_>, warning: W) {
1248        self.insert_warning(element.id(), warning, || Element::from_json(element));
1249    }
1250
1251    /// Insert [`Warning`] defined in a domain module and it's associated [`Element`].
1252    ///
1253    /// Note: The [`Element`] is created lazily.
1254    #[track_caller]
1255    fn insert_warning<F>(&mut self, elem_id: json::ElemId, warning: W, f: F)
1256    where
1257        F: FnOnce() -> Element,
1258    {
1259        use std::collections::btree_map::Entry;
1260
1261        match self.0.entry(elem_id) {
1262            Entry::Vacant(entry) => {
1263                let element = f();
1264                entry.insert_entry(Group {
1265                    element,
1266                    warnings: vec![Source::new(warning)],
1267                });
1268            }
1269            Entry::Occupied(mut entry) => {
1270                entry.get_mut().warnings.push(Source::new(warning));
1271            }
1272        }
1273    }
1274
1275    /// Consume the set and insert a [`Warning`] while returning a [`Verdict`].
1276    ///
1277    /// This is designed for use as the last [`Warning`] of a function. The function should exit with the `Err` returned.
1278    #[track_caller]
1279    pub fn bail<T>(self, element: &json::Element<'_>, warning: W) -> Verdict<T, W> {
1280        let Self(warnings) = self;
1281
1282        Err(ErrorSet {
1283            error: Box::new(Error {
1284                warning,
1285                element: Element::from_json(element),
1286            }),
1287            warnings,
1288        })
1289    }
1290
1291    /// Bail with `warning` located at an [`Element`] taken from the schema IR.
1292    ///
1293    /// [`bail`](Self::bail) needs a [`json::Element`], which an absent or unbuildable IR
1294    /// field does not have; [`Integrity::Missing`]/[`Integrity::Err`] carry this flat
1295    /// [`Element`] instead. Use this where the feature has its own reason to report and
1296    /// [`ok_or_bail`](Self::ok_or_bail)'s content-free [`Rejected`] would lose it.
1297    #[track_caller]
1298    pub(crate) fn bail_at<T>(self, element: Element, warning: W) -> Verdict<T, W> {
1299        let Self(warnings) = self;
1300
1301        Err(ErrorSet {
1302            error: Box::new(Error { warning, element }),
1303            warnings,
1304        })
1305    }
1306
1307    /// Borrow the built value of a required schema-IR field, or reject the object.
1308    ///
1309    /// Used by `FromSchema` lowering: an [`Integrity::Ok`] field yields a borrow of its
1310    /// value; a [`Integrity::Missing`]/[`Integrity::Err`] field cannot be built, so the set
1311    /// bails with the [`Rejected`] marker anchored to the field's element.
1312    ///
1313    /// The precise cause was already reported by the schema walk, so no
1314    /// new detail is added here - the marker only signals "this feature rejected the IR for
1315    /// this object" and is easily filtered (see [`Warning::is_rejected`]).
1316    #[track_caller]
1317    pub(crate) fn ok_or_bail<'a, T>(
1318        &mut self,
1319        integrity: &'a Integrity<T>,
1320    ) -> Result<&'a T, ErrorSet<W>>
1321    where
1322        W: From<Rejected>,
1323    {
1324        let element = match integrity {
1325            Integrity::Ok(value) => return Ok(value),
1326            Integrity::Missing(element) | Integrity::Err(element) => element.clone(),
1327        };
1328
1329        let warnings = std::mem::take(&mut self.0);
1330
1331        Err(ErrorSet {
1332            error: Box::new(Error {
1333                warning: Rejected.into(),
1334                element,
1335            }),
1336            warnings,
1337        })
1338    }
1339
1340    /// Remove every [`Rejected`] marker from the set (see [`Warning::is_rejected`]).
1341    ///
1342    /// The schema walk already reported the located structural cause, so a consumer that has
1343    /// those warnings can drop these follow-on markers.
1344    pub fn remove_rejected(&mut self) {
1345        self.retain(|warning| !warning.is_rejected());
1346    }
1347
1348    /// Converts `Set<W>` into `Set<WA>` using the `impl Into<WA> for W`.
1349    ///
1350    /// This is used by the [`from_warning_all`] macro.
1351    #[allow(
1352        dead_code,
1353        reason = "Kept for the tariff linter being rebuilt on the schema IR; see `docs/lint-catalogue.md`"
1354    )]
1355    pub(crate) fn into_other<WA>(self) -> Set<WA>
1356    where
1357        W: Into<WA>,
1358        WA: Warning,
1359    {
1360        let Set(warnings) = self;
1361        let warnings = warnings
1362            .into_iter()
1363            .map(|(elem_id, group)| (elem_id, group.into_other()))
1364            .collect();
1365        Set(warnings)
1366    }
1367
1368    /// Retain only the [`Warning`]s for which `keep` returns `true`.
1369    ///
1370    /// Any [`Group`] left without warnings is removed from the set.
1371    pub fn retain<F>(&mut self, mut keep: F)
1372    where
1373        F: FnMut(&W) -> bool,
1374    {
1375        self.0.retain(|_elem_id, group| {
1376            group.warnings.retain(|source| keep(&source.warning));
1377            !group.warnings.is_empty()
1378        });
1379    }
1380
1381    /// Return true if the [`Warning`] set is empty.
1382    pub fn is_empty(&self) -> bool {
1383        self.0.is_empty()
1384    }
1385
1386    /// Return the amount of [`Element`]s in this set.
1387    ///
1388    /// Each [`Element`] can have many [`Warning`]s associated with it.
1389    pub fn len_elements(&self) -> usize {
1390        self.0.len()
1391    }
1392
1393    /// Return the total amount of [`Warning`]s in this set for all [`Element`]s.
1394    pub fn len_warnings(&self) -> usize {
1395        self.0
1396            .values()
1397            .fold(0, |acc, group| acc.saturating_add(group.warnings.len()))
1398    }
1399
1400    /// Return an iterator of [`Warning`]s grouped by [`json::Element`].
1401    pub fn iter(&self) -> Iter<'_, W> {
1402        Iter {
1403            warnings: self.0.iter(),
1404        }
1405    }
1406
1407    /// Return a collection of `Id`s mapped to the paths they occurred at.
1408    pub fn id_path_map(&self, config: Limit) -> IdPathMap<'_> {
1409        let report = match config {
1410            Limit::None => limit_none(&self.0),
1411            Limit::WarningTypes(max_warning_types) => {
1412                limit_warning_types(max_warning_types, &self.0)
1413            }
1414            Limit::ElemPathsPerId(max_elem_paths_per_warning_id) => {
1415                limit_elem_paths_per_id(max_elem_paths_per_warning_id, &self.0)
1416            }
1417            Limit::All {
1418                max_warning_types,
1419                max_elem_paths_per_warning_id,
1420            } => limit_all(max_warning_types, max_elem_paths_per_warning_id, &self.0),
1421        };
1422
1423        let LimitReport {
1424            elements_filtered,
1425            warning_distinct_types_filtered,
1426            warnings,
1427        } = report;
1428
1429        IdPathMap {
1430            total_warnings: self.len_warnings(),
1431            total_elements: self.len_elements(),
1432            elements_filtered,
1433            warning_distinct_types_filtered,
1434            warnings,
1435        }
1436    }
1437
1438    /// Return a collection of warning messages mapped to the paths they occurred at.
1439    pub fn msg_path_map(&self, config: Limit) -> MsgPathMap<'_> {
1440        let IdPathMap {
1441            total_warnings,
1442            total_elements,
1443            elements_filtered,
1444            warning_distinct_types_filtered,
1445            warnings,
1446        } = self.id_path_map(config);
1447
1448        let warnings = warnings
1449            .into_iter()
1450            .map(|(id, paths)| (id.to_string(), paths))
1451            .collect();
1452
1453        MsgPathMap {
1454            total_warnings,
1455            total_elements,
1456            elements_filtered,
1457            warning_distinct_types_filtered,
1458            warnings,
1459        }
1460    }
1461
1462    /// Return a map of [`json::Element`] paths to a list of [`Warning`].
1463    ///
1464    /// This is designed to be used to print out maps of warnings associated with elements.
1465    /// You can use the debug alternate format `{:#?}` to print the map 'pretty' over multiple lines
1466    /// with indentation.
1467    pub fn path_map(&self) -> BTreeMap<&str, Vec<&W>> {
1468        self.0
1469            .values()
1470            .map(|Group { element, warnings }| {
1471                let path = element.path.as_str();
1472                let warnings = warnings.iter().map(|w| &**w).collect();
1473                (path, warnings)
1474            })
1475            .collect()
1476    }
1477
1478    /// Consume the `Set` and return a map of [`json::Element`] paths to a list of [`Warning`]s.
1479    ///
1480    /// This is designed to be used to print out maps of warnings associated with elements.
1481    pub fn into_path_map(self) -> BTreeMap<json::Path, Vec<W>> {
1482        self.0
1483            .into_values()
1484            .map(|Group { element, warnings }| {
1485                let warnings = warnings.into_iter().map(Source::into_warning).collect();
1486                (element.path, warnings)
1487            })
1488            .collect()
1489    }
1490
1491    /// Return a map of [`json::Element`] paths to a list of [`Warning`] ids as Strings.
1492    ///
1493    /// This is designed to be used to print out maps of warnings associated with elements.
1494    /// You can use the debug alternate format `{:#?}` to print the map 'pretty' over multiple lines
1495    /// with indentation.
1496    ///
1497    /// Note: This representation is also valid JSON and can be copied directly to
1498    /// a test expectation file.
1499    pub fn path_id_map(&self) -> BTreeMap<&str, Vec<Id>> {
1500        self.0
1501            .values()
1502            .map(|group| {
1503                let warnings = group.warnings.iter().map(|w| w.id()).collect();
1504                (group.element.path.as_str(), warnings)
1505            })
1506            .collect()
1507    }
1508
1509    /// Return a map of [`json::Element`] paths to a list of [`Warning`] messages as Strings.
1510    ///
1511    /// This is designed to be used to print out maps of warnings associated with elements.
1512    /// You can use the debug alternate format `{:#?}` to print the map 'pretty' over multiple lines
1513    /// with indentation.
1514    pub fn path_msg_map(&self) -> BTreeMap<&str, Vec<String>> {
1515        self.0
1516            .values()
1517            .map(|group| {
1518                let warnings = group.warnings.iter().map(|w| w.to_string()).collect();
1519                (group.element.path.as_str(), warnings)
1520            })
1521            .collect()
1522    }
1523
1524    /// Deescalate an [`Error`] by subsuming it back into a `Set`.
1525    pub(crate) fn deescalate_error(&mut self, err_set: ErrorSet<W>) {
1526        let ErrorSet { error, warnings } = err_set;
1527        let Error { warning, element } = *error;
1528        self.0.extend(warnings);
1529        self.insert_warning(element.id, warning, || element);
1530    }
1531
1532    /// Extend this set with the warnings of another.
1533    ///
1534    /// The other set's warnings will be converted if necessary.
1535    pub(crate) fn extend(&mut self, warnings: impl Iterator<Item = (json::ElemId, Group<W>)>) {
1536        use std::collections::btree_map::Entry;
1537
1538        for (elem_id, group) in warnings {
1539            match self.0.entry(elem_id) {
1540                Entry::Vacant(entry) => {
1541                    entry.insert_entry(group);
1542                }
1543                Entry::Occupied(mut entry) => {
1544                    let Group {
1545                        element: _,
1546                        warnings,
1547                    } = group;
1548                    entry.get_mut().warnings.extend(warnings);
1549                }
1550            }
1551        }
1552    }
1553}
1554
1555/// The outcome of calling the `limit_*` functions related to the [`Set::id_path_map`] function.
1556#[derive(Debug)]
1557struct LimitReport<'set> {
1558    /// The amount of [`json::Element`] paths filtered due to a [`Limit`] being set.
1559    pub elements_filtered: usize,
1560
1561    /// The amount of [`Warning`] [`Id`]s filtered due to a [`Limit`] being set.
1562    ///
1563    /// Note: This is not a count of how many warnings were filtered. It's a count of how many
1564    /// types of warnings were filtered. If seven `excessive_precision` warnings are filtered,
1565    /// this counts as one type, as all the IDs that were filtered are the same.
1566    pub warning_distinct_types_filtered: usize,
1567
1568    /// The map of all [`Warning`] [`Id`]s mapped to the [`json::Element`] paths where they occurred.
1569    pub warnings: BTreeMap<Id, Vec<&'set str>>,
1570}
1571
1572/// The logic for the [`Limit::None`] variant.
1573fn limit_none<W: Warning>(warnings: &BTreeMap<json::ElemId, Group<W>>) -> LimitReport<'_> {
1574    let mut out = BTreeMap::new();
1575
1576    for group in warnings.values() {
1577        let Group { element, warnings } = group;
1578        let path = element.path.as_str();
1579
1580        for w in warnings {
1581            match out.entry(w.id()) {
1582                btree_map::Entry::Vacant(entry) => {
1583                    entry.insert(vec![path]);
1584                }
1585                btree_map::Entry::Occupied(mut entry) => {
1586                    entry.get_mut().push(path);
1587                }
1588            }
1589        }
1590    }
1591
1592    LimitReport {
1593        warnings: out,
1594        elements_filtered: 0,
1595        warning_distinct_types_filtered: 0,
1596    }
1597}
1598
1599/// The logic for the [`Limit::WarningTypes`] variant.
1600fn limit_warning_types<W: Warning>(
1601    max_warning_types: usize,
1602    warnings: &BTreeMap<json::ElemId, Group<W>>,
1603) -> LimitReport<'_> {
1604    let mut out = BTreeMap::new();
1605    // A set of element paths encountered and filtered. Element paths can be encountered more
1606    // than once, so a simple `usize` can't be used to count the encounters.
1607    let mut elements_filtered = HashSet::new();
1608    // A set of warnings encountered and filtered. Warnings can be encountered more than once,
1609    // so a simple `usize` can't be used to count the encounters.
1610    let mut warning_distinct_types_filtered = HashSet::new();
1611
1612    for group in warnings.values() {
1613        let Group { element, warnings } = group;
1614        let path = element.path.as_str();
1615        // True if any of the warnings are filtered,
1616        // therefore this element should be considered filtered too.
1617        let mut filtered = false;
1618
1619        for w in warnings {
1620            let len = out.len();
1621            let id = w.id();
1622
1623            match out.entry(id.clone()) {
1624                btree_map::Entry::Vacant(entry) => {
1625                    if len < max_warning_types {
1626                        entry.insert(vec![path]);
1627                    } else {
1628                        warning_distinct_types_filtered.insert(id);
1629                        filtered = true;
1630                    }
1631                }
1632                btree_map::Entry::Occupied(mut entry) => {
1633                    entry.get_mut().push(path);
1634                }
1635            }
1636        }
1637
1638        if filtered {
1639            elements_filtered.insert(path);
1640        }
1641    }
1642
1643    LimitReport {
1644        warnings: out,
1645        elements_filtered: elements_filtered.len(),
1646        warning_distinct_types_filtered: warning_distinct_types_filtered.len(),
1647    }
1648}
1649
1650/// The logic for the [`Limit::ElemPathsPerId`] variant.
1651fn limit_elem_paths_per_id<W: Warning>(
1652    max_elem_paths_per_warning_id: usize,
1653    warnings: &BTreeMap<json::ElemId, Group<W>>,
1654) -> LimitReport<'_> {
1655    let mut out = BTreeMap::new();
1656    // A set of element paths encountered and filtered. Element paths can be encountered more
1657    // than once, so a simple `usize` can't be used to count the encounters.
1658    let mut elements_filtered = HashSet::new();
1659
1660    if max_elem_paths_per_warning_id == 0 {
1661        for group in warnings.values() {
1662            let Group { element, warnings } = group;
1663
1664            for w in warnings {
1665                if let btree_map::Entry::Vacant(entry) = out.entry(w.id()) {
1666                    entry.insert(vec![]);
1667                }
1668            }
1669            elements_filtered.insert(element.path.as_str());
1670        }
1671    } else {
1672        for group in warnings.values() {
1673            let Group { element, warnings } = group;
1674            let path = element.path.as_str();
1675
1676            for w in warnings {
1677                let id = w.id();
1678
1679                match out.entry(id.clone()) {
1680                    btree_map::Entry::Vacant(entry) => {
1681                        entry.insert(vec![path]);
1682                    }
1683                    btree_map::Entry::Occupied(mut entry) => {
1684                        if entry.get().len() < max_elem_paths_per_warning_id {
1685                            entry.get_mut().push(path);
1686                        } else {
1687                            elements_filtered.insert(path);
1688                        }
1689                    }
1690                }
1691            }
1692        }
1693    }
1694
1695    LimitReport {
1696        warnings: out,
1697        elements_filtered: elements_filtered.len(),
1698        warning_distinct_types_filtered: 0,
1699    }
1700}
1701
1702/// The logic for the [`Limit::All`] variant.
1703fn limit_all<W: Warning>(
1704    max_warning_types: usize,
1705    max_elem_paths_per_warning_id: usize,
1706    warnings: &BTreeMap<json::ElemId, Group<W>>,
1707) -> LimitReport<'_> {
1708    let mut out = BTreeMap::new();
1709    // A set of element paths encountered and filtered. Element paths can be encountered more
1710    // than once, so a simple `usize` can't be used to count the encounters.
1711    let mut elements_filtered = HashSet::new();
1712    // A set of warnings encountered and filtered. Warnings can be encountered more than once,
1713    // so a simple `usize` can't be used to count the encounters.
1714    let mut warning_distinct_types_filtered = HashSet::new();
1715
1716    if max_warning_types > 0 && max_elem_paths_per_warning_id == 0 {
1717        for group in warnings.values() {
1718            let Group { element, warnings } = group;
1719            let path = element.path.as_str();
1720
1721            for w in warnings {
1722                let len = out.len();
1723                let id = w.id();
1724
1725                if let btree_map::Entry::Vacant(entry) = out.entry(id.clone()) {
1726                    if len < max_warning_types {
1727                        entry.insert(vec![]);
1728                    } else {
1729                        warning_distinct_types_filtered.insert(id);
1730                    }
1731                }
1732            }
1733
1734            elements_filtered.insert(path);
1735        }
1736    } else {
1737        for group in warnings.values() {
1738            let Group { element, warnings } = group;
1739            let path = element.path.as_str();
1740
1741            for w in warnings {
1742                let len = out.len();
1743                let id = w.id();
1744
1745                match out.entry(id.clone()) {
1746                    btree_map::Entry::Vacant(entry) => {
1747                        if len < max_warning_types {
1748                            entry.insert(vec![path]);
1749                        } else {
1750                            warning_distinct_types_filtered.insert(id);
1751                            elements_filtered.insert(path);
1752                        }
1753                    }
1754                    btree_map::Entry::Occupied(mut entry) => {
1755                        if entry.get().len() < max_elem_paths_per_warning_id {
1756                            entry.get_mut().push(path);
1757                        } else {
1758                            elements_filtered.insert(path);
1759                        }
1760                    }
1761                }
1762            }
1763        }
1764    }
1765
1766    LimitReport {
1767        warnings: out,
1768        elements_filtered: elements_filtered.len(),
1769        warning_distinct_types_filtered: warning_distinct_types_filtered.len(),
1770    }
1771}
1772
1773/// The outcome of calling the [`Set::id_path_map`] function.
1774#[derive(Debug)]
1775pub struct IdPathMap<'set> {
1776    /// The total amount of [`Warning`]s [`Id`]s in the source [`Set`].
1777    pub total_warnings: usize,
1778
1779    /// The total amount of [`json::Element`] paths in the source [`Set`].
1780    pub total_elements: usize,
1781
1782    /// The amount of [`json::Element`] paths filtered due to a [`Limit`] being set.
1783    pub elements_filtered: usize,
1784
1785    /// The amount of [`Warning`] [`Id`]s filtered due to a [`Limit`] being set.
1786    ///
1787    /// Note: This is not a count of how many warnings were filtered. It's a count of how many
1788    /// types of warnings were filtered. If seven `excessive_precision` warnings are filtered,
1789    /// this counts as one type, as all the IDs that were filtered are the same.
1790    pub warning_distinct_types_filtered: usize,
1791
1792    /// The map of all [`Warning`] [`Id`]s mapped to the [`json::Element`] paths where they occurred.
1793    pub warnings: BTreeMap<Id, Vec<&'set str>>,
1794}
1795
1796/// The outcome of calling the [`Set::msg_path_map`] function.
1797#[derive(Debug)]
1798pub struct MsgPathMap<'set> {
1799    /// The total amount of [`Warning`]s [`Id`]s in the source [`Set`].
1800    pub total_warnings: usize,
1801
1802    /// The total amount of [`json::Element`] paths in the source [`Set`].
1803    pub total_elements: usize,
1804
1805    /// The amount of [`json::Element`] paths filtered due to a [`Limit`] being set.
1806    pub elements_filtered: usize,
1807
1808    /// The amount of [`Warning`] [`Id`]s filtered due to a [`Limit`] being set.
1809    ///
1810    /// Note: This is not a count of how many warnings were filtered. It's a count of how many
1811    /// types of warnings were filtered. If seven `excessive_precision` warnings are filtered,
1812    /// this counts as one type, as all the IDs that were filtered are the same.
1813    pub warning_distinct_types_filtered: usize,
1814
1815    /// The map of all [`Warning`] [`Id`]s mapped to the [`json::Element`] paths where they occurred.
1816    pub warnings: BTreeMap<String, Vec<&'set str>>,
1817}
1818
1819/// The limiting configuration of the [`Set::path_id_map`] function.
1820#[derive(Copy, Clone, Debug)]
1821pub enum Limit {
1822    /// Don't enforce any limits on [`Warning`] [`Id`]s or [`json::Element`] [`json::Path`]s.
1823    None,
1824
1825    /// Forbid more than this amount of [`Warning`] [`Id`]s to be inserted into the map.
1826    WarningTypes(usize),
1827
1828    /// Forbid more than this amount of [`json::Element`] [`json::Path`]s to be inserted into the list for each [`Id`].
1829    ElemPathsPerId(usize),
1830
1831    All {
1832        /// Forbid more than this amount of [`Warning`] [`Id`]s to be inserted into the map.
1833        max_warning_types: usize,
1834
1835        /// Forbid more than this amount of [`json::Element`] [`json::Path`]s to be inserted into the list for each [`Id`].
1836        max_elem_paths_per_warning_id: usize,
1837    },
1838}
1839
1840/// A set of [`Warning`]s and a [`Warning`] that caused an operation to fail to be represented as an [`Error`].
1841///
1842/// This set is transported through the system using a [`Verdict`]s `Err` variant.
1843#[derive(Debug)]
1844pub struct ErrorSet<W: Warning> {
1845    /// The warning that caused an operation to fail.
1846    ///
1847    /// The warning is converted to an [`Error`] so it's ready to take part in Rust's error system.
1848    error: Box<Error<W>>,
1849
1850    /// The warnings accumulated up until the failure moment.
1851    ///
1852    /// This list does not included the warning that caused the operation to fail.
1853    warnings: BTreeMap<json::ElemId, Group<W>>,
1854}
1855
1856impl<W> ErrorSet<W>
1857where
1858    W: Warning,
1859{
1860    /// Consume the [`ErrorSet`] and return the [`Error`] and warnings as a `Set`.
1861    pub fn into_parts(self) -> (Error<W>, Set<W>) {
1862        let Self { error, warnings } = self;
1863        (*error, Set(warnings))
1864    }
1865
1866    /// Converts `ErrorSet<W>` into `ErrorSet<WA>` using the `impl Into<WA> for K`.
1867    ///
1868    /// This is used by the [`from_warning_all`] macro.
1869    pub(crate) fn into_other<WA>(self) -> ErrorSet<WA>
1870    where
1871        W: Into<WA>,
1872        WA: Warning,
1873    {
1874        let Self { error, warnings } = self;
1875        let warnings = warnings
1876            .into_iter()
1877            .map(|(elem_id, group)| (elem_id, group.into_other()))
1878            .collect();
1879        ErrorSet {
1880            error: Box::new(Error::into_other(*error)),
1881            warnings,
1882        }
1883    }
1884}
1885
1886/// A group of warning `Warning`s associated with an `Element`.
1887///
1888/// This group is emitted from the `IntoGroupByElem` iterator.
1889/// The warning `Warning`s are owned and so can be moved to another location.
1890#[derive(Debug)]
1891pub struct Group<W: Warning> {
1892    /// The [`json::Element`] that has [`Warning`]s.
1893    element: Element,
1894
1895    /// The list of warnings for the [`json::Element`].
1896    warnings: Vec<Source<W>>,
1897}
1898
1899impl<W> Group<W>
1900where
1901    W: Warning,
1902{
1903    /// Consume the `Group` and return the constituent parts.
1904    pub fn into_parts(self) -> (Element, Vec<W>) {
1905        let Self { element, warnings } = self;
1906        let warnings = warnings.into_iter().map(Source::into_warning).collect();
1907        (element, warnings)
1908    }
1909
1910    /// Map the `Warning` contained in the `Group` to another `Warning` type.
1911    #[allow(
1912        dead_code,
1913        reason = "Kept for the tariff linter being rebuilt on the schema IR; see `docs/lint-catalogue.md`"
1914    )]
1915    pub(crate) fn map<F, WA>(self, mut f: F) -> Group<WA>
1916    where
1917        F: FnMut(W) -> WA,
1918        WA: Warning,
1919    {
1920        let Self { element, warnings } = self;
1921        let warnings = warnings
1922            .into_iter()
1923            .map(|source| source.map(&mut f))
1924            .collect();
1925        Group { element, warnings }
1926    }
1927
1928    pub fn to_parts(&self) -> (&Element, Vec<&W>) {
1929        let Self { element, warnings } = self;
1930        let warnings = warnings.iter().map(|w| &**w).collect();
1931        (element, warnings)
1932    }
1933
1934    pub fn warnings(&self) -> Vec<&W> {
1935        self.warnings.iter().map(|w| &**w).collect()
1936    }
1937
1938    pub fn into_warnings(self) -> Vec<W> {
1939        let Self {
1940            element: _,
1941            warnings,
1942        } = self;
1943        warnings.into_iter().map(Source::into_warning).collect()
1944    }
1945
1946    /// Converts `IntoGroup<W>` into `IntoGroup<WA>` using the `impl Into<WA> for K`.
1947    ///
1948    /// This is used by the [`from_warning_all`] macro.
1949    fn into_other<WA>(self) -> Group<WA>
1950    where
1951        W: Into<WA>,
1952        WA: Warning,
1953    {
1954        let Self { element, warnings } = self;
1955        let warnings = warnings.into_iter().map(Source::into_other).collect();
1956        Group { element, warnings }
1957    }
1958}
1959
1960/// An iterator of borrowed [`Warning`]s grouped by [`json::Element`].
1961pub struct Iter<'caller, W>
1962where
1963    W: Warning,
1964{
1965    /// The iterator over every [`Warning`].
1966    warnings: btree_map::Iter<'caller, json::ElemId, Group<W>>,
1967}
1968
1969impl<W> Iter<'_, W> where W: Warning {}
1970
1971impl<'caller, W: Warning> Iterator for Iter<'caller, W> {
1972    type Item = &'caller Group<W>;
1973
1974    fn next(&mut self) -> Option<Self::Item> {
1975        let (_elem_id, group) = self.warnings.next()?;
1976        Some(group)
1977    }
1978}
1979
1980/// An iterator of borrowed [`Warning`]s grouped by [`json::Element`].
1981pub struct IntoIter<W>
1982where
1983    W: Warning,
1984{
1985    /// The iterator over every [`Warning`].
1986    warnings: btree_map::IntoIter<json::ElemId, Group<W>>,
1987}
1988
1989impl<W: Warning> Iterator for IntoIter<W> {
1990    type Item = Group<W>;
1991
1992    fn next(&mut self) -> Option<Self::Item> {
1993        let (_elem_id, group) = self.warnings.next()?;
1994        Some(group)
1995    }
1996}
1997
1998impl<W: Warning> IntoIterator for Set<W> {
1999    type Item = Group<W>;
2000    type IntoIter = IntoIter<W>;
2001
2002    fn into_iter(self) -> Self::IntoIter {
2003        let Set(warnings) = self;
2004        IntoIter {
2005            warnings: warnings.into_iter(),
2006        }
2007    }
2008}
2009
2010impl<'a, W: Warning> IntoIterator for &'a Set<W> {
2011    type Item = &'a Group<W>;
2012    type IntoIter = Iter<'a, W>;
2013
2014    fn into_iter(self) -> Self::IntoIter {
2015        self.iter()
2016    }
2017}