As requested on the discord, posting a detailed issue about trying valuable
for Tera.
Background
I am trying to see if valuable could replace serde_json::Value
for Tera.
The goal in a template engine is to find the value the user wants to use in a given context.
Right now, the context in Tera is just serde_json::Value
, which means we serialise everything even though we don’t really need it. See https://docs.rs/tera/1.10.0/tera/struct.Context.html for examples of Context.
In Tera, you need to evaluate expressions based on the context. For example:
{{ get_metadata(path=page.path ~ “image.jpg”) }}
: this concatenates into a single string which is then passed as an argument to a function
{% if user.is_admin or data.public %}
: this evaluates the fields truthiness
{{ item.price * current_rate | format_currency }}
: this multiplies the value found in the context and then applies a filter on the output
{% for result in data.results[user.birth_year] %}
There are more examples on the Tera documentation: https://tera.netlify.app/docs/
If you are familiar with Django/Jinja2/Twig/Liquid this is essentially the same thing.
The way it currently work is the following:
- Convert the whole context received into serde_json
- Evaluates values in brackets, like
user.birth_year
in the example. If the value is an integer, we assume it’s the index of the array so we don’t change anything
- Replace the values in brackets by their actual value and create a JSON pointer based on it
- Use https://docs.serde.rs/serde_json/enum.Value.html#method.pointer on the context to retrieve the value (or not if it wasn’t found)
- Do pattern matching on the
Value
kind: if we’re doing a math operation like *
, we need to make sure both operands resolve to a Value::Number
for example, string concatenation with ~
only work on Value::String
and so on
Where valuable comes in
As mentioned, it currently serialises the whole context which can be very costly. For example, rendering a section in a Zola site will clone the content of all the sub-pages if pagination isn’t set up, which can quickly become slow.
The whole Tera cloning the context is actually one of the biggest current perf bottleneck in Zola as you need to clone the content every time you render the page, which can be pretty big considering it’s potentially thousands of HTML pages.
Examples
What I’d like to accomplish is be able to visit the context and extract the Value
for each identifier. Let’s use https://github.com/tokio-rs/valuable/blob/master/valuable/examples/print.rs as an example data and say that the person
variable is our context.
{{ name }}
The path is [“name”]
so we only need to visit Structable and Mappable and look for that key
{{ addresses.1.city }}
The path is [“addresses”, 1, “city”]
. We visit Structable/Mappable because it begins with a string and visit the field where it is equal to addresses
. This is an array so we visit Listable. We know we want to only care about the element at index 1 so we could only visit that one ideally (see #52). Once there, there is only city
left to match so we visit in Structable/Mappable again.
{% for address in addresses %}
Here we want to iterate on the Vec<Address>
so I think we just need to visit the addresses and get a Vec<Structable>
which we visit in its entirety one by one (since you can break
from the loop).
The goal
In all cases above we want to get the Value
corresponding to the path out of the visit
and pattern match later. Since visit
doesn’t return anything, I need to store it on the visitor struct. The Value
found could be a primitive, a Mappable/Structable (for loops on key values) or not found at all.
However I get a lifetime error if I try to assign a Value
in a small toy example: https://gist.github.com/Keats/150acda601910c5f8a6bd9aceba2ddfd so I’m not sure how I would accomplish that. Is there an example of a visitor holding a Value somewhere?
The article at https://tokio.rs/blog/2021-05-valuable specifically mention template engine so I’m wondering if the idea was limited to writing the value to a string buffer or whether it is too limited for Tera.