config_maint/
error.rs

1use nom;
2use serde::de;
3use serde::ser;
4use std::borrow::Cow;
5use std::error::Error;
6use std::fmt;
7use std::result;
8
9#[derive(Debug)]
10pub enum Unexpected {
11    Bool(bool),
12    Integer(i64),
13    Float(f64),
14    Str(String),
15    Unit,
16    Seq,
17    Map,
18}
19
20impl fmt::Display for Unexpected {
21    fn fmt(&self, f: &mut fmt::Formatter) -> result::Result<(), fmt::Error> {
22        match *self {
23            Unexpected::Bool(b) => write!(f, "boolean `{}`", b),
24            Unexpected::Integer(i) => write!(f, "integer `{}`", i),
25            Unexpected::Float(v) => write!(f, "floating point `{}`", v),
26            Unexpected::Str(ref s) => write!(f, "string {:?}", s),
27            Unexpected::Unit => write!(f, "unit value"),
28            Unexpected::Seq => write!(f, "sequence"),
29            Unexpected::Map => write!(f, "map"),
30        }
31    }
32}
33
34/// Represents all possible errors that can occur when working with
35/// configuration.
36pub enum ConfigError {
37    /// Configuration is frozen and no further mutations can be made.
38    Frozen,
39
40    /// Configuration property was not found
41    NotFound(String),
42
43    /// Configuration path could not be parsed.
44    PathParse(nom::error::ErrorKind),
45
46    /// Configuration could not be parsed from file.
47    FileParse {
48        /// The URI used to access the file (if not loaded from a string).
49        /// Example: `/path/to/config.json`
50        uri: Option<String>,
51
52        /// The captured error from attempting to parse the file in its desired format.
53        /// This is the actual error object from the library used for the parsing.
54        cause: Box<dyn Error + Send + Sync>,
55    },
56
57    /// Value could not be converted into the requested type.
58    Type {
59        /// The URI that references the source that the value came from.
60        /// Example: `/path/to/config.json` or `Environment` or `etcd://localhost`
61        // TODO: Why is this called Origin but FileParse has a uri field?
62        origin: Option<String>,
63
64        /// What we found when parsing the value
65        unexpected: Unexpected,
66
67        /// What was expected when parsing the value
68        expected: &'static str,
69
70        /// The key in the configuration hash of this value (if available where the
71        /// error is generated).
72        key: Option<String>,
73    },
74
75    /// Custom message
76    Message(String),
77
78    /// Unadorned error from a foreign origin.
79    Foreign(Box<dyn Error + Send + Sync>),
80}
81
82impl ConfigError {
83    // FIXME: pub(crate)
84    #[doc(hidden)]
85    pub fn invalid_type(
86        origin: Option<String>,
87        unexpected: Unexpected,
88        expected: &'static str,
89    ) -> Self {
90        ConfigError::Type {
91            origin,
92            unexpected,
93            expected,
94            key: None,
95        }
96    }
97
98    // FIXME: pub(crate)
99    #[doc(hidden)]
100    pub fn extend_with_key(self, key: &str) -> Self {
101        match self {
102            ConfigError::Type {
103                origin,
104                unexpected,
105                expected,
106                ..
107            } => ConfigError::Type {
108                origin,
109                unexpected,
110                expected,
111                key: Some(key.into()),
112            },
113
114            _ => self,
115        }
116    }
117
118    fn prepend(self, segment: String, add_dot: bool) -> Self {
119        let concat = |key: Option<String>| {
120            let key = key.unwrap_or_else(String::new);
121            let dot = if add_dot && key.as_bytes().get(0).unwrap_or(&b'[') != &b'[' {
122                "."
123            } else {
124                ""
125            };
126            format!("{}{}{}", segment, dot, key)
127        };
128        match self {
129            ConfigError::Type {
130                origin,
131                unexpected,
132                expected,
133                key,
134            } => ConfigError::Type {
135                origin,
136                unexpected,
137                expected,
138                key: Some(concat(key)),
139            },
140            ConfigError::NotFound(key) => ConfigError::NotFound(concat(Some(key))),
141            _ => self,
142        }
143    }
144
145    pub(crate) fn prepend_key(self, key: String) -> Self {
146        self.prepend(key, true)
147    }
148
149    pub(crate) fn prepend_index(self, idx: usize) -> Self {
150        self.prepend(format!("[{}]", idx), false)
151    }
152}
153
154/// Alias for a `Result` with the error type set to `ConfigError`.
155pub type Result<T> = result::Result<T, ConfigError>;
156
157// Forward Debug to Display for readable panic! messages
158impl fmt::Debug for ConfigError {
159    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
160        write!(f, "{}", *self)
161    }
162}
163
164impl fmt::Display for ConfigError {
165    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
166        match *self {
167            ConfigError::Frozen => write!(f, "configuration is frozen"),
168
169            ConfigError::PathParse(ref kind) => write!(f, "{}", kind.description()),
170
171            ConfigError::Message(ref s) => write!(f, "{}", s),
172
173            ConfigError::Foreign(ref cause) => write!(f, "{}", cause),
174
175            ConfigError::NotFound(ref key) => {
176                write!(f, "configuration property {:?} not found", key)
177            }
178
179            ConfigError::Type {
180                ref origin,
181                ref unexpected,
182                expected,
183                ref key,
184            } => {
185                write!(f, "invalid type: {}, expected {}", unexpected, expected)?;
186
187                if let Some(ref key) = *key {
188                    write!(f, " for key `{}`", key)?;
189                }
190
191                if let Some(ref origin) = *origin {
192                    write!(f, " in {}", origin)?;
193                }
194
195                Ok(())
196            }
197
198            ConfigError::FileParse { ref cause, ref uri } => {
199                write!(f, "{}", cause)?;
200
201                if let Some(ref uri) = *uri {
202                    write!(f, " in {}", uri)?;
203                }
204
205                Ok(())
206            }
207        }
208    }
209}
210
211impl Error for ConfigError {}
212
213impl de::Error for ConfigError {
214    fn custom<T: fmt::Display>(msg: T) -> Self {
215        ConfigError::Message(msg.to_string())
216    }
217}
218
219impl ser::Error for ConfigError {
220    fn custom<T: fmt::Display>(msg: T) -> Self {
221        ConfigError::Message(msg.to_string())
222    }
223}