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}