blob: f955a281b09398483270f38f43069fef476dfdb0 [file] [log] [blame] [edit]
use std::error::Error;
use std::fmt;
use std::result;
use serde::de;
use serde::ser;
#[derive(Debug)]
pub enum Unexpected {
Bool(bool),
I64(i64),
I128(i128),
U64(u64),
U128(u128),
Float(f64),
Str(String),
Unit,
Seq,
Map,
}
impl fmt::Display for Unexpected {
fn fmt(&self, f: &mut fmt::Formatter) -> result::Result<(), fmt::Error> {
match *self {
Unexpected::Bool(b) => write!(f, "boolean `{}`", b),
Unexpected::I64(i) => write!(f, "integer 64 bit `{}`", i),
Unexpected::I128(i) => write!(f, "integer 128 bit `{}`", i),
Unexpected::U64(i) => write!(f, "unsigned integer 64 bit `{}`", i),
Unexpected::U128(i) => write!(f, "unsigned integer 128 bit `{}`", i),
Unexpected::Float(v) => write!(f, "floating point `{}`", v),
Unexpected::Str(ref s) => write!(f, "string {:?}", s),
Unexpected::Unit => write!(f, "unit value"),
Unexpected::Seq => write!(f, "sequence"),
Unexpected::Map => write!(f, "map"),
}
}
}
/// Represents all possible errors that can occur when working with
/// configuration.
pub enum ConfigError {
/// Configuration is frozen and no further mutations can be made.
Frozen,
/// Configuration property was not found
NotFound(String),
/// Configuration path could not be parsed.
PathParse(nom::error::ErrorKind),
/// Configuration could not be parsed from file.
FileParse {
/// The URI used to access the file (if not loaded from a string).
/// Example: `/path/to/config.json`
uri: Option<String>,
/// The captured error from attempting to parse the file in its desired format.
/// This is the actual error object from the library used for the parsing.
cause: Box<dyn Error + Send + Sync>,
},
/// Value could not be converted into the requested type.
Type {
/// The URI that references the source that the value came from.
/// Example: `/path/to/config.json` or `Environment` or `etcd://localhost`
// TODO: Why is this called Origin but FileParse has a uri field?
origin: Option<String>,
/// What we found when parsing the value
unexpected: Unexpected,
/// What was expected when parsing the value
expected: &'static str,
/// The key in the configuration hash of this value (if available where the
/// error is generated).
key: Option<String>,
},
/// Custom message
Message(String),
/// Unadorned error from a foreign origin.
Foreign(Box<dyn Error + Send + Sync>),
}
impl ConfigError {
// FIXME: pub(crate)
#[doc(hidden)]
pub fn invalid_type(
origin: Option<String>,
unexpected: Unexpected,
expected: &'static str,
) -> Self {
Self::Type {
origin,
unexpected,
expected,
key: None,
}
}
// Have a proper error fire if the root of a file is ever not a Table
// TODO: for now only json5 checked, need to finish others
#[doc(hidden)]
pub fn invalid_root(origin: Option<&String>, unexpected: Unexpected) -> Box<Self> {
Box::new(Self::Type {
origin: origin.cloned(),
unexpected,
expected: "a map",
key: None,
})
}
// FIXME: pub(crate)
#[doc(hidden)]
#[must_use]
pub fn extend_with_key(self, key: &str) -> Self {
match self {
Self::Type {
origin,
unexpected,
expected,
..
} => Self::Type {
origin,
unexpected,
expected,
key: Some(key.into()),
},
_ => self,
}
}
#[must_use]
fn prepend(self, segment: &str, add_dot: bool) -> Self {
let concat = |key: Option<String>| {
let key = key.unwrap_or_default();
let dot = if add_dot && key.as_bytes().get(0).unwrap_or(&b'[') != &b'[' {
"."
} else {
""
};
format!("{}{}{}", segment, dot, key)
};
match self {
Self::Type {
origin,
unexpected,
expected,
key,
} => Self::Type {
origin,
unexpected,
expected,
key: Some(concat(key)),
},
Self::NotFound(key) => Self::NotFound(concat(Some(key))),
_ => self,
}
}
#[must_use]
pub(crate) fn prepend_key(self, key: &str) -> Self {
self.prepend(key, true)
}
#[must_use]
pub(crate) fn prepend_index(self, idx: usize) -> Self {
self.prepend(&format!("[{}]", idx), false)
}
}
/// Alias for a `Result` with the error type set to `ConfigError`.
pub type Result<T> = result::Result<T, ConfigError>;
// Forward Debug to Display for readable panic! messages
impl fmt::Debug for ConfigError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}", *self)
}
}
impl fmt::Display for ConfigError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match *self {
ConfigError::Frozen => write!(f, "configuration is frozen"),
ConfigError::PathParse(ref kind) => write!(f, "{}", kind.description()),
ConfigError::Message(ref s) => write!(f, "{}", s),
ConfigError::Foreign(ref cause) => write!(f, "{}", cause),
ConfigError::NotFound(ref key) => {
write!(f, "configuration property {:?} not found", key)
}
ConfigError::Type {
ref origin,
ref unexpected,
expected,
ref key,
} => {
write!(f, "invalid type: {}, expected {}", unexpected, expected)?;
if let Some(ref key) = *key {
write!(f, " for key `{}`", key)?;
}
if let Some(ref origin) = *origin {
write!(f, " in {}", origin)?;
}
Ok(())
}
ConfigError::FileParse { ref cause, ref uri } => {
write!(f, "{}", cause)?;
if let Some(ref uri) = *uri {
write!(f, " in {}", uri)?;
}
Ok(())
}
}
}
}
impl Error for ConfigError {}
impl de::Error for ConfigError {
fn custom<T: fmt::Display>(msg: T) -> Self {
Self::Message(msg.to_string())
}
}
impl ser::Error for ConfigError {
fn custom<T: fmt::Display>(msg: T) -> Self {
Self::Message(msg.to_string())
}
}