fix(jsonschema): preserve scalar unions nested in a single-branch anyOf wrapper #3

Merged
deranjer merged 1 commit from fix/anyof-scalar-union-collapse into main 2026-07-08 19:46:56 +00:00
deranjer commented 2026-07-08 18:25:51 +00:00 (Migrated from github.com)

Problem

A property written as {"anyOf":[{"$ref":X}],"nullable":true} — the OpenAPI idiom for attaching nullable/description to a $ref — is a passthrough, not a union.

When X is itself a scalar union (e.g. the acl match/ip/protocol def: integer(0-255) | string enum (tcp,udp,icmp,...)), isScalarUnion sees the wrapper's single non-scalar branch and falls through to FlattenComposite, which merges anyOf/oneOf branches with first-Type-wins and never copies Enum — collapsing the union to a lone integer.

The generated schema then rejects valid values like protocol: tcp with got string, want null or integer. This affects effectively every ACL rule across all Cumulus versions.

Fix

  • scalarUnionVariants: surface the inner union's variants through a single-branch anyOf/oneOf wrapper, so the union is preserved rather than collapsed.
  • scalarUnionToJSONSchema: carry each branch's scalar constraints (minimum/maximum, minLength/maxLength, pattern) so e.g. the integer branch keeps its 0-255 bounds.

Test

TestScalarUnion_NestedInSingleBranchWrapper_Preserved reproduces the exact protocol shape and asserts the output is an anyOf retaining both the integer(0-255) and string-enum(tcp,...) branches rather than collapsing to a scalar.

🤖 Generated with Claude Code

## Problem A property written as `{"anyOf":[{"$ref":X}],"nullable":true}` — the OpenAPI idiom for attaching `nullable`/description to a `$ref` — is a passthrough, not a union. When `X` is itself a scalar union (e.g. the acl `match/ip/protocol` def: `integer(0-255) | string enum (tcp,udp,icmp,...)`), `isScalarUnion` sees the wrapper's single **non-scalar** branch and falls through to `FlattenComposite`, which merges `anyOf`/`oneOf` branches with first-`Type`-wins and **never copies `Enum`** — collapsing the union to a lone integer. The generated schema then rejects valid values like `protocol: tcp` with `got string, want null or integer`. This affects effectively every ACL rule across all Cumulus versions. ## Fix - `scalarUnionVariants`: surface the inner union's variants through a single-branch `anyOf`/`oneOf` wrapper, so the union is preserved rather than collapsed. - `scalarUnionToJSONSchema`: carry each branch's scalar constraints (`minimum`/`maximum`, `minLength`/`maxLength`, `pattern`) so e.g. the integer branch keeps its 0-255 bounds. ## Test `TestScalarUnion_NestedInSingleBranchWrapper_Preserved` reproduces the exact `protocol` shape and asserts the output is an `anyOf` retaining both the integer(0-255) and string-enum(tcp,...) branches rather than collapsing to a scalar. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
Sign in to join this conversation.
No description provided.