Skip to content

fix: allow x-go-type and x-go-type-skip-optional-pointer for allOf#1610

Open
turip wants to merge 5 commits intooapi-codegen:mainfrom
turip:fix-allof-type-override
Open

fix: allow x-go-type and x-go-type-skip-optional-pointer for allOf#1610
turip wants to merge 5 commits intooapi-codegen:mainfrom
turip:fix-allof-type-override

Conversation

@turip
Copy link

@turip turip commented May 14, 2024

Right now if a schema contains x-go-type, for example:

components:
  schemas:
    Pet:
      x-go-type: pet.Pet
      x-go-type-import:
        path: github.com/somepetproject/pkg/pet
      type: object
      required:
        - pet_type
      properties:
        pet_type:
          type: string
      discriminator:
        propertyName: pet_type
    Dog:     # "Dog" is a value for the pet_type property (the discriminator value)
      x-go-type: dog.Dog
      x-go-type-import:
        path: github.com/somepetproject/pkg/dog
      allOf: # Combines the main `Pet` schema with `Dog`-specific properties 
        - $ref: '#/components/schemas/Pet'
        - type: object
          # all other properties specific to a `Dog`
          properties:
            bark:
              type: boolean
            breed:
              type: string
              enum: [Dingo, Husky, Retriever, Shepherd]
    Cat:     # "Cat" is a value for the pet_type property (the discriminator value)
      x-go-type: cat.Cat
      x-go-type-import:
        path: github.com/somepetproject/pkg/cat
      allOf: # Combines the main `Pet` schema with `Cat`-specific properties 
        - $ref: '#/components/schemas/Pet'
        - type: object
          # all other properties specific to a `Cat`
          properties:
            hunts:
              type: boolean
            age:
              type: integer

old behavior

The generated code will still take the pet as the x-go-type:


	"github.com/getkin/kin-openapi/openapi3"
	"github.com/labstack/echo/v4"
	"github.com/somepetproject/pkg/pet"
)

// Cat defines model for Cat.
type Cat = pet.Pet

// Dog defines model for Dog.
type Dog = pet.Pet

// Pet defines model for Pet.
type Pet = pet.Pet

new behavior

This patch makes sure that we are generating the expected output:

	"github.com/somepetproject/pkg/cat"
	"github.com/somepetproject/pkg/dog"
	"github.com/somepetproject/pkg/pet"
)

// Cat defines model for Cat.
type Cat = cat.Cat

// Dog defines model for Dog.
type Dog = dog.Dog

// Pet defines model for Pet.
type Pet = pet.Pet

This is a corner case, as in most cases the same type is used (e.g. splitting the ID from the CreateRequest but using the same underlying object), but in case we would want to compose the objects it would make sense to make this overridable on the parent level.

This patch doesn't introduce a breaking change, as if the x-go-* values are not set, the same inheritance will hapen.

turip added 2 commits May 14, 2024 21:09
This patch ensures that if a schema is defined using allOf the main
schema entry's x-go-type and x-go-type-skip-optional-pointer values
are considered instead of a random allOf member's.
@jamietanna jamietanna requested a review from a team as a code owner November 28, 2024 11:35
@jamietanna jamietanna modified the milestones: v2.5.0, v2.6.0 Jul 15, 2025
@mromaszewicz
Copy link
Member

@greptileai

@greptile-apps
Copy link

greptile-apps bot commented Mar 5, 2026

Greptile Summary

This PR fixes a bug where x-go-type (and x-go-type-skip-optional-pointer) extensions defined at the top level of an allOf schema were silently ignored — the generated type alias inherited from the first $ref in the allOf list instead. The fix inserts early-exit handling in GenerateGoSchema that checks for these extensions after the allOf schemas have been merged, which correctly overrides the merged result.

Key changes:

  • pkg/codegen/schema.go: After MergeSchemas runs for an allOf schema, the code now checks for x-go-type and x-go-type-skip-optional-pointer on the parent schema and applies them to mergedSchema before returning.
  • pkg/codegen/codegen_test.go: New test TestGoAllofTypeOverride validates that type Cat = cat.Cat, type Dog = dog.Dog, and type Pet = pet.Pet are all generated correctly, along with their respective imports.
  • pkg/codegen/test_specs/x-go-type-pet-allof.yaml: New YAML fixture for the Pet/Dog/Cat discriminator scenario.

