pub(crate) mod backtrace;
use std::fmt::Display;
use derive_more::Debug;
use thiserror::Error;
use crate::error::backtrace::{__cot_create_backtrace, Backtrace as CotBacktrace};
#[derive(Debug)]
pub struct Error {
pub(crate) inner: ErrorRepr,
#[debug(skip)]
backtrace: CotBacktrace,
}
impl Error {
#[must_use]
pub(crate) fn new(inner: ErrorRepr) -> Self {
Self {
inner,
backtrace: __cot_create_backtrace(),
}
}
#[must_use]
pub fn custom<E>(error: E) -> Self
where
E: Into<Box<dyn std::error::Error + Send + Sync + 'static>>,
{
Self::new(ErrorRepr::Custom(error.into()))
}
pub fn admin<E>(error: E) -> Self
where
E: Into<Box<dyn std::error::Error + Send + Sync + 'static>>,
{
Self::new(ErrorRepr::AdminError(error.into()))
}
#[must_use]
pub fn not_found() -> Self {
Self::new(ErrorRepr::NotFound { message: None })
}
#[must_use]
pub fn not_found_message(message: String) -> Self {
Self::new(ErrorRepr::NotFound {
message: Some(message),
})
}
#[must_use]
pub(crate) fn backtrace(&self) -> &CotBacktrace {
&self.backtrace
}
}
impl Display for Error {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
Display::fmt(&self.inner, f)
}
}
impl std::error::Error for Error {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
self.inner.source()
}
}
impl From<ErrorRepr> for Error {
fn from(value: ErrorRepr) -> Self {
Self::new(value)
}
}
macro_rules! impl_error_from_repr {
($ty:ty) => {
impl From<$ty> for Error {
fn from(value: $ty) -> Self {
Error::from(ErrorRepr::from(value))
}
}
};
}
impl From<Error> for askama::Error {
fn from(value: Error) -> Self {
askama::Error::Custom(Box::new(value))
}
}
impl_error_from_repr!(toml::de::Error);
impl_error_from_repr!(askama::Error);
impl_error_from_repr!(crate::router::path::ReverseError);
#[cfg(feature = "db")]
impl_error_from_repr!(crate::db::DatabaseError);
impl_error_from_repr!(tower_sessions::session::Error);
impl_error_from_repr!(crate::form::FormError);
impl_error_from_repr!(crate::auth::AuthError);
impl_error_from_repr!(crate::request::PathParamsDeserializerError);
impl_error_from_repr!(crate::request::extractors::StaticFilesGetError);
#[derive(Debug, Error)]
#[non_exhaustive]
pub(crate) enum ErrorRepr {
#[error(transparent)]
Custom(Box<dyn std::error::Error + Send + Sync>),
#[error("Could not read the config file at `{config}` or `config/{config}.toml`")]
LoadConfig {
config: String,
source: std::io::Error,
},
#[error("Could not parse the config: {source}")]
ParseConfig {
#[from]
source: toml::de::Error,
},
#[error("Could not start server: {source}")]
StartServer { source: std::io::Error },
#[error("Could not collect static files: {source}")]
CollectStatic { source: std::io::Error },
#[error("Could not retrieve request body: {source}")]
ReadRequestBody {
#[source]
source: Box<dyn std::error::Error + Send + Sync>,
},
#[error("Invalid content type; expected `{expected}`, found `{actual}`")]
InvalidContentType {
expected: &'static str,
actual: String,
},
#[error("Not found: {message:?}")]
NotFound { message: Option<String> },
#[error("Could not create a response object: {0}")]
ResponseBuilder(#[from] http::Error),
#[error("Failed to reverse route `{view_name}` due to view not existing")]
NoViewToReverse {
app_name: Option<String>,
view_name: String,
},
#[error("Failed to reverse route: {0}")]
ReverseRoute(#[from] crate::router::path::ReverseError),
#[error("Failed to render template: {0}")]
TemplateRender(#[from] askama::Error),
#[error("Database error: {0}")]
#[cfg(feature = "db")]
Database(#[from] crate::db::DatabaseError),
#[error("Error while accessing the session object")]
SessionAccess(#[from] tower_sessions::session::Error),
#[error("Failed to process a form: {0}")]
Form(#[from] crate::form::FormError),
#[error("Failed to authenticate user: {0}")]
Authentication(#[from] crate::auth::AuthError),
#[error("JSON error: {0}")]
#[cfg(feature = "json")]
Json(serde_path_to_error::Error<serde_json::Error>),
#[error(transparent)]
MiddlewareWrapped {
source: Box<dyn std::error::Error + Send + Sync>,
},
#[error("Could not parse path parameters: {0}")]
PathParametersParse(#[from] crate::request::PathParamsDeserializerError),
#[error("Could not parse query parameters: {0}")]
QueryParametersParse(serde_path_to_error::Error<serde::de::value::Error>),
#[error("Admin error: {0}")]
AdminError(#[source] Box<dyn std::error::Error + Send + Sync>),
#[error("Could not get URL for a static file: {0}")]
StaticFilesGetError(#[from] crate::request::extractors::StaticFilesGetError),
}
#[cfg(test)]
mod tests {
use std::io;
use super::*;
#[test]
fn test_error_new() {
let inner = ErrorRepr::StartServer {
source: io::Error::other("server error"),
};
let error = Error::new(inner);
assert!(std::error::Error::source(&error).is_some());
}
#[test]
fn test_error_display() {
let inner = ErrorRepr::InvalidContentType {
expected: "application/json",
actual: "text/html".to_string(),
};
let error = Error::new(inner);
let display = format!("{error}");
assert_eq!(
display,
"Invalid content type; expected `application/json`, found `text/html`"
);
}
#[test]
fn test_error_from_repr() {
let inner = ErrorRepr::NoViewToReverse {
app_name: None,
view_name: "home".to_string(),
};
let error: Error = inner.into();
assert_eq!(
format!("{error}"),
"Failed to reverse route `home` due to view not existing"
);
}
}