okcodes_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.
41pub enum ConfigError {
42    /// Configuration is frozen and no further mutations can be made.
43    Frozen,
44
45    /// Configuration property was not found
46    NotFound(String),
47
48    /// Configuration path could not be parsed.
49    PathParse(nom::error::ErrorKind),
50
51    /// Configuration could not be parsed from file.
52    FileParse {
53        /// The URI used to access the file (if not loaded from a string).
54        /// Example: `/path/to/config.json`
55        uri: Option<String>,
56
57        /// The captured error from attempting to parse the file in its desired format.
58        /// This is the actual error object from the library used for the parsing.
59        cause: Box<dyn Error + Send + Sync>,
60    },
61
62    /// Value could not be converted into the requested type.
63    Type {
64        /// The URI that references the source that the value came from.
65        /// Example: `/path/to/config.json` or `Environment` or `etcd://localhost`
66        // TODO: Why is this called Origin but FileParse has a uri field?
67        origin: Option<String>,
68
69        /// What we found when parsing the value
70        unexpected: Unexpected,
71
72        /// What was expected when parsing the value
73        expected: &'static str,
74
75        /// The key in the configuration hash of this value (if available where the
76        /// error is generated).
77        key: Option<String>,
78    },
79
80    /// Custom message
81    Message(String),
82
83    /// Unadorned error from a foreign origin.
84    Foreign(Box<dyn Error + Send + Sync>),
85}
86
87impl ConfigError {
88    // FIXME: pub(crate)
89    #[doc(hidden)]
90    pub fn invalid_type(
91        origin: Option<String>,
92        unexpected: Unexpected,
93        expected: &'static str,
94    ) -> Self {
95        Self::Type {
96            origin,
97            unexpected,
98            expected,
99            key: None,
100        }
101    }
102
103    // Have a proper error fire if the root of a file is ever not a Table
104    // TODO: for now only json5 checked, need to finish others
105    #[doc(hidden)]
106    pub fn invalid_root(origin: Option<&String>, unexpected: Unexpected) -> Box<Self> {
107        Box::new(Self::Type {
108            origin: origin.cloned(),
109            unexpected,
110            expected: "a map",
111            key: None,
112        })
113    }
114
115    // FIXME: pub(crate)
116    #[doc(hidden)]
117    #[must_use]
118    pub fn extend_with_key(self, key: &str) -> Self {
119        match self {
120            Self::Type {
121                origin,
122                unexpected,
123                expected,
124                ..
125            } => Self::Type {
126                origin,
127                unexpected,
128                expected,
129                key: Some(key.into()),
130            },
131
132            _ => self,
133        }
134    }
135
136    #[must_use]
137    fn prepend(self, segment: &str, add_dot: bool) -> Self {
138        let concat = |key: Option<String>| {
139            let key = key.unwrap_or_default();
140            let dot = if add_dot && key.as_bytes().first().unwrap_or(&b'[') != &b'[' {
141                "."
142            } else {
143                ""
144            };
145            format!("{}{}{}", segment, dot, key)
146        };
147        match self {
148            Self::Type {
149                origin,
150                unexpected,
151                expected,
152                key,
153            } => Self::Type {
154                origin,
155                unexpected,
156                expected,
157                key: Some(concat(key)),
158            },
159            Self::NotFound(key) => Self::NotFound(concat(Some(key))),
160            _ => self,
161        }
162    }
163
164    #[must_use]
165    pub(crate) fn prepend_key(self, key: &str) -> Self {
166        self.prepend(key, true)
167    }
168
169    #[must_use]
170    pub(crate) fn prepend_index(self, idx: usize) -> Self {
171        self.prepend(&format!("[{}]", idx), false)
172    }
173}
174
175/// Alias for a `Result` with the error type set to `ConfigError`.
176pub type Result<T> = result::Result<T, ConfigError>;
177
178// Forward Debug to Display for readable panic! messages
179impl fmt::Debug for ConfigError {
180    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
181        write!(f, "{}", *self)
182    }
183}
184
185impl fmt::Display for ConfigError {
186    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
187        match *self {
188            ConfigError::Frozen => write!(f, "configuration is frozen"),
189
190            ConfigError::PathParse(ref kind) => write!(f, "{}", kind.description()),
191
192            ConfigError::Message(ref s) => write!(f, "{}", s),
193
194            ConfigError::Foreign(ref cause) => write!(f, "{}", cause),
195
196            ConfigError::NotFound(ref key) => {
197                write!(f, "configuration property {:?} not found", key)
198            }
199
200            ConfigError::Type {
201                ref origin,
202                ref unexpected,
203                expected,
204                ref key,
205            } => {
206                write!(f, "invalid type: {}, expected {}", unexpected, expected)?;
207
208                if let Some(ref key) = *key {
209                    write!(f, " for key `{}`", key)?;
210                }
211
212                if let Some(ref origin) = *origin {
213                    write!(f, " in {}", origin)?;
214                }
215
216                Ok(())
217            }
218
219            ConfigError::FileParse { ref cause, ref uri } => {
220                write!(f, "{}", cause)?;
221
222                if let Some(ref uri) = *uri {
223                    write!(f, " in {}", uri)?;
224                }
225
226                Ok(())
227            }
228        }
229    }
230}
231
232impl Error for ConfigError {}
233
234impl de::Error for ConfigError {
235    fn custom<T: fmt::Display>(msg: T) -> Self {
236        Self::Message(msg.to_string())
237    }
238}
239
240impl ser::Error for ConfigError {
241    fn custom<T: fmt::Display>(msg: T) -> Self {
242        Self::Message(msg.to_string())
243    }
244}