config/
error.rs

1use std::error::Error;
2use std::fmt;
3use std::result;
4
5use serde::de;
6use serde::ser;
7
8#[derive(Debug)]
9pub enum Unexpected {
10    Bool(bool),
11    I64(i64),
12    I128(i128),
13    U64(u64),
14    U128(u128),
15    Float(f64),
16    Str(String),
17    Unit,
18    Seq,
19    Map,
20}
21
22impl fmt::Display for Unexpected {
23    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> result::Result<(), fmt::Error> {
24        match *self {
25            Unexpected::Bool(b) => write!(f, "boolean `{b}`"),
26            Unexpected::I64(i) => write!(f, "64-bit integer `{i}`"),
27            Unexpected::I128(i) => write!(f, "128-bit integer `{i}`"),
28            Unexpected::U64(i) => write!(f, "64-bit unsigned integer `{i}`"),
29            Unexpected::U128(i) => write!(f, "128-bit unsigned integer `{i}`"),
30            Unexpected::Float(v) => write!(f, "floating point `{v}`"),
31            Unexpected::Str(ref s) => write!(f, "string {s:?}"),
32            Unexpected::Unit => write!(f, "unit value"),
33            Unexpected::Seq => write!(f, "sequence"),
34            Unexpected::Map => write!(f, "map"),
35        }
36    }
37}
38
39/// Represents all possible errors that can occur when working with
40/// configuration.
41#[non_exhaustive]
42pub enum ConfigError {
43    /// Configuration is frozen and no further mutations can be made.
44    Frozen,
45
46    /// Configuration property was not found
47    NotFound(String),
48
49    /// Configuration path could not be parsed.
50    PathParse { cause: Box<dyn Error + Send + Sync> },
51
52    /// Configuration could not be parsed from file.
53    FileParse {
54        /// The URI used to access the file (if not loaded from a string).
55        /// Example: `/path/to/config.json`
56        uri: Option<String>,
57
58        /// The captured error from attempting to parse the file in its desired format.
59        /// This is the actual error object from the library used for the parsing.
60        cause: Box<dyn Error + Send + Sync>,
61    },
62
63    /// Value could not be converted into the requested type.
64    Type {
65        /// The URI that references the source that the value came from.
66        /// Example: `/path/to/config.json` or `Environment` or `etcd://localhost`
67        // TODO: Why is this called Origin but FileParse has a uri field?
68        origin: Option<String>,
69
70        /// What we found when parsing the value
71        unexpected: Unexpected,
72
73        /// What was expected when parsing the value
74        expected: &'static str,
75
76        /// The key in the configuration hash of this value (if available where the
77        /// error is generated).
78        key: Option<String>,
79    },
80
81    /// Custom message
82    At {
83        /// Error being extended with a path
84        error: Box<ConfigError>,
85
86        /// The URI that references the source that the value came from.
87        /// Example: `/path/to/config.json` or `Environment` or `etcd://localhost`
88        // TODO: Why is this called Origin but FileParse has a uri field?
89        origin: Option<String>,
90
91        /// The key in the configuration hash of this value (if available where the
92        /// error is generated).
93        key: Option<String>,
94    },
95
96    /// Custom message
97    Message(String),
98
99    /// Unadorned error from a foreign origin.
100    Foreign(Box<dyn Error + Send + Sync>),
101}
102
103impl ConfigError {
104    // FIXME: pub(crate)
105    #[doc(hidden)]
106    pub fn invalid_type(
107        origin: Option<String>,
108        unexpected: Unexpected,
109        expected: &'static str,
110    ) -> Self {
111        Self::Type {
112            origin,
113            unexpected,
114            expected,
115            key: None,
116        }
117    }
118
119    // Have a proper error fire if the root of a file is ever not a Table
120    // TODO: for now only json5 checked, need to finish others
121    #[doc(hidden)]
122    pub fn invalid_root(origin: Option<&String>, unexpected: Unexpected) -> Box<Self> {
123        Box::new(Self::Type {
124            origin: origin.cloned(),
125            unexpected,
126            expected: "a map",
127            key: None,
128        })
129    }
130
131    // FIXME: pub(crate)
132    #[doc(hidden)]
133    #[must_use]
134    pub fn extend_with_key(self, key: &str) -> Self {
135        match self {
136            Self::Type {
137                origin,
138                unexpected,
139                expected,
140                ..
141            } => Self::Type {
142                origin,
143                unexpected,
144                expected,
145                key: Some(key.into()),
146            },
147
148            Self::At { origin, error, .. } => Self::At {
149                error,
150                origin,
151                key: Some(key.into()),
152            },
153
154            other => Self::At {
155                error: Box::new(other),
156                origin: None,
157                key: Some(key.into()),
158            },
159        }
160    }
161
162    #[must_use]
163    fn prepend(self, segment: &str, add_dot: bool) -> Self {
164        let concat = |key: Option<String>| {
165            let key = key.unwrap_or_default();
166            let dot = if add_dot && key.as_bytes().first().unwrap_or(&b'[') != &b'[' {
167                "."
168            } else {
169                ""
170            };
171            format!("{segment}{dot}{key}")
172        };
173        match self {
174            Self::Type {
175                origin,
176                unexpected,
177                expected,
178                key,
179            } => Self::Type {
180                origin,
181                unexpected,
182                expected,
183                key: Some(concat(key)),
184            },
185            Self::At { error, origin, key } => Self::At {
186                error,
187                origin,
188                key: Some(concat(key)),
189            },
190            Self::NotFound(key) => Self::NotFound(concat(Some(key))),
191            other => Self::At {
192                error: Box::new(other),
193                origin: None,
194                key: Some(concat(None)),
195            },
196        }
197    }
198
199    #[must_use]
200    pub(crate) fn prepend_key(self, key: &str) -> Self {
201        self.prepend(key, true)
202    }
203
204    #[must_use]
205    pub(crate) fn prepend_index(self, idx: usize) -> Self {
206        self.prepend(&format!("[{idx}]"), false)
207    }
208}
209
210/// Alias for a `Result` with the error type set to `ConfigError`.
211pub(crate) type Result<T, E = ConfigError> = result::Result<T, E>;
212
213// Forward Debug to Display for readable panic! messages
214impl fmt::Debug for ConfigError {
215    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
216        write!(f, "{}", *self)
217    }
218}
219
220impl fmt::Display for ConfigError {
221    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
222        match *self {
223            ConfigError::Frozen => write!(f, "configuration is frozen"),
224
225            ConfigError::PathParse { ref cause } => write!(f, "{cause}"),
226
227            ConfigError::Message(ref s) => write!(f, "{s}"),
228
229            ConfigError::Foreign(ref cause) => write!(f, "{cause}"),
230
231            ConfigError::NotFound(ref key) => {
232                write!(f, "configuration property {key:?} not found")
233            }
234
235            ConfigError::Type {
236                ref origin,
237                ref unexpected,
238                expected,
239                ref key,
240            } => {
241                write!(f, "invalid type: {unexpected}, expected {expected}")?;
242
243                if let Some(ref key) = *key {
244                    write!(f, " for key `{key}`")?;
245                }
246
247                if let Some(ref origin) = *origin {
248                    write!(f, " in {origin}")?;
249                }
250
251                Ok(())
252            }
253
254            ConfigError::At {
255                ref error,
256                ref origin,
257                ref key,
258            } => {
259                write!(f, "{error}")?;
260
261                if let Some(ref key) = *key {
262                    write!(f, " for key `{key}`")?;
263                }
264
265                if let Some(ref origin) = *origin {
266                    write!(f, " in {origin}")?;
267                }
268
269                Ok(())
270            }
271
272            ConfigError::FileParse { ref cause, ref uri } => {
273                write!(f, "{cause}")?;
274
275                if let Some(ref uri) = *uri {
276                    write!(f, " in {uri}")?;
277                }
278
279                Ok(())
280            }
281        }
282    }
283}
284
285impl Error for ConfigError {}
286
287impl de::Error for ConfigError {
288    fn custom<T: fmt::Display>(msg: T) -> Self {
289        Self::Message(msg.to_string())
290    }
291}
292
293impl ser::Error for ConfigError {
294    fn custom<T: fmt::Display>(msg: T) -> Self {
295        Self::Message(msg.to_string())
296    }
297}