mirror of
https://github.com/askama-rs/askama.git
synced 2025-09-29 14:03:59 +00:00
Make working with Error
s a bit easier
This commit is contained in:
parent
e31f4e210e
commit
ed5f485642
@ -96,7 +96,7 @@ async fn not_found_handler(req: HttpRequest) -> Result<impl Responder> {
|
||||
};
|
||||
Ok(HttpResponse::NotFound()
|
||||
.insert_header(ContentType::html())
|
||||
.body(tmpl.render().map_err(<Box<dyn std::error::Error>>::from)?))
|
||||
.body(tmpl.render().map_err(|err| err.into_io_error())?))
|
||||
} else {
|
||||
Ok(HttpResponse::MethodNotAllowed().finish())
|
||||
}
|
||||
@ -155,9 +155,7 @@ async fn index_handler(
|
||||
name: query.name,
|
||||
};
|
||||
Ok(Html::new(
|
||||
template
|
||||
.render()
|
||||
.map_err(<Box<dyn std::error::Error>>::from)?,
|
||||
template.render().map_err(|err| err.into_io_error())?,
|
||||
))
|
||||
}
|
||||
|
||||
@ -192,8 +190,6 @@ async fn greeting_handler(
|
||||
name: query.name,
|
||||
};
|
||||
Ok(Html::new(
|
||||
template
|
||||
.render()
|
||||
.map_err(<Box<dyn std::error::Error>>::from)?,
|
||||
template.render().map_err(|err| err.into_io_error())?,
|
||||
))
|
||||
}
|
||||
|
@ -1,62 +1,94 @@
|
||||
use std::convert::Infallible;
|
||||
use std::fmt::{self, Display};
|
||||
use std::error::Error as StdError;
|
||||
use std::{fmt, io};
|
||||
|
||||
/// The [`Result`](std::result::Result) type with [`Error`] as default error type
|
||||
pub type Result<I, E = Error> = std::result::Result<I, E>;
|
||||
|
||||
/// rinja error type
|
||||
/// rinja's error type
|
||||
///
|
||||
/// # Feature Interaction
|
||||
///
|
||||
/// If the feature `serde_json` is enabled an
|
||||
/// additional error variant `Json` is added.
|
||||
///
|
||||
/// # Why not `failure`/`error-chain`?
|
||||
///
|
||||
/// Error from `error-chain` are not `Sync` which
|
||||
/// can lead to problems e.g. when this is used
|
||||
/// by a crate which use `failure`. Implementing
|
||||
/// `Fail` on the other hand prevents the implementation
|
||||
/// of `std::error::Error` until specialization lands
|
||||
/// on stable. While errors impl. `Fail` can be
|
||||
/// converted to a type impl. `std::error::Error`
|
||||
/// using a adapter the benefits `failure` would
|
||||
/// bring to this crate are small, which is why
|
||||
/// `std::error::Error` was used.
|
||||
/// Used as error value for e.g. [`Template::render()`][crate::Template::render()]
|
||||
/// and custom filters.
|
||||
#[non_exhaustive]
|
||||
#[derive(Debug)]
|
||||
pub enum Error {
|
||||
/// formatting error
|
||||
/// Generic, unspecified formatting error
|
||||
Fmt,
|
||||
/// an error raised by using `?` in a template
|
||||
Custom(Box<dyn std::error::Error + Send + Sync>),
|
||||
/// json conversion error
|
||||
/// An error raised by using `?` in a template
|
||||
Custom(Box<dyn StdError + Send + Sync>),
|
||||
/// JSON conversion error
|
||||
#[cfg(feature = "serde_json")]
|
||||
Json(serde_json::Error),
|
||||
}
|
||||
|
||||
impl std::error::Error for Error {
|
||||
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
|
||||
match *self {
|
||||
Error::Fmt => None,
|
||||
Error::Custom(ref err) => Some(err.as_ref()),
|
||||
impl Error {
|
||||
/// Capture an [`StdError`]
|
||||
#[inline]
|
||||
pub fn custom(err: impl Into<Box<dyn StdError + Send + Sync>>) -> Self {
|
||||
Self::Custom(err.into())
|
||||
}
|
||||
|
||||
/// Convert this [`Error`] into a
|
||||
/// <code>[Box]<dyn [StdError] + [Send] + [Sync]></code>
|
||||
pub fn into_box(self) -> Box<dyn StdError + Send + Sync> {
|
||||
match self {
|
||||
Error::Fmt => fmt::Error.into(),
|
||||
Error::Custom(err) => err,
|
||||
#[cfg(feature = "serde_json")]
|
||||
Error::Json(ref err) => Some(err),
|
||||
Error::Json(err) => err.into(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Convert this [`Error`] into an [`io::Error`]
|
||||
///
|
||||
/// Not this error itself, but the contained [`source`][StdError::source] is returned.
|
||||
pub fn into_io_error(self) -> io::Error {
|
||||
io::Error::other(match self {
|
||||
Error::Custom(err) => match err.downcast() {
|
||||
Ok(err) => return *err,
|
||||
Err(err) => err,
|
||||
},
|
||||
err => err.into_box(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl StdError for Error {
|
||||
fn source(&self) -> Option<&(dyn StdError + 'static)> {
|
||||
match self {
|
||||
Error::Fmt => Some(&fmt::Error),
|
||||
Error::Custom(err) => Some(err.as_ref()),
|
||||
#[cfg(feature = "serde_json")]
|
||||
Error::Json(err) => Some(err),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for Error {
|
||||
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
impl fmt::Display for Error {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
Error::Fmt => write!(formatter, "formatting error"),
|
||||
Error::Custom(err) => write!(formatter, "{err}"),
|
||||
Error::Fmt => fmt::Error.fmt(f),
|
||||
Error::Custom(err) => err.fmt(f),
|
||||
#[cfg(feature = "serde_json")]
|
||||
Error::Json(err) => write!(formatter, "json conversion error: {err}"),
|
||||
Error::Json(err) => err.fmt(f),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Error> for fmt::Error {
|
||||
#[inline]
|
||||
fn from(_: Error) -> Self {
|
||||
Self
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Error> for io::Error {
|
||||
#[inline]
|
||||
fn from(err: Error) -> Self {
|
||||
err.into_io_error()
|
||||
}
|
||||
}
|
||||
|
||||
impl From<fmt::Error> for Error {
|
||||
#[inline]
|
||||
fn from(_: fmt::Error) -> Self {
|
||||
@ -64,10 +96,103 @@ impl From<fmt::Error> for Error {
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Error> for fmt::Error {
|
||||
/// This conversion inspects the argument and chooses the best fitting [`Error`] variant
|
||||
impl From<Box<dyn StdError + Send + Sync>> for Error {
|
||||
#[inline]
|
||||
fn from(_: Error) -> Self {
|
||||
Self
|
||||
fn from(err: Box<dyn StdError + Send + Sync>) -> Self {
|
||||
error_from_stderror(err, MAX_ERROR_UNWRAP_COUNT)
|
||||
}
|
||||
}
|
||||
|
||||
/// This conversion inspects the argument and chooses the best fitting [`Error`] variant
|
||||
impl From<io::Error> for Error {
|
||||
#[inline]
|
||||
fn from(err: io::Error) -> Self {
|
||||
from_from_io_error(err, MAX_ERROR_UNWRAP_COUNT)
|
||||
}
|
||||
}
|
||||
|
||||
const MAX_ERROR_UNWRAP_COUNT: usize = 5;
|
||||
|
||||
fn error_from_stderror(err: Box<dyn StdError + Send + Sync>, unwraps: usize) -> Error {
|
||||
let Some(unwraps) = unwraps.checked_sub(1) else {
|
||||
return Error::Custom(err);
|
||||
};
|
||||
match ErrorKind::inspect(err.as_ref()) {
|
||||
ErrorKind::Fmt => Error::Fmt,
|
||||
ErrorKind::Custom => Error::Custom(err),
|
||||
#[cfg(feature = "serde_json")]
|
||||
ErrorKind::Json => match err.downcast() {
|
||||
Ok(err) => Error::Json(*err),
|
||||
Err(_) => Error::Fmt, // unreachable
|
||||
},
|
||||
ErrorKind::Io => match err.downcast() {
|
||||
Ok(err) => from_from_io_error(*err, unwraps),
|
||||
Err(_) => Error::Fmt, // unreachable
|
||||
},
|
||||
ErrorKind::Rinja => match err.downcast() {
|
||||
Ok(err) => *err,
|
||||
Err(_) => Error::Fmt, // unreachable
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
fn from_from_io_error(err: io::Error, unwraps: usize) -> Error {
|
||||
let Some(inner) = err.get_ref() else {
|
||||
return Error::custom(err);
|
||||
};
|
||||
let Some(unwraps) = unwraps.checked_sub(1) else {
|
||||
return match err.into_inner() {
|
||||
Some(err) => Error::Custom(err),
|
||||
None => Error::Fmt, // unreachable
|
||||
};
|
||||
};
|
||||
match ErrorKind::inspect(inner) {
|
||||
ErrorKind::Fmt => Error::Fmt,
|
||||
ErrorKind::Rinja => match err.downcast() {
|
||||
Ok(err) => err,
|
||||
Err(_) => Error::Fmt, // unreachable
|
||||
},
|
||||
#[cfg(feature = "serde_json")]
|
||||
ErrorKind::Json => match err.downcast() {
|
||||
Ok(err) => Error::Json(err),
|
||||
Err(_) => Error::Fmt, // unreachable
|
||||
},
|
||||
ErrorKind::Custom => match err.into_inner() {
|
||||
Some(err) => Error::Custom(err),
|
||||
None => Error::Fmt, // unreachable
|
||||
},
|
||||
ErrorKind::Io => match err.downcast() {
|
||||
Ok(inner) => from_from_io_error(inner, unwraps),
|
||||
Err(_) => Error::Fmt, // unreachable
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
enum ErrorKind {
|
||||
Fmt,
|
||||
Custom,
|
||||
#[cfg(feature = "serde_json")]
|
||||
Json,
|
||||
Io,
|
||||
Rinja,
|
||||
}
|
||||
|
||||
impl ErrorKind {
|
||||
fn inspect(err: &(dyn StdError + 'static)) -> ErrorKind {
|
||||
if err.is::<fmt::Error>() {
|
||||
ErrorKind::Fmt
|
||||
} else if err.is::<io::Error>() {
|
||||
ErrorKind::Io
|
||||
} else if err.is::<Error>() {
|
||||
ErrorKind::Rinja
|
||||
} else {
|
||||
#[cfg(feature = "serde_json")]
|
||||
if err.is::<serde_json::Error>() {
|
||||
return ErrorKind::Json;
|
||||
}
|
||||
ErrorKind::Custom
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -87,10 +212,7 @@ impl From<Infallible> for Error {
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::Error;
|
||||
|
||||
#[allow(dead_code)]
|
||||
const _: () = {
|
||||
trait AssertSendSyncStatic: Send + Sync + 'static {}
|
||||
impl AssertSendSyncStatic for Error {}
|
||||
}
|
||||
};
|
||||
|
@ -263,3 +263,11 @@ impl<L: FastWritable, R: FastWritable> FastWritable for Concat<L, R> {
|
||||
self.1.write_into(dest)
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn map_try<T, E>(result: Result<T, E>) -> Result<T, crate::Error>
|
||||
where
|
||||
E: Into<Box<dyn std::error::Error + Send + Sync>>,
|
||||
{
|
||||
result.map_err(crate::Error::custom)
|
||||
}
|
||||
|
@ -1493,9 +1493,9 @@ impl<'a, 'h> Generator<'a, 'h> {
|
||||
buf: &mut Buffer,
|
||||
expr: &WithSpan<'_, Expr<'_>>,
|
||||
) -> Result<DisplayWrap, CompileError> {
|
||||
buf.write("rinja::helpers::core::result::Result::map_err(");
|
||||
buf.write("rinja::helpers::map_try(");
|
||||
self.visit_expr(ctx, buf, expr)?;
|
||||
buf.write(", |err| rinja::shared::Error::Custom(rinja::helpers::core::convert::Into::into(err)))?");
|
||||
buf.write(")?");
|
||||
Ok(DisplayWrap::Unwrapped)
|
||||
}
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user