Description
A policy parameter declared as integer, number, or boolean cannot be set to a Go-template expression ({{ env "X" }}, {{ secret "X" }}). The gateway-controller resolves templates before validation, but the resolved value is always a string, so it fails in two places: JSON-schema validation rejects it as a type mismatch, and — even if validation passed — the string flows unchanged to the policy engine, which type-asserts the value and breaks. Literal numeric/boolean values work; only templated ones fail. This defeats the purpose of resolving templates in the gateway for any non-string param.
Example: the advanced-ratelimit limit/burst params (integers) and slugify-body's maxLength (integer) cannot use {{ env … }}.
Root cause (traced)
templateengine.RenderSpec resolves templates before validation (by design), but renderValue returns every rendered leaf as a string — gateway/gateway-controller/pkg/templateengine/spec.go:152-162. So limit: "{{ env \"LIMIT\" }}" becomes the string "100", never the number 100.
- Validation order: render then validate —
gateway/gateway-controller/pkg/utils/api_deployment.go:316 (render) → :326 (validate).
validatePolicyParams passes params straight to gojsonschema with no coercion — gateway/gateway-controller/pkg/config/policy_validator.go:278-286. A JSON string "100" fails type: integer/number.
- Even past validation, the transform copies params through untouched —
gateway/gateway-controller/pkg/transform/restapi.go:632-650 — into RuntimeDeployConfig (pkg/models/runtime_deploy_config.go:114) → xDS → the policy engine, which hands the bare map[string]interface{} to the policy factory (gateway-runtime/policy-engine/internal/registry/registry.go:84-122). Policies then type-assert values (e.g. gateway/sample-policies/transform-payload-case/transformpayloadcase.go:47), so a string where a number is expected breaks at runtime.
Proposed solution
Add a schema-aware, recursive coercion of resolved policy-param values to their declared JSON-schema type, applied after RenderSpec and before Validate, so both validation and everything downstream (xDS, policy engine) receive correctly typed values:
- The policy definition schema is already reachable at this stage —
PolicyValidator and RestAPITransformer both hold policyDefinitions (pkg/models/policy_definition.go:27), so no new plumbing is needed to look up a param's declared type.
- For each leaf whose schema type is
integer/number/boolean, convert a clean scalar string to the typed value; leave everything else untouched.
- It must walk the schema recursively (
properties/items), because the real target (advanced-ratelimit.limit) is nested under quotas[].limits[]. This mirrors the algorithm the AI Workspace frontend already has in portals/ai-workspace/.../schemaUtils.ts:320 (coerceValuesToSchemaTypes).
- Coerce then validate: this preserves real validation — if a template resolves to a non-numeric string (e.g. env var
"abc" for an integer), coercion can't produce a number and gojsonschema still rejects it.
Alternative considered: schema-unaware re-typing in templateengine (JSON-parse a pure-template scalar leaf back to number/bool). Rejected as riskier — it would over-coerce genuine string params that happen to resolve to numeric-looking strings.
Version
No response
Related Issue
#2872
Description
A policy parameter declared as
integer,number, orbooleancannot be set to a Go-template expression ({{ env "X" }},{{ secret "X" }}). The gateway-controller resolves templates before validation, but the resolved value is always a string, so it fails in two places: JSON-schema validation rejects it as a type mismatch, and — even if validation passed — the string flows unchanged to the policy engine, which type-asserts the value and breaks. Literal numeric/boolean values work; only templated ones fail. This defeats the purpose of resolving templates in the gateway for any non-string param.Example: the advanced-ratelimit
limit/burstparams (integers) andslugify-body'smaxLength(integer) cannot use{{ env … }}.Root cause (traced)
templateengine.RenderSpecresolves templates before validation (by design), butrenderValuereturns every rendered leaf as a string —gateway/gateway-controller/pkg/templateengine/spec.go:152-162. Solimit: "{{ env \"LIMIT\" }}"becomes the string"100", never the number100.gateway/gateway-controller/pkg/utils/api_deployment.go:316(render) →:326(validate).validatePolicyParamspasses params straight togojsonschemawith no coercion —gateway/gateway-controller/pkg/config/policy_validator.go:278-286. A JSON string"100"failstype: integer/number.gateway/gateway-controller/pkg/transform/restapi.go:632-650— intoRuntimeDeployConfig(pkg/models/runtime_deploy_config.go:114) → xDS → the policy engine, which hands the baremap[string]interface{}to the policy factory (gateway-runtime/policy-engine/internal/registry/registry.go:84-122). Policies then type-assert values (e.g.gateway/sample-policies/transform-payload-case/transformpayloadcase.go:47), so a string where a number is expected breaks at runtime.Proposed solution
Add a schema-aware, recursive coercion of resolved policy-param values to their declared JSON-schema type, applied after
RenderSpecand beforeValidate, so both validation and everything downstream (xDS, policy engine) receive correctly typed values:PolicyValidatorandRestAPITransformerboth holdpolicyDefinitions(pkg/models/policy_definition.go:27), so no new plumbing is needed to look up a param's declared type.integer/number/boolean, convert a clean scalar string to the typed value; leave everything else untouched.properties/items), because the real target (advanced-ratelimit.limit) is nested underquotas[].limits[]. This mirrors the algorithm the AI Workspace frontend already has inportals/ai-workspace/.../schemaUtils.ts:320(coerceValuesToSchemaTypes)."abc"for an integer), coercion can't produce a number andgojsonschemastill rejects it.Alternative considered: schema-unaware re-typing in
templateengine(JSON-parse a pure-template scalar leaf back to number/bool). Rejected as riskier — it would over-coerce genuine string params that happen to resolve to numeric-looking strings.Version
No response
Related Issue
#2872