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