generator: do argument coercion for |truncate / |center

Don't simply pass any arguments to the filter. The error message won't
be useful otherwise. Also ensure that the argument is a `usize`.
This commit is contained in:
René Kijewski 2025-04-19 01:26:26 +02:00 committed by René Kijewski
parent de9d6b7d0e
commit 4be302338a
5 changed files with 138 additions and 9 deletions

View File

@ -5,7 +5,7 @@ use parser::{Expr, IntKind, Num, Span, StrLit, StrPrefix, TyGenerics, WithSpan};
use super::{DisplayWrap, Generator, TargetIsize, TargetUsize};
use crate::heritage::Context;
use crate::integration::Buffer;
use crate::{BUILTIN_FILTERS, BUILTIN_FILTERS_NEED_ALLOC, CompileError, MsgValidEscapers};
use crate::{BUILTIN_FILTERS, CompileError, MsgValidEscapers};
impl<'a> Generator<'a, '_> {
pub(super) fn visit_filter(
@ -18,6 +18,7 @@ impl<'a> Generator<'a, '_> {
node: Span<'_>,
) -> Result<DisplayWrap, CompileError> {
let filter = match name {
"center" => Self::visit_center_filter,
"deref" => Self::visit_deref_filter,
"escape" | "e" => Self::visit_escape_filter,
"filesizeformat" => Self::visit_humansize,
@ -32,6 +33,7 @@ impl<'a> Generator<'a, '_> {
"pluralize" => Self::visit_pluralize_filter,
"ref" => Self::visit_ref_filter,
"safe" => Self::visit_safe_filter,
"truncate" => Self::visit_truncate_filter,
"urlencode" => Self::visit_urlencode_filter,
"urlencode_strict" => Self::visit_urlencode_strict_filter,
"value" => return self.visit_value(ctx, buf, args, generics, node, "`value` filter"),
@ -79,9 +81,6 @@ impl<'a> Generator<'a, '_> {
generics: &[WithSpan<'a, TyGenerics<'a>>],
node: Span<'_>,
) -> Result<DisplayWrap, CompileError> {
if BUILTIN_FILTERS_NEED_ALLOC.contains(&name) {
ensure_filter_has_feature_alloc(ctx, name, node)?;
}
if !generics.is_empty() {
return Err(
ctx.generate_error(format_args!("unexpected generics on filter `{name}`"), node)
@ -516,6 +515,60 @@ impl<'a> Generator<'a, '_> {
buf.write(")?");
Ok(DisplayWrap::Unwrapped)
}
fn visit_center_filter(
&mut self,
ctx: &Context<'_>,
buf: &mut Buffer,
args: &[WithSpan<'a, Expr<'a>>],
node: Span<'_>,
) -> Result<DisplayWrap, CompileError> {
self.visit_center_truncate_filter(ctx, buf, args, node, "center")
}
fn visit_truncate_filter(
&mut self,
ctx: &Context<'_>,
buf: &mut Buffer,
args: &[WithSpan<'a, Expr<'a>>],
node: Span<'_>,
) -> Result<DisplayWrap, CompileError> {
self.visit_center_truncate_filter(ctx, buf, args, node, "truncate")
}
fn visit_center_truncate_filter(
&mut self,
ctx: &Context<'_>,
buf: &mut Buffer,
args: &[WithSpan<'a, Expr<'a>>],
node: Span<'_>,
name: &str,
) -> Result<DisplayWrap, CompileError> {
ensure_filter_has_feature_alloc(ctx, name, node)?;
let [arg, length] = args else {
return Err(ctx.generate_error(
format_args!("`{name}` filter needs one argument, the `length`"),
node,
));
};
buf.write(format_args!("askama::filters::{name}("));
self.visit_arg(ctx, buf, arg)?;
buf.write(
"\
,\
askama::helpers::core::primitive::usize::try_from(\
askama::helpers::get_primitive_value(&(",
);
self.visit_arg(ctx, buf, length)?;
buf.write(
"\
))\
).map_err(|_| askama::Error::Fmt)?\
)?",
);
Ok(DisplayWrap::Unwrapped)
}
}
fn ensure_filter_has_feature_alloc(

View File

@ -602,15 +602,10 @@ pub(crate) use {fmt_left, fmt_right};
// Askama or should refer to a local `filters` module.
const BUILTIN_FILTERS: &[&str] = &[
"capitalize",
"center",
"lower",
"lowercase",
"title",
"trim",
"truncate",
"upper",
"uppercase",
];
// Built-in filters that need the `alloc` feature.
const BUILTIN_FILTERS_NEED_ALLOC: &[&str] = &["center", "truncate"];

View File

@ -3,6 +3,7 @@
extern crate serde_json;
use askama::Template;
use assert_matches::assert_matches;
#[cfg(feature = "serde_json")]
use serde_json::Value;
@ -298,6 +299,32 @@ fn test_filter_truncate() {
foo: "alpha bar".into(),
};
assert_eq!(t.render().unwrap(), "alpha baralpha...");
#[derive(Template)]
#[template(source = "{{ foo | truncate(length) }}", ext = "txt")]
struct TruncateFilterLength<'a> {
foo: String,
length: &'a &'a &'a i32,
}
assert_eq!(
TruncateFilterLength {
foo: "alpha bar".into(),
length: &&&5,
}
.render()
.unwrap(),
"alpha..."
);
assert_matches!(
TruncateFilterLength {
foo: "alpha bar".into(),
length: &&&-5,
}
.render()
.unwrap_err(),
askama::Error::Fmt
);
}
#[cfg(feature = "serde_json")]

View File

@ -0,0 +1,24 @@
use askama::Template;
#[derive(Template)]
#[template(source = r#"{{ text | truncate }}"#, ext = "html")]
struct NoArgument<'a> {
text: &'a str,
}
#[derive(Template)]
#[template(source = r#"{{ text | truncate(length) }}"#, ext = "html")]
struct WrongArgumentType<'a> {
text: &'a str,
length: f32,
}
#[derive(Template)]
#[template(source = r#"{{ text | truncate(length, extra) }}"#, ext = "html")]
struct TooManyArguments<'a> {
text: &'a str,
length: usize,
extra: bool,
}
fn main() {}

View File

@ -0,0 +1,30 @@
error: filter `truncate` needs one argument, the `length`
--> NoArgument.html:1:3
"text | truncate }}"
--> tests/ui/truncate.rs:4:21
|
4 | #[template(source = r#"{{ text | truncate }}"#, ext = "html")]
| ^^^^^^^^^^^^^^^^^^^^^^^^^^
error: filter `truncate` needs one argument, the `length`
--> TooManyArguments.html:1:3
"text | truncate(length, extra) }}"
--> tests/ui/truncate.rs:17:21
|
17 | #[template(source = r#"{{ text | truncate(length, extra) }}"#, ext = "html")]
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
error[E0277]: the trait bound `usize: TryFrom<f32>` is not satisfied
--> tests/ui/truncate.rs:9:10
|
9 | #[derive(Template)]
| ^^^^^^^^ the trait `From<f32>` is not implemented for `usize`
|
= help: the following other types implement trait `From<T>`:
`usize` implements `From<bool>`
`usize` implements `From<std::ptr::Alignment>`
`usize` implements `From<u16>`
`usize` implements `From<u8>`
= note: required for `f32` to implement `Into<usize>`
= note: required for `usize` to implement `TryFrom<f32>`
= note: this error originates in the derive macro `Template` (in Nightly builds, run with -Z macro-backtrace for more info)