Allow partial evaluation of templates#282
Conversation
Codecov Report
@@ Coverage Diff @@
## master #282 +/- ##
============================================
- Coverage 72.35% 72.35% -0.01%
- Complexity 1483 1503 +20
============================================
Files 231 234 +3
Lines 4597 4662 +65
Branches 737 743 +6
============================================
+ Hits 3326 3373 +47
- Misses 1016 1034 +18
Partials 255 255
Continue to review full report at Codecov.
|
7802685 to
95471b6
Compare
| builder.append(n.getMaster().getImage()); | ||
| } | ||
|
|
||
| builder.append("{% ").append(getEndName()). append(" %}"); |
There was a problem hiding this comment.
can you use/create constants for {% and %}? People have requested to make these configurable and I'd like to consolidate them.
|
|
||
|
|
||
| private String reconstructImage() { | ||
| StringBuilder builder = new StringBuilder().append(master.getImage()); |
There was a problem hiding this comment.
I wonder if we could store a reference to the original template string and an index value of the start and end n the node object. I think this would solve the raw case and preserve all the whitespace.
There was a problem hiding this comment.
I'll try to get that working in a separate PR. If I can get it to work, I'll loop back and update this PR to use it.
There was a problem hiding this comment.
Actually, it looks like it might require a larger version bump because I will need to mess around with Tag / Node etc constructors. I'll go ahead with this for now and come back to that as I refine this.
This PR implements partial evaluation of templates, or, deferring the evaluation of parts of a template, however you want to think about it.
The aim here is to support a use case for HubSpot's email infrastructure. We want to evaluate as much as possible of an email's template, except for the parts which are specific to an individual contact. This will allow us to minimise the work which needs to be repeated per-contact for large email sends to 100,000s of contacts. In this case the CPU time for evaluating the template isn't really a concern, but many Tags perform I/O, and this is the part we really don't want to repeat more than necessary.
How it works:
DeferredValue.context.put("contact, DeferredValue.instance()), because we want to defer the per-contact properties which will appear in the email.ExpressionResolverencounters one of these objects, for example when encountering the expression{{contact.firstname}}while rendering a template, it will throw aDeferredValueException.DeferredValueExceptionis caught in a few specific places. When caught the engine attempts to just echo the Node / Tag / Expression which caused the exception, leaving it untouched.Concerns:
{% raw %}wrapped content will be lost in the deferred evaluation scenario, because they will be evaluated twice. I could add a new mode to the tag which rewrapped its output in another raw wrapper?TagNode. It seems to work, but will definitely need some more work.resolvedExpressionscollection and throwing if we see something being added which we don't like. Maybe this would be better? I think the exception approach is more flexible, because we can do things like making functions throwDeferredValueExceptionunder certain circumstances, like if the function returned the current time and we are in deferred evaluation mode.