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::escape::FastWritable;
use crate::{Error, Result};
use crate::{Error, Result, Values};
/// 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> {
#[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
.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> {
#[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 {
Pluralize::Singular(value) => value.write_into(dest),
Pluralize::Plural(value) => value.write_into(dest),
Pluralize::Singular(value) => value.write_into(dest, values),
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> {
#[inline]
fn write_into<W: fmt::Write + ?Sized>(&self, dest: &mut W) -> crate::Result<()> {
self.0.write_into(&mut EscapeWriter(dest, self.1))
fn write_into<W: fmt::Write + ?Sized>(
&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.
impl<T: FastWritable> FastWritable for MaybeSafe<T> {
#[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 {
MaybeSafe::Safe(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> {
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 {
Wrapped::Safe(t) => t.write_into(dest),
Wrapped::NeedsEscaping(t, e) => EscapeDisplay(t, e).write_into(dest),
Wrapped::Safe(t) => t.write_into(dest, values),
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.
impl<T: FastWritable> FastWritable for Safe<T> {
#[inline]
fn write_into<W: fmt::Write + ?Sized>(&self, dest: &mut W) -> crate::Result<()> {
self.0.write_into(dest)
fn write_into<W: fmt::Write + ?Sized>(
&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`].
pub trait FastWritable {
/// 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 _: () = {
crate::impl_for_ref! {
impl FastWritable for T {
#[inline]
fn write_into<W: fmt::Write + ?Sized>(&self, dest: &mut W) -> crate::Result<()> {
<T>::write_into(self, dest)
fn write_into<W: fmt::Write + ?Sized>(
&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,
{
#[inline]
fn write_into<W: fmt::Write + ?Sized>(&self, dest: &mut W) -> crate::Result<()> {
self.as_ref().get_ref().write_into(dest)
fn write_into<W: fmt::Write + ?Sized>(
&self,
dest: &mut W,
values: &dyn Values,
) -> crate::Result<()> {
self.as_ref().get_ref().write_into(dest, values)
}
}
#[cfg(feature = "alloc")]
impl<T: FastWritable + alloc::borrow::ToOwned> FastWritable for alloc::borrow::Cow<'_, T> {
#[inline]
fn write_into<W: fmt::Write + ?Sized>(&self, dest: &mut W) -> crate::Result<()> {
T::write_into(self.as_ref(), dest)
fn write_into<W: fmt::Write + ?Sized>(
&self,
dest: &mut W,
values: &dyn Values,
) -> crate::Result<()> {
T::write_into(self.as_ref(), dest, values)
}
}
@ -540,8 +572,12 @@ const _: () = {
($($ty:ty)*) => { $(
impl FastWritable for $ty {
#[inline]
fn write_into<W: fmt::Write + ?Sized>(&self, dest: &mut W) -> crate::Result<()> {
itoa::Buffer::new().format(*self).write_into(dest)
fn write_into<W: fmt::Write + ?Sized>(
&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)*) => { $(
impl FastWritable for core::num::$id {
#[inline]
fn write_into<W: fmt::Write + ?Sized>(&self, dest: &mut W) -> crate::Result<()> {
self.get().write_into(dest)
fn write_into<W: fmt::Write + ?Sized>(
&self,
dest: &mut W,
values: &dyn Values,
) -> crate::Result<()> {
self.get().write_into(dest, values)
}
}
)* };
@ -571,7 +611,11 @@ const _: () = {
impl FastWritable for str {
#[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)?)
}
}
@ -579,14 +623,22 @@ const _: () = {
#[cfg(feature = "alloc")]
impl FastWritable for alloc::string::String {
#[inline]
fn write_into<W: fmt::Write + ?Sized>(&self, dest: &mut W) -> crate::Result<()> {
self.as_str().write_into(dest)
fn write_into<W: fmt::Write + ?Sized>(
&self,
dest: &mut W,
values: &dyn Values,
) -> crate::Result<()> {
self.as_str().write_into(dest, values)
}
}
impl FastWritable for bool {
#[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 {
true => "true",
false => "false",
@ -596,13 +648,21 @@ const _: () = {
impl FastWritable for char {
#[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)?)
}
}
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() {
Some(s) => dest.write_str(s),
None => dest.write_fmt(*self),
@ -626,9 +686,9 @@ const _: () = {
fn askama_write<W: fmt::Write + ?Sized>(
&self,
dest: &mut W,
_: &dyn Values,
values: &dyn Values,
) -> 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 crate::ascii_str::{AsciiChar, AsciiStr};
use crate::{NO_VALUES, Values};
/// Returns adequate string representation (in KB, ..) of number of bytes
///
@ -33,14 +34,18 @@ pub struct FilesizeFormatFilter(f32);
impl fmt::Display for FilesizeFormatFilter {
#[inline]
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 {
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 {
(self.0 as u32).write_into(dest)?;
(self.0 as u32).write_into(dest, values)?;
Ok(dest.write_str(" B")?)
} else if let Some((prefix, factor)) = SI_PREFIXES
.iter()
@ -49,8 +54,8 @@ impl FastWritable for FilesizeFormatFilter {
{
// u32 is big enough to hold the number 999_999
let scaled = (self.0 * factor) as u32;
(scaled / 100).write_into(dest)?;
format_frac(&mut MaybeUninit::uninit(), prefix, scaled).write_into(dest)
(scaled / 100).write_into(dest, values)?;
format_frac(&mut MaybeUninit::uninit(), prefix, scaled).write_into(dest, values)
} else {
too_big(self.0, dest)
}

View File

@ -8,6 +8,7 @@ use serde_json::ser::{CompactFormatter, PrettyFormatter, Serializer};
use super::FastWritable;
use crate::ascii_str::{AsciiChar, AsciiStr};
use crate::{NO_VALUES, Values};
/// Serialize to JSON (requires `json` feature)
///
@ -188,7 +189,8 @@ where
}
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)
}
}
@ -196,12 +198,13 @@ impl<S: Serialize> FastWritable for ToJson<S> {
impl<S: Serialize> fmt::Display for ToJson<S> {
#[inline]
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> {
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,
@ -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> {
#[inline]
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>
where
S: Serialize + ?Sized,

View File

@ -4,6 +4,7 @@ use std::fmt::Write;
use percent_encoding::{AsciiSet, NON_ALPHANUMERIC, utf8_percent_encode};
use crate::Values;
use crate::filters::{FastWritable, HtmlSafeOutput};
// 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> {
#[inline]
fn write_into<W: fmt::Write + ?Sized>(&self, f: &mut W) -> crate::Result<()> {
self.0.write_into(&mut UrlencodeWriter(f, self.1))
fn write_into<W: fmt::Write + ?Sized>(
&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::pin::Pin;
use crate::Values;
pub use crate::error::{ErrorMarker, ResultConverter};
use crate::filters::FastWritable;
pub use crate::values::get_value;
@ -245,7 +246,7 @@ impl fmt::Display for Empty {
impl FastWritable for Empty {
#[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(())
}
}
@ -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> {
#[inline]
fn write_into<W: fmt::Write + ?Sized>(&self, dest: &mut W) -> crate::Result<()> {
self.0.write_into(dest)?;
self.1.write_into(dest)
fn write_into<W: fmt::Write + ?Sized>(
&self,
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 {
#[inline]
fn write_into<W: fmt::Write + ?Sized>(&self, f: &mut W) -> crate::Result<()> {
self.render_into(f)
fn write_into<W: fmt::Write + ?Sized>(
&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
for #wrapper_id #wrapper_ty_generics #wrapper_where_clause {
#[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
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(
"\
#[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 \
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 {
#[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
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));
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
);
}