use std::error::Error as StdError;
use std::fmt::Display;
use std::ops::Deref;
use derive_more::with_trait::Debug;
use crate::StatusCode;
use crate::error::backtrace::{__cot_create_backtrace, Backtrace as CotBacktrace};
use crate::error::not_found::NotFound;
pub struct Error {
repr: Box<ErrorImpl>,
}
impl Error {
#[must_use]
pub fn wrap<E>(error: E) -> Self
where
E: Into<Box<dyn StdError + Send + Sync + 'static>>,
{
Self {
repr: Box::new(ErrorImpl {
inner: error.into(),
status_code: None,
backtrace: __cot_create_backtrace(),
}),
}
}
#[deprecated(
note = "Use `cot::Error::internal` or `cot::Error::with_status` instead",
since = "0.4.0"
)]
#[must_use]
pub fn custom<E>(error: E) -> Self
where
E: Into<Box<dyn std::error::Error + Send + Sync + 'static>>,
{
Self::internal(error)
}
#[must_use]
pub fn internal<E>(error: E) -> Self
where
E: Into<Box<dyn StdError + Send + Sync + 'static>>,
{
Self::with_status(error, StatusCode::INTERNAL_SERVER_ERROR)
}
#[must_use]
pub fn with_status<E>(error: E, status_code: StatusCode) -> Self
where
E: Into<Box<dyn StdError + Send + Sync + 'static>>,
{
let error = Self {
repr: Box::new(ErrorImpl {
inner: error.into(),
status_code: Some(status_code),
backtrace: __cot_create_backtrace(),
}),
};
Self::wrap(WithStatusCode(error))
}
#[deprecated(
note = "Use `cot::Error::wrap`, `cot::Error::internal`, or \
`cot::Error::with_status` directly instead",
since = "0.4.0"
)]
pub fn admin<E>(error: E) -> Self
where
E: Into<Box<dyn StdError + Send + Sync + 'static>>,
{
Self::internal(error)
}
#[must_use]
#[deprecated(
note = "Use `cot::Error::from(cot::error::NotFound::new())` instead",
since = "0.4.0"
)]
pub fn not_found() -> Self {
Self::from(NotFound::new())
}
#[must_use]
#[deprecated(
note = "Use `cot::Error::from(cot::error::NotFound::with_message())` instead",
since = "0.4.0"
)]
pub fn not_found_message(message: String) -> Self {
Self::from(NotFound::with_message(message))
}
#[must_use]
pub fn status_code(&self) -> StatusCode {
self.inner()
.repr
.status_code
.unwrap_or(StatusCode::INTERNAL_SERVER_ERROR)
}
#[must_use]
pub(crate) fn backtrace(&self) -> &CotBacktrace {
&self.repr.backtrace
}
#[must_use]
pub fn inner(&self) -> &Self {
let mut error: &dyn StdError = self;
while let Some(inner) = error.source() {
if let Some(error) = inner.downcast_ref::<Self>()
&& !error.is_wrapper()
{
return error;
}
error = inner;
}
self
}
#[must_use]
pub fn is_wrapper(&self) -> bool {
self.repr.status_code.is_none()
}
}
impl Debug for Error {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
Debug::fmt(&self.repr, f)
}
}
impl Display for Error {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
Display::fmt(&self.repr.inner, f)
}
}
impl StdError for Error {
fn source(&self) -> Option<&(dyn StdError + 'static)> {
self.repr.inner.source()
}
}
impl Deref for Error {
type Target = dyn StdError + Send + Sync;
fn deref(&self) -> &Self::Target {
&*self.repr.inner
}
}
#[derive(Debug)]
struct ErrorImpl {
inner: Box<dyn StdError + Send + Sync>,
status_code: Option<StatusCode>,
#[debug(skip)]
backtrace: CotBacktrace,
}
#[derive(Debug)]
struct WithStatusCode(Error);
impl Display for WithStatusCode {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
Display::fmt(&self.0, f)
}
}
impl StdError for WithStatusCode {
fn source(&self) -> Option<&(dyn StdError + 'static)> {
Some(&self.0)
}
}
impl From<Error> for askama::Error {
fn from(value: Error) -> Self {
askama::Error::Custom(Box::new(value))
}
}
macro_rules! impl_into_cot_error {
($error_ty:ty) => {
impl From<$error_ty> for $crate::Error {
fn from(err: $error_ty) -> Self {
$crate::Error::internal(err)
}
}
};
($error_ty:ty, $status_code:ident) => {
impl From<$error_ty> for $crate::Error {
fn from(err: $error_ty) -> Self {
$crate::Error::with_status(err, $crate::StatusCode::$status_code)
}
}
};
}
pub(crate) use impl_into_cot_error;
#[derive(Debug, thiserror::Error)]
#[error("failed to render template: {0}")]
struct TemplateRender(#[from] askama::Error);
impl_into_cot_error!(TemplateRender);
impl From<askama::Error> for Error {
fn from(err: askama::Error) -> Self {
Error::from(TemplateRender(err))
}
}
#[derive(Debug, thiserror::Error)]
#[error("error while accessing the session object")]
struct SessionAccess(#[from] tower_sessions::session::Error);
impl_into_cot_error!(SessionAccess);
impl From<tower_sessions::session::Error> for Error {
fn from(err: tower_sessions::session::Error) -> Self {
Error::from(SessionAccess(err))
}
}
#[cfg(test)]
mod tests {
use serde::ser::Error as _;
use super::*;
#[test]
fn error_new() {
let inner = std::io::Error::other("server error");
let error = Error::wrap(inner);
assert!(StdError::source(&error).is_none());
assert_eq!(error.status_code(), StatusCode::INTERNAL_SERVER_ERROR);
}
#[test]
fn error_display() {
let inner = std::io::Error::other("server error");
let error = Error::internal(inner);
let display = format!("{error}");
assert_eq!(display, "server error");
}
#[test]
fn error_wrap_and_is_wrapper() {
let inner = std::io::Error::other("wrapped");
let error = Error::wrap(inner);
assert!(error.is_wrapper());
assert_eq!(error.status_code(), StatusCode::INTERNAL_SERVER_ERROR);
}
#[test]
fn error_with_status_propagation() {
let error = Error::with_status("bad request", StatusCode::BAD_REQUEST);
assert_eq!(error.status_code(), StatusCode::BAD_REQUEST);
let wrapped = Error::wrap(error);
assert_eq!(wrapped.status_code(), StatusCode::BAD_REQUEST);
}
#[test]
fn error_inner_returns_original() {
let error = Error::with_status("bad request", StatusCode::BAD_REQUEST);
let wrapped = Error::wrap(error);
assert_eq!(wrapped.inner().status_code(), StatusCode::BAD_REQUEST);
}
#[test]
fn error_inner_multiple_wrapped() {
let error = Error::with_status("bad request", StatusCode::BAD_REQUEST);
let wrapped = Error::wrap(error);
let wrapped_twice = Error::wrap(wrapped);
let wrapped_thrice = Error::wrap(wrapped_twice);
assert_eq!(wrapped_thrice.to_string(), "bad request");
assert!(wrapped_thrice.source().is_some());
assert!(wrapped_thrice.source().unwrap().source().is_none());
assert_eq!(
wrapped_thrice.inner().status_code(),
StatusCode::BAD_REQUEST
);
}
#[test]
fn error_deref_to_inner() {
let error = Error::internal("deref test");
let msg = format!("{}", &*error);
assert_eq!(msg, "deref test");
}
#[test]
fn error_from_template_render() {
let askama_err = askama::Error::Custom(Box::new(std::io::Error::other("fail")));
let error: Error = askama_err.into();
assert!(error.to_string().contains("failed to render template"));
}
#[test]
fn error_from_session_access() {
let session_err =
tower_sessions::session::Error::SerdeJson(serde_json::Error::custom("session error"));
let error: Error = session_err.into();
assert!(
error
.to_string()
.contains("error while accessing the session object")
);
}
}