Honor x-ms-client-flatten in Bicep type generator#12001
Conversation
There was a problem hiding this comment.
Pull request overview
Updates the Radius Autorest-based Bicep type generator to honor x-ms-client-flatten, hoisting fields from flattened object properties (notably the ARM properties envelope) onto the parent resource/object shape to remove .properties. from user-authored Bicep.
Changes:
- Implement
x-ms-client-flattenin the generator for both resource bodies and nested object types, with safe fallbacks (discriminator / name-collision) and warnings. - Add/extend integration + functional tests to validate flattening behavior end-to-end and refresh integration baselines.
- Regenerate published Radius Bicep type artifacts (
generated/**/types.jsonandgenerated/index.json) and add a functional RP test + template using the new flat authoring syntax.
Reviewed changes
Copilot reviewed 5 out of 15 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| test/functional-portable/corerp/noncloud/resources/testdata/corerp-resources-container-flatten.bicep | New functional-test Bicep template exercising flat (no .properties) authoring syntax. |
| test/functional-portable/corerp/noncloud/resources/container_flatten_test.go | New RP functional test deploying the flat-syntax template and validating RP + K8s outputs. |
| hack/bicep-types-radius/src/autorest.bicep/src/type-generator.ts | Core generator change: implement safe flattening + warnings in resource and object parsing. |
| hack/bicep-types-radius/src/autorest.bicep/test/flatten/flatten.test.ts | New Jest suite running Autorest end-to-end and asserting flatten contract on emitted types.json. |
| hack/bicep-types-radius/src/autorest.bicep/test/integration/specs/basic/resource-manager/Test.Rp1/stable/2021-10-31/spec.json | Extends integration spec with flatten happy-path + discriminator/collision fallback cases. |
| hack/bicep-types-radius/src/autorest.bicep/test/integration/generated/basic/test.rp1/2021-10-31/types.json | Refreshed baseline types output reflecting flattening and new test resources. |
| hack/bicep-types-radius/src/autorest.bicep/test/integration/generated/basic/test.rp1/2021-10-31/types.md | Refreshed markdown baseline reflecting flattened shapes and new resources. |
| hack/bicep-types-radius/src/autorest.bicep/test/integration/generated/basic/test.rp1/2021-10-31/docs/testtype1.md | New/updated per-resource docs baseline for flattened TestType1. |
| hack/bicep-types-radius/src/autorest.bicep/test/integration/generated/basic/test.rp1/2021-10-31/docs/testtype2.md | New per-resource docs baseline for discriminator fallback TestType2. |
| hack/bicep-types-radius/src/autorest.bicep/test/integration/generated/basic/test.rp1/2021-10-31/docs/testtype3.md | New per-resource docs baseline for collision fallback TestType3. |
| hack/bicep-types-radius/generated/applications/applications.core/2023-10-01-preview/types.json | Regenerated published types with flattened resource bodies (notably removing wrapper property objects). |
| hack/bicep-types-radius/generated/applications/applications.datastores/2023-10-01-preview/types.json | Regenerated published types with flattened resource bodies. |
| hack/bicep-types-radius/generated/applications/applications.messaging/2023-10-01-preview/types.json | Regenerated published types with flattened resource bodies. |
| hack/bicep-types-radius/generated/radius/radius.core/2025-08-01-preview/types.json | Regenerated published types with flattened resource bodies. |
| hack/bicep-types-radius/generated/index.json | Updates index refs to match regenerated type-array indices. |
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #12001 +/- ##
==========================================
- Coverage 52.26% 52.24% -0.02%
==========================================
Files 736 736
Lines 47033 47033
==========================================
- Hits 24581 24573 -8
- Misses 20100 20104 +4
- Partials 2352 2356 +4 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
Implements flattening for properties annotated with x-ms-client-flatten=true in the Radius autorest.bicep type generator, eliminating the .properties. envelope from Bicep authoring for Radius resources. Generator changes (hack/bicep-types-radius/src/autorest.bicep): - Add isFlattenSafe, flagsForFlattenedChild, expandFlattenedInto helpers in type-generator.ts. - Wire flattening into processResourceBody and parseObjectType so the extension is honored on both the resource envelope and on nested objects. - Fall back to the nested representation (with a warning) when flattening would lose information: schemas using a discriminator (polymorphic variants) or schemas whose flattened child names would collide with an existing parent property. - Propagate parent ReadOnly/WriteOnly flags to flattened children; Required/Identifier/DeployTimeConstant are not propagated. Tests: - Extend the autorest.bicep integration spec with TestType1 (happy path), TestType2 (discriminator fallback), TestType3 (name-collision fallback) and refresh checked-in baselines. - Add programmatic functional test test/flatten/flatten.test.ts that asserts the flatten contract end-to-end against the regenerated types.json. - Regenerate Radius Bicep types (applications.core, datastores, messaging, radius.core, generated/index.json) so users get the flat syntax. - Add Radius functional test Test_Container_Flatten under test/functional-portable/corerp/noncloud/resources demonstrating the flat authoring syntax for Applications.Core/applications and Applications.Core/containers. Signed-off-by: willdavsmith <willdavsmith@gmail.com>
Extend the functional test template with output statements that read flattened fields back from the deployed resources (app.environment, container.application, container.container.image, container.container.ports.web.containerPort). If the type generator had failed to hoist any of these, Bicep compilation would fail and the deploy step would error out, so the outputs act as a smoke test that the flat syntax works symmetrically for authoring and for cross-resource references. Signed-off-by: willdavsmith <willdavsmith@gmail.com>
…ences Replace the bicep `output` statements with a second container resource (`ctnr2`) that declares its application, image, and listening port by reading flattened fields directly off the first container (`ctnr`). Also rename the first container's bicep symbol from `container` to `ctnr` to disambiguate it from the inner `container` field. This is a closer analogue to how real templates consume flattened fields from other resources. Signed-off-by: willdavsmith <willdavsmith@gmail.com>
52d2c42 to
672cffb
Compare
Preserve the writable properties envelope (so existing Bicep templates compile unchanged and the wire payload still matches the RP's OpenAPI schema) while ALSO hoisting each child of properties onto the parent object as a ReadOnly alias. This gives users a clean reference syntax (e.g. ctnr.container.image) without breaking authoring. - type-generator.ts: flagsForFlattenedChild forces ReadOnly and strips Required; processResourceBody and parseObjectType no longer skip emitting 'properties' after hoisting children. - flatten.test.ts: assert hoisted children are ReadOnly + properties envelope is preserved. - Re-recorded integration baselines; regenerated Radius Bicep types. - Functional test authors via legacy envelope and reads via flat aliases. Signed-off-by: willdavsmith <willdavsmith@gmail.com>
Implements response-side flatten in the ARM-RPC layer so Bicep templates can reference flat field names on Radius resources (e.g. ctnr.application instead of ctnr.properties.application). This pairs with the additive ReadOnly aliases emitted by the Bicep type generator so that flat references actually resolve at deploy time via ARM's reference() function. - New helper flattenPropertiesAliases walks a marshaled body, splats each child of 'properties' onto the parent object as an alias, and preserves the original 'properties' envelope unchanged. Reserved envelope keys are never overwritten and existing top-level keys win on collision. - Wired into OKResponse, CreatedResponse, CreatedAsyncResponse and AcceptedAsyncResponse via a shared marshalResourceBody helper. Flatten is best-effort: on error the request still succeeds with the unflattened body. - PaginatedList bodies are recursed; non-resource bodies (async operation status, error envelopes) are pass-through. - 14 new table-driven unit tests + alias-identity test. Signed-off-by: willdavsmith <willdavsmith@gmail.com>
Documents the response-side flatten + Bicep type aliases approach, non-goals, reserved-keys list, collision rule, alternatives considered, risks, and test coverage. Signed-off-by: willdavsmith <willdavsmith@gmail.com>
Signed-off-by: willdavsmith <willdavsmith@gmail.com>
Decoding each resource response on the wire and splatting properties to
the top level has no effect on ARM reference() resolution, because the
deployment engine rebuilds the resource shape from its typed model
before evaluating expressions. The wire-level aliases are silently
dropped on the way to the expression engine.
The flat-reference behavior now lives in the companion
deployment-engine change
(azure-octo/deployment-engine#willdavsmith/flatten-properties-aliases),
which flattens the JToken stored in referenceValueLookup so that
reference('r').foo resolves to reference('r').properties.foo.
This commit reverts the RP-side flatten helper and its wiring on the
four success Apply methods, and rewrites the design note to reflect
the deployment-engine approach.
Signed-off-by: willdavsmith <willdavsmith@gmail.com>
Dropping the design note from this PR. The feature is documented in the PR body and the existing in-code commentary in the Bicep type generator (hack/bicep-types-radius/src/autorest.bicep/src/type-generator.ts) and the deployment-engine flatten helper. Signed-off-by: willdavsmith <willdavsmith@gmail.com>
Signed-off-by: willdavsmith <willdavsmith@gmail.com>
type-generator.ts: - Mask WriteOnly from flattened-child flags (brooke-hamilton, L501). Without this, a PUT-only child would be emitted as ReadOnly|WriteOnly — a contradictory combination that contradicts the doc-comment. Latent today because the test specs are symmetric, but the code/comment mismatch is fixed. - Remove the unused parentFlags parameter from flagsForFlattenedChild and expandFlattenedInto (brooke-hamilton, L486). The function never read it, and the PR description's claim that parent flags propagated was wrong. Drop both call-site parentFlags computations as well. - Change expandFlattenedInto's return type to void (brooke-hamilton, L136). Both call sites discarded the boolean and the function is atomic, so the unused return was misleading. - Detect collisions against the *full* sibling-name set, not just keys already present in target (brooke-hamilton, L531). In the nested parseObjectType path, expansion runs mid-loop while siblings are still being added, so a hoisted child colliding with a *later* sibling was silently slipping through. Now precompute siblingNames upfront in both call sites and pass it to expandFlattenedInto. flatten.test.ts: - Use mkdtemp under os.tmpdir() and clean up in afterAll, so the staging output is no longer left in the source tree across runs (Copilot L118 + brooke-hamilton L116). - Drop the misleading 'warning behavior' phrase from the file header comment — the test only asserts types.json structure (Copilot L24). - Replace magic numbers 0x01/0x02 with ObjectTypePropertyFlags.Required / .ReadOnly imports (brooke-hamilton L133), and additionally assert that WriteOnly is masked off (matches the new generator behavior above). - Assert that the non-colliding 'extra' child is NOT partially hoisted to the resource body in TestType3 (brooke-hamilton L207) — this is the fall-back-to-nested-representation contract. Generated artifacts (Radius bicep-types and integration baselines) are unchanged: the test specs use symmetric PUT/GET and have no flatten-child-vs-later-sibling collisions, so neither latent bug fix changes today's output. Signed-off-by: willdavsmith <willdavsmith@gmail.com>
|
Addressed all 9 review comments from @copilot and @brooke-hamilton in f98963f. Each inline thread has a detailed reply. Summary
Verification: |
Fixes the 'Format check with Prettier' CI failure introduced by the review-feedback commit. Signed-off-by: willdavsmith <willdavsmith@gmail.com>
- type-generator.ts: track hoisted child names in siblingNames as they are
added, so a *later* flattened wrapper that produces a colliding child
name is detected and falls back to the nested representation instead of
silently overwriting the earlier alias (Copilot L568). Both flatten paths
already shared one Set per parent, so this just calls siblingNames.add()
after each hoist.
- flatten.test.ts: remove the stray trailing '.' on the closing license
banner separator (Copilot L15).
PR description: rewrite the Compatibility bullet that incorrectly claimed
the legacy 'properties: { ... }' authoring syntax would no longer be valid.
The implementation only adds ReadOnly flat aliases; the wrapper is
preserved as the writable authoring surface (Copilot L145).
Signed-off-by: willdavsmith <willdavsmith@gmail.com>
|
Addressed all 3 new Copilot review comments in ae7fd26 (rebased on the latest Code changes
PR body
Functional-test failures on the previous run were infra flakes, not regressions:
Both are unrelated to this PR. The fresh push triggers a re-run automatically. |
Radius functional test overviewClick here to see the test run details
Test Status⌛ Building Radius and pushing container images for functional tests... |
# Description Implements [`x-ms-client-flatten`](https://github.com/Azure/autorest/tree/main/docs/extensions#x-ms-client-flatten) in the Radius Bicep type generator so the ARM `.properties.` envelope no longer leaks into user-authored Bicep templates. Before: ```bicep container.properties.container.image gateway.properties.url ``` After: ```bicep container.container.image gateway.url ``` The annotation is already present on every Radius resource (via `TrackedResourceRequired` in `typespec/radius/v1/trackedresource.tsp`); the generator just had not been honoring it. ## Type of change - This pull request adds or changes features of Radius and has an approved issue (issue link required). Fixes: radius-project#12000 # Contributor checklist - [x] Existing tests pass - [x] New tests added (unit + integration + functional) - [x] Generated artifacts refreshed - [N/A] Schema/typespec changes — annotation already existed - [N/A] Sample/doc Bicep rewrites — deferred to a follow-up; this PR is backward compatible # Changes ## Generator (`hack/bicep-types-radius/src/autorest.bicep`) - `src/type-generator.ts` - New helpers: `isFlattenSafe`, `flagsForFlattenedChild`, `expandFlattenedInto`. - Wire flattening into both `processResourceBody` (resource envelope) and `parseObjectType` (nested objects). - Fall back to the nested representation (with a warning via `logWarning`) when flattening would lose information: - The schema uses a discriminator (polymorphic variants). - A flattened child name collides with an existing parent property. - Each flattened child is emitted as `ReadOnly`. `Required`, `WriteOnly`, `Identifier`, and `DeployTimeConstant` are intentionally not propagated from the wrapper — the flat alias is unambiguously a read-side projection of the underlying `properties` payload. ## Tests - `test/integration/specs/basic/.../spec.json` — extended with: - `TestType1` — happy-path flatten on a resource body. - `TestType2` — polymorphic child, exercises the discriminator fallback. - `TestType3` — child property names collide with the resource envelope, exercises the collision fallback. - Checked-in baselines (`test/integration/generated/basic/...`) refreshed. - New programmatic Jest suite `test/flatten/flatten.test.ts` asserts the contract end-to-end: walks the generated `types.json` and verifies hoisting, fallback emission, and warning behavior. ## Regenerated Bicep types Ran `make generate-bicep-types`. Updated: - `hack/bicep-types-radius/generated/applications/applications.core/2023-10-01-preview/types.json` - `hack/bicep-types-radius/generated/applications/applications.datastores/2023-10-01-preview/types.json` - `hack/bicep-types-radius/generated/applications/applications.messaging/2023-10-01-preview/types.json` - `hack/bicep-types-radius/generated/radius/radius.core/2025-08-01-preview/types.json` - `hack/bicep-types-radius/generated/index.json` Spot-checked: e.g. `Applications.Core/containers` now exposes `application`, `container`, `connections`, `extensions`, `environment`, `identity`, `restartPolicy`, `runtimes`, `resources`, `provisioningState`, `status` directly on the resource body, with no `ApplicationProperties` / `ContainerProperties` wrapper. ## Functional test `test/functional-portable/corerp/noncloud/resources/container_flatten_test.go` + `testdata/corerp-resources-container-flatten.bicep` — `Test_Container_Flatten` deploys an Application + Container written entirely in the flat syntax (no `.properties.`) and validates the RP resources and the rendered pod in the app namespace. The bicep template only compiles against the regenerated types, so the test doubles as a regression guard. # Compatibility - Flattening is purely **additive and read-side only**. The wrapper `properties: { ... }` is preserved as the writable authoring surface — existing templates that author via `.properties.{ ... }` continue to work unchanged. Hoisted flat aliases are emitted with the `ReadOnly` flag, so they can be consumed via `reference('foo').bar` / `output x = foo.bar` but cannot be assigned from a template body. This is intentional: it eliminates `.properties.` from read-side expressions without requiring any breaking authoring-side change. - Existing sample/doc/test Bicep files in this repo are NOT rewritten in this PR; that is a deliberately scoped follow-up. # Deployment-engine dependency (resolved) The deploy-time half of this feature shipped in [azure-octo/deployment-engine#583](azure-octo/deployment-engine#583) (merged), which flattens the JToken stored in the reference value lookup so flat references like `reference('myCtnr').application` resolve at deploy time. The `:latest` `ghcr.io/radius-project/deployment-engine` image picks up the fix automatically. Validated end-to-end on a local kind cluster prior to merge: `Test_Container_Flatten` passes. --------- Signed-off-by: willdavsmith <willdavsmith@gmail.com> Co-authored-by: Dariusz Porowski <3431813+DariuszPorowski@users.noreply.github.com> Signed-off-by: Reshma Abdul Rahim <reshmarahim.abdul@microsoft.com>
Description
Implements
x-ms-client-flattenin the Radius Bicep type generator so the ARM.properties.envelope no longer leaks into user-authored Bicep templates.Before:
After:
The annotation is already present on every Radius resource (via
TrackedResourceRequiredintypespec/radius/v1/trackedresource.tsp); the generator just had not been honoring it.Type of change
Fixes: #12000
Contributor checklist
Changes
Generator (
hack/bicep-types-radius/src/autorest.bicep)src/type-generator.tsisFlattenSafe,flagsForFlattenedChild,expandFlattenedInto.processResourceBody(resource envelope) andparseObjectType(nested objects).logWarning) when flattening would lose information:ReadOnly.Required,WriteOnly,Identifier, andDeployTimeConstantare intentionally not propagated from the wrapper — the flat alias is unambiguously a read-side projection of the underlyingpropertiespayload.Tests
test/integration/specs/basic/.../spec.json— extended with:TestType1— happy-path flatten on a resource body.TestType2— polymorphic child, exercises the discriminator fallback.TestType3— child property names collide with the resource envelope, exercises the collision fallback.test/integration/generated/basic/...) refreshed.test/flatten/flatten.test.tsasserts the contract end-to-end: walks the generatedtypes.jsonand verifies hoisting, fallback emission, and warning behavior.Regenerated Bicep types
Ran
make generate-bicep-types. Updated:hack/bicep-types-radius/generated/applications/applications.core/2023-10-01-preview/types.jsonhack/bicep-types-radius/generated/applications/applications.datastores/2023-10-01-preview/types.jsonhack/bicep-types-radius/generated/applications/applications.messaging/2023-10-01-preview/types.jsonhack/bicep-types-radius/generated/radius/radius.core/2025-08-01-preview/types.jsonhack/bicep-types-radius/generated/index.jsonSpot-checked: e.g.
Applications.Core/containersnow exposesapplication,container,connections,extensions,environment,identity,restartPolicy,runtimes,resources,provisioningState,statusdirectly on the resource body, with noApplicationProperties/ContainerPropertieswrapper.Functional test
test/functional-portable/corerp/noncloud/resources/container_flatten_test.go+testdata/corerp-resources-container-flatten.bicep—Test_Container_Flattendeploys an Application + Container written entirely in the flat syntax (no.properties.) and validates the RP resources and the rendered pod in the app namespace. The bicep template only compiles against the regenerated types, so the test doubles as a regression guard.Compatibility
properties: { ... }is preserved as the writable authoring surface — existing templates that author via.properties.{ ... }continue to work unchanged. Hoisted flat aliases are emitted with theReadOnlyflag, so they can be consumed viareference('foo').bar/output x = foo.barbut cannot be assigned from a template body. This is intentional: it eliminates.properties.from read-side expressions without requiring any breaking authoring-side change.Deployment-engine dependency (resolved)
The deploy-time half of this feature shipped in azure-octo/deployment-engine#583 (merged), which flattens the JToken stored in the reference value lookup so flat references like
reference('myCtnr').applicationresolve at deploy time. The:latestghcr.io/radius-project/deployment-engineimage picks up the fix automatically.Validated end-to-end on a local kind cluster prior to merge:
Test_Container_Flattenpasses.