Keats / tera

A template engine for Rust based on Jinja2/Django
http://keats.github.io/tera/
MIT License
3.43k stars 279 forks source link

Property that is None returns `true` with "is defined" #848

Closed tobymurray closed 1 year ago

tobymurray commented 1 year ago

What I'm trying to do

I want to express a Vec<Optional<i32>> in JavaScript, I figured an array of either numbers or null would be sensible. E.g. I want [ Some(0), Some(1), None ] to render as [ 0, 1, null ]

I'm assuming I'm just using it wrong, but not sure what else is expected. Following the comment here

I have a template:

{% for value in values %}{{ value.metric }}: [{% if value.metric is defined %}true{% else %}false{% endif %}]
{% endfor%}

And this reproduction example:

use serde::Serialize;
use std::fs;
use tera::{Context, Tera};

#[derive(Serialize)]
struct Test {
    metric: Option<i32>,
}

fn main() {
    let test1 = Test { metric: Some(0) };
    let test2 = Test { metric: Some(1) };
    let test3 = Test { metric: None };

    let mut context = Context::new();
    context.insert("values", &vec![test1, test2, test3]);
    let output = Tera::new("templates/**/*.html")
        .unwrap()
        .render("template.html", &context)
        .unwrap();

    fs::write("index.html", output).unwrap();
}

Expected

0: [true]
1: [true]
: [false]

Actual

0: [true]
1: [true]
: [true]

Is this because it's within a loop?

Keats commented 1 year ago

It's due to the current type system of Tera, we only have null. In v2 we will have undefined which will be clearer.

In short here, value.metric is defined in every iteration, it's just that it has a value of None for the last one. It would fail if you tried to do something like if value.something is defined. It's a bit wonky and should be better in the next version.

A bit hacky but you could have your template be:

{% for value in values %}
{{ value.metric }}: [{% if value.metric is number %}true{% else %}false{% endif %}]
{% endfor%}
tobymurray commented 1 year ago

I worked around it in a similarly hacky way, I converted to a String representation of the number or null. Feels gross, but at the same time good to keep logic out of the view? I'm happy with this, and I'll keep my eyes open for a v2 some day.