Skip to content

Evaluate template conditional guards before expanding other variables - #252

Merged
jogrogan merged 1 commit into
mainfrom
jogrogan/templateGuardOrdering
Sep 16, 2026
Merged

jogrogan merged 1 commit into
mainfrom
jogrogan/templateGuardOrdering

Conversation

@jogrogan

Copy link
Copy Markdown
Collaborator

Problem

A JobTemplate/TableTemplate guarded with {{var==value}} (or {{var!=value}}) is meant to render only when the condition holds and be skipped entirely otherwise. But SimpleTemplate.render() expanded {{...}} placeholders in text order, so a guard placed after another variable did not protect it — the earlier variable was resolved first.

This breaks the "two templates with mirroring guards" pattern. With a Flink SQL job template and a Flink Beam job template both installed (guarded on li.flink.app.type==SQL and ==BEAM), deploying a Beam job should skip the SQL template. But because {{flinksql}} appears before its {{li.flink.app.type==SQL}} guard, the SQL body was generated first and could throw (e.g. failing type validation on a cast the Beam path doesn't care about), breaking the deployment instead of cleanly skipping the template.

Fix

Parse the template once into an ordered list of literal/placeholder tokens, then render in two passes over that list:

  1. Evaluate every conditional guard first, regardless of position — if any fails, skip the whole template (return null) before expanding anything else.
  2. Expand the remaining {{var}} / {{var:default}} values.

Guards can now sit anywhere in the template and reliably protect expensive or fail-prone variables.

The token model also lets rendering append directly to a StringBuilder, dropping the Matcher.appendReplacement/quoteReplacement escaping.

Validation

  • Unit tests (TemplateTest, +11): guard-before/after a throwing variable, multi-guard fail-fast, plus refactor-safety ($/\ literal rendering, adjacent placeholders, empty template, guard-only template). All green.
  • Differential check (throwaway): embedded the original single-pass algorithm and diffed it against the new one across the real flink-template.yaml / flink-beam-template.yaml shapes (multiline bodies, $ in SQL, guards passing/failing) and edge inputs — byte-for-byte identical. The only intended behavior change is the guard ordering.
  • :hoptimator-util, :hoptimator-jdbc, :hoptimator-k8s unit suites green.

Docs

docs/kubernetes/templates.md — the "guard can sit anywhere" note now states the real guarantee: guards are evaluated before any other variable, so a guard reliably protects a variable that appears earlier in the text.

A JobTemplate/TableTemplate guarded with {{var==value}} (or {{var!=value}})
should render only when the condition holds, and skip entirely otherwise. But
the renderer expanded placeholders in text order, so a guard placed after
another variable did not protect it: the earlier variable was resolved first.

This breaks the "two templates with mirroring guards" pattern. A Flink SQL job
template and a Flink Beam job template can both be installed, guarded on
li.flink.app.type==SQL and ==BEAM respectively. When deploying a Beam job, the
SQL template must be skipped — but because {{flinksql}} appears before its
{{li.flink.app.type==SQL}} guard, the SQL body was generated first and could
throw (e.g. failing type validation on a cast the Beam path doesn't care about),
breaking the deployment instead of cleanly skipping the template.

Parse the template once into an ordered list of literal/placeholder tokens, then
render in two passes over that list: evaluate every conditional guard first
(regardless of position) and skip the whole template if any fails, then expand
the remaining {{var}} / {{var:default}} values. Guards can now sit anywhere.

The token model also lets rendering append directly to a StringBuilder, dropping
the Matcher.appendReplacement/quoteReplacement escaping dance. Verified
byte-for-byte equivalent to the previous renderer on the real Flink SQL/Beam
templates and edge cases (multiline values, `$`/`\`, defaults, transforms,
adjacent placeholders); the only intended behavior change is the guard ordering.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@jogrogan
jogrogan enabled auto-merge (squash) September 16, 2026 01:11
@github-actions

github-actions Bot commented Sep 16, 2026

Copy link
Copy Markdown

Code Coverage

Overall Project 84.91% -0.05% 🟢
Files changed 92.42% 🟢

File Coverage
Template.java 96.82% -2.9% 🟢

@ryannedolan ryannedolan left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

interesting!

@jogrogan
jogrogan merged commit d42f4c7 into main Sep 16, 2026
1 check passed
@jogrogan
jogrogan deleted the jogrogan/templateGuardOrdering branch September 16, 2026 15:46
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants