Support nested and chained ternaries in recipe parameter resolver#12239
Conversation
The direct-module recipe parameter resolver previously evaluated only a
single flat `==` ternary with string-literal arms via one regex. Chained
or nested enum mappings such as
`size == "S" ? "Basic" : size == "M" ? "Standard" : "Premium"` mis-bound
the regex capture groups, so the literal `{{...}}` expression was passed
through verbatim to the IaC engine instead of being resolved.
Replace the single-level `ternaryPattern` regex with a condition-only
`conditionPattern` and rewrite `evaluateTernary` as a thin wrapper over a
recursive core (`evalTernaryExpr`, `evalTernaryArm`, `splitTopLevelTernary`).
`splitTopLevelTernary` scans for the outermost `?`/`:` outside quoted string
literals while tracking nested ternary depth, so chained and nested arms
split and resolve correctly.
The public `ResolveParameterExpressions` signature and the bicep/terraform
call sites are unchanged. Documented limitations are intentionally kept:
the only operator is `==`, arms must be string literals or nested ternaries,
and expressions whose chosen branch cannot be resolved still pass through
unchanged rather than failing.
Addresses #12238
Signed-off-by: willdavsmith <willdavsmith@gmail.com>
Dependency Review✅ No vulnerabilities or license issues or OpenSSF Scorecard issues found.Scanned FilesNone |
Radius functional test overviewClick here to see the test run details
Test Status⌛ Building Radius and pushing container images for functional tests... |
There was a problem hiding this comment.
Pull request overview
This PR updates the direct-module recipe parameter resolver’s ternary evaluation logic so chained and nested ternary expressions inside {{ ... }} are parsed and resolved correctly, addressing the regex mis-binding issue described in #12238.
Changes:
- Replaces the single-level ternary regex with a condition-only regex and introduces a recursive ternary evaluator.
- Adds a scanner (
splitTopLevelTernary) to split at the outermost?/ matching:while skipping quoted strings and tracking nesting depth. - Expands unit tests to cover chained and nested ternary scenarios, plus passthrough cases.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 2 comments.
| File | Description |
|---|---|
| pkg/recipes/paramresolver/resolver.go | Reworks ternary parsing/evaluation to support chained/nested ternaries via recursion and a top-level split scanner. |
| pkg/recipes/paramresolver/resolver_test.go | Adds/updates ternary-focused test cases covering chained/nested resolution and passthrough behavior. |
Unit Tests 2 files ±0 442 suites ±0 7m 23s ⏱️ -10s Results for commit ce2fd90. ± Comparison against base commit 547cf38. This pull request removes 1 and adds 6 tests. Note that renamed tests count towards both. |
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #12239 +/- ##
==========================================
+ Coverage 52.82% 52.83% +0.01%
==========================================
Files 743 743
Lines 47788 47828 +40
==========================================
+ Hits 25242 25269 +27
- Misses 20195 20202 +7
- Partials 2351 2357 +6 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
Description
Fixes the direct-module recipe parameter resolver (
pkg/recipes/paramresolver/resolver.go) so that chained and nested ternary expressions resolve correctly.Problem
The resolver evaluated only a single flat
==ternary with string-literal arms using one regex. A chained enum mapping like:mis-bound the regex capture groups, so the literal
{{...}}expression was passed through verbatim to the IaC engine (Bicep/Terraform) instead of being resolved to the intended value.Approach (smallest-diff slice)
Only the ternary evaluator is made recursive — nothing else changes:
ternaryPatternregex with a condition-onlyconditionPattern(<context path> == "value").evaluateTernaryas a thin wrapper over a recursive core:evalTernaryExpr,evalTernaryArm, andsplitTopLevelTernary.splitTopLevelTernarylocates the outermost?/:while skipping quoted string literals and tracking nested ternary depth, so chained/nested arms split and resolve at the correct level.The public
ResolveParameterExpressionssignature is unchanged, and the call sites inpkg/recipes/driver/bicep/bicep.goandpkg/recipes/terraform/execute.goare untouched.Limitations kept by design
These remain as documented limitations (tracked separately on the issue):
==.Test coverage
Test_TernaryExpressionsnow covers 11 cases (5 new), including chained first/middle/default-branch matches, a nested ternary in the true arm, unresolvable-chosen-branch passthrough, and a clarified brace-wrapped passthrough case. All 11 subtests pass;paramresolver,driver/bicep, anddriver/terraformpackages all pass;go build ./...,go vet, andgofmtare clean.Addresses #12238