askama/
lib.rs

1//! [![Crates.io](https://img.shields.io/crates/v/askama?logo=rust&style=flat-square&logoColor=white "Crates.io")](https://crates.io/crates/askama)
2//! [![GitHub Workflow Status](https://img.shields.io/github/actions/workflow/status/askama-rs/askama/rust.yml?branch=main&logo=github&style=flat-square&logoColor=white "GitHub Workflow Status")](https://github.com/askama-rs/askama/actions/workflows/rust.yml)
3//! [![Book](https://img.shields.io/readthedocs/askama?label=book&logo=readthedocs&style=flat-square&logoColor=white "Book")](https://askama.rs/)
4//! [![docs.rs](https://img.shields.io/docsrs/askama?logo=docsdotrs&style=flat-square&logoColor=white "docs.rs")](https://docs.rs/askama/)
5//!
6//! Askama implements a type-safe compiler for Jinja-like templates.
7//! It lets you write templates in a Jinja-like syntax,
8//! which are linked to a `struct` or an `enum` defining the template context.
9//! This is done using a custom derive implementation (implemented
10//! in [`askama_macros`](https://crates.io/crates/askama_macros)).
11//!
12//! For feature highlights and a quick start, please review the
13//! [README](https://github.com/askama-rs/askama/blob/main/README.md).
14//!
15//! You can find the documentation about our syntax, features, configuration in our book:
16//! [askama.rs](https://askama.rs/).
17//!
18//! # Creating Askama templates
19//!
20//! The main feature of Askama is the [`Template`] derive macro
21//! which reads your template code, so your `struct` or `enum` can implement
22//! the [`Template`] trait and [`Display`][std::fmt::Display], type-safe and fast:
23//!
24//! ```rust
25//! # use askama::Template;
26//! #[derive(Template)]
27//! #[template(
28//!     ext = "html",
29//!     source = "<p>© {{ year }} {{ enterprise|upper }}</p>"
30//! )]
31//! struct Footer<'a> {
32//!     year: u16,
33//!     enterprise: &'a str,
34//! }
35//!
36//! assert_eq!(
37//!     Footer { year: 2025, enterprise: "<em>Askama</em> developers" }.to_string(),
38//!     "<p>© 2025 &#60;EM&#62;ASKAMA&#60;/EM&#62; DEVELOPERS</p>",
39//! );
40//! // In here you see can Askama's auto-escaping. You, the developer,
41//! // can easily disable the auto-escaping with the `|safe` filter,
42//! // but a malicious user cannot insert e.g. HTML scripts this way.
43//! ```
44//!
45//! An Askama template is a `struct` or `enum` definition which provides the template
46//! context combined with a UTF-8 encoded text file (or inline source).
47//! Askama can be used to generate any kind of text-based format.
48//! The template file's extension may be used to provide content type hints.
49//!
50//! A template consists of **text contents**, which are passed through as-is,
51//! **expressions**, which get replaced with content while being rendered, and
52//! **tags**, which control the template's logic.
53//! The template syntax is very similar to [Jinja](http://jinja.pocoo.org/),
54//! as well as Jinja-derivatives like [Twig](https://twig.symfony.com/) or
55//! [Tera](https://github.com/Keats/tera).
56
57#![cfg_attr(docsrs, feature(doc_cfg))]
58#![deny(elided_lifetimes_in_paths)]
59#![deny(unreachable_pub)]
60#![deny(missing_docs)]
61#![no_std]
62
63#[cfg(feature = "alloc")]
64extern crate alloc;
65#[cfg(feature = "std")]
66extern crate std;
67
68mod ascii_str;
69mod error;
70pub mod filters;
71#[doc(hidden)]
72pub mod helpers;
73mod html;
74mod values;
75
76#[cfg(feature = "alloc")]
77use alloc::string::String;
78use core::fmt;
79use core::ops::Deref;
80#[cfg(feature = "std")]
81use std::io;
82
83#[cfg(feature = "derive")]
84pub use askama_macros::Template;
85#[cfg(feature = "derive")]
86pub use askama_macros::filter_fn;
87
88pub use crate::error::{Error, Result};
89pub use crate::helpers::PrimitiveType;
90pub use crate::values::{NO_VALUES, Value, Values, get_value};
91
92/// Main `Template` trait; implementations are generally derived
93///
94/// If you need an object-safe template, use [`DynTemplate`].
95///
96/// ## Rendering performance
97///
98/// When rendering a askama template, you should prefer the methods
99///
100/// * [`.render()`][Template::render] (to render the content into a new string),
101/// * [`.render_into()`][Template::render_into] (to render the content into an [`fmt::Write`]
102///   object, e.g. [`String`]) or
103/// * [`.write_into()`][Template::write_into] (to render the content into an [`io::Write`] object,
104///   e.g. [`Vec<u8>`][alloc::vec::Vec])
105///
106/// over [`.to_string()`][std::string::ToString::to_string] or [`format!()`][alloc::format].
107/// While `.to_string()` and `format!()` give you the same result, they generally perform much worse
108/// than askama's own methods, because [`fmt::Write`] uses [dynamic methods calls] instead of
109/// monomorphised code. On average, expect `.to_string()` to be 100% to 200% slower than
110/// `.render()`.
111///
112/// [dynamic methods calls]: <https://doc.rust-lang.org/stable/std/keyword.dyn.html>
113pub trait Template: fmt::Display + FastWritable {
114    /// Helper method which allocates a new `String` and renders into it.
115    ///
116    /// # Errors
117    ///
118    /// It internally uses the [`core::fmt::Write`] trait so it can fail and return `Err` for the
119    /// same reasons. For other potential errors, please take a look at the [`Error`] enum variants
120    /// documentation.
121    #[inline]
122    #[cfg(feature = "alloc")]
123    fn render(&self) -> Result<String> {
124        self.render_with_values(NO_VALUES)
125    }
126
127    /// Helper method which allocates a new `String` and renders into it with provided [`Values`].
128    ///
129    /// # Errors
130    ///
131    /// It internally uses the [`core::fmt::Write`] trait so it can fail and return `Err` for the
132    /// same reasons. For other potential errors, please take a look at the [`Error`] enum variants
133    /// documentation.
134    #[inline]
135    #[cfg(feature = "alloc")]
136    fn render_with_values(&self, values: &dyn Values) -> Result<String> {
137        let mut buf = String::new();
138        let _ = buf.try_reserve(Self::SIZE_HINT);
139        self.render_into_with_values(&mut buf, values)?;
140        Ok(buf)
141    }
142
143    /// Renders the template to the given `writer` fmt buffer.
144    ///
145    /// # Errors
146    ///
147    /// It internally uses the [`core::fmt::Write`] trait so it can fail and return `Err` for the
148    /// same reasons. For other potential errors, please take a look at the [`Error`] enum variants
149    /// documentation.
150    #[inline]
151    fn render_into(&self, writer: &mut dyn fmt::Write) -> Result<()> {
152        self.render_into_with_values(writer, NO_VALUES)
153    }
154
155    /// Renders the template to the given `writer` fmt buffer with provided [`Values`].
156    ///
157    /// # Errors
158    ///
159    /// It internally uses the [`core::fmt::Write`] trait so it can fail and return `Err` for the
160    /// same reasons. For other potential errors, please take a look at the [`Error`] enum variants
161    /// documentation.
162    fn render_into_with_values(
163        &self,
164        writer: &mut dyn fmt::Write,
165        values: &dyn Values,
166    ) -> Result<()>;
167
168    /// Renders the template to the given `writer` io buffer.
169    ///
170    /// # Errors
171    ///
172    /// It internally uses the [`std::io::Write`] trait so it can fail and return `Err` for the
173    /// same reasons. For other potential errors, please take a look at the [`Error`] enum variants
174    /// documentation.
175    #[inline]
176    #[cfg(feature = "std")]
177    fn write_into(&self, writer: &mut dyn io::Write) -> io::Result<()> {
178        self.write_into_with_values(writer, NO_VALUES)
179    }
180
181    /// Renders the template to the given `writer` io buffer with provided [`Values`].
182    ///
183    /// # Errors
184    ///
185    /// It internally uses the [`std::io::Write`] trait so it can fail and return `Err` for the
186    /// same reasons. For other potential errors, please take a look at the [`Error`] enum variants
187    /// documentation.
188    #[cfg(feature = "std")]
189    fn write_into_with_values(
190        &self,
191        writer: &mut dyn io::Write,
192        values: &dyn Values,
193    ) -> io::Result<()> {
194        struct Wrapped<W: io::Write> {
195            writer: W,
196            err: Option<io::Error>,
197        }
198
199        impl<W: io::Write> fmt::Write for Wrapped<W> {
200            #[inline]
201            fn write_str(&mut self, s: &str) -> fmt::Result {
202                if let Err(err) = self.writer.write_all(s.as_bytes()) {
203                    self.err = Some(err);
204                    Err(fmt::Error)
205                } else {
206                    Ok(())
207                }
208            }
209        }
210
211        let mut wrapped = Wrapped { writer, err: None };
212        if self.render_into_with_values(&mut wrapped, values).is_ok() {
213            Ok(())
214        } else {
215            let err = wrapped.err.take();
216            Err(err.unwrap_or_else(|| io::Error::other(fmt::Error)))
217        }
218    }
219
220    /// Provides a rough estimate of the expanded length of the rendered template. Larger
221    /// values result in higher memory usage but fewer reallocations. Smaller values result in the
222    /// opposite. This value only affects [`render`]. It does not take effect when calling
223    /// [`render_into`], [`write_into`], the [`fmt::Display`] implementation, or the blanket
224    /// [`ToString::to_string`] implementation.
225    ///
226    /// [`render`]: Template::render
227    /// [`render_into`]: Template::render_into
228    /// [`write_into`]: Template::write_into
229    /// [`ToString::to_string`]: alloc::string::ToString::to_string
230    const SIZE_HINT: usize;
231}
232
233impl<T: Template + ?Sized> Template for &T {
234    #[inline]
235    #[cfg(feature = "alloc")]
236    fn render(&self) -> Result<String> {
237        <T as Template>::render(self)
238    }
239
240    #[inline]
241    #[cfg(feature = "alloc")]
242    fn render_with_values(&self, values: &dyn Values) -> Result<String> {
243        <T as Template>::render_with_values(self, values)
244    }
245
246    #[inline]
247    fn render_into(&self, writer: &mut dyn fmt::Write) -> Result<()> {
248        <T as Template>::render_into(self, writer)
249    }
250
251    #[inline]
252    fn render_into_with_values(
253        &self,
254        writer: &mut dyn fmt::Write,
255        values: &dyn Values,
256    ) -> Result<()> {
257        <T as Template>::render_into_with_values(self, writer, values)
258    }
259
260    #[inline]
261    #[cfg(feature = "std")]
262    fn write_into(&self, writer: &mut dyn io::Write) -> io::Result<()> {
263        <T as Template>::write_into(self, writer)
264    }
265
266    #[inline]
267    #[cfg(feature = "std")]
268    fn write_into_with_values(
269        &self,
270        writer: &mut dyn io::Write,
271        values: &dyn Values,
272    ) -> io::Result<()> {
273        <T as Template>::write_into_with_values(self, writer, values)
274    }
275
276    const SIZE_HINT: usize = T::SIZE_HINT;
277}
278
279/// [`dyn`-compatible] wrapper trait around [`Template`] implementers
280///
281/// This trades reduced performance (mostly due to writing into `dyn Write`) for dyn-compatibility.
282///
283/// [`dyn`-compatible]: https://doc.rust-lang.org/stable/reference/items/traits.html#dyn-compatibility
284pub trait DynTemplate {
285    /// Helper method which allocates a new `String` and renders into it.
286    #[cfg(feature = "alloc")]
287    fn dyn_render(&self) -> Result<String>;
288
289    /// Helper method which allocates a new `String` and renders into it with provided [`Values`].
290    #[cfg(feature = "alloc")]
291    fn dyn_render_with_values(&self, values: &dyn Values) -> Result<String>;
292
293    /// Renders the template to the given `writer` fmt buffer.
294    fn dyn_render_into(&self, writer: &mut dyn fmt::Write) -> Result<()>;
295
296    /// Renders the template to the given `writer` fmt buffer with provided [`Values`].
297    fn dyn_render_into_with_values(
298        &self,
299        writer: &mut dyn fmt::Write,
300        values: &dyn Values,
301    ) -> Result<()>;
302
303    /// Renders the template to the given `writer` io buffer.
304    #[cfg(feature = "std")]
305    fn dyn_write_into(&self, writer: &mut dyn io::Write) -> io::Result<()>;
306
307    /// Renders the template to the given `writer` io buffer with provided [`Values`].
308    #[cfg(feature = "std")]
309    fn dyn_write_into_with_values(
310        &self,
311        writer: &mut dyn io::Write,
312        values: &dyn Values,
313    ) -> io::Result<()>;
314
315    /// Provides a conservative estimate of the expanded length of the rendered template.
316    fn size_hint(&self) -> usize;
317}
318
319impl<T: Template> DynTemplate for T {
320    #[inline]
321    #[cfg(feature = "alloc")]
322    fn dyn_render(&self) -> Result<String> {
323        <Self as Template>::render(self)
324    }
325
326    #[inline]
327    #[cfg(feature = "alloc")]
328    fn dyn_render_with_values(&self, values: &dyn Values) -> Result<String> {
329        <Self as Template>::render_with_values(self, values)
330    }
331
332    #[inline]
333    fn dyn_render_into(&self, writer: &mut dyn fmt::Write) -> Result<()> {
334        <Self as Template>::render_into(self, writer)
335    }
336
337    #[inline]
338    fn dyn_render_into_with_values(
339        &self,
340        writer: &mut dyn fmt::Write,
341        values: &dyn Values,
342    ) -> Result<()> {
343        <Self as Template>::render_into_with_values(self, writer, values)
344    }
345
346    #[inline]
347    #[cfg(feature = "std")]
348    fn dyn_write_into(&self, writer: &mut dyn io::Write) -> io::Result<()> {
349        <Self as Template>::write_into(self, writer)
350    }
351
352    #[inline]
353    #[cfg(feature = "std")]
354    fn dyn_write_into_with_values(
355        &self,
356        writer: &mut dyn io::Write,
357        values: &dyn Values,
358    ) -> io::Result<()> {
359        <Self as Template>::write_into_with_values(self, writer, values)
360    }
361
362    #[inline]
363    fn size_hint(&self) -> usize {
364        <Self as Template>::SIZE_HINT
365    }
366}
367
368impl fmt::Display for dyn DynTemplate {
369    #[inline]
370    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
371        self.dyn_render_into(f).map_err(|_| fmt::Error {})
372    }
373}
374
375/// Implement the trait `$Trait` for a list of reference (wrapper) types to `$T: $Trait + ?Sized`
376macro_rules! impl_for_ref {
377    (impl $Trait:ident for $T:ident $body:tt) => {
378        const _: () = {
379            crate::impl_for_ref! {
380                impl<$T> $Trait for [
381                    &T
382                    &mut T
383                    core::cell::Ref<'_, T>
384                    core::cell::RefMut<'_, T>
385                ] $body
386            }
387        };
388        #[cfg(feature = "alloc")]
389        const _: () = {
390            crate::impl_for_ref! {
391                impl<$T> $Trait for [
392                    alloc::boxed::Box<T>
393                    alloc::rc::Rc<T>
394                    alloc::sync::Arc<T>
395                ] $body
396            }
397        };
398        #[cfg(feature = "std")]
399        const _: () = {
400            crate::impl_for_ref! {
401                impl<$T> $Trait for [
402                    std::sync::MutexGuard<'_, T>
403                    std::sync::RwLockReadGuard<'_, T>
404                    std::sync::RwLockWriteGuard<'_, T>
405                ] $body
406            }
407        };
408    };
409    (impl<$T:ident> $Trait:ident for [$($ty:ty)*] $body:tt) => {
410        $(impl<$T: $Trait + ?Sized> $Trait for $ty $body)*
411    }
412}
413
414/// Types implementing this trait can be written without needing to employ an [`fmt::Formatter`].
415pub trait FastWritable {
416    /// Used internally by askama to speed up writing some types.
417    fn write_into(&self, dest: &mut dyn fmt::Write, values: &dyn Values) -> crate::Result<()>;
418}
419
420const _: () = {
421    crate::impl_for_ref! {
422        impl FastWritable for T {
423            #[inline]
424            fn write_into(
425                &self,
426                dest: &mut dyn fmt::Write,
427                values: &dyn Values,
428            ) -> crate::Result<()> {
429                <T>::write_into(self, dest, values)
430            }
431        }
432    }
433
434    impl<T> FastWritable for core::pin::Pin<T>
435    where
436        T: Deref,
437        <T as Deref>::Target: FastWritable,
438    {
439        #[inline]
440        fn write_into(&self, dest: &mut dyn fmt::Write, values: &dyn Values) -> crate::Result<()> {
441            self.as_ref().get_ref().write_into(dest, values)
442        }
443    }
444
445    #[cfg(feature = "alloc")]
446    impl<T: FastWritable + alloc::borrow::ToOwned + ?Sized> FastWritable for alloc::borrow::Cow<'_, T> {
447        #[inline]
448        fn write_into(&self, dest: &mut dyn fmt::Write, values: &dyn Values) -> crate::Result<()> {
449            T::write_into(self.as_ref(), dest, values)
450        }
451    }
452
453    // implement FastWritable for a list of types
454    macro_rules! impl_for_int {
455        ($($ty:ty)*) => { $(
456            impl FastWritable for $ty {
457                #[inline]
458                fn write_into(
459                    &self,
460                    dest: &mut dyn fmt::Write,
461                    values: &dyn Values,
462                ) -> crate::Result<()> {
463                    itoa::Buffer::new().format(*self).write_into(dest, values)
464                }
465            }
466        )* };
467    }
468
469    impl_for_int!(
470        u8 u16 u32 u64 u128 usize
471        i8 i16 i32 i64 i128 isize
472    );
473
474    // implement FastWritable for a list of non-zero integral types
475    macro_rules! impl_for_nz_int {
476        ($($id:ident)*) => { $(
477            impl FastWritable for core::num::$id {
478                #[inline]
479                fn write_into(
480                    &self,
481                    dest: &mut dyn fmt::Write,
482                    values: &dyn Values,
483                ) -> crate::Result<()> {
484                    self.get().write_into(dest, values)
485                }
486            }
487        )* };
488    }
489
490    impl_for_nz_int!(
491        NonZeroU8 NonZeroU16 NonZeroU32 NonZeroU64 NonZeroU128 NonZeroUsize
492        NonZeroI8 NonZeroI16 NonZeroI32 NonZeroI64 NonZeroI128 NonZeroIsize
493    );
494
495    impl FastWritable for str {
496        #[inline]
497        fn write_into(&self, dest: &mut dyn fmt::Write, _: &dyn Values) -> crate::Result<()> {
498            Ok(dest.write_str(self)?)
499        }
500    }
501
502    #[cfg(feature = "alloc")]
503    impl FastWritable for alloc::string::String {
504        #[inline]
505        fn write_into(&self, dest: &mut dyn fmt::Write, values: &dyn Values) -> crate::Result<()> {
506            self.as_str().write_into(dest, values)
507        }
508    }
509
510    impl FastWritable for bool {
511        #[inline]
512        fn write_into(&self, dest: &mut dyn fmt::Write, _: &dyn Values) -> crate::Result<()> {
513            Ok(dest.write_str(match self {
514                true => "true",
515                false => "false",
516            })?)
517        }
518    }
519
520    impl FastWritable for char {
521        #[inline]
522        fn write_into(&self, dest: &mut dyn fmt::Write, _: &dyn Values) -> crate::Result<()> {
523            Ok(dest.write_char(*self)?)
524        }
525    }
526
527    impl FastWritable for fmt::Arguments<'_> {
528        #[inline]
529        fn write_into(&self, dest: &mut dyn fmt::Write, _: &dyn Values) -> crate::Result<()> {
530            Ok(match self.as_str() {
531                Some(s) => dest.write_str(s),
532                None => dest.write_fmt(*self),
533            }?)
534        }
535    }
536
537    impl<S: crate::Template + ?Sized> filters::WriteWritable for &filters::Writable<'_, S> {
538        #[inline]
539        fn askama_write(
540            &self,
541            dest: &mut dyn fmt::Write,
542            values: &dyn Values,
543        ) -> crate::Result<()> {
544            self.0.render_into_with_values(dest, values)
545        }
546    }
547
548    impl<S: FastWritable + ?Sized> filters::WriteWritable for &&filters::Writable<'_, S> {
549        #[inline]
550        fn askama_write(
551            &self,
552            dest: &mut dyn fmt::Write,
553            values: &dyn Values,
554        ) -> crate::Result<()> {
555            self.0.write_into(dest, values)
556        }
557    }
558
559    impl<S: fmt::Display + ?Sized> filters::WriteWritable for &&&filters::Writable<'_, S> {
560        #[inline]
561        fn askama_write(&self, dest: &mut dyn fmt::Write, _: &dyn Values) -> crate::Result<()> {
562            Ok(write!(dest, "{}", self.0)?)
563        }
564    }
565};
566
567pub(crate) use impl_for_ref;
568
569#[cfg(all(test, feature = "alloc"))]
570mod tests {
571    use std::fmt;
572
573    use super::*;
574    use crate::{DynTemplate, Template};
575
576    #[test]
577    fn dyn_template() {
578        use alloc::string::ToString;
579
580        struct Test;
581
582        impl Template for Test {
583            fn render_into_with_values(
584                &self,
585                writer: &mut dyn fmt::Write,
586                _values: &dyn Values,
587            ) -> Result<()> {
588                Ok(writer.write_str("test")?)
589            }
590
591            const SIZE_HINT: usize = 4;
592        }
593
594        impl fmt::Display for Test {
595            #[inline]
596            fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
597                self.render_into(f).map_err(|_| fmt::Error {})
598            }
599        }
600
601        impl FastWritable for Test {
602            #[inline]
603            fn write_into(&self, f: &mut dyn fmt::Write, values: &dyn Values) -> crate::Result<()> {
604                self.render_into_with_values(f, values)
605            }
606        }
607
608        fn render(t: &dyn DynTemplate) -> String {
609            t.dyn_render().unwrap()
610        }
611
612        let test = &Test as &dyn DynTemplate;
613
614        assert_eq!(render(test), "test");
615
616        assert_eq!(test.to_string(), "test");
617
618        assert_eq!(alloc::format!("{test}"), "test");
619
620        let mut vec = alloc::vec![];
621        test.dyn_write_into(&mut vec).unwrap();
622        assert_eq!(vec, alloc::vec![b't', b'e', b's', b't']);
623    }
624}