module/merge/
error.rs

1//! [`Error`] & [`Context`].
2//!
3//! This module contains all the machinery used to present nice and useful error
4//! messages from merge operations.
5
6use core::fmt::{self, Debug, Display};
7use core::mem::discriminant;
8
9use alloc::boxed::Box;
10
11use super::trace::Trace;
12
13/// Kind of [`Error`].
14#[non_exhaustive]
15pub enum ErrorKind {
16    /// Values cannot be merged.
17    ///
18    /// This error should be returned by [`Merge`] implementations when it is
19    /// not clear how to merge the values. For example, the 2 values may have
20    /// the same priority.
21    ///
22    /// For many types, the term "merge" does not make sense. How should one
23    /// merge 2 [`i32`]s for instance? Types which do not have an obvious merge
24    /// strategy or types on which the notion of "merging" cannot be defined
25    /// clearly are called "unmergeable". Such types should have a [`Merge`]
26    /// implementation but it should unconditionally return this error.
27    ///
28    /// [`Merge`]: crate::merge::Merge
29    Collision,
30
31    /// Cyclic module imports.
32    ///
33    /// This error should not need to be raised by [`Merge`] implementations. It
34    /// is supposed to be raised by evaluators when they encounter cyclic module
35    /// imports.
36    ///
37    /// [`Merge`]: crate::merge::Merge
38    Cycle,
39
40    /// A custom error that occurred during merging or evaluating.
41    ///
42    /// Contains a [`Box`]ed error object.
43    Custom(Box<dyn Display + Send + Sync + 'static>),
44}
45
46impl ErrorKind {
47    /// Check whether `self` is [`ErrorKind::Collision`].
48    pub fn is_collision(&self) -> bool {
49        matches!(self, Self::Collision)
50    }
51
52    /// Check whether `self` is [`ErrorKind::Cycle`].
53    pub fn is_cycle(&self) -> bool {
54        matches!(self, Self::Cycle)
55    }
56
57    /// Check whether `self` is [`ErrorKind::Custom`].
58    pub fn is_custom(&self) -> bool {
59        matches!(self, Self::Custom(_))
60    }
61}
62
63impl Debug for ErrorKind {
64    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
65        match self {
66            Self::Collision => write!(f, "Collision"),
67            Self::Cycle => write!(f, "Cycle"),
68            Self::Custom(x) => write!(f, "Custom(\"{x}\")"),
69        }
70    }
71}
72
73impl Display for ErrorKind {
74    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
75        match self {
76            Self::Collision => write!(f, "value collision"),
77            Self::Cycle => write!(f, "cyclic imports"),
78            Self::Custom(x) => x.fmt(f),
79        }
80    }
81}
82
83impl PartialEq for ErrorKind {
84    fn eq(&self, other: &Self) -> bool {
85        discriminant(self) == discriminant(other)
86    }
87}
88
89impl Eq for ErrorKind {}
90
91/// Error returned by [`Merge`].
92///
93/// # Display
94///
95/// The default [`Display`] implementation may not fit into the style of
96/// your app.
97///
98/// ```rust
99/// # use module::merge::{Merge, Error, Context};
100/// # let a = 42i32;
101/// # let b = 43i32;
102/// let r = a.merge(b)
103///     .value("count")
104///     .value("settings")
105///     .module("user.json")
106///     .module("config.json");
107///
108/// let err = r.unwrap_err();
109///
110/// assert_eq!(err.to_string(),
111/// r#"value collision while evaluating 'settings.count'
112///
113///     in user.json
114///   from config.json
115/// "#);
116/// ```
117///
118/// For this reason, the [`Error`] type tries to make all relevant
119/// information publically accessible. This way you can write another
120/// [`Display`] implementation that fits more inline with your vision.
121///
122/// [`Merge`]: crate::Merge
123#[derive(Debug)]
124#[allow(clippy::manual_non_exhaustive)]
125pub struct Error {
126    _priv: (),
127
128    /// Error kind.
129    pub kind: ErrorKind,
130
131    /// Module trace.
132    ///
133    /// This field holds information regarding the module in which the error
134    /// occurred. It is a [`Trace`] containing that module and all parent
135    /// modules.
136    pub modules: Trace,
137
138    /// Value name.
139    ///
140    /// This field holds the full path of the value which caused the merge
141    /// error. The path is stored as a list of components and can be accessed as
142    /// an [`Iterator`].
143    pub value: Trace,
144}
145
146impl From<ErrorKind> for Error {
147    fn from(kind: ErrorKind) -> Self {
148        Self::with_kind(kind)
149    }
150}
151
152impl Error {
153    /// Raised when [`Merge`] encounters 2 values which cannot be merged using
154    /// the current strategy.
155    ///
156    /// [`Merge`]: crate::Merge
157    pub fn collision() -> Self {
158        Self::with_kind(ErrorKind::Collision)
159    }
160
161    /// Raised when evaluation encounters cyclic imports.
162    pub fn cycle() -> Self {
163        Self::with_kind(ErrorKind::Cycle)
164    }
165
166    /// Raised when there is a general error when merging 2 values.
167    pub fn custom<T>(msg: T) -> Self
168    where
169        T: Display + Send + Sync + 'static,
170    {
171        Self::with_kind(ErrorKind::Custom(Box::new(msg)))
172    }
173
174    fn with_kind(kind: ErrorKind) -> Self {
175        Self {
176            _priv: (),
177            kind,
178            modules: Trace::new(),
179            value: Trace::new(),
180        }
181    }
182}
183
184impl Display for Error {
185    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
186        write!(f, "{}", self.kind)?;
187
188        let mut value = self.value.iter();
189        if let Some(first) = value.next() {
190            write!(f, " while evaluating '{first}")?;
191            value.try_for_each(|x| write!(f, ".{x}"))?;
192            write!(f, "'")?;
193        }
194
195        writeln!(f)?;
196
197        let mut modules = self.modules.iter().rev();
198        if let Some(first) = modules.next() {
199            writeln!(f)?;
200            writeln!(f, "    in {first}")?;
201            modules.try_for_each(|x| writeln!(f, "  from {x}"))?;
202        }
203
204        Ok(())
205    }
206}
207
208impl core::error::Error for Error {}