mirror of
https://github.com/askama-rs/askama.git
synced 2025-10-02 23:35:07 +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()
|
Ok(HttpResponse::NotFound()
|
||||||
.insert_header(ContentType::html())
|
.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 {
|
} else {
|
||||||
Ok(HttpResponse::MethodNotAllowed().finish())
|
Ok(HttpResponse::MethodNotAllowed().finish())
|
||||||
}
|
}
|
||||||
@ -155,9 +155,7 @@ async fn index_handler(
|
|||||||
name: query.name,
|
name: query.name,
|
||||||
};
|
};
|
||||||
Ok(Html::new(
|
Ok(Html::new(
|
||||||
template
|
template.render().map_err(|err| err.into_io_error())?,
|
||||||
.render()
|
|
||||||
.map_err(<Box<dyn std::error::Error>>::from)?,
|
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -192,8 +190,6 @@ async fn greeting_handler(
|
|||||||
name: query.name,
|
name: query.name,
|
||||||
};
|
};
|
||||||
Ok(Html::new(
|
Ok(Html::new(
|
||||||
template
|
template.render().map_err(|err| err.into_io_error())?,
|
||||||
.render()
|
|
||||||
.map_err(<Box<dyn std::error::Error>>::from)?,
|
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
@ -1,62 +1,94 @@
|
|||||||
use std::convert::Infallible;
|
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
|
/// The [`Result`](std::result::Result) type with [`Error`] as default error type
|
||||||
pub type Result<I, E = Error> = std::result::Result<I, E>;
|
pub type Result<I, E = Error> = std::result::Result<I, E>;
|
||||||
|
|
||||||
/// rinja error type
|
/// rinja's error type
|
||||||
///
|
///
|
||||||
/// # Feature Interaction
|
/// Used as error value for e.g. [`Template::render()`][crate::Template::render()]
|
||||||
///
|
/// and custom filters.
|
||||||
/// 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.
|
|
||||||
#[non_exhaustive]
|
#[non_exhaustive]
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub enum Error {
|
pub enum Error {
|
||||||
/// formatting error
|
/// Generic, unspecified formatting error
|
||||||
Fmt,
|
Fmt,
|
||||||
/// an error raised by using `?` in a template
|
/// An error raised by using `?` in a template
|
||||||
Custom(Box<dyn std::error::Error + Send + Sync>),
|
Custom(Box<dyn StdError + Send + Sync>),
|
||||||
/// json conversion error
|
/// JSON conversion error
|
||||||
#[cfg(feature = "serde_json")]
|
#[cfg(feature = "serde_json")]
|
||||||
Json(serde_json::Error),
|
Json(serde_json::Error),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl std::error::Error for Error {
|
impl Error {
|
||||||
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
|
/// Capture an [`StdError`]
|
||||||
match *self {
|
#[inline]
|
||||||
Error::Fmt => None,
|
pub fn custom(err: impl Into<Box<dyn StdError + Send + Sync>>) -> Self {
|
||||||
Error::Custom(ref err) => Some(err.as_ref()),
|
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")]
|
#[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 {
|
impl fmt::Display for Error {
|
||||||
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
match self {
|
match self {
|
||||||
Error::Fmt => write!(formatter, "formatting error"),
|
Error::Fmt => fmt::Error.fmt(f),
|
||||||
Error::Custom(err) => write!(formatter, "{err}"),
|
Error::Custom(err) => err.fmt(f),
|
||||||
#[cfg(feature = "serde_json")]
|
#[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 {
|
impl From<fmt::Error> for Error {
|
||||||
#[inline]
|
#[inline]
|
||||||
fn from(_: fmt::Error) -> Self {
|
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]
|
#[inline]
|
||||||
fn from(_: Error) -> Self {
|
fn from(err: Box<dyn StdError + Send + Sync>) -> Self {
|
||||||
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)]
|
#[cfg(test)]
|
||||||
mod tests {
|
const _: () = {
|
||||||
use super::Error;
|
|
||||||
|
|
||||||
#[allow(dead_code)]
|
|
||||||
trait AssertSendSyncStatic: Send + Sync + 'static {}
|
trait AssertSendSyncStatic: Send + Sync + 'static {}
|
||||||
impl AssertSendSyncStatic for Error {}
|
impl AssertSendSyncStatic for Error {}
|
||||||
}
|
};
|
||||||
|
@ -263,3 +263,11 @@ impl<L: FastWritable, R: FastWritable> FastWritable for Concat<L, R> {
|
|||||||
self.1.write_into(dest)
|
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,
|
buf: &mut Buffer,
|
||||||
expr: &WithSpan<'_, Expr<'_>>,
|
expr: &WithSpan<'_, Expr<'_>>,
|
||||||
) -> Result<DisplayWrap, CompileError> {
|
) -> Result<DisplayWrap, CompileError> {
|
||||||
buf.write("rinja::helpers::core::result::Result::map_err(");
|
buf.write("rinja::helpers::map_try(");
|
||||||
self.visit_expr(ctx, buf, expr)?;
|
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)
|
Ok(DisplayWrap::Unwrapped)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user