Merge pull request #311 from GuillaumeGomez/runtime-values

Add support for "runtime" values
This commit is contained in:
Guillaume Gomez 2025-01-24 00:08:43 +01:00 committed by GitHub
commit cae09c50f4
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
15 changed files with 673 additions and 62 deletions

View File

@ -2,6 +2,7 @@
- [Introduction](./introduction.md)
- [Creating templates](./creating_templates.md)
- [Runtime values](./runtime.md)
- [Debugging](./debugging.md)
- [Configuration](./configuration.md)
- [Template syntax](./template_syntax.md)

38
book/src/runtime.md Normal file
View File

@ -0,0 +1,38 @@
# Runtime values
It is possible to define variables at runtime and to use them in the templates using the `values`
filter or the `rinja::get_value` function and to call the `_with_values` variants of the `render`
methods. It expects an extra argument implementing the `Values` trait. This trait is implemented on
a few types provided by the `std`, like `HashMap`:
```rust
use std::collections::HashMap;
let mut values: HashMap<&str, Box<dyn Any>> = HashMap::new();
// We add a new value named "name" with the value "Bibop".
values.insert("name", Box::new("Bibop"));
values.insert("age", Box::new(12u32));
```
The `Values` trait is expecting types storing data with the `Any` trait, allowing to store any type.
Then to render with these values:
```rust
template_struct.render_with_values(&values).unwrap();
```
There are two ways to get the values from the template, either by using the `value` filter
or by calling directly the `rinja::get_value` function:
```jinja
{% if let Ok(name) = "name"|value::<&str> %}
name is {{ name }}
{% endif %}
{% if let Ok(age) = rinja::get_value::<u32>("age") %}
age is {{ age }}
{% endif %}
```
If you try to retrieve a value with the wrong type or that you didn't set, you will get an
`Err(ValueError)`.

View File

@ -33,6 +33,7 @@ serde_json = { version = "1.0", optional = true, default-features = false, featu
itoa = "1.0.11"
[dev-dependencies]
assert_matches = "1.5.0"
criterion = "0.5"
[badges]

View File

@ -19,6 +19,10 @@ pub type Result<I, E = Error> = core::result::Result<I, E>;
pub enum Error {
/// Generic, unspecified formatting error
Fmt,
/// Key not present in [`Values`][crate::Values]
ValueMissing,
/// Incompatible value type for key in [`Values`][crate::Values]
ValueType,
/// An error raised by using `?` in a template
#[cfg(feature = "alloc")]
Custom(Box<dyn StdError + Send + Sync>),
@ -41,6 +45,8 @@ impl Error {
pub fn into_box(self) -> Box<dyn StdError + Send + Sync> {
match self {
Error::Fmt => fmt::Error.into(),
Error::ValueMissing => Box::new(Error::ValueMissing),
Error::ValueType => Box::new(Error::ValueType),
Error::Custom(err) => err,
#[cfg(feature = "serde_json")]
Error::Json(err) => err.into(),
@ -66,6 +72,8 @@ impl StdError for Error {
fn source(&self) -> Option<&(dyn StdError + 'static)> {
match self {
Error::Fmt => Some(&fmt::Error),
Error::ValueMissing => None,
Error::ValueType => None,
#[cfg(feature = "alloc")]
Error::Custom(err) => Some(err.as_ref()),
#[cfg(feature = "serde_json")]
@ -78,6 +86,8 @@ impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Error::Fmt => fmt::Error.fmt(f),
Error::ValueMissing => f.write_str("key missing in values"),
Error::ValueType => f.write_str("value has wrong type"),
#[cfg(feature = "alloc")]
Error::Custom(err) => err.fmt(f),
#[cfg(feature = "serde_json")]

View File

@ -4,6 +4,8 @@ use core::ops::Deref;
use core::pin::Pin;
use core::str;
use crate::Values;
/// Marks a string (or other `Display` type) as safe
///
/// Use this if you want to allow markup in an expression, or if you know
@ -489,7 +491,11 @@ pub struct Writable<'a, S: ?Sized>(pub &'a S);
/// Used internally by rinja to select the appropriate [`write!()`] mechanism
pub trait WriteWritable {
/// Used internally by rinja to select the appropriate [`write!()`] mechanism
fn rinja_write<W: fmt::Write + ?Sized>(&self, dest: &mut W) -> crate::Result<()>;
fn rinja_write<W: fmt::Write + ?Sized>(
&self,
dest: &mut W,
values: &dyn Values,
) -> crate::Result<()>;
}
/// Used internally by rinja to speed up writing some types.
@ -604,16 +610,35 @@ const _: () = {
}
}
impl<S: FastWritable + ?Sized> WriteWritable for &Writable<'_, S> {
impl<S: crate::Template + ?Sized> WriteWritable for &Writable<'_, S> {
#[inline]
fn rinja_write<W: fmt::Write + ?Sized>(&self, dest: &mut W) -> crate::Result<()> {
fn rinja_write<W: fmt::Write + ?Sized>(
&self,
dest: &mut W,
values: &dyn Values,
) -> crate::Result<()> {
self.0.render_into_with_values(dest, values)
}
}
impl<S: FastWritable + ?Sized> WriteWritable for &&Writable<'_, S> {
#[inline]
fn rinja_write<W: fmt::Write + ?Sized>(
&self,
dest: &mut W,
_: &dyn Values,
) -> crate::Result<()> {
self.0.write_into(dest)
}
}
impl<S: fmt::Display + ?Sized> WriteWritable for &&Writable<'_, S> {
impl<S: fmt::Display + ?Sized> WriteWritable for &&&Writable<'_, S> {
#[inline]
fn rinja_write<W: fmt::Write + ?Sized>(&self, dest: &mut W) -> crate::Result<()> {
fn rinja_write<W: fmt::Write + ?Sized>(
&self,
dest: &mut W,
_: &dyn Values,
) -> crate::Result<()> {
Ok(write!(dest, "{}", self.0)?)
}
}

View File

@ -14,6 +14,7 @@ use core::pin::Pin;
pub use crate::error::{ErrorMarker, ResultConverter};
use crate::filters::FastWritable;
pub use crate::values::get_value;
pub struct TemplateLoop<I>
where

View File

@ -70,6 +70,7 @@ pub mod filters;
#[doc(hidden)]
pub mod helpers;
mod html;
mod values;
#[cfg(feature = "alloc")]
use alloc::string::String;
@ -83,6 +84,7 @@ pub use rinja_derive::Template;
pub use crate as shared;
pub use crate::error::{Error, Result};
pub use crate::helpers::PrimitiveType;
pub use crate::values::{NO_VALUES, Value, Values, get_value};
/// Main `Template` trait; implementations are generally derived
///
@ -106,21 +108,50 @@ pub use crate::helpers::PrimitiveType;
///
/// [dynamic methods calls]: <https://doc.rust-lang.org/stable/std/keyword.dyn.html>
pub trait Template: fmt::Display + filters::FastWritable {
/// Helper method which allocates a new `String` and renders into it
/// Helper method which allocates a new `String` and renders into it.
#[inline]
#[cfg(feature = "alloc")]
fn render(&self) -> Result<String> {
self.render_with_values(NO_VALUES)
}
/// Helper method which allocates a new `String` and renders into it with provided [`Values`].
#[inline]
#[cfg(feature = "alloc")]
fn render_with_values(&self, values: &dyn Values) -> Result<String> {
let mut buf = String::new();
let _ = buf.try_reserve(Self::SIZE_HINT);
self.render_into(&mut buf)?;
self.render_into_with_values(&mut buf, values)?;
Ok(buf)
}
/// Renders the template to the given `writer` fmt buffer
fn render_into<W: fmt::Write + ?Sized>(&self, writer: &mut W) -> Result<()>;
/// Renders the template to the given `writer` fmt buffer.
#[inline]
fn render_into<W: fmt::Write + ?Sized>(&self, writer: &mut W) -> Result<()> {
self.render_into_with_values(writer, NO_VALUES)
}
/// Renders the template to the given `writer` io buffer
/// Renders the template to the given `writer` fmt buffer with provided [`Values`].
fn render_into_with_values<W: fmt::Write + ?Sized>(
&self,
writer: &mut W,
values: &dyn Values,
) -> Result<()>;
/// Renders the template to the given `writer` io buffer.
#[inline]
#[cfg(feature = "std")]
fn write_into<W: io::Write + ?Sized>(&self, writer: &mut W) -> io::Result<()> {
self.write_into_with_values(writer, NO_VALUES)
}
/// Renders the template to the given `writer` io buffer with provided [`Values`].
#[cfg(feature = "std")]
fn write_into_with_values<W: io::Write + ?Sized>(
&self,
writer: &mut W,
values: &dyn Values,
) -> io::Result<()> {
struct Wrapped<W: io::Write> {
writer: W,
err: Option<io::Error>,
@ -138,7 +169,7 @@ pub trait Template: fmt::Display + filters::FastWritable {
}
let mut wrapped = Wrapped { writer, err: None };
if self.render_into(&mut wrapped).is_ok() {
if self.render_into_with_values(&mut wrapped, values).is_ok() {
Ok(())
} else {
let err = wrapped.err.take();
@ -160,23 +191,48 @@ pub trait Template: fmt::Display + filters::FastWritable {
}
impl<T: Template + ?Sized> Template for &T {
#[inline]
fn render_into<W: fmt::Write + ?Sized>(&self, writer: &mut W) -> Result<()> {
<T as Template>::render_into(self, writer)
}
#[inline]
#[cfg(feature = "alloc")]
fn render(&self) -> Result<String> {
<T as Template>::render(self)
}
#[inline]
#[cfg(feature = "alloc")]
fn render_with_values(&self, values: &dyn Values) -> Result<String> {
<T as Template>::render_with_values(self, values)
}
#[inline]
fn render_into<W: fmt::Write + ?Sized>(&self, writer: &mut W) -> Result<()> {
<T as Template>::render_into(self, writer)
}
#[inline]
fn render_into_with_values<W: fmt::Write + ?Sized>(
&self,
writer: &mut W,
values: &dyn Values,
) -> Result<()> {
<T as Template>::render_into_with_values(self, writer, values)
}
#[inline]
#[cfg(feature = "std")]
fn write_into<W: io::Write + ?Sized>(&self, writer: &mut W) -> io::Result<()> {
<T as Template>::write_into(self, writer)
}
#[inline]
#[cfg(feature = "std")]
fn write_into_with_values<W: io::Write + ?Sized>(
&self,
writer: &mut W,
values: &dyn Values,
) -> io::Result<()> {
<T as Template>::write_into_with_values(self, writer, values)
}
const SIZE_HINT: usize = T::SIZE_HINT;
}
@ -186,18 +242,37 @@ impl<T: Template + ?Sized> Template for &T {
///
/// [`dyn`-compatible]: https://doc.rust-lang.org/stable/reference/items/traits.html#dyn-compatibility
pub trait DynTemplate {
/// Helper method which allocates a new `String` and renders into it
/// Helper method which allocates a new `String` and renders into it.
#[cfg(feature = "alloc")]
fn dyn_render(&self) -> Result<String>;
/// Renders the template to the given `writer` fmt buffer
/// Helper method which allocates a new `String` and renders into it with provided [`Values`].
#[cfg(feature = "alloc")]
fn dyn_render_with_values(&self, values: &dyn Values) -> Result<String>;
/// Renders the template to the given `writer` fmt buffer.
fn dyn_render_into(&self, writer: &mut dyn fmt::Write) -> Result<()>;
/// Renders the template to the given `writer` io buffer
/// Renders the template to the given `writer` fmt buffer with provided [`Values`].
fn dyn_render_into_with_values(
&self,
writer: &mut dyn fmt::Write,
values: &dyn Values,
) -> Result<()>;
/// Renders the template to the given `writer` io buffer.
#[cfg(feature = "std")]
fn dyn_write_into(&self, writer: &mut dyn io::Write) -> io::Result<()>;
/// Provides a conservative estimate of the expanded length of the rendered template
/// Renders the template to the given `writer` io buffer with provided [`Values`].
#[cfg(feature = "std")]
fn dyn_write_into_with_values(
&self,
writer: &mut dyn io::Write,
values: &dyn Values,
) -> io::Result<()>;
/// Provides a conservative estimate of the expanded length of the rendered template.
fn size_hint(&self) -> usize;
}
@ -208,17 +283,39 @@ impl<T: Template> DynTemplate for T {
<Self as Template>::render(self)
}
#[cfg(feature = "alloc")]
fn dyn_render_with_values(&self, values: &dyn Values) -> Result<String> {
<Self as Template>::render_with_values(self, values)
}
#[inline]
fn dyn_render_into(&self, writer: &mut dyn fmt::Write) -> Result<()> {
<Self as Template>::render_into(self, writer)
}
#[inline]
fn dyn_render_into_with_values(
&self,
writer: &mut dyn fmt::Write,
values: &dyn Values,
) -> Result<()> {
<Self as Template>::render_into_with_values(self, writer, values)
}
#[cfg(feature = "std")]
fn dyn_write_into(&self, writer: &mut dyn io::Write) -> io::Result<()> {
<Self as Template>::write_into(self, writer)
}
#[inline]
#[cfg(feature = "std")]
fn dyn_write_into_with_values(
&self,
writer: &mut dyn io::Write,
values: &dyn Values,
) -> io::Result<()> {
<Self as Template>::write_into_with_values(self, writer, values)
}
#[inline]
fn size_hint(&self) -> usize {
<Self as Template>::SIZE_HINT
@ -287,7 +384,11 @@ mod tests {
struct Test;
impl Template for Test {
fn render_into<W: fmt::Write + ?Sized>(&self, writer: &mut W) -> Result<()> {
fn render_into_with_values<W: fmt::Write + ?Sized>(
&self,
writer: &mut W,
_values: &dyn Values,
) -> Result<()> {
Ok(writer.write_str("test")?)
}

200
rinja/src/values.rs Normal file
View File

@ -0,0 +1,200 @@
use core::any::Any;
use core::borrow::Borrow;
use crate::Error;
/// No runtime values provided.
pub const NO_VALUES: &dyn Values = &();
/// Try to find `key` in `values` and then to convert it to `T`.
pub fn get_value<T: Any>(values: &dyn Values, key: impl AsRef<str>) -> Result<&T, Error> {
let Some(src) = values.get_value(key.as_ref()) else {
return Err(Error::ValueMissing);
};
if let Some(value) = src.downcast_ref::<T>() {
return Ok(value);
} else if let Some(value) = src.downcast_ref::<&T>() {
return Ok(value);
}
#[cfg(feature = "alloc")]
if let Some(value) = src.downcast_ref::<alloc::boxed::Box<T>>() {
return Ok(value);
} else if let Some(value) = src.downcast_ref::<alloc::rc::Rc<T>>() {
return Ok(value);
} else if let Some(value) = src.downcast_ref::<alloc::sync::Arc<T>>() {
return Ok(value);
}
Err(Error::ValueType)
}
/// A runtime value store for [`Template::render_with_values()`][crate::Template::render_with_values].
pub trait Values {
/// Try to find `key` in this store.
fn get_value<'a>(&'a self, key: &str) -> Option<&'a dyn Any>;
}
crate::impl_for_ref! {
impl Values for T {
#[inline]
fn get_value<'a>(&'a self, key: &str) -> Option<&'a dyn Any> {
T::get_value(self, key)
}
}
}
impl Values for () {
#[inline]
fn get_value<'a>(&'a self, _: &str) -> Option<&'a dyn Any> {
None
}
}
impl<T: Values> Values for Option<T> {
#[inline]
fn get_value<'a>(&'a self, key: &str) -> Option<&'a dyn Any> {
self.as_ref()?.get_value(key)
}
}
impl<K, V, const N: usize> Values for [(K, V); N]
where
K: Borrow<str>,
V: Value,
{
#[inline]
fn get_value<'a>(&'a self, key: &str) -> Option<&'a dyn Any> {
self.as_slice().get_value(key)
}
}
impl<K, V> Values for [(K, V)]
where
K: Borrow<str>,
V: Value,
{
fn get_value<'a>(&'a self, key: &str) -> Option<&'a dyn Any> {
for (k, v) in self {
if k.borrow() == key {
return v.ref_any();
}
}
None
}
}
#[cfg(feature = "alloc")]
impl<K, V> Values for alloc::collections::BTreeMap<K, V>
where
K: Borrow<str> + core::cmp::Ord,
V: Value,
{
#[inline]
fn get_value<'a>(&'a self, key: &str) -> Option<&'a dyn Any> {
self.get(key)?.ref_any()
}
}
#[cfg(feature = "std")]
impl<K, V, S> Values for std::collections::HashMap<K, V, S>
where
K: Borrow<str> + Eq + core::hash::Hash,
V: Value,
S: core::hash::BuildHasher,
{
#[inline]
fn get_value<'a>(&'a self, key: &str) -> Option<&'a dyn Any> {
self.get(key)?.ref_any()
}
}
/// A value in a [`Values`] collection.
///
/// This is <code>[dyn](https://doc.rust-lang.org/stable/std/keyword.dyn.html) [Any]</code>,
/// <code>[Option]&lt;dyn Any&gt;</code>, or a reference to either.
pub trait Value {
/// Returns a reference to this value unless it is `None`.
fn ref_any(&self) -> Option<&dyn Any>;
}
crate::impl_for_ref! {
impl Value for T {
#[inline]
fn ref_any(&self) -> Option<&dyn Any> {
T::ref_any(self)
}
}
}
impl Value for dyn Any {
#[inline]
fn ref_any(&self) -> Option<&dyn Any> {
Some(self)
}
}
impl<T: Value> Value for Option<T> {
#[inline]
fn ref_any(&self) -> Option<&dyn Any> {
T::ref_any(self.as_ref()?)
}
}
#[cfg(test)]
mod tests {
use assert_matches::assert_matches;
use super::*;
#[cfg(feature = "std")]
#[test]
fn values_on_hashmap() {
use alloc::boxed::Box;
use alloc::string::String;
use std::collections::HashMap;
let mut values: HashMap<String, Box<dyn Any>> = HashMap::new();
values.insert("a".into(), Box::new(10u32));
values.insert("c".into(), Box::new("blam"));
assert_matches!(get_value::<u32>(&values, "a"), Ok(&10u32));
assert_matches!(get_value::<&str>(&values, "c"), Ok(&"blam"));
assert_matches!(get_value::<u8>(&values, "a"), Err(Error::ValueType));
assert_matches!(get_value::<u8>(&values, "d"), Err(Error::ValueMissing));
let mut values: HashMap<&str, Box<dyn Any>> = HashMap::new();
values.insert("a", Box::new(10u32));
assert_matches!(get_value::<u32>(&values, "a"), Ok(&10u32));
}
#[cfg(feature = "alloc")]
#[test]
fn values_on_btreemap() {
use alloc::boxed::Box;
use alloc::collections::BTreeMap;
use alloc::string::String;
let mut values: BTreeMap<String, Box<dyn Any>> = BTreeMap::new();
values.insert("a".into(), Box::new(10u32));
values.insert("c".into(), Box::new("blam"));
assert_matches!(get_value::<u32>(&values, "a"), Ok(&10u32));
assert_matches!(get_value::<&str>(&values, "c"), Ok(&"blam"));
assert_matches!(get_value::<u8>(&values, "a"), Err(Error::ValueType));
assert_matches!(get_value::<u8>(&values, "d"), Err(Error::ValueMissing));
let mut values: BTreeMap<&str, Box<dyn Any>> = BTreeMap::new();
values.insert("a", Box::new(10u32));
assert_matches!(get_value::<u32>(&values, "a"), Ok(&10u32));
}
#[test]
fn values_on_slice() {
let values: &[(&str, &dyn Any)] = &[("a", &12u32), ("c", &"blam")];
assert_matches!(get_value::<u32>(&values, "a"), Ok(12u32));
assert_matches!(get_value::<&str>(&values, "c"), Ok(&"blam"));
assert_matches!(get_value::<u8>(&values, "a"), Err(Error::ValueType));
assert_matches!(get_value::<u8>(&values, "d"), Err(Error::ValueMissing));
}
}

View File

@ -124,12 +124,16 @@ impl<'a, 'h> Generator<'a, 'h> {
) -> Result<usize, CompileError> {
write_header(self.input.ast, buf, target);
buf.write(
"fn render_into<RinjaW>(&self, __rinja_writer: &mut RinjaW) -> rinja::Result<()>\
"fn render_into_with_values<RinjaW>(\
&self,\
__rinja_writer: &mut RinjaW,\
__rinja_values: &dyn rinja::Values\
) -> rinja::Result<()>\
where \
RinjaW: rinja::helpers::core::fmt::Write + ?rinja::helpers::core::marker::Sized\
{\
use rinja::filters::{AutoEscape as _, WriteWritable as _};\
use rinja::helpers::ResultConverter as _;
use rinja::helpers::ResultConverter as _;\
use rinja::helpers::core::fmt::Write as _;",
);

View File

@ -264,6 +264,7 @@ impl<'a> Generator<'a, '_> {
"safe" => Self::_visit_safe_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"),
name if BUILTIN_FILTERS.contains(&name) => {
return self._visit_builtin_filter(ctx, buf, name, args, generics, node);
}
@ -676,6 +677,37 @@ impl<'a> Generator<'a, '_> {
Ok(DisplayWrap::Unwrapped)
}
fn _visit_value(
&mut self,
ctx: &Context<'_>,
buf: &mut Buffer,
args: &[WithSpan<'_, Expr<'a>>],
generics: &[WithSpan<'_, TyGenerics<'_>>],
node: Span<'_>,
kind: &str,
) -> Result<DisplayWrap, CompileError> {
let [key] = args else {
return Err(ctx.generate_error(
format_args!("{kind} only takes one argument, found {}", args.len()),
node,
));
};
let [gen] = generics else {
return Err(ctx.generate_error(
format_args!("{kind} expects one generic, found {}", generics.len()),
node,
));
};
buf.write("rinja::helpers::get_value");
buf.write("::<");
self.visit_ty_generic(buf, gen);
buf.write('>');
buf.write("(&__rinja_values, &(");
self._visit_arg(ctx, buf, key)?;
buf.write("))");
Ok(DisplayWrap::Unwrapped)
}
fn _visit_args(
&mut self,
ctx: &Context<'_>,
@ -893,6 +925,17 @@ impl<'a> Generator<'a, '_> {
}
}
}
// We special-case "rinja::get_value".
Expr::Path(path) if path == &["rinja", "get_value"] => {
self._visit_value(
ctx,
buf,
args,
generics,
left.span(),
"`get_value` function",
)?;
}
sub_left => {
match sub_left {
Expr::Var(name) => match self.locals.resolve(name) {

View File

@ -1168,7 +1168,8 @@ impl<'a> Generator<'a, '_> {
idx
};
lines.write(format_args!(
"(&&rinja::filters::Writable(expr{idx})).rinja_write(__rinja_writer)?;",
"(&&&rinja::filters::Writable(expr{idx})).\
rinja_write(__rinja_writer, __rinja_values)?;",
));
}
}

View File

@ -27,7 +27,11 @@ fn compare(jinja: &str, expected: &str, fields: &[(&str, &str)], size_hint: usiz
extern crate rinja as rinja;
impl rinja::Template for Foo {
fn render_into<RinjaW>(&self, __rinja_writer: &mut RinjaW) -> rinja::Result<()>
fn render_into_with_values<RinjaW>(
&self,
__rinja_writer: &mut RinjaW,
__rinja_values: &dyn rinja::Values,
) -> rinja::Result<()>
where
RinjaW: rinja::helpers::core::fmt::Write + ?rinja::helpers::core::marker::Sized,
{
@ -153,7 +157,7 @@ fn check_if_let() {
&((&&rinja::filters::AutoEscaper::new(&(query), rinja::filters::Text)).rinja_auto_escape()?),
) {
(expr0,) => {
(&&rinja::filters::Writable(expr0)).rinja_write(__rinja_writer)?;
(&&&rinja::filters::Writable(expr0)).rinja_write(__rinja_writer, __rinja_values)?;
}
}
}",
@ -170,7 +174,7 @@ fn check_if_let() {
&((&&rinja::filters::AutoEscaper::new(&(s), rinja::filters::Text)).rinja_auto_escape()?),
) {
(expr0,) => {
(&&rinja::filters::Writable(expr0)).rinja_write(__rinja_writer)?;
(&&&rinja::filters::Writable(expr0)).rinja_write(__rinja_writer, __rinja_values)?;
}
}
}",
@ -187,7 +191,7 @@ fn check_if_let() {
&((&&rinja::filters::AutoEscaper::new(&(s), rinja::filters::Text)).rinja_auto_escape()?),
) {
(expr0,) => {
(&&rinja::filters::Writable(expr0)).rinja_write(__rinja_writer)?;
(&&&rinja::filters::Writable(expr0)).rinja_write(__rinja_writer, __rinja_values)?;
}
}
}",
@ -211,9 +215,9 @@ fn check_if_let_chain() {
.rinja_auto_escape()?),
) {
(expr0, expr2) => {
(&&rinja::filters::Writable(expr0)).rinja_write(__rinja_writer)?;
(&&&rinja::filters::Writable(expr0)).rinja_write(__rinja_writer, __rinja_values)?;
__rinja_writer.write_str(" ")?;
(&&rinja::filters::Writable(expr2)).rinja_write(__rinja_writer)?;
(&&&rinja::filters::Writable(expr2)).rinja_write(__rinja_writer, __rinja_values)?;
}
}
}"#,
@ -236,9 +240,9 @@ fn check_if_let_chain() {
.rinja_auto_escape()?),
) {
(expr0, expr2) => {
(&&rinja::filters::Writable(expr0)).rinja_write(__rinja_writer)?;
(&&&rinja::filters::Writable(expr0)).rinja_write(__rinja_writer, __rinja_values)?;
__rinja_writer.write_str(" ")?;
(&&rinja::filters::Writable(expr2)).rinja_write(__rinja_writer)?;
(&&&rinja::filters::Writable(expr2)).rinja_write(__rinja_writer, __rinja_values)?;
}
}
}"#,
@ -265,9 +269,9 @@ fn check_if_let_chain() {
.rinja_auto_escape()?),
) {
(expr0, expr2) => {
(&&rinja::filters::Writable(expr0)).rinja_write(__rinja_writer)?;
(&&&rinja::filters::Writable(expr0)).rinja_write(__rinja_writer, __rinja_values)?;
__rinja_writer.write_str(" ")?;
(&&rinja::filters::Writable(expr2)).rinja_write(__rinja_writer)?;
(&&&rinja::filters::Writable(expr2)).rinja_write(__rinja_writer, __rinja_values)?;
}
}
}"#,
@ -292,9 +296,9 @@ fn check_if_let_chain() {
.rinja_auto_escape()?),
) {
(expr0, expr2) => {
(&&rinja::filters::Writable(expr0)).rinja_write(__rinja_writer)?;
(&&&rinja::filters::Writable(expr0)).rinja_write(__rinja_writer, __rinja_values)?;
__rinja_writer.write_str(" ")?;
(&&rinja::filters::Writable(expr2)).rinja_write(__rinja_writer)?;
(&&&rinja::filters::Writable(expr2)).rinja_write(__rinja_writer, __rinja_values)?;
}
}
}"#,
@ -429,7 +433,7 @@ __rinja_writer.write_str("12")?;
&((&&rinja::filters::AutoEscaper::new(&(self.x), rinja::filters::Text)).rinja_auto_escape()?),
) {
(expr0,) => {
(&&rinja::filters::Writable(expr0)).rinja_write(__rinja_writer)?;
(&&&rinja::filters::Writable(expr0)).rinja_write(__rinja_writer, __rinja_values)?;
}
}
}
@ -443,7 +447,7 @@ __rinja_writer.write_str("12")?;
&((&&rinja::filters::AutoEscaper::new(&(self.x), rinja::filters::Text)).rinja_auto_escape()?),
) {
(expr0,) => {
(&&rinja::filters::Writable(expr0)).rinja_write(__rinja_writer)?;
(&&&rinja::filters::Writable(expr0)).rinja_write(__rinja_writer, __rinja_values)?;
}
}
",
@ -474,7 +478,7 @@ if rinja::helpers::as_bool(&(self.y == 12)) {
.rinja_auto_escape()?),
) {
(expr0,) => {
(&&rinja::filters::Writable(expr0)).rinja_write(__rinja_writer)?;
(&&&rinja::filters::Writable(expr0)).rinja_write(__rinja_writer, __rinja_values)?;
}
}
} else {
@ -496,7 +500,7 @@ match (
.rinja_auto_escape()?),
) {
(expr0,) => {
(&&rinja::filters::Writable(expr0)).rinja_write(__rinja_writer)?;
(&&&rinja::filters::Writable(expr0)).rinja_write(__rinja_writer, __rinja_values)?;
}
}
",
@ -616,7 +620,7 @@ fn check_bool_conditions() {
&((&&rinja::filters::AutoEscaper::new(&(self.x), rinja::filters::Text)).rinja_auto_escape()?),
) {
(expr0,) => {
(&&rinja::filters::Writable(expr0)).rinja_write(__rinja_writer)?;
(&&&rinja::filters::Writable(expr0)).rinja_write(__rinja_writer, __rinja_values)?;
}
}
",
@ -634,7 +638,7 @@ fn check_bool_conditions() {
.rinja_auto_escape()?),
) {
(expr0,) => {
(&&rinja::filters::Writable(expr0)).rinja_write(__rinja_writer)?;
(&&&rinja::filters::Writable(expr0)).rinja_write(__rinja_writer, __rinja_values)?;
}
}
}
@ -658,7 +662,7 @@ fn check_bool_conditions() {
.rinja_auto_escape()?),
) {
(expr0,) => {
(&&rinja::filters::Writable(expr0)).rinja_write(__rinja_writer)?;
(&&&rinja::filters::Writable(expr0)).rinja_write(__rinja_writer, __rinja_values)?;
}
}
}
@ -678,7 +682,7 @@ fn check_bool_conditions() {
.rinja_auto_escape()?),
) {
(expr0,) => {
(&&rinja::filters::Writable(expr0)).rinja_write(__rinja_writer)?;
(&&&rinja::filters::Writable(expr0)).rinja_write(__rinja_writer, __rinja_values)?;
}
}
",
@ -699,7 +703,7 @@ if rinja::helpers::as_bool(&(self.y == 3))
.rinja_auto_escape()?),
) {
(expr0,) => {
(&&rinja::filters::Writable(expr0)).rinja_write(__rinja_writer)?;
(&&&rinja::filters::Writable(expr0)).rinja_write(__rinja_writer, __rinja_values)?;
}
}
}
@ -899,9 +903,9 @@ fn test_pluralize() {
)?),
) {
(expr0, expr3) => {
(&&rinja::filters::Writable(expr0)).rinja_write(__rinja_writer)?;
(&&&rinja::filters::Writable(expr0)).rinja_write(__rinja_writer, __rinja_values)?;
__rinja_writer.write_str(" dog")?;
(&&rinja::filters::Writable(expr3)).rinja_write(__rinja_writer)?;
(&&&rinja::filters::Writable(expr3)).rinja_write(__rinja_writer, __rinja_values)?;
}
}"#,
&[("dogs", "i8")],
@ -923,9 +927,9 @@ fn test_pluralize() {
)?),
) {
(expr0, expr3) => {
(&&rinja::filters::Writable(expr0)).rinja_write(__rinja_writer)?;
(&&&rinja::filters::Writable(expr0)).rinja_write(__rinja_writer, __rinja_values)?;
__rinja_writer.write_str(" dog")?;
(&&rinja::filters::Writable(expr3)).rinja_write(__rinja_writer)?;
(&&&rinja::filters::Writable(expr3)).rinja_write(__rinja_writer, __rinja_values)?;
}
}"#,
&[("dogs", "i8")],
@ -947,9 +951,9 @@ fn test_pluralize() {
)?),
) {
(expr0, expr2) => {
(&&rinja::filters::Writable(expr0)).rinja_write(__rinja_writer)?;
(&&&rinja::filters::Writable(expr0)).rinja_write(__rinja_writer, __rinja_values)?;
__rinja_writer.write_str(" ")?;
(&&rinja::filters::Writable(expr2)).rinja_write(__rinja_writer)?;
(&&&rinja::filters::Writable(expr2)).rinja_write(__rinja_writer, __rinja_values)?;
}
}"#,
&[("dogs", "i8")],
@ -975,7 +979,7 @@ fn test_pluralize() {
)?),
) {
(expr0,) => {
(&&rinja::filters::Writable(expr0)).rinja_write(__rinja_writer)?;
(&&&rinja::filters::Writable(expr0)).rinja_write(__rinja_writer, __rinja_values)?;
}
}
",
@ -991,7 +995,7 @@ fn test_pluralize() {
.rinja_auto_escape()?),
) {
(expr0,) => {
(&&rinja::filters::Writable(expr0)).rinja_write(__rinja_writer)?;
(&&&rinja::filters::Writable(expr0)).rinja_write(__rinja_writer, __rinja_values)?;
}
}
",
@ -1006,7 +1010,7 @@ fn test_pluralize() {
.rinja_auto_escape()?),
) {
(expr0,) => {
(&&rinja::filters::Writable(expr0)).rinja_write(__rinja_writer)?;
(&&&rinja::filters::Writable(expr0)).rinja_write(__rinja_writer, __rinja_values)?;
}
}
",
@ -1019,7 +1023,7 @@ fn test_pluralize() {
r#"
match (&(rinja::filters::Safe("pl")),) {
(expr0,) => {
(&&rinja::filters::Writable(expr0)).rinja_write(__rinja_writer)?;
(&&&rinja::filters::Writable(expr0)).rinja_write(__rinja_writer, __rinja_values)?;
}
}
"#,
@ -1031,7 +1035,7 @@ fn test_pluralize() {
r#"
match (&(rinja::filters::Safe("sg")),) {
(expr0,) => {
(&&rinja::filters::Writable(expr0)).rinja_write(__rinja_writer)?;
(&&&rinja::filters::Writable(expr0)).rinja_write(__rinja_writer, __rinja_values)?;
}
}
"#,
@ -1044,7 +1048,7 @@ fn test_pluralize() {
r#"
match (&(rinja::filters::Safe("s")),) {
(expr0,) => {
(&&rinja::filters::Writable(expr0)).rinja_write(__rinja_writer)?;
(&&&rinja::filters::Writable(expr0)).rinja_write(__rinja_writer, __rinja_values)?;
}
}
"#,
@ -1056,7 +1060,7 @@ fn test_pluralize() {
r"
match (&(rinja::helpers::Empty),) {
(expr0,) => {
(&&rinja::filters::Writable(expr0)).rinja_write(__rinja_writer)?;
(&&&rinja::filters::Writable(expr0)).rinja_write(__rinja_writer, __rinja_values)?;
}
}
",
@ -1078,9 +1082,9 @@ fn test_concat() {
.rinja_auto_escape()?),
) {
(expr1, expr3) => {
(&&rinja::filters::Writable(expr1)).rinja_write(__rinja_writer)?;
(&&&rinja::filters::Writable(expr1)).rinja_write(__rinja_writer, __rinja_values)?;
__rinja_writer.write_str("|")?;
(&&rinja::filters::Writable(expr3)).rinja_write(__rinja_writer)?;
(&&&rinja::filters::Writable(expr3)).rinja_write(__rinja_writer, __rinja_values)?;
}
}
__rinja_writer.write_str(">")?;
@ -1105,7 +1109,7 @@ fn test_concat() {
.rinja_auto_escape()?),
) {
(expr0,) => {
(&&rinja::filters::Writable(expr0)).rinja_write(__rinja_writer)?;
(&&&rinja::filters::Writable(expr0)).rinja_write(__rinja_writer, __rinja_values)?;
}
}
"#,

View File

@ -0,0 +1,45 @@
use rinja::Template;
#[derive(Template)]
#[template(
ext = "html",
source = r#"{% if let Ok(x) = "a"|value %}{% endif %}"#,
)]
struct A;
#[derive(Template)]
#[template(
ext = "html",
source = r#"{% if let Ok(x) = "a"|value::<u8, u8> %}{% endif %}"#,
)]
struct B;
#[derive(Template)]
#[template(
ext = "html",
source = r#"{% if let Ok(x) = rinja::get_value("a") %}{% endif %}"#,
)]
struct C;
#[derive(Template)]
#[template(
ext = "html",
source = r#"{% if let Ok(x) = rinja::get_value::<u8, u8>("a") %}{% endif %}"#,
)]
struct D;
#[derive(Template)]
#[template(
ext = "html",
source = r#"{% if let Ok(x) = rinja::get_value::<u8>() %}{% endif %}"#,
)]
struct E;
#[derive(Template)]
#[template(
ext = "html",
source = r#"{% if let Ok(x) = rinja::get_value::<u8>("a", "b") %}{% endif %}"#,
)]
struct F;
fn main() {}

View File

@ -0,0 +1,47 @@
error: `value` filter expects one generic, found 0
--> A.html:1:18
"\"a\"|value %}{% endif %}"
--> tests/ui/values.rs:6:14
|
6 | source = r#"{% if let Ok(x) = "a"|value %}{% endif %}"#,
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
error: `value` filter expects one generic, found 2
--> B.html:1:18
"\"a\"|value::<u8, u8> %}{% endif %}"
--> tests/ui/values.rs:13:14
|
13 | source = r#"{% if let Ok(x) = "a"|value::<u8, u8> %}{% endif %}"#,
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
error: `get_value` function expects one generic, found 0
--> C.html:1:18
"rinja::get_value(\"a\") %}{% endif %}"
--> tests/ui/values.rs:20:14
|
20 | source = r#"{% if let Ok(x) = rinja::get_value("a") %}{% endif %}"#,
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
error: `get_value` function expects one generic, found 2
--> D.html:1:18
"rinja::get_value::<u8, u8>(\"a\") %}{% endif %}"
--> tests/ui/values.rs:27:14
|
27 | source = r#"{% if let Ok(x) = rinja::get_value::<u8, u8>("a") %}{% endif %}"#,
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
error: `get_value` function only takes one argument, found 0
--> E.html:1:18
"rinja::get_value::<u8>() %}{% endif %}"
--> tests/ui/values.rs:34:14
|
34 | source = r#"{% if let Ok(x) = rinja::get_value::<u8>() %}{% endif %}"#,
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
error: `get_value` function only takes one argument, found 2
--> F.html:1:18
"rinja::get_value::<u8>(\"a\", \"b\") %}{% endif %}"
--> tests/ui/values.rs:41:14
|
41 | source = r#"{% if let Ok(x) = rinja::get_value::<u8>("a", "b") %}{% endif %}"#,
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

90
testing/tests/values.rs Normal file
View File

@ -0,0 +1,90 @@
use std::any::Any;
use std::collections::HashMap;
use rinja::Template;
#[test]
fn test_values() {
#[derive(Template)]
#[template(
source = r#"{% if let Ok(bla) = "a" | value::<u32> %}{{bla}}{% endif %}"#,
ext = "txt"
)]
struct V;
let mut values: HashMap<String, Box<dyn Any>> = HashMap::default();
assert_eq!(V.render_with_values(&values).unwrap(), "");
values.insert("a".to_string(), Box::new(12u32));
assert_eq!(V.render_with_values(&values).unwrap(), "12");
values.insert("a".to_string(), Box::new(false));
assert_eq!(V.render_with_values(&values).unwrap(), "");
}
#[test]
fn test_values2() {
#[derive(Template)]
#[template(
source = r#"
{%- if let Ok(bla) = "a" | value::<&str> %}{{bla}}{% endif -%}
{%- if let Ok(bla) = "b" | value::<bool> %} {{bla}}{% endif -%}
"#,
ext = "txt"
)]
struct V;
let mut values: HashMap<String, Box<dyn Any>> = HashMap::default();
assert_eq!(V.render_with_values(&values).unwrap(), "");
values.insert("a".to_string(), Box::new("hey"));
assert_eq!(V.render_with_values(&values).unwrap(), "hey");
values.insert("b".to_string(), Box::new(false));
assert_eq!(V.render_with_values(&values).unwrap(), "hey false");
values.remove("a");
assert_eq!(V.render_with_values(&values).unwrap(), " false");
}
#[test]
fn test_values3() {
#[derive(Template)]
#[template(
source = r#"
{%- match "data" | value::<&str> -%}
{%- when Ok(data) -%} ok={{ data }}
{%- when Err(err) -%} err={{ err }}
{%- endmatch -%}
"#,
ext = "txt"
)]
struct V;
let mut values: HashMap<String, Box<dyn Any>> = HashMap::default();
assert_eq!(
V.render_with_values(&values).unwrap(),
"err=key missing in values"
);
values.insert("data".to_string(), Box::new(false));
assert_eq!(
V.render_with_values(&values).unwrap(),
"err=value has wrong type"
);
values.insert("data".to_string(), Box::new("hey"));
assert_eq!(V.render_with_values(&values).unwrap(), "ok=hey");
values.insert("data".to_string(), Box::new(Box::new("hey")));
assert_eq!(V.render_with_values(&values).unwrap(), "ok=hey");
}
#[test]
fn test_value_function_getter() {
#[derive(Template)]
#[template(
source = r#"{% if let Ok(bla) = rinja::get_value::<u32>("a") %}{{bla}}{% endif %}"#,
ext = "txt"
)]
struct V;
let mut values: HashMap<String, Box<dyn Any>> = HashMap::default();
assert_eq!(V.render_with_values(&values).unwrap(), "");
values.insert("a".to_string(), Box::new(12u32));
assert_eq!(V.render_with_values(&values).unwrap(), "12");
values.insert("a".to_string(), Box::new(false));
assert_eq!(V.render_with_values(&values).unwrap(), "");
}