Fix discriminator type on unions#2453
Conversation
…fields Closes: #2297 The From*/Merge* union helpers assigned the discriminator value as a raw string literal to the variant's field (v.Code = "resource_exists"), which does not compile when the discriminator property is optional and renders as a pointer to a named enum type. The assignment also assumed the Go field name matches the JSON property name, and that the variant declares the field at all. When the union struct does not itself declare the discriminator property, the value is now stamped into the marshaled JSON instead, via runtime.JSONMerge with the JSON property name. This is type-agnostic: it works for pointer fields, named enum types, x-go-name renames, absent fields, and import-mapped external variants. When the union struct does declare the property, the field assignment remains (it feeds the union's MarshalJSON), now taking an address through a typed local variable when the field is pointer-typed (new Property.IsPointer helper). Output is unchanged for unions without discriminators and for the existing non-pointer discriminator cases, except that From* on a discriminated union now emits JSON with the same alphabetical key order Merge* always produced. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Move as much logic as possible into Go code, instead of complicating templates. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Greptile SummaryThis PR fixes discriminator stamping in generated union helpers (
Confidence Score: 5/5Safe to merge; the change is a targeted bug fix with well-scoped regenerated output and full test coverage for the three new code paths. The template refactor is clean, the new DiscriminatorStampFor logic is straightforward, and both the JSON-stamp-only path and the struct-field+stamp path are exercised by new tests that verify Discriminator() and ValueByDiscriminator() immediately after From*. The only noteworthy asymmetry is that From* does not guard against a failed JSONMerge before assigning t.union, while Merge* does — but since the second argument to JSONMerge is always a hardcoded literal, this path cannot fail in practice. No files require special attention.
|
| Filename | Overview |
|---|---|
| pkg/codegen/templates/union.tmpl | Core template change: switches From*/Merge* discriminator stamping from struct-field mutation to JSONMerge; adds pointer-typed field handling via IsPointer branch; minor inconsistency in From* where JSONMerge error is not checked before assigning t.union |
| pkg/codegen/schema.go | Adds IsPointer() on Property (strings.HasPrefix on GoTypeDef) and DiscriminatorStampFor() on Schema; logic correctly gates on mapping 1:1 coverage, matches discriminator by JSON property name (not Go name), and produces a pre-baked JSONPatch literal |
| internal/test/aggregates/oneof/discriminator.gen.go | New types and From*/Merge* helpers for ConflictError (JSON-stamp-only path) and PetByKind/RenamedPetByKind (struct-field + JSON-stamp path); Discriminator() reads from t.union which now includes the discriminator key after From*, fixing the issue |
| internal/test/aggregates/oneof/components.gen.go | Regenerated: From*/Merge* for existing discriminated unions now add error check on json.Marshal and stamp discriminator via JSONMerge instead of field assignment; changes are scoped to discriminated unions and consistent across all affected types |
| internal/test/aggregates/oneof/discriminator_test.go | Three new test cases cover the pointer-discriminator-on-variant path (ConflictError), pointer-discriminator-on-union path (PetByKind), and renamed-field path (RenamedPetByKind); all exercise Discriminator() and ValueByDiscriminator() after From*, fully covering the fix |
| internal/test/aggregates/oneof/spec_discriminator.yaml | Adds three new schema scenarios (ConflictError, PetByKind, RenamedPetByKind) with appropriate discriminator mappings; anchored via path operations to prevent pruning; correctly placed in the existing aggregates/oneof fixture |
| internal/test/schemas/nullable/spec31/types.gen.go | Regenerated DiscriminatedPet From*/Merge* methods: removes variant field mutation, adds error check on json.Marshal, stamps discriminator via JSONMerge; change is consistent with the template fix |
Reviews (2): Last reviewed commit: "Address code review comments" | Re-trigger Greptile
Closes: #2297
The From*/Merge* union helpers assigned the discriminator value as a raw string literal to the variant's field (v.Code = "resource_exists"), which does not compile when the discriminator property is optional and renders as a pointer to a named enum type. The assignment also assumed the Go field name matches the JSON property name, and that the variant declares the field at all.
When the union struct does not itself declare the discriminator property, the value is now stamped into the marshaled JSON instead, via runtime.JSONMerge with the JSON property name. This is type-agnostic: it works for pointer fields, named enum types, x-go-name renames, absent fields, and import-mapped external variants. When the union struct does declare the property, the field assignment remains (it feeds the union's MarshalJSON), now taking an address through a typed local variable when the field is pointer-typed (new Property.IsPointer helper).
Output is unchanged for unions without discriminators and for the existing non-pointer discriminator cases, except that From* on a discriminated union now emits JSON with the same alphabetical key order Merge* always produced.