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 {}