Skip to content

Fix allOf/nullable detection and recursion#2473

Merged
mromaszewicz merged 1 commit into
oapi-codegen:mainfrom
mromaszewicz:fix/issue-1898
Jul 16, 2026
Merged

Fix allOf/nullable detection and recursion#2473
mromaszewicz merged 1 commit into
oapi-codegen:mainfrom
mromaszewicz:fix/issue-1898

Conversation

@mromaszewicz

Copy link
Copy Markdown
Member

Closes: #1898

The OpenAPI 3.0 idiom for making a $ref nullable is to wrap it in an allOf alongside an inline {nullable: true}, since 3.0 forbids siblings next to $ref. This failed generation with "error merging schemas for AllOf: merging two schemas with different Nullable".

Two problems:

  1. mergeOpenapiSchemas errored whenever two allOf members disagreed on nullability. kin-openapi models Nullable as a plain bool, so an unset value is indistinguishable from an explicit nullable: false, which made this widely-used idiom unusable. Union the flag instead (if any member is nullable, the merged schema is nullable) — consistent with how Required is already merged.

  2. schemaIsNullable only read the schema's own Nullable flag. For the allOf idiom the flag lives on a member, so the field was generated as non-nullable (no pointer / nullable.Nullable[T] wrapping). Descend transitively into allOf members, matching the transitive allOf flattening in mergeOpenapiSchemas, with a lazily-allocated cycle guard so a self-referential allOf cannot cause unbounded recursion.

Adds unit tests for the nullable union and an end-to-end fixture in internal/test/schemas/nullable exercising the decorated-$ref idiom under nullable-type:true for both required and optional properties.

Closes: oapi-codegen#1898

The OpenAPI 3.0 idiom for making a $ref nullable is to wrap it in an
allOf alongside an inline `{nullable: true}`, since 3.0 forbids siblings
next to $ref. This failed generation with "error merging schemas for
AllOf: merging two schemas with different Nullable".

Two problems:

1. mergeOpenapiSchemas errored whenever two allOf members disagreed on
   nullability. kin-openapi models Nullable as a plain bool, so an unset
   value is indistinguishable from an explicit `nullable: false`, which
   made this widely-used idiom unusable. Union the flag instead (if any
   member is nullable, the merged schema is nullable) — consistent with
   how Required is already merged.

2. schemaIsNullable only read the schema's own Nullable flag. For the
   allOf idiom the flag lives on a member, so the field was generated as
   non-nullable (no pointer / nullable.Nullable[T] wrapping). Descend
   transitively into allOf members, matching the transitive allOf
   flattening in mergeOpenapiSchemas, with a lazily-allocated cycle guard
   so a self-referential allOf cannot cause unbounded recursion.

Adds unit tests for the nullable union and an end-to-end fixture in
internal/test/schemas/nullable exercising the decorated-$ref idiom under
nullable-type:true for both required and optional properties.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@mromaszewicz
mromaszewicz requested a review from a team as a code owner July 16, 2026 17:01
@mromaszewicz mromaszewicz added the bug Something isn't working label Jul 16, 2026
@greptile-apps

greptile-apps Bot commented Jul 16, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR fixes the OpenAPI 3.0 pattern of making a $ref nullable by wrapping it in an allOf alongside an inline {nullable: true} member — a pattern that previously failed with "merging two schemas with different Nullable". Two coordinated fixes address the root causes.

  • merge_schemas.go: Replaces the strict equality check on nullability with a union: if any allOf member reports nullable, the merged schema is nullable. This is consistent with how Required is already merged, and correctly handles the fact that kin-openapi cannot distinguish an unset Nullable from an explicit false.
  • schema.go: Extends schemaIsNullable to transitively descend into allOf members via a new schemaIsNullableRec helper with a lazily-allocated pointer-keyed cycle guard, so the nullable flag residing on a member is surfaced at the use site and the field is wrapped in nullable.Nullable[T].
  • internal/test/schemas/nullable/: The regression test is correctly placed in the existing schemas/nullable leaf (not a new issue-numbered directory), extends the existing spec.yaml and nullable_test.go with a provenance comment, and the regenerated nullable.gen.go shows only the expected new types with no unrelated drift.

Confidence Score: 5/5

Safe to merge. Both the merge logic and the nullability detection are correct, the cycle guard in schemaIsNullableRec is sound, and the end-to-end fixture confirms the generated types are right.

Both code paths are logically correct: nil-map reads in Go are safe (returning the zero value), the lazily-allocated seen map is shared across recursive frames via Go reference semantics, and unioning nullability rather than requiring equality is the right semantic for the allOf decorator idiom. The regression test covers required/optional variants and JSON marshalling round-trips. Generated output shows only the expected new types with no unrelated drift.

No files require special attention.

Important Files Changed

Filename Overview
pkg/codegen/merge_schemas.go Replaces the error-on-mismatch nullability check with a union: if any allOf member is nullable, the merged schema is nullable. Logically correct and consistent with how Required is already merged.
pkg/codegen/schema.go Adds schemaIsNullableRec that descends transitively into allOf members with a lazily-allocated pointer-keyed cycle guard. Logic is correct: nil-map reads in Go return the zero value safely, and the seen map is shared across recursive calls via reference semantics.
pkg/codegen/merge_schemas_test.go Adds four table-driven sub-tests covering nullable union: one/both/neither nullable combinations, all passing.
internal/test/schemas/nullable/spec.yaml Adds DecoratedRef and DecoratableValue schemas exercising the allOf-wraps-$ref-with-nullable idiom for both required and optional fields, with provenance comment for issue #1898.
internal/test/schemas/nullable/nullable.gen.go Adds DecoratableValue type alias and DecoratedRef struct with nullable.Nullable[string] fields as expected; no unrelated drift.
internal/test/schemas/nullable/nullable_test.go Adds TestDecoratedRef_AllOfNullable with compile-time type assertions and JSON marshalling round-trip tests for both required and optional nullable refs.

Reviews (1): Last reviewed commit: "Fix allOf/nullable detection and recursi..." | Re-trigger Greptile

@mromaszewicz
mromaszewicz merged commit 6d3c864 into oapi-codegen:main Jul 16, 2026
15 checks passed
@mromaszewicz
mromaszewicz deleted the fix/issue-1898 branch July 16, 2026 17:12
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

error merging schemas for AllOf: merging two schemas with different Nullable

1 participant