Skip to content
Open
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
5 changes: 5 additions & 0 deletions configuration-schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -277,6 +277,11 @@
"description": "When set to true, automatically renames types that collide across different OpenAPI component sections (schemas, parameters, requestBodies, responses, headers) by appending a suffix based on the component section. Also detects collisions between component types and client response wrapper types. Without this, the codegen will error on duplicate type names, requiring manual resolution via x-go-name.",
"default": false
},
"generate-types-for-anonymous-schemas": {
"type": "boolean",
"description": "When true, every inline schema that would otherwise generate as an anonymous Go struct is instead emitted as a named type with a path-derived name (e.g. `GetRolesIdResponseBody_Data`). Equivalent to adding `x-go-type-name` to every inline schema; when both are present at the same site, `x-go-type-name` wins. Default false. See https://github.com/oapi-codegen/oapi-codegen/issues/1139",
"default": false
},
"type-mapping": {
"type": "object",
"additionalProperties": false,
Expand Down
7 changes: 7 additions & 0 deletions internal/test/anonymous_inner_hoisting/global/cfg.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package: global
output: client.gen.go
generate:
models: true
client: true
output-options:
generate-types-for-anonymous-schemas: true

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

IMO, I'd prefer this by default - most folks don't control their specs, and so they get anonymous structs used which can be awkward

I'd personally swap this with a compatibility-options to disable this functionality

Using an OpenAPI Overlay is one way to get around this, but managing all areas this may apply will be quite hard

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

I have the opposite view.

In specs with lots of inline schemas, this positively litters the file with new type definitions, and in many cases, you don't need to ever refer to the type, so the code, IMO, is nicer. I'm open to defaulting it to be on, however, I dislike the output.

Whenever you have an inline schema in a request body, or a parameter, you will never need to create an instance of that type, deserialization does that for you.

The place where it is nice to have is when you have inline schemas in things you are constructing; usually response bodies, and in those cases, it's so much nicer to declare those specifically. I realize that some people don't control their specs.

279 changes: 279 additions & 0 deletions internal/test/anonymous_inner_hoisting/global/client.gen.go

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

44 changes: 44 additions & 0 deletions internal/test/anonymous_inner_hoisting/global/client_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
package global

import (
"encoding/json"
"testing"

"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)

// TestHoistedTypesExist verifies that with output-options.generate-types-for-
// anonymous-schemas enabled, the inline schemas in spec.yaml become named
// Go types we can reference directly. The spec is the canonical issue #1139
// shape: a response body using `allOf` to merge a $ref with sibling
// `properties:` containing an inline `data` object.
func TestHoistedTypesExist(t *testing.T) {
// Both the response root and the nested inline `data` schema should be
// emitted as named types — assigning a typed zero value would not
// compile if either were still anonymous structs.
var responseBody GetRolesId200JSONResponseBody
var dataField GetRolesId200JSONResponseBody_Data

// Field-level type identity: GetRolesId200JSONResponseBody.Data must be
// of the hoisted GetRolesId200JSONResponseBody_Data type. This
// assignment fails to compile if Data is still an anonymous struct.
responseBody.Data = dataField
_ = responseBody
}

func TestHoistedTypesRoundTrip(t *testing.T) {
body := GetRolesId200JSONResponseBody{
Data: GetRolesId200JSONResponseBody_Data{
Role: Role{Id: 7, Name: "admin"},
},
Ok: true,
}

encoded, err := json.Marshal(body)
require.NoError(t, err)

var decoded GetRolesId200JSONResponseBody
require.NoError(t, json.Unmarshal(encoded, &decoded))
assert.Equal(t, body, decoded)
}
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
package anonymous_inner_hoisting
package global

//go:generate go run github.com/oapi-codegen/oapi-codegen/v2/cmd/oapi-codegen --config=cfg.yaml spec.yaml
Loading
Loading