use std::fmt;
use std::path::PathBuf;
pub struct Error {
pub(crate) inner: Box<ErrorInner>,
}
impl Error {
pub(crate) fn field_validation(msg: impl fmt::Display) -> Self {
ErrorInner::FieldValidation { msg: msg.to_string() }.into()
}
}
#[cfg_attr(
not(any(feature = "toml", feature = "yaml", feature = "json5")),
allow(dead_code)
)]
pub(crate) enum ErrorInner {
MissingValue(String),
Io {
path: Option<PathBuf>,
err: std::io::Error,
},
Deserialization {
source: Option<String>,
err: Box<dyn std::error::Error + Send + Sync>,
},
EnvNotUnicode { field: String, key: String },
EnvDeserialization {
field: String,
key: String,
msg: String,
},
EnvParseError {
field: String,
key: String,
err: Box<dyn std::error::Error + Send + Sync>,
},
UnsupportedFileFormat { path: PathBuf },
MissingFileExtension { path: PathBuf },
MissingRequiredFile { path: PathBuf },
FieldValidation { msg: String },
StructValidation { name: String, msg: String },
}
impl std::error::Error for Error {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
match &*self.inner {
ErrorInner::Io { err, .. } => Some(err),
ErrorInner::Deserialization { err, .. } => Some(&**err),
ErrorInner::MissingValue(_) => None,
ErrorInner::EnvNotUnicode { .. } => None,
ErrorInner::EnvDeserialization { .. } => None,
ErrorInner::EnvParseError { err, .. } => Some(&**err),
ErrorInner::UnsupportedFileFormat { .. } => None,
ErrorInner::MissingFileExtension { .. } => None,
ErrorInner::MissingRequiredFile { .. } => None,
ErrorInner::FieldValidation { .. } => None,
ErrorInner::StructValidation { .. } => None,
}
}
}
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match &*self.inner {
ErrorInner::MissingValue(path) => {
std::write!(f, "required configuration value is missing: '{path}'")
}
ErrorInner::Io { path: Some(path), .. } => {
std::write!(f,
"IO error occured while reading configuration file '{}'",
path.display(),
)
}
ErrorInner::Io { path: None, .. } => {
std::write!(f, "IO error occured while loading configuration")
}
ErrorInner::Deserialization { source: Some(source), err } => {
std::write!(f, "failed to deserialize configuration from {source}")?;
if f.alternate() {
f.write_str(": ")?;
fmt::Display::fmt(&err, f)?;
}
Ok(())
}
ErrorInner::Deserialization { source: None, err } => {
std::write!(f, "failed to deserialize configuration")?;
if f.alternate() {
f.write_str(": ")?;
fmt::Display::fmt(&err, f)?;
}
Ok(())
}
ErrorInner::EnvNotUnicode { field, key } => {
std::write!(f, "failed to load value `{field}` from \
environment variable `{key}`: value is not valid unicode")
}
ErrorInner::EnvDeserialization { field, key, msg } => {
std::write!(f, "failed to deserialize value `{field}` from \
environment variable `{key}`: {msg}")
}
ErrorInner::EnvParseError { field, key, err } => {
std::write!(f, "failed to parse environment variable `{key}` into \
field `{field}`")?;
if f.alternate() {
f.write_str(": ")?;
fmt::Display::fmt(&err, f)?;
}
Ok(())
}
ErrorInner::UnsupportedFileFormat { path } => {
std::write!(f,
"unknown configuration file format/extension: '{}'",
path.display(),
)
}
ErrorInner::MissingFileExtension { path } => {
std::write!(f,
"cannot guess configuration file format due to missing file extension in '{}'",
path.display(),
)
}
ErrorInner::MissingRequiredFile { path } => {
std::write!(f,
"required configuration file does not exist: '{}'",
path.display(),
)
}
ErrorInner::FieldValidation { msg } => {
std::write!(f, "validation failed: {msg}")
}
ErrorInner::StructValidation { name, msg } => {
std::write!(f, "config validation of `{name}` failed: {msg}")
}
}
}
}
impl fmt::Debug for Error {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
fmt::Display::fmt(self, f)
}
}
impl From<ErrorInner> for Error {
fn from(inner: ErrorInner) -> Self {
Self { inner: Box::new(inner) }
}
}