HubSpot / jinjava

Jinja template engine for Java
Apache License 2.0
690 stars 168 forks source link

Improve support for PartiallyDeferredValues #1158

Closed jasmith-hs closed 6 months ago

jasmith-hs commented 7 months ago

A PartiallyDeferredValue is one which does not need to be reconstructed separately when it isn't resolved in an expression. Such that if I have a normal Map {'a': 'b'} for the key foo, given {{ foo }}, it needs to be reconstructed if it needs to be deferred: {% set foo = {'a': 'b'} %}{{ foo }} For a PartiallyDeferredValue, it is always deferred so it won't get an explicit {% set %} tag reconstruction, and certain calls to the object may resolve.

So on its own, a PartiallyDeferredValue should be marked as a meta-context variable or specify a pyish serialization that will work without relying on a {% set %} tag prefix.

Giving a bit of a real example for what this means, at HubSpot, we when pre-rendering with eager execution we will pre-populate many variables onto the context. Some of these will be DeferredValueImpl some can be PartiallyDeferredValue. An example is the key request, which holds some HttpServletRequest data as documented here. Some of these values can be resolved when pre-rendering while others (such as query parameters) cannot. We mark it as a meta-context variable as it will never require reconstruction as we'll always pre-populate the context with its value. So deferring {{ request.query_dict.foo }} does not require a PrefixToPreserveState since request is a meta-context variable. However, if is aliased into another variable, for example:

{% set config = {'data': request} %}
Config foo param: {{ config.data.query_dict.foo }}

We need a way to reconstruct config. If the request object isn't able to be serialized properly, we'll end up deferring the {{config.data...}} node (meaning we aren't able to process it at all). But if the request object defines a PyishSerialization that will always succeed, such as saying that it should just be represented with the variable name request, then we end up with an output like:

Config foo param: {{ request.query_dict.foo }}

As previously stated, we always pre-populate the context with the request variable so despite nothing in the reconstructed output explicitly defining what request is, it will have a value if we pass it through jinjava again with a re-populated context.