Pass variables to sub-templates more reliably even if indirectly

This commit is contained in:
René Kijewski 2025-04-11 12:09:45 +02:00
parent 4a17df55b5
commit d6bf966048
11 changed files with 182 additions and 58 deletions

View File

@ -6,7 +6,7 @@ use core::pin::Pin;
use super::MAX_LEN; use super::MAX_LEN;
use super::escape::FastWritable; use super::escape::FastWritable;
use crate::{Error, Result}; use crate::{Error, Result, Values};
/// Limit string length, appends '...' if truncated /// Limit string length, appends '...' if truncated
/// ///
@ -50,9 +50,13 @@ impl<S: fmt::Display> fmt::Display for TruncateFilter<S> {
impl<S: FastWritable> FastWritable for TruncateFilter<S> { impl<S: FastWritable> FastWritable for TruncateFilter<S> {
#[inline] #[inline]
fn write_into<W: fmt::Write + ?Sized>(&self, dest: &mut W) -> crate::Result<()> { fn write_into<W: fmt::Write + ?Sized>(
&self,
dest: &mut W,
values: &dyn Values,
) -> crate::Result<()> {
self.source self.source
.write_into(&mut TruncateWriter::new(dest, self.remaining)) .write_into(&mut TruncateWriter::new(dest, self.remaining), values)
} }
} }
@ -440,10 +444,14 @@ impl<S: fmt::Display, P: fmt::Display> fmt::Display for Pluralize<S, P> {
impl<S: FastWritable, P: FastWritable> FastWritable for Pluralize<S, P> { impl<S: FastWritable, P: FastWritable> FastWritable for Pluralize<S, P> {
#[inline] #[inline]
fn write_into<W: fmt::Write + ?Sized>(&self, dest: &mut W) -> crate::Result<()> { fn write_into<W: fmt::Write + ?Sized>(
&self,
dest: &mut W,
values: &dyn Values,
) -> crate::Result<()> {
match self { match self {
Pluralize::Singular(value) => value.write_into(dest), Pluralize::Singular(value) => value.write_into(dest, values),
Pluralize::Plural(value) => value.write_into(dest), Pluralize::Plural(value) => value.write_into(dest, values),
} }
} }
} }

View File

@ -82,8 +82,12 @@ impl<T: fmt::Display, E: Escaper> fmt::Display for EscapeDisplay<T, E> {
impl<T: FastWritable, E: Escaper> FastWritable for EscapeDisplay<T, E> { impl<T: FastWritable, E: Escaper> FastWritable for EscapeDisplay<T, E> {
#[inline] #[inline]
fn write_into<W: fmt::Write + ?Sized>(&self, dest: &mut W) -> crate::Result<()> { fn write_into<W: fmt::Write + ?Sized>(
self.0.write_into(&mut EscapeWriter(dest, self.1)) &self,
dest: &mut W,
values: &dyn Values,
) -> crate::Result<()> {
self.0.write_into(&mut EscapeWriter(dest, self.1), values)
} }
} }
@ -303,12 +307,16 @@ const _: () = {
// This is the fallback. The filter is not the last element of the filter chain. // This is the fallback. The filter is not the last element of the filter chain.
impl<T: FastWritable> FastWritable for MaybeSafe<T> { impl<T: FastWritable> FastWritable for MaybeSafe<T> {
#[inline] #[inline]
fn write_into<W: fmt::Write + ?Sized>(&self, dest: &mut W) -> crate::Result<()> { fn write_into<W: fmt::Write + ?Sized>(
&self,
dest: &mut W,
values: &dyn Values,
) -> crate::Result<()> {
let inner = match self { let inner = match self {
MaybeSafe::Safe(inner) => inner, MaybeSafe::Safe(inner) => inner,
MaybeSafe::NeedsEscaping(inner) => inner, MaybeSafe::NeedsEscaping(inner) => inner,
}; };
inner.write_into(dest) inner.write_into(dest, values)
} }
} }
@ -338,10 +346,14 @@ const _: () = {
} }
impl<T: FastWritable + ?Sized, E: Escaper> FastWritable for Wrapped<'_, T, E> { impl<T: FastWritable + ?Sized, E: Escaper> FastWritable for Wrapped<'_, T, E> {
fn write_into<W: fmt::Write + ?Sized>(&self, dest: &mut W) -> crate::Result<()> { fn write_into<W: fmt::Write + ?Sized>(
&self,
dest: &mut W,
values: &dyn Values,
) -> crate::Result<()> {
match *self { match *self {
Wrapped::Safe(t) => t.write_into(dest), Wrapped::Safe(t) => t.write_into(dest, values),
Wrapped::NeedsEscaping(t, e) => EscapeDisplay(t, e).write_into(dest), Wrapped::NeedsEscaping(t, e) => EscapeDisplay(t, e).write_into(dest, values),
} }
} }
} }
@ -409,8 +421,12 @@ const _: () = {
// This is the fallback. The filter is not the last element of the filter chain. // This is the fallback. The filter is not the last element of the filter chain.
impl<T: FastWritable> FastWritable for Safe<T> { impl<T: FastWritable> FastWritable for Safe<T> {
#[inline] #[inline]
fn write_into<W: fmt::Write + ?Sized>(&self, dest: &mut W) -> crate::Result<()> { fn write_into<W: fmt::Write + ?Sized>(
self.0.write_into(dest) &self,
dest: &mut W,
values: &dyn Values,
) -> crate::Result<()> {
self.0.write_into(dest, values)
} }
} }
@ -503,15 +519,23 @@ pub trait WriteWritable {
/// Types implementing this trait can be written without needing to employ an [`fmt::Formatter`]. /// Types implementing this trait can be written without needing to employ an [`fmt::Formatter`].
pub trait FastWritable { pub trait FastWritable {
/// Used internally by askama to speed up writing some types. /// Used internally by askama to speed up writing some types.
fn write_into<W: fmt::Write + ?Sized>(&self, dest: &mut W) -> crate::Result<()>; fn write_into<W: fmt::Write + ?Sized>(
&self,
dest: &mut W,
values: &dyn Values,
) -> crate::Result<()>;
} }
const _: () = { const _: () = {
crate::impl_for_ref! { crate::impl_for_ref! {
impl FastWritable for T { impl FastWritable for T {
#[inline] #[inline]
fn write_into<W: fmt::Write + ?Sized>(&self, dest: &mut W) -> crate::Result<()> { fn write_into<W: fmt::Write + ?Sized>(
<T>::write_into(self, dest) &self,
dest: &mut W,
values: &dyn Values,
) -> crate::Result<()> {
<T>::write_into(self, dest, values)
} }
} }
} }
@ -522,16 +546,24 @@ const _: () = {
<T as Deref>::Target: FastWritable, <T as Deref>::Target: FastWritable,
{ {
#[inline] #[inline]
fn write_into<W: fmt::Write + ?Sized>(&self, dest: &mut W) -> crate::Result<()> { fn write_into<W: fmt::Write + ?Sized>(
self.as_ref().get_ref().write_into(dest) &self,
dest: &mut W,
values: &dyn Values,
) -> crate::Result<()> {
self.as_ref().get_ref().write_into(dest, values)
} }
} }
#[cfg(feature = "alloc")] #[cfg(feature = "alloc")]
impl<T: FastWritable + alloc::borrow::ToOwned> FastWritable for alloc::borrow::Cow<'_, T> { impl<T: FastWritable + alloc::borrow::ToOwned> FastWritable for alloc::borrow::Cow<'_, T> {
#[inline] #[inline]
fn write_into<W: fmt::Write + ?Sized>(&self, dest: &mut W) -> crate::Result<()> { fn write_into<W: fmt::Write + ?Sized>(
T::write_into(self.as_ref(), dest) &self,
dest: &mut W,
values: &dyn Values,
) -> crate::Result<()> {
T::write_into(self.as_ref(), dest, values)
} }
} }
@ -540,8 +572,12 @@ const _: () = {
($($ty:ty)*) => { $( ($($ty:ty)*) => { $(
impl FastWritable for $ty { impl FastWritable for $ty {
#[inline] #[inline]
fn write_into<W: fmt::Write + ?Sized>(&self, dest: &mut W) -> crate::Result<()> { fn write_into<W: fmt::Write + ?Sized>(
itoa::Buffer::new().format(*self).write_into(dest) &self,
dest: &mut W,
values: &dyn Values,
) -> crate::Result<()> {
itoa::Buffer::new().format(*self).write_into(dest, values)
} }
} }
)* }; )* };
@ -557,8 +593,12 @@ const _: () = {
($($id:ident)*) => { $( ($($id:ident)*) => { $(
impl FastWritable for core::num::$id { impl FastWritable for core::num::$id {
#[inline] #[inline]
fn write_into<W: fmt::Write + ?Sized>(&self, dest: &mut W) -> crate::Result<()> { fn write_into<W: fmt::Write + ?Sized>(
self.get().write_into(dest) &self,
dest: &mut W,
values: &dyn Values,
) -> crate::Result<()> {
self.get().write_into(dest, values)
} }
} }
)* }; )* };
@ -571,7 +611,11 @@ const _: () = {
impl FastWritable for str { impl FastWritable for str {
#[inline] #[inline]
fn write_into<W: fmt::Write + ?Sized>(&self, dest: &mut W) -> crate::Result<()> { fn write_into<W: fmt::Write + ?Sized>(
&self,
dest: &mut W,
_: &dyn Values,
) -> crate::Result<()> {
Ok(dest.write_str(self)?) Ok(dest.write_str(self)?)
} }
} }
@ -579,14 +623,22 @@ const _: () = {
#[cfg(feature = "alloc")] #[cfg(feature = "alloc")]
impl FastWritable for alloc::string::String { impl FastWritable for alloc::string::String {
#[inline] #[inline]
fn write_into<W: fmt::Write + ?Sized>(&self, dest: &mut W) -> crate::Result<()> { fn write_into<W: fmt::Write + ?Sized>(
self.as_str().write_into(dest) &self,
dest: &mut W,
values: &dyn Values,
) -> crate::Result<()> {
self.as_str().write_into(dest, values)
} }
} }
impl FastWritable for bool { impl FastWritable for bool {
#[inline] #[inline]
fn write_into<W: fmt::Write + ?Sized>(&self, dest: &mut W) -> crate::Result<()> { fn write_into<W: fmt::Write + ?Sized>(
&self,
dest: &mut W,
_: &dyn Values,
) -> crate::Result<()> {
Ok(dest.write_str(match self { Ok(dest.write_str(match self {
true => "true", true => "true",
false => "false", false => "false",
@ -596,13 +648,21 @@ const _: () = {
impl FastWritable for char { impl FastWritable for char {
#[inline] #[inline]
fn write_into<W: fmt::Write + ?Sized>(&self, dest: &mut W) -> crate::Result<()> { fn write_into<W: fmt::Write + ?Sized>(
&self,
dest: &mut W,
_: &dyn Values,
) -> crate::Result<()> {
Ok(dest.write_char(*self)?) Ok(dest.write_char(*self)?)
} }
} }
impl FastWritable for fmt::Arguments<'_> { impl FastWritable for fmt::Arguments<'_> {
fn write_into<W: fmt::Write + ?Sized>(&self, dest: &mut W) -> crate::Result<()> { fn write_into<W: fmt::Write + ?Sized>(
&self,
dest: &mut W,
_: &dyn Values,
) -> crate::Result<()> {
Ok(match self.as_str() { Ok(match self.as_str() {
Some(s) => dest.write_str(s), Some(s) => dest.write_str(s),
None => dest.write_fmt(*self), None => dest.write_fmt(*self),
@ -626,9 +686,9 @@ const _: () = {
fn askama_write<W: fmt::Write + ?Sized>( fn askama_write<W: fmt::Write + ?Sized>(
&self, &self,
dest: &mut W, dest: &mut W,
_: &dyn Values, values: &dyn Values,
) -> crate::Result<()> { ) -> crate::Result<()> {
self.0.write_into(dest) self.0.write_into(dest, values)
} }
} }

View File

@ -4,6 +4,7 @@ use core::mem::MaybeUninit;
use super::FastWritable; use super::FastWritable;
use crate::ascii_str::{AsciiChar, AsciiStr}; use crate::ascii_str::{AsciiChar, AsciiStr};
use crate::{NO_VALUES, Values};
/// Returns adequate string representation (in KB, ..) of number of bytes /// Returns adequate string representation (in KB, ..) of number of bytes
/// ///
@ -33,14 +34,18 @@ pub struct FilesizeFormatFilter(f32);
impl fmt::Display for FilesizeFormatFilter { impl fmt::Display for FilesizeFormatFilter {
#[inline] #[inline]
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
Ok(self.write_into(f)?) Ok(self.write_into(f, NO_VALUES)?)
} }
} }
impl FastWritable for FilesizeFormatFilter { impl FastWritable for FilesizeFormatFilter {
fn write_into<W: fmt::Write + ?Sized>(&self, dest: &mut W) -> crate::Result<()> { fn write_into<W: fmt::Write + ?Sized>(
&self,
dest: &mut W,
values: &dyn Values,
) -> crate::Result<()> {
if self.0 < 1e3 { if self.0 < 1e3 {
(self.0 as u32).write_into(dest)?; (self.0 as u32).write_into(dest, values)?;
Ok(dest.write_str(" B")?) Ok(dest.write_str(" B")?)
} else if let Some((prefix, factor)) = SI_PREFIXES } else if let Some((prefix, factor)) = SI_PREFIXES
.iter() .iter()
@ -49,8 +54,8 @@ impl FastWritable for FilesizeFormatFilter {
{ {
// u32 is big enough to hold the number 999_999 // u32 is big enough to hold the number 999_999
let scaled = (self.0 * factor) as u32; let scaled = (self.0 * factor) as u32;
(scaled / 100).write_into(dest)?; (scaled / 100).write_into(dest, values)?;
format_frac(&mut MaybeUninit::uninit(), prefix, scaled).write_into(dest) format_frac(&mut MaybeUninit::uninit(), prefix, scaled).write_into(dest, values)
} else { } else {
too_big(self.0, dest) too_big(self.0, dest)
} }

View File

@ -8,6 +8,7 @@ use serde_json::ser::{CompactFormatter, PrettyFormatter, Serializer};
use super::FastWritable; use super::FastWritable;
use crate::ascii_str::{AsciiChar, AsciiStr}; use crate::ascii_str::{AsciiChar, AsciiStr};
use crate::{NO_VALUES, Values};
/// Serialize to JSON (requires `json` feature) /// Serialize to JSON (requires `json` feature)
/// ///
@ -188,7 +189,8 @@ where
} }
impl<S: Serialize> FastWritable for ToJson<S> { impl<S: Serialize> FastWritable for ToJson<S> {
fn write_into<W: fmt::Write + ?Sized>(&self, f: &mut W) -> crate::Result<()> { #[inline]
fn write_into<W: fmt::Write + ?Sized>(&self, f: &mut W, _: &dyn Values) -> crate::Result<()> {
serialize(f, &self.value, CompactFormatter) serialize(f, &self.value, CompactFormatter)
} }
} }
@ -196,12 +198,13 @@ impl<S: Serialize> FastWritable for ToJson<S> {
impl<S: Serialize> fmt::Display for ToJson<S> { impl<S: Serialize> fmt::Display for ToJson<S> {
#[inline] #[inline]
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
Ok(self.write_into(f)?) Ok(self.write_into(f, NO_VALUES)?)
} }
} }
impl<S: Serialize, I: AsIndent> FastWritable for ToJsonPretty<S, I> { impl<S: Serialize, I: AsIndent> FastWritable for ToJsonPretty<S, I> {
fn write_into<W: fmt::Write + ?Sized>(&self, f: &mut W) -> crate::Result<()> { #[inline]
fn write_into<W: fmt::Write + ?Sized>(&self, f: &mut W, _: &dyn Values) -> crate::Result<()> {
serialize( serialize(
f, f,
&self.value, &self.value,
@ -213,11 +216,10 @@ impl<S: Serialize, I: AsIndent> FastWritable for ToJsonPretty<S, I> {
impl<S: Serialize, I: AsIndent> fmt::Display for ToJsonPretty<S, I> { impl<S: Serialize, I: AsIndent> fmt::Display for ToJsonPretty<S, I> {
#[inline] #[inline]
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
Ok(self.write_into(f)?) Ok(self.write_into(f, NO_VALUES)?)
} }
} }
#[inline]
fn serialize<S, W, F>(dest: &mut W, value: &S, formatter: F) -> Result<(), crate::Error> fn serialize<S, W, F>(dest: &mut W, value: &S, formatter: F) -> Result<(), crate::Error>
where where
S: Serialize + ?Sized, S: Serialize + ?Sized,

View File

@ -4,6 +4,7 @@ use std::fmt::Write;
use percent_encoding::{AsciiSet, NON_ALPHANUMERIC, utf8_percent_encode}; use percent_encoding::{AsciiSet, NON_ALPHANUMERIC, utf8_percent_encode};
use crate::Values;
use crate::filters::{FastWritable, HtmlSafeOutput}; use crate::filters::{FastWritable, HtmlSafeOutput};
// Urlencode char encoding set. Only the characters in the unreserved set don't // Urlencode char encoding set. Only the characters in the unreserved set don't
@ -110,8 +111,12 @@ impl<T: fmt::Display> fmt::Display for UrlencodeFilter<T> {
impl<T: FastWritable> FastWritable for UrlencodeFilter<T> { impl<T: FastWritable> FastWritable for UrlencodeFilter<T> {
#[inline] #[inline]
fn write_into<W: fmt::Write + ?Sized>(&self, f: &mut W) -> crate::Result<()> { fn write_into<W: fmt::Write + ?Sized>(
self.0.write_into(&mut UrlencodeWriter(f, self.1)) &self,
f: &mut W,
values: &dyn Values,
) -> crate::Result<()> {
self.0.write_into(&mut UrlencodeWriter(f, self.1), values)
} }
} }

View File

@ -12,6 +12,7 @@ use core::iter::{Enumerate, Peekable};
use core::ops::Deref; use core::ops::Deref;
use core::pin::Pin; use core::pin::Pin;
use crate::Values;
pub use crate::error::{ErrorMarker, ResultConverter}; pub use crate::error::{ErrorMarker, ResultConverter};
use crate::filters::FastWritable; use crate::filters::FastWritable;
pub use crate::values::get_value; pub use crate::values::get_value;
@ -245,7 +246,7 @@ impl fmt::Display for Empty {
impl FastWritable for Empty { impl FastWritable for Empty {
#[inline] #[inline]
fn write_into<W: fmt::Write + ?Sized>(&self, _: &mut W) -> crate::Result<()> { fn write_into<W: fmt::Write + ?Sized>(&self, _: &mut W, _: &dyn Values) -> crate::Result<()> {
Ok(()) Ok(())
} }
} }
@ -267,9 +268,13 @@ impl<L: fmt::Display, R: fmt::Display> fmt::Display for Concat<L, R> {
impl<L: FastWritable, R: FastWritable> FastWritable for Concat<L, R> { impl<L: FastWritable, R: FastWritable> FastWritable for Concat<L, R> {
#[inline] #[inline]
fn write_into<W: fmt::Write + ?Sized>(&self, dest: &mut W) -> crate::Result<()> { fn write_into<W: fmt::Write + ?Sized>(
self.0.write_into(dest)?; &self,
self.1.write_into(dest) dest: &mut W,
values: &dyn Values,
) -> crate::Result<()> {
self.0.write_into(dest, values)?;
self.1.write_into(dest, values)
} }
} }

View File

@ -406,8 +406,12 @@ mod tests {
impl filters::FastWritable for Test { impl filters::FastWritable for Test {
#[inline] #[inline]
fn write_into<W: fmt::Write + ?Sized>(&self, f: &mut W) -> crate::Result<()> { fn write_into<W: fmt::Write + ?Sized>(
self.render_into(f) &self,
f: &mut W,
values: &dyn Values,
) -> crate::Result<()> {
self.render_into_with_values(f, values)
} }
} }

View File

@ -310,11 +310,15 @@ impl<'a, 'h> Generator<'a, 'h> {
impl #wrapper_impl_generics askama::filters::FastWritable impl #wrapper_impl_generics askama::filters::FastWritable
for #wrapper_id #wrapper_ty_generics #wrapper_where_clause { for #wrapper_id #wrapper_ty_generics #wrapper_where_clause {
#[inline] #[inline]
fn write_into<AskamaW>(&self, dest: &mut AskamaW) -> askama::Result<()> fn write_into<AskamaW>(
&self,
dest: &mut AskamaW,
values: &dyn askama::Values,
) -> askama::Result<()>
where where
AskamaW: askama::helpers::core::fmt::Write + ?askama::helpers::core::marker::Sized AskamaW: askama::helpers::core::fmt::Write + ?askama::helpers::core::marker::Sized
{ {
<_ as askama::Template>::render_into(self, dest) <_ as askama::Template>::render_into_with_values(self, dest, values)
} }
} }

View File

@ -64,11 +64,15 @@ fn impl_fast_writable(ast: &DeriveInput, buf: &mut Buffer) {
buf.write( buf.write(
"\ "\
#[inline]\ #[inline]\
fn write_into<AskamaW>(&self, dest: &mut AskamaW) -> askama::Result<()> \ fn write_into<AskamaW>(\
&self,\
dest: &mut AskamaW,\
values: &dyn askama::Values\
) -> askama::Result<()> \
where \ where \
AskamaW: askama::helpers::core::fmt::Write + ?askama::helpers::core::marker::Sized,\ AskamaW: askama::helpers::core::fmt::Write + ?askama::helpers::core::marker::Sized,\
{\ {\
askama::Template::render_into(self, dest)\ askama::Template::render_into_with_values(self, dest, values)\
}\ }\
}", }",
); );

View File

@ -69,11 +69,15 @@ fn compare_ex(
impl askama::filters::FastWritable for Foo { impl askama::filters::FastWritable for Foo {
#[inline] #[inline]
fn write_into<AskamaW>(&self, dest: &mut AskamaW) -> askama::Result<()> fn write_into<AskamaW>(
&self,
dest: &mut AskamaW,
values: &dyn askama::Values,
) -> askama::Result<()>
where where
AskamaW: askama::helpers::core::fmt::Write + ?askama::helpers::core::marker::Sized, AskamaW: askama::helpers::core::fmt::Write + ?askama::helpers::core::marker::Sized,
{ {
askama::Template::render_into(self, dest) askama::Template::render_into_with_values(self, dest, values)
} }
} }
}; };

View File

@ -88,3 +88,26 @@ fn test_value_function_getter() {
values.insert("a".to_string(), Box::new(false)); values.insert("a".to_string(), Box::new(false));
assert_eq!(V.render_with_values(&values).unwrap(), ""); assert_eq!(V.render_with_values(&values).unwrap(), "");
} }
#[test]
fn test_value_in_subtemplates() {
// In this test we make sure that values are passed down to transcluded sub-templates,
// even if there is a filter in the mix, e.g. the implicit `|escape` filter.
#[derive(Template)]
#[template(source = r#"{{ Child }}"#, ext = "html")]
struct Parent;
#[derive(Template)]
#[template(
source = r#"Hello, {{ askama::get_value::<String>("who")? }}!"#,
ext = "html"
)]
struct Child;
let values: (&str, &dyn Any) = ("who", &"<world>".to_owned());
assert_eq!(
Parent.render_with_values(&values).unwrap(),
"Hello, &#38;#60;world&#38;#62;!", // sic: escaped twice
);
}