Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions configuration-schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,10 @@
"type": "boolean",
"description": "In the past, we merged schemas for `allOf` by inlining each schema within the schema list. This approach, though, is incorrect because `allOf` merges at the schema definition level, not at the resulting model level. So, new behavior merges OpenAPI specs but generates different code than we have in the past. Set OldMergeSchemas to true for the old behavior. Please see https://github.com/oapi-codegen/oapi-codegen/issues/531"
},
"old-allof-sibling-merging": {
"type": "boolean",
"description": "In the past, when a schema combined `allOf` with sibling fields at the same level (`properties`, `required`, `additionalProperties`, `description`), those siblings were silently discarded and the schema was emitted as a Go type alias to its sole `allOf` target. New behavior merges the parent's siblings with the `allOf` members so the generated type carries every field declared in the spec. This is a more accurate translation of OpenAPI semantics, but it changes the shape of generated types: a schema that previously produced `type X = Y` may now produce a distinct struct embedding Y with extra fields, which is not interchangeable with Y in downstream Go code. Set OldAllOfSiblingMerging to true to restore the prior behavior. Please see https://github.com/oapi-codegen/oapi-codegen/issues/697"
},
"old-enum-conflicts": {
"type": "boolean",
"description": "Enum values can generate conflicting typenames, so we've updated the code for enum generation to avoid these conflicts, but it will result in some enum types being renamed in existing code. Set OldEnumConflicts to true to revert to old behavior. Please see: Please see https://github.com/oapi-codegen/oapi-codegen/issues/549"
Expand Down
11 changes: 11 additions & 0 deletions internal/test/all_of/openapi.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -51,3 +51,14 @@ components:
type: integer
format: int64
required: [ ID ]
PersonWithMorePropertiesOutsideOfAllOf:
type: object
description: |
This is a person record as returned from a Create endpoint. It contains
all the fields of a Person, with an additional property outside of allOf directives.
properties:
additionalProperty:
type: string
required: [ additionalProperty ]
allOf:
- $ref: "#/components/schemas/Person"
39 changes: 26 additions & 13 deletions internal/test/all_of/v1/openapi.gen.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

39 changes: 26 additions & 13 deletions internal/test/all_of/v2/openapi.gen.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 3 additions & 3 deletions internal/test/components/components.gen.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

17 changes: 16 additions & 1 deletion internal/test/issues/issue-1087/deps/deps.gen.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

13 changes: 13 additions & 0 deletions pkg/codegen/configuration.go
Original file line number Diff line number Diff line change
Expand Up @@ -215,6 +215,19 @@ type CompatibilityOptions struct {
// than we have in the past. Set OldMergeSchemas to true for the old behavior.
// Please see https://github.com/oapi-codegen/oapi-codegen/issues/531
OldMergeSchemas bool `yaml:"old-merge-schemas,omitempty"`
// In the past, when a schema combined `allOf` with sibling fields at the
// same level (`properties`, `required`, `additionalProperties`,
// `description`), those siblings were silently discarded and the schema
// was emitted as a Go type alias to its sole `allOf` target. New behavior
// merges the parent's siblings with the `allOf` members so the generated
// type carries every field declared in the spec. This is a more accurate
// translation of OpenAPI semantics, but it changes the shape of generated
// types: a schema that previously produced `type X = Y` may now produce
// a distinct struct embedding Y with extra fields, which is not
// interchangeable with Y in downstream Go code. Set OldAllOfSiblingMerging
// to true to restore the prior behavior.
// Please see https://github.com/oapi-codegen/oapi-codegen/issues/697
OldAllOfSiblingMerging bool `yaml:"old-allof-sibling-merging,omitempty"`
// Enum values can generate conflicting typenames, so we've updated the
// code for enum generation to avoid these conflicts, but it will result
// in some enum types being renamed in existing code. Set OldEnumConflicts to true
Expand Down
58 changes: 57 additions & 1 deletion pkg/codegen/schema.go
Original file line number Diff line number Diff line change
Expand Up @@ -403,11 +403,48 @@ func GenerateGoSchema(sref *openapi3.SchemaRef, path []string) (Schema, error) {
// so that in a RESTful paradigm, the Create operation can return
// (object, id), so that other operations can refer to (id)
if schema.AllOf != nil {
mergedSchema, err := MergeSchemas(schema.AllOf, path)
var mergedSchema Schema
var err error
// Behavior is gated on Compatibility.OldAllOfSiblingMerging:
// when set, the parent's structural siblings and Description are
// silently discarded (the historical behavior). When unset
// (default), they are merged into the result.
mergeSiblings := !globalState.options.Compatibility.OldAllOfSiblingMerging
if mergeSiblings && hasStructuralSiblings(schema) {
// Inject the parent (with AllOf cleared) as the final allOf
// member so its structural siblings — Properties, Required,
// AdditionalProperties — are merged with the allOf members
// rather than discarded. Issues #697, #931, #1710, #2102.
//
// Allocate a fresh slice rather than appending to schema.AllOf
// directly: if kin-openapi gave us a slice with spare capacity,
// `append` would write the new element into the shared backing
// array, mutating any other view that has been extended past
// len(schema.AllOf).
s := *schema
s.AllOf = nil
allOfRefs := make([]*openapi3.SchemaRef, 0, len(schema.AllOf)+1)
allOfRefs = append(allOfRefs, schema.AllOf...)
allOfRefs = append(allOfRefs, &openapi3.SchemaRef{Value: &s})
mergedSchema, err = MergeSchemas(allOfRefs, path)
} else {
// Either the user opted into legacy behavior, or the parent is
// a pure wrapper with no structural siblings. In the wrapper
// case, MergeSchemas' single-element fast path returns the
// referenced type unchanged, preserving named-type identity.
mergedSchema, err = MergeSchemas(schema.AllOf, path)
}
if err != nil {
return Schema{}, fmt.Errorf("error merging schemas: %w", err)
}
mergedSchema.OAPISchema = schema
// Description is metadata, not a structural constraint, so it
// doesn't go through the merge. Copy it from the parent when set.
// Issue #1960. Gated on the same compatibility flag as the
// sibling-merge above.
if mergeSiblings && schema.Description != "" {
mergedSchema.Description = schema.Description
}
// x-go-type on the parent is handled by the early return above
// (combined extensions). For x-go-type-skip-optional-pointer, only
// override the merged value when the parent sets it explicitly —
Expand Down Expand Up @@ -1012,3 +1049,22 @@ func combinedSchemaExtensions(r *openapi3.SchemaRef) map[string]any {

return combined
}

// hasStructuralSiblings reports whether a schema with allOf also carries
// fields outside allOf that materially affect the generated Go type.
// Such fields must be merged with the allOf members rather than discarded.
//
// Description and Title are excluded — they are metadata, not structural,
// and the caller propagates them separately. Nullable/ReadOnly/WriteOnly
// are also excluded for now: their strict-equality check in
// mergeOpenapiSchemas conflates the bool zero value with "unset" and would
// regress simple wrappers like {allOf: [X-with-nullable:true]}.
func hasStructuralSiblings(s *openapi3.Schema) bool {
if s == nil {
return false
}
return len(s.Properties) > 0 ||
len(s.Required) > 0 ||
s.AdditionalProperties.Has != nil ||
s.AdditionalProperties.Schema != nil
}
Comment thread
greptile-apps[bot] marked this conversation as resolved.
Loading