feat(parser): detect discriminated unions through allOf-wrapped variants#91
Merged
Conversation
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>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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 inallOf— e.g.ConnectApplication's OAuth branch, which merges OAuth-specific fields with a nestedoneOffor first-party / dynamically-registered sub-variants — the detector silently fell back to flattening, and the flatten path skipped any variant without top-levelproperties. The result: emitters consumingModel.discriminator(Python's dispatcher) lost the OAuth variant entirely, and the parent model carried onlyapplication_type: literal 'm2m'.This walks
allOfwrappers in the four places that gated this:detectAllOfVariantDiscriminatorfinds the shared const property throughallOfon each variant.deriveDiscriminatedVariantNamereads the const value throughallOfand qualifies single-word consts (oauth,m2m) with the parent name so they don't collide globally. Multi-wordEventSchemavariant names (ActionAuthenticationDenied, …) keep their form.extractDiscriminatedAllOfVariantModelsflattens variantallOfbodies and merges nestedoneOfinto 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.collectReferencedNameschasesModel.discriminator.mappingwhen seeding reachability so the variant models survive — previouslyisDiscriminatedModelonly matchedevent/type/object-style literal fields, missingapplication_type.Separately, applies
schemaNameTransformto synthetic enums built inschemaToTypeRefandbuildSyntheticEnumRef. The hook was only wired into component-schema names, so a project-level rename map inoagen.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 typecheckcleannpm test— 1423/1423 passingworkos-gogo build ./...cleanworkos-rustcargo checkclean (only pre-existing unrelated warning)workos-nodenpm run typecheckshows only pre-existing hand-owned errors (radar wiring, organization-membership,lastUsedAttest fixture)🤖 Generated with Claude Code