Issues found:

  • In the new allOf + x-go-type early-return block, the SkipOptionalPointer field is not propagated to mergedSchema. This means the global PreferSkipOptionalPointer output option and any co-located x-go-type-skip-optional-pointer extension are silently ignored when x-go-type is also present on the same allOf schema. The non-allOf code path avoids this because it operates on outSchema, which is initialized with SkipOptionalPointer at creation time.

Confidence Score: 3/5

  • Safe to merge after addressing the SkipOptionalPointer propagation bug in the allOf + x-go-type path
  • The core fix is correct and well-tested. However, there is a logic bug where SkipOptionalPointer (derived from the global PreferSkipOptionalPointer option or a co-located x-go-type-skip-optional-pointer extension) is not applied to the returned mergedSchema in the allOf + x-go-type path. This is inconsistent with the non-allOf code path and could produce incorrect pointer/non-pointer behavior for fields whose type is an allOf schema with x-go-type.
  • pkg/codegen/schema.go — the new allOf + x-go-type block at lines 330–339 needs mergedSchema.SkipOptionalPointer = skipOptionalPointer before the early return.

Important Files Changed

Filename Overview
pkg/codegen/schema.go Adds allOf-level handling for x-go-type and x-go-type-skip-optional-pointer extensions. The x-go-type path has a bug where SkipOptionalPointer (from the global default or a co-located x-go-type-skip-optional-pointer extension) is not propagated to the returned mergedSchema.
pkg/codegen/codegen_test.go Adds TestGoAllofTypeOverride to verify that top-level x-go-type on an allOf schema produces the correct type aliases and imports. Coverage is straightforward and correct, though it doesn't cover the x-go-type-skip-optional-pointer + allOf interaction.
pkg/codegen/test_specs/x-go-type-pet-allof.yaml New test fixture for the allOf + x-go-type scenario using a Pet/Dog/Cat discriminator pattern. Minor: missing a trailing newline at EOF.

Last reviewed commit: 0c9b5b0

Comment on lines +330 to +339
if extension, ok := schema.Extensions[extPropGoType]; ok {
typeName, err := extTypeName(extension)
if err != nil {
return outSchema, fmt.Errorf("invalid value for %q: %w", extPropGoType, err)
}
mergedSchema.GoType = typeName
mergedSchema.DefineViaAlias = true

return mergedSchema, nil
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

SkipOptionalPointer not propagated to mergedSchema in the allOf + x-go-type path

At the top of GenerateGoSchema, skipOptionalPointer is computed by combining the global PreferSkipOptionalPointer default with any top-level x-go-type-skip-optional-pointer extension:

skipOptionalPointer := globalState.options.OutputOptions.PreferSkipOptionalPointer
if extension, ok := schema.Extensions[extPropGoTypeSkipOptionalPointer]; ok {
    skipOptionalPointer, err = extParsePropGoTypeSkipOptionalPointer(extension)
    ...
}

This value is applied to outSchema.SkipOptionalPointer, but in the new allOf + x-go-type early-return block, mergedSchema is returned without that value. This means:

  1. If the global PreferSkipOptionalPointer: true output option is set, it won't be respected for allOf schemas that also use x-go-type.
  2. If a schema uses all three — allOf + x-go-type + x-go-type-skip-optional-pointer — the early return skips the second block entirely, so the x-go-type-skip-optional-pointer extension is silently ignored.

The non-allOf x-go-type path doesn't have this issue because it returns outSchema which already has SkipOptionalPointer set from initialization.

The fix is to apply skipOptionalPointer to mergedSchema before the early return:

Suggested change
if extension, ok := schema.Extensions[extPropGoType]; ok {
typeName, err := extTypeName(extension)
if err != nil {
return outSchema, fmt.Errorf("invalid value for %q: %w", extPropGoType, err)
}
mergedSchema.GoType = typeName
mergedSchema.DefineViaAlias = true
return mergedSchema, nil
}
if extension, ok := schema.Extensions[extPropGoType]; ok {
typeName, err := extTypeName(extension)
if err != nil {
return outSchema, fmt.Errorf("invalid value for %q: %w", extPropGoType, err)
}
mergedSchema.GoType = typeName
mergedSchema.DefineViaAlias = true
mergedSchema.SkipOptionalPointer = skipOptionalPointer
return mergedSchema, nil
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants