Repository navigation
fix(compilers/openapi): place or keep every union's discriminator - #808
Open
fuad-daoud wants to merge 4 commits into
Open
fuad-daoud wants to merge 4 commits into
fuad-daoud wants to merge 4 commits into
Conversation
A discriminated union kept verbatim beside a model could leave the model carrying a discriminator that names targets which are not its subtypes, so a valid document failed validation. Separately, a discriminator beside a union that collapsed to a nullable reference, a null-only branch set, a union beside a non-model body, or a union beside a $ref was dropped silently. The discriminator now stays on the model only when every target it routes to (mapping values and defaultMapping, or the union branches when no mapping entry resolves) is a subtype along the allOf base chain. The check reads the raw source schemas, not the registry, so a subtype declared later cannot change the answer. Otherwise, and at every site whose node has no field for it, the whole object is kept verbatim in Unmodeled["openapi:discriminator"] with an info diagnostic, through one shared helper. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Model-bodied unions kept verbatim for inline branches, oneOf beside anyOf, validation-only branches and unresolved refs kept the discriminator on the model without checking its targets are subtypes, so valid documents failed pass/discriminator-missing-variant. The gate now sits in the one closure every degraded outcome goes through. An inline or unresolved branch counts as a non-subtype when no mapping entry resolves. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
A co-declared union reported through the validation-only keyword family moved its discriminator to Unmodeled without any diagnostic saying so. The beside closure now emits one info degraded-construct diagnostic at the discriminator pointer when no other diagnostic already names the move, so every degraded path reports it exactly once. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Picks up #804 (Go 1.27.2, golangci-lint v2.14.0, openapi v1.25.5). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016MQFAkYBVaqgSxGwe9ZJSn
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
Closes #593. Closes #595.
A union's
discriminatoreither went somewhere it was wrong or went nowhere at all.type: objectwith a discriminator fails validation #593: a valid document exited 1. AoneOfwritten besidetype: objectwith a discriminator is degraded: the body lowers to aModeland the union is kept verbatim underUnmodeled. The discriminator, though, stayed on thatModelwith a mapping toCat, which was neither a variant (there is no union any more) nor a subtype.pass/validaterejected it withpass/discriminator-missing-variant.oneOf: [X, {type: 'null'}]collapses to a nullable reference, the discriminator beside it went into no field and noUnmodeledentry, with nothing reported.The rule for a degraded union with a model body
The
Modelkeeps its discriminator only if every target it can route to is a real subtype of that model. A target counts as a subtype when it reaches the model through theallOfbase the compiler elects, which is what lowers toModel.Base. The targets are the mapping values,defaultMapping, and, when no mapping entry resolves, the union branches. Otherwise the whole discriminator moves verbatim toUnmodeled["openapi:discriminator"](degraded_lowering) with an info diagnostic.pass/validatejudges legality from loweredBaselinks. But component schemas lower in source order, so a subtype declared after its base has noBaseyet when the base is lowered. The compiler therefore decides from the raw schemas, mirroring whatBasewill be. Both declaration orders now produce identical documents.besideclosure that every degraded outcome goes through: discriminated, inline branch,oneOfbesideanyOf, validation-only branches, and unresolved branches. openapi: a oneOf besidetype: objectwith a discriminator fails validation #593's shape is one of five. The other four failed the same way, and each was probed before the fix.CatandDogdeclareallOf: [{$ref: Pet}],Petkeeps its discriminator and validates clean, in either order.Union, is separate.Every place a discriminator could be dropped
The discriminator is now kept verbatim, with an info diagnostic, wherever a union lowering cannot place it:
items,additionalProperties, parameter or response body;enum, aconstor a scalar type;$ref-site union, as an alias or on its carrier.Unions that do place it (
oneOfbesideanyOfwith no body, model-bodied shapes whose targets are subtypes) are unchanged. Docs:ir-design§4.8 and the §14 OpenAPI row.Test plan
make gatepasses after mergingmain(build: move to Go 1.27.2, golangci-lint v2.14.0, openapi v1.25.5 #804), with 100% statement coverage, lint (golangci-lint v2.14.0), fuzz and bench smoke.Probes with
morphic validate: openapi: a oneOf besidetype: objectwith a discriminator fails validation #593's document and the inline-branch,oneOf-beside-anyOfand validation-only shapes all exit 0. Onmain, each exits 1 withpass/discriminator-missing-variant. openapi: a oneOf of one schema and null drops its discriminator when it collapses to a nullable reference #595's document keeps the discriminator underUnmodeledwith an info diagnostic.Every degraded shape is tested with
pass.Validatein both declaration orders, the subtypes declared before the base. Each has a true-subtype counterpart, with acmp.Diffbetween the two orders.Each guard was checked by planting the defect and watching the test go red, then reverting:
TestCollapsedUnion_KeepsItsDiscriminatorAtEveryPositionandTestUnplacedDiscriminator_IsKeptAtEverySiteConformance: two new specs,
discriminated-union-beside-modelandnullable-union-discriminator, witnesstagged-unions, plusinheritanceandoptionality-vs-nullabilityrespectively. Deleting the discriminator from the second spec reddens its golden. No existing golden changed.🤖 Generated with Claude Code
https://claude.ai/code/session_016MQFAkYBVaqgSxGwe9ZJSn