use crate::error::ConfigError as ConfigErrorRepr;
use crate::local::LocalError;
#[non_exhaustive]
pub struct ConfigError(ConfigErrorRepr);
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[non_exhaustive]
pub enum ConfigErrorKind {
Read,
Parse,
Interpolation,
UnresolvedVar,
Validation,
IncludeCycle,
IncludeDepth,
}
impl ConfigError {
#[must_use]
pub fn kind(&self) -> ConfigErrorKind {
match self.0 {
ConfigErrorRepr::Read { .. } => ConfigErrorKind::Read,
ConfigErrorRepr::Parse { .. } => ConfigErrorKind::Parse,
ConfigErrorRepr::Interpolation(_) => ConfigErrorKind::Interpolation,
ConfigErrorRepr::UnresolvedVar(_) => ConfigErrorKind::UnresolvedVar,
ConfigErrorRepr::Validation(_) => ConfigErrorKind::Validation,
ConfigErrorRepr::IncludeCycle { .. } => ConfigErrorKind::IncludeCycle,
ConfigErrorRepr::IncludeDepth { .. } => ConfigErrorKind::IncludeDepth,
}
}
}
impl std::fmt::Debug for ConfigError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
std::fmt::Debug::fmt(&self.0, f)
}
}
impl std::fmt::Display for ConfigError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
std::fmt::Display::fmt(&self.0, f)
}
}
impl std::error::Error for ConfigError {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
self.0.source()
}
}
impl From<ConfigErrorRepr> for ConfigError {
fn from(repr: ConfigErrorRepr) -> Self {
ConfigError(repr)
}
}
#[non_exhaustive]
pub struct StartupError(StartupRepr);
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[non_exhaustive]
pub enum StartupErrorKind {
Config,
Provisioning,
Bind,
Serve,
}
#[derive(Debug, thiserror::Error)]
enum StartupRepr {
#[error("configuration error")]
Config(#[source] ConfigErrorRepr),
#[error("local provisioning error")]
Provisioning(#[source] LocalError),
#[error("failed to bind the listener")]
Bind(#[source] std::io::Error),
#[error("serve error")]
Serve(#[source] ServeReprSource),
}
impl StartupError {
#[must_use]
pub fn kind(&self) -> StartupErrorKind {
match self.0 {
StartupRepr::Config(_) => StartupErrorKind::Config,
StartupRepr::Provisioning(_) => StartupErrorKind::Provisioning,
StartupRepr::Bind(_) => StartupErrorKind::Bind,
StartupRepr::Serve(_) => StartupErrorKind::Serve,
}
}
pub(crate) fn config(err: ConfigErrorRepr) -> Self {
StartupError(StartupRepr::Config(err))
}
pub(crate) fn provisioning(err: LocalError) -> Self {
StartupError(StartupRepr::Provisioning(err))
}
pub(crate) fn bind(err: std::io::Error) -> Self {
StartupError(StartupRepr::Bind(err))
}
pub(crate) fn serve(err: ServeError) -> Self {
StartupError(StartupRepr::Serve(err.0))
}
}
impl std::fmt::Debug for StartupError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
std::fmt::Debug::fmt(&self.0, f)
}
}
impl std::fmt::Display for StartupError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
std::fmt::Display::fmt(&self.0, f)
}
}
impl std::error::Error for StartupError {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
std::error::Error::source(&self.0)
}
}
#[non_exhaustive]
pub struct ServeError(ServeReprSource);
type ServeReprSource = std::io::Error;
impl ServeError {
pub(crate) fn io(err: std::io::Error) -> Self {
ServeError(err)
}
}
impl std::fmt::Debug for ServeError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_tuple("ServeError").field(&self.0).finish()
}
}
impl std::fmt::Display for ServeError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str("serve error")
}
}
impl std::error::Error for ServeError {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
Some(&self.0)
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::error::Error as _;
use std::path::PathBuf;
fn de_error() -> toml::de::Error {
toml::from_str::<toml::Table>("= bad").unwrap_err()
}
#[test]
fn config_error_kind_is_table_driven() {
let cases: Vec<(ConfigErrorRepr, ConfigErrorKind)> = vec![
(
ConfigErrorRepr::Read {
path: PathBuf::from("c.toml"),
source: std::io::Error::other("x"),
},
ConfigErrorKind::Read,
),
(
ConfigErrorRepr::Parse {
path: None,
source: Box::new(de_error()),
},
ConfigErrorKind::Parse,
),
(
ConfigErrorRepr::UnresolvedVar("V".to_owned()),
ConfigErrorKind::UnresolvedVar,
),
(
ConfigErrorRepr::Interpolation("bad".to_owned()),
ConfigErrorKind::Interpolation,
),
(
ConfigErrorRepr::Validation("bad".to_owned()),
ConfigErrorKind::Validation,
),
(
ConfigErrorRepr::IncludeCycle {
path: PathBuf::from("a.toml"),
chain: vec![PathBuf::from("a.toml")],
},
ConfigErrorKind::IncludeCycle,
),
(
ConfigErrorRepr::IncludeDepth {
path: PathBuf::from("a.toml"),
max: 16,
},
ConfigErrorKind::IncludeDepth,
),
];
for (repr, kind) in cases {
assert_eq!(ConfigError::from(repr).kind(), kind);
}
}
#[test]
fn config_error_read_and_parse_preserve_source_and_path() {
let read = ConfigError::from(ConfigErrorRepr::Read {
path: PathBuf::from("c.toml"),
source: std::io::Error::other("x"),
});
assert!(read.source().is_some());
assert!(read.to_string().contains("c.toml"));
let parse = ConfigError::from(ConfigErrorRepr::Parse {
path: Some(PathBuf::from("inc.toml")),
source: Box::new(de_error()),
});
assert!(parse.source().is_some());
assert!(parse.to_string().contains("inc.toml"));
}
#[test]
fn startup_error_kind_is_table_driven_and_source_preserving() {
let cfg = StartupError::config(ConfigErrorRepr::Validation("bad".to_owned()));
assert_eq!(cfg.kind(), StartupErrorKind::Config);
assert!(cfg.source().is_some());
let bind = StartupError::bind(std::io::Error::other("x"));
assert_eq!(bind.kind(), StartupErrorKind::Bind);
assert!(bind.source().is_some());
}
}