tera
tera copied to clipboard
undefined test has weird behavior with an Option<T>
Hi, creating an issue since after 2 hours of debugging, I think there is something weird going on here.
I have roughly this snippet of code
let mut ctx = tera::Context::new();
ctx.insert("foo", &serializable_object)
let v = ctx.get("foo.x.y");
eprintln!("{:?}", v);
tera_instance.render_to("whatever.tpl", &ctx, output)?;
Where the serializable_object has a field x that is Option<Y> and Y has a field y that is Option<String>.
The issue happens when x is Some(Y) and y is None. In that case, the value obtained from the context when accessing key "foo.x.y" is None (which is expected), but in the whatever.tpl template, I have something like this:
{%- if foo.x.y is undefined %}{{ throw(message="Bad Bad Bad") }}{%- endif %}
and it never throws, the undefined check returns true.
I fired up LLDB, and set up a breakpoint in renderer.process.eval_test in the Tera source code
fn eval_test(&mut self, test: &'a Test) -> Result<bool> {
let tester_fn = self.tera.get_tester(&test.name)?;
let err_wrap = |e| Error::call_test(&test.name, e);
let mut tester_args = vec![];
for arg in &test.args {
tester_args
.push(self.safe_eval_expression(arg).map_err(err_wrap)?.clone().into_owned());
}
let found = self.lookup_ident(&test.ident).map(|found| found.clone().into_owned()).ok();
let result = tester_fn.test(found.as_ref(), &tester_args).map_err(err_wrap)?;
if test.negated {
Ok(!result)
} else {
Ok(result)
}
}
and it appears that the found variable is Some{...} instead of None, which is not what I expect it to be. Is this the normal behavior or a bug ?
Thanks for any help on this :) and great work on Tera by the way !
If I understood correctly, it's the expected behaviour. y
is defined as None
, {%- if foo.x.z is undefined %}
should throw though, assuming there's no field named z
.
So a None value is considered as a defined one ? In that case, is there some way to check whether a value is Some or None from the template ? I haven't seen any is_null check or anything similar in the Tera docs
I will need to double-check, the undefined
test code seem like it should fail on that but I haven't looked at that part of the code in a long time.
I ran into what I think is a similar/the same issue. From what I have observed the defined
tester function will only evaluate to false if the argument being checked doesn't exist in the Context
used to render the template, and the inverse being true for undefined
. That is, calling defined
with Some(_) | None
will evaluate to true, and will only be false if the Context
doesn't have the key being tested.
To get around this I registered a custom tester function
fn is_null(value: Option<&Value>, _args: &[Value]) -> tera::Result<bool> {
match value {
Some(Value::Null) | None => Ok(true),
_ => Ok(false),
}
}
Personally, I can see the current behavior of the defined
and undefined
testers being useful. But then I would expect there to be analogous null
and notnull
testers implemented similarly to the custom is_null
tester mentioned above.