askama/
error.rs

1#[cfg(feature = "alloc")]
2use alloc::boxed::Box;
3use core::convert::Infallible;
4use core::error::Error as StdError;
5use core::fmt;
6use core::marker::PhantomData;
7#[cfg(feature = "std")]
8use std::io;
9
10/// The [`Result`](std::result::Result) type with [`Error`] as default error type
11pub type Result<I, E = Error> = core::result::Result<I, E>;
12
13/// askama's error type
14///
15/// Used as error value for e.g. [`Template::render()`][crate::Template::render()]
16/// and custom filters.
17#[non_exhaustive]
18#[derive(Debug)]
19pub enum Error {
20    /// Generic, unspecified formatting error
21    Fmt,
22    /// Key not present in [`Values`][crate::Values]
23    ValueMissing,
24    /// Incompatible value type for key in [`Values`][crate::Values]
25    ValueType,
26    /// An error raised by using `?` in a template
27    #[cfg(feature = "alloc")]
28    Custom(Box<dyn StdError + Send + Sync>),
29    /// JSON conversion error
30    #[cfg(feature = "serde_json")]
31    Json(serde_json::Error),
32}
33
34impl Error {
35    /// Capture an [`StdError`]
36    #[inline]
37    #[cfg(feature = "alloc")]
38    pub fn custom(err: impl Into<Box<dyn StdError + Send + Sync>>) -> Self {
39        Self::Custom(err.into())
40    }
41
42    /// Convert this [`Error`] into a
43    /// <code>[Box]&lt;dyn [StdError] + [Send] + [Sync]&gt;</code>
44    #[cfg(feature = "alloc")]
45    pub fn into_box(self) -> Box<dyn StdError + Send + Sync> {
46        match self {
47            Error::Fmt => fmt::Error.into(),
48            Error::ValueMissing => Box::new(Error::ValueMissing),
49            Error::ValueType => Box::new(Error::ValueType),
50            Error::Custom(err) => err,
51            #[cfg(feature = "serde_json")]
52            Error::Json(err) => err.into(),
53        }
54    }
55
56    /// Convert this [`Error`] into an [`io::Error`]
57    ///
58    /// Not this error itself, but the contained [`source`][StdError::source] is returned.
59    #[cfg(feature = "std")]
60    pub fn into_io_error(self) -> io::Error {
61        io::Error::other(match self {
62            Error::Custom(err) => match err.downcast() {
63                Ok(err) => return *err,
64                Err(err) => err,
65            },
66            err => err.into_box(),
67        })
68    }
69}
70
71impl StdError for Error {
72    fn source(&self) -> Option<&(dyn StdError + 'static)> {
73        match self {
74            Error::Fmt => Some(&fmt::Error),
75            Error::ValueMissing => None,
76            Error::ValueType => None,
77            #[cfg(feature = "alloc")]
78            Error::Custom(err) => Some(err.as_ref()),
79            #[cfg(feature = "serde_json")]
80            Error::Json(err) => Some(err),
81        }
82    }
83}
84
85impl fmt::Display for Error {
86    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
87        match self {
88            Error::Fmt => fmt::Error.fmt(f),
89            Error::ValueMissing => f.write_str("key missing in values"),
90            Error::ValueType => f.write_str("value has wrong type"),
91            #[cfg(feature = "alloc")]
92            Error::Custom(err) => err.fmt(f),
93            #[cfg(feature = "serde_json")]
94            Error::Json(err) => err.fmt(f),
95        }
96    }
97}
98
99impl From<Error> for fmt::Error {
100    #[inline]
101    fn from(_: Error) -> Self {
102        Self
103    }
104}
105
106#[cfg(feature = "std")]
107impl From<Error> for io::Error {
108    #[inline]
109    fn from(err: Error) -> Self {
110        err.into_io_error()
111    }
112}
113
114impl From<fmt::Error> for Error {
115    #[inline]
116    fn from(_: fmt::Error) -> Self {
117        Error::Fmt
118    }
119}
120
121/// This conversion inspects the argument and chooses the best fitting [`Error`] variant
122#[cfg(feature = "alloc")]
123impl From<Box<dyn StdError + Send + Sync>> for Error {
124    #[inline]
125    fn from(err: Box<dyn StdError + Send + Sync>) -> Self {
126        error_from_stderror(err, MAX_ERROR_UNWRAP_COUNT)
127    }
128}
129
130/// This conversion inspects the argument and chooses the best fitting [`Error`] variant
131#[cfg(feature = "std")]
132impl From<io::Error> for Error {
133    #[inline]
134    fn from(err: io::Error) -> Self {
135        error_from_io_error(err, MAX_ERROR_UNWRAP_COUNT)
136    }
137}
138
139#[cfg(feature = "alloc")]
140const MAX_ERROR_UNWRAP_COUNT: usize = 5;
141
142#[cfg(feature = "alloc")]
143fn error_from_stderror(err: Box<dyn StdError + Send + Sync>, unwraps: usize) -> Error {
144    let Some(unwraps) = unwraps.checked_sub(1) else {
145        return Error::Custom(err);
146    };
147    #[cfg(not(feature = "std"))]
148    let _ = unwraps;
149    match ErrorKind::inspect(err.as_ref()) {
150        ErrorKind::Fmt => Error::Fmt,
151        ErrorKind::Custom => Error::Custom(err),
152        #[cfg(feature = "serde_json")]
153        ErrorKind::Json => match err.downcast() {
154            Ok(err) => Error::Json(*err),
155            Err(_) => Error::Fmt, // unreachable
156        },
157        #[cfg(feature = "std")]
158        ErrorKind::Io => match err.downcast() {
159            Ok(err) => error_from_io_error(*err, unwraps),
160            Err(_) => Error::Fmt, // unreachable
161        },
162        ErrorKind::Askama => match err.downcast() {
163            Ok(err) => *err,
164            Err(_) => Error::Fmt, // unreachable
165        },
166    }
167}
168
169#[cfg(feature = "std")]
170fn error_from_io_error(err: io::Error, unwraps: usize) -> Error {
171    let Some(inner) = err.get_ref() else {
172        return Error::custom(err);
173    };
174    let Some(unwraps) = unwraps.checked_sub(1) else {
175        return match err.into_inner() {
176            Some(err) => Error::Custom(err),
177            None => Error::Fmt, // unreachable
178        };
179    };
180    match ErrorKind::inspect(inner) {
181        ErrorKind::Fmt => Error::Fmt,
182        ErrorKind::Askama => match err.downcast() {
183            Ok(err) => err,
184            Err(_) => Error::Fmt, // unreachable
185        },
186        #[cfg(feature = "serde_json")]
187        ErrorKind::Json => match err.downcast() {
188            Ok(err) => Error::Json(err),
189            Err(_) => Error::Fmt, // unreachable
190        },
191        ErrorKind::Custom => match err.into_inner() {
192            Some(err) => Error::Custom(err),
193            None => Error::Fmt, // unreachable
194        },
195        ErrorKind::Io => match err.downcast() {
196            Ok(inner) => error_from_io_error(inner, unwraps),
197            Err(_) => Error::Fmt, // unreachable
198        },
199    }
200}
201
202#[cfg(feature = "alloc")]
203enum ErrorKind {
204    Fmt,
205    Custom,
206    #[cfg(feature = "serde_json")]
207    Json,
208    #[cfg(feature = "std")]
209    Io,
210    Askama,
211}
212
213#[cfg(feature = "alloc")]
214impl ErrorKind {
215    fn inspect(err: &(dyn StdError + 'static)) -> ErrorKind {
216        if err.is::<fmt::Error>() {
217            return ErrorKind::Fmt;
218        }
219
220        #[cfg(feature = "std")]
221        if err.is::<io::Error>() {
222            return ErrorKind::Io;
223        }
224
225        if err.is::<Error>() {
226            return ErrorKind::Askama;
227        }
228
229        #[cfg(feature = "serde_json")]
230        if err.is::<serde_json::Error>() {
231            return ErrorKind::Json;
232        }
233
234        ErrorKind::Custom
235    }
236}
237
238#[cfg(feature = "serde_json")]
239impl From<serde_json::Error> for Error {
240    #[inline]
241    fn from(err: serde_json::Error) -> Self {
242        Error::Json(err)
243    }
244}
245
246impl From<Infallible> for Error {
247    #[inline]
248    fn from(value: Infallible) -> Self {
249        match value {}
250    }
251}
252
253#[cfg(test)]
254const _: () = {
255    trait AssertSendSyncStatic: Send + Sync + 'static {}
256    impl AssertSendSyncStatic for Error {}
257};
258
259/// Helper trait to convert a custom `?` call into a [`crate::Result`]
260pub trait ResultConverter {
261    /// Okay Value type of the output
262    type Value;
263    /// Input type
264    type Input;
265
266    /// Consume an interior mutable `self`, and turn it into a [`crate::Result`]
267    fn askama_conv_result(self, result: Self::Input) -> Result<Self::Value, Error>;
268}
269
270/// Helper marker to be used with [`ResultConverter`]
271#[derive(Debug, Clone, Copy)]
272pub struct ErrorMarker<T>(PhantomData<Result<T>>);
273
274impl<T> ErrorMarker<T> {
275    /// Get marker for a [`Result`] type
276    #[inline]
277    pub fn of(_: &T) -> Self {
278        Self(PhantomData)
279    }
280}
281
282#[cfg(feature = "alloc")]
283impl<T, E> ResultConverter for &ErrorMarker<Result<T, E>>
284where
285    E: Into<Box<dyn StdError + Send + Sync>>,
286{
287    type Value = T;
288    type Input = Result<T, E>;
289
290    #[inline]
291    fn askama_conv_result(self, result: Self::Input) -> Result<Self::Value, Error> {
292        result.map_err(Error::custom)
293    }
294}
295
296impl<T, E> ResultConverter for &&ErrorMarker<Result<T, E>>
297where
298    E: Into<Error>,
299{
300    type Value = T;
301    type Input = Result<T, E>;
302
303    #[inline]
304    fn askama_conv_result(self, result: Self::Input) -> Result<Self::Value, Error> {
305        result.map_err(Into::into)
306    }
307}