Skip to content

feat(parser): detect discriminated unions through allOf-wrapped variants#91

Merged
gjtorikian merged 1 commit into
mainfrom
feat/discriminated-allof-oneof
May 21, 2026
Merged

feat(parser): detect discriminated unions through allOf-wrapped variants#91
gjtorikian merged 1 commit into
mainfrom
feat/discriminated-allof-oneof

Conversation

@gjtorikian
Copy link
Copy Markdown
Collaborator

Summary

Discriminator detection on allOf [base, oneOf […]] schemas only worked when each oneOf variant exposed its const property directly. When a variant was itself wrapped in allOf — e.g. ConnectApplication's OAuth branch, which merges OAuth-specific fields with a nested oneOf for first-party / dynamically-registered sub-variants — the detector silently fell back to flattening, and the flatten path skipped any variant without top-level properties. The result: emitters consuming Model.discriminator (Python's dispatcher) lost the OAuth variant entirely, and the parent model carried only application_type: literal 'm2m'.

This walks allOf wrappers in the four places that gated this:

  • detectAllOfVariantDiscriminator finds the shared const property through allOf on each variant.
  • deriveDiscriminatedVariantName reads the const value through allOf and qualifies single-word consts (oauth, m2m) with the parent name so they don't collide globally. Multi-word EventSchema variant names (ActionAuthenticationDenied, …) keep their form.
  • extractDiscriminatedAllOfVariantModels flattens variant allOf bodies and merges nested oneOf into a single shape (literal conflicts widen — is_first_party: true | falseboolean), then merges the outer base object's fields into every variant so each emitted variant is self-contained.
  • collectReferencedNames chases Model.discriminator.mapping when seeding reachability so the variant models survive — previously isDiscriminatedModel only matched event/type/object-style literal fields, missing application_type.

Separately, applies schemaNameTransform to synthetic enums built in schemaToTypeRef and buildSyntheticEnumRef. The hook was only wired into component-schema names, so a project-level rename map in oagen.config.ts (e.g. RadarAction: 'RadarListAction' for the inline /radar/lists/{type}/{action} path-param enum) couldn't reach inline parameter or field enums. Now it can.

Test plan

  • npm run typecheck clean
  • npm test — 1423/1423 passing
  • Regenerated workos-{node,python,go,kotlin,dotnet,ruby,php,rust} against this branch + the paired oagen-emitters PR; verified ConnectApplication output across every SDK (see commit body for shapes)
  • workos-go go build ./... clean
  • workos-rust cargo check clean (only pre-existing unrelated warning)
  • workos-node npm run typecheck shows only pre-existing hand-owned errors (radar wiring, organization-membership, lastUsedAt test fixture)
  • Paired oagen-emitters PR: workos/oagen-emitters#TBD

🤖 Generated with Claude Code

Discriminator detection only worked when each oneOf variant exposed its
const property directly. `ConnectApplication`'s OAuth branch is itself
wrapped in `allOf` (it merges OAuth-specific fields with a nested oneOf
for the first-party / dynamically-registered sub-variants), so the
detector silently fell back to flattening — and the flatten path then
skipped any variant without top-level `properties`. Every emitter that
relies on `Model.discriminator` (notably Python's dispatcher path) lost
the OAuth variant entirely; the parent model carried only
`application_type: literal 'm2m'`.

Walk allOf wrappers in the four places that gated this:

  - `detectAllOfVariantDiscriminator` finds the shared const property
    through allOf on each variant.
  - `deriveDiscriminatedVariantName` reads the const value through
    allOf and qualifies single-word consts (`oauth`, `m2m`) with the
    parent name so they don't collide globally. Multi-word EventSchema
    variant names (`ActionAuthenticationDenied`, …) keep their form.
  - `extractDiscriminatedAllOfVariantModels` flattens variant `allOf`
    bodies and merges nested `oneOf` into a single shape (literal
    conflicts widen — `is_first_party: true | false` → `boolean`),
    then merges the outer base object's fields into every variant so
    each emitted variant is self-contained.
  - `collectReferencedNames` chases `Model.discriminator.mapping` when
    seeding reachability so the variant models survive — previously
    `isDiscriminatedModel` only matched `event`/`type`/`object`-style
    literal fields, missing `application_type`.

Separately, apply `schemaNameTransform` to synthetic enums built in
`schemaToTypeRef` and `buildSyntheticEnumRef`. The hook was only wired
into component-schema names, so a project-level rename map in
`oagen.config.ts` (e.g. `RadarAction: 'RadarListAction'` for the
inline `/radar/lists/{type}/{action}` path-param enum) couldn't reach
inline parameter or field enums. Now it can.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@gjtorikian gjtorikian merged commit a0907b9 into main May 21, 2026
5 checks passed
@gjtorikian gjtorikian deleted the feat/discriminated-allof-oneof branch May 21, 2026 17:45
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

1 participant