//! The library error type. //! //! Variants carry the data a caller needs to act or an operator needs to fix //! the cause, rather than a pre-formatted sentence. use std::fmt; use std::io; use std::path::PathBuf; #[derive(Debug)] pub enum Error { /// Reading or canonicalising a path named by the configuration failed. Io { path: PathBuf, cause: io::Error }, /// A configuration file is not valid TOML, or does not match the schema. Toml { path: PathBuf, cause: toml::de::Error }, /// A rule the schema cannot express was violated. Config { message: String }, /// Two sites claim one hostname, so a request naming it could not be routed. DuplicateHost { host: String, first: String, second: String }, /// A listener names a format no enabled feature provides. `available` is /// the registry's full id set, which is what tells an operator whether the /// name is a typo or a missing cargo feature. UnknownFormat { listener: String, format: String, available: Vec }, /// A document nests blocks or inlines deeper than the parser allows. Refused /// rather than parsed, because every walk over the result recurses and a /// stack overflow aborts the process rather than unwinding. TooDeep { limit: usize }, /// An include or ASCII-art target cannot be used. `path` is where the /// directive actually pointed, resolved, which is the thing an author needs /// to see when a relative target is wrong. Include { path: PathBuf, reason: IncludeReason }, } /// Why a directive's target was refused. #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum IncludeReason { /// Resolved outside the content root, or is not a regular file. Outside, Missing, /// Already being expanded further up the stack. Circular, TooDeep, /// The expansion exceeded its line or byte budget. TooLarge, } impl fmt::Display for IncludeReason { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { let text = match self { IncludeReason::Outside => "is outside the content root", IncludeReason::Missing => "does not exist", IncludeReason::Circular => "is already being included", IncludeReason::TooDeep => "includes nest too deeply", IncludeReason::TooLarge => "expands to more than the size limit", }; f.write_str(text) } } impl fmt::Display for Error { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { // The cause is reachable through `source`, so it is not repeated here. Error::Io { path, .. } => write!(f, "cannot read {}", path.display()), Error::Toml { path, .. } => write!(f, "invalid TOML in {}", path.display()), Error::Config { message } => write!(f, "{message}"), Error::DuplicateHost { host, first, second } => { write!(f, "host '{host}' is claimed by both site '{first}' and site '{second}'") } Error::TooDeep { limit } => { write!(f, "document nests more than {limit} levels deep") } Error::Include { path, reason } => { write!(f, "include target {} {reason}", path.display()) } Error::UnknownFormat { listener, format, available } => write!( f, "listener '{listener}' wants format '{format}', which this build does not \ provide; available: {}", if available.is_empty() { "none".to_string() } else { available.join(", ") } ), } } } impl std::error::Error for Error { fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { match self { Error::Io { cause, .. } => Some(cause), Error::Toml { cause, .. } => Some(cause), _ => None, } } } impl Error { pub(crate) fn config(message: impl Into) -> Self { Error::Config { message: message.into() } } }