From 8a21753ebe647b8b8e76fb685376f162625f5462 Mon Sep 17 00:00:00 2001 From: Marcin Romaszewicz Date: Wed, 29 Apr 2026 14:00:24 -0700 Subject: [PATCH] Overhaul anonymous schema hoisting MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit BREAKING: this rename affects generated type names for inline `oneOf`, `anyOf`, and `additionalProperties` response schemas. Two prior name patterns are gone and folded into one canonical form: GetPets_200_Data_Item → GetPets200JSONResponseBody_Data_Item GetPets200JSONResponse_Data_Item → GetPets200JSONResponseBody_Data_Item The pre-existing types had no As/From/Merge accessors and the response roots were anonymous structs that could not be method-bound — that's the bug being fixed (oapi-codegen issues #1900 and #1496). User code referencing the old names could only have done so via reflection or json.RawMessage round-trips, since the types were practically unusable. The documented workaround (hoist to `#/components/schemas/` and `$ref` it) sidesteps this surface entirely, so users who hit the bug and switched to the workaround see no name change. Component schema names, `Params`, request body types, the strict envelope name `Response`, and the client-with-responses envelope name `Response` are all stable. Inline `oneOf`/`anyOf` schemas at any operation root or nested below previously bypassed the union-accessor / additionalProperties boilerplate passes, leaving generated wrapper types method-less (or, for response roots, anonymous structs that no method could be attached to). This change unifies the boilerplate-emission pipeline so every schema root — components, parameters, request bodies, response bodies — gets the same method-emitting passes. Codegen pipeline: - GenerateTypeDefinitions now builds two slices. allTypes stays components-only and feeds GenerateTypes (typedef.tmpl). boilerplateTypes is the wider set — components plus op.TypeDefinitions plus each ResponseTypeDefinition's AdditionalTypeDefinitions — and feeds the method-emitting passes (enum scanning, additionalProperties marshalers, union accessors, union+additionalProperties combined). Operation-derived types now get the same method emission as components, while declarations stay in their per-context templates. - GenerateResponseDefinitions hoists inline response-root schemas with UnionElements / HasAdditionalProperties to a synthetic TypeDefinition named ResponseBody. The strict envelope keeps its Response name and references the body type from inside (struct envelope) or as an alias (no-headers case). Two distinct names so the strict envelope never self-references. - GetResponseTypeDefinitions (client-with-responses path) computes the same body name and points the JSON field at it. - Multi-content-type disambiguation generalized: the responseTypeName itself includes the content-type tag, so separate JSON/XML/YAML content types in the same response naturally produce distinct names. New isMediaTypeSupported helper hardcodes today's expectations (intended to be user-configurable in a future change). - GenerateBodyDefinitions and GetResponseTypeDefinitions skip the local hoist when the operation came from an externally-ref'd path item — the imported package already declared the same hoisted type (PathToTypeName is deterministic), so the schema's RefType is set to externalPkg. instead. Avoids the AsExternalRef0Foo accessor names that would otherwise appear in the importing package. - New OperationDefinition.PathItemRef carries the path item ref through to GetResponseTypeDefinitions; GenerateBodyDefinitions and GenerateResponseDefinitions take it as an explicit parameter for symmetry. - ensureExternalRefsInSchema also patches UnionElements with the imported package qualifier when the enclosing schema came from an external file. - Schema.HasCustomMarshalJSON returns true for inline-but-external union shapes. The strict envelope is rendered as a defined type (`type X externalRef0.Y`), so methods on Y don't transfer; the delegator emitted by the strict template (which calls externalRef0.Y(t).MarshalJSON()) is needed for the response to serialize the union payload instead of the empty struct value. - GenerateUnionBoilerplate and GenerateUnionAndAdditionalProopertiesBoilerplate dedup by TypeName, matching the pre-existing pattern in GenerateAdditionalPropertyBoilerplate. Templates: - client-with-responses.tmpl no longer emits AdditionalTypeDefinitions declarations inline. Those nested response types now flow through op.TypeDefinitions (collected in GenerateTypeDefsForOperation by upstream PR #2284) and get declared once via param-types.tmpl with union/additionalProperties accessor methods attached. - strict-server templates (strict-interface.tmpl, strict-fiber-interface.tmpl, strict-iris-interface.tmpl) encode the response body without touching the unexported union field when the schema is from an external package — the type's MarshalJSON delegator handles it instead. Tests: - New internal/test/anonymous_inner_hoisting fixture exercises As/From/Merge round-trips on every shape: response root oneOf, response root anyOf, response items oneOf, deeply nested oneOf, request body root oneOf, request body property oneOf, plus a cross-branch Merge test. - New internal/test/issues/issue-1378/fooservice_test.go TestExternalRefUnionResponseSerialization locks in the cross-package union response serialization fix; without the HasCustomMarshalJSON change it produces `{}` instead of the union payload. Generated fixtures across internal/test and examples regenerated to reflect the new method emission and naming. Two follow-up cleanups in the strict-server templates were identified during review and deferred — extracting the .union-vs-MarshalJSON access decision into a Go-side EncodeAccessor helper, and pre-computing the full strict envelope declaration line in Go. Both are notes, not blockers. Issue #2010 (cross-module strict-server type embedding) is adjacent but a separate fix surface. Co-Authored-By: Claude Opus 4.7 (1M context) --- .../test/anonymous_inner_hoisting/cfg.yaml | 5 + .../anonymous_inner_hoisting/client.gen.go | 1320 +++++++++++++++++ .../anonymous_inner_hoisting/client_test.go | 141 ++ .../test/anonymous_inner_hoisting/generate.go | 3 + .../test/anonymous_inner_hoisting/spec.yaml | 132 ++ .../test/any_of/codegen/inline/openapi.gen.go | 100 +- internal/test/components/components.gen.go | 162 +- .../issues/issue-1277/content-array.gen.go | 31 +- .../issue-1378/bionicle/bionicle.gen.go | 45 +- .../issue-1378/fooservice/fooservice.gen.go | 12 +- .../issue-1378/fooservice/fooservice_test.go | 27 + .../name_conflict_resolution.gen.go | 20 +- internal/test/strict-server/chi/server.gen.go | 4 +- internal/test/strict-server/chi/types.gen.go | 77 +- .../test/strict-server/client/client.gen.go | 80 +- .../test/strict-server/echo/server.gen.go | 4 +- internal/test/strict-server/echo/types.gen.go | 77 +- .../test/strict-server/fiber/server.gen.go | 5 +- .../test/strict-server/fiber/types.gen.go | 77 +- internal/test/strict-server/gin/server.gen.go | 4 +- internal/test/strict-server/gin/types.gen.go | 77 +- .../test/strict-server/gorilla/server.gen.go | 4 +- .../test/strict-server/gorilla/types.gen.go | 77 +- .../test/strict-server/iris/server.gen.go | 5 +- internal/test/strict-server/iris/types.gen.go | 77 +- .../test/strict-server/stdhttp/server.gen.go | 4 +- .../test/strict-server/stdhttp/types.gen.go | 77 +- pkg/codegen/codegen.go | 47 +- pkg/codegen/externalref.go | 35 +- pkg/codegen/operations.go | 182 ++- pkg/codegen/schema.go | 16 +- pkg/codegen/template_helpers.go | 22 + .../templates/client-with-responses.tmpl | 6 - .../strict/strict-fiber-interface.tmpl | 2 +- .../templates/strict/strict-interface.tmpl | 2 +- .../strict/strict-iris-interface.tmpl | 2 +- 36 files changed, 2690 insertions(+), 271 deletions(-) create mode 100644 internal/test/anonymous_inner_hoisting/cfg.yaml create mode 100644 internal/test/anonymous_inner_hoisting/client.gen.go create mode 100644 internal/test/anonymous_inner_hoisting/client_test.go create mode 100644 internal/test/anonymous_inner_hoisting/generate.go create mode 100644 internal/test/anonymous_inner_hoisting/spec.yaml create mode 100644 internal/test/issues/issue-1378/fooservice/fooservice_test.go diff --git a/internal/test/anonymous_inner_hoisting/cfg.yaml b/internal/test/anonymous_inner_hoisting/cfg.yaml new file mode 100644 index 0000000000..bbdee926a3 --- /dev/null +++ b/internal/test/anonymous_inner_hoisting/cfg.yaml @@ -0,0 +1,5 @@ +package: anonymous_inner_hoisting +output: client.gen.go +generate: + models: true + client: true diff --git a/internal/test/anonymous_inner_hoisting/client.gen.go b/internal/test/anonymous_inner_hoisting/client.gen.go new file mode 100644 index 0000000000..74fe6907c2 --- /dev/null +++ b/internal/test/anonymous_inner_hoisting/client.gen.go @@ -0,0 +1,1320 @@ +// Package anonymous_inner_hoisting provides primitives to interact with the openapi HTTP API. +// +// Code generated by github.com/oapi-codegen/oapi-codegen/v2 version v2.0.0-00010101000000-000000000000 DO NOT EDIT. +package anonymous_inner_hoisting + +import ( + "bytes" + "context" + "encoding/json" + "fmt" + "io" + "net/http" + "net/url" + "strings" + + "github.com/oapi-codegen/runtime" +) + +// Defines values for CatKind. +const ( + CatKindCat CatKind = "cat" +) + +// Valid indicates whether the value is a known member of the CatKind enum. +func (e CatKind) Valid() bool { + switch e { + case CatKindCat: + return true + default: + return false + } +} + +// Defines values for DogKind. +const ( + DogKindDog DogKind = "dog" +) + +// Valid indicates whether the value is a known member of the DogKind enum. +func (e DogKind) Valid() bool { + switch e { + case DogKindDog: + return true + default: + return false + } +} + +// Cat defines model for Cat. +type Cat struct { + Kind CatKind `json:"kind"` + Name *string `json:"name,omitempty"` +} + +// CatKind defines model for Cat.Kind. +type CatKind string + +// Dog defines model for Dog. +type Dog struct { + Kind DogKind `json:"kind"` + Name *string `json:"name,omitempty"` +} + +// DogKind defines model for Dog.Kind. +type DogKind string + +// PostBodyPropertyOneOfJSONBody defines parameters for PostBodyPropertyOneOf. +type PostBodyPropertyOneOfJSONBody struct { + Pet *PostBodyPropertyOneOfJSONBody_Pet `json:"pet,omitempty"` +} + +// PostBodyPropertyOneOfJSONBody_Pet defines parameters for PostBodyPropertyOneOf. +type PostBodyPropertyOneOfJSONBody_Pet struct { + union json.RawMessage +} + +// PostBodyRootOneOfJSONBody defines parameters for PostBodyRootOneOf. +type PostBodyRootOneOfJSONBody struct { + union json.RawMessage +} + +// GetResponseDeepNested200JSONResponseBody_Wrapper_Inner defines parameters for GetResponseDeepNested. +type GetResponseDeepNested200JSONResponseBody_Wrapper_Inner struct { + union json.RawMessage +} + +// GetResponseItemsOneOf200JSONResponseBody_Items_Item defines parameters for GetResponseItemsOneOf. +type GetResponseItemsOneOf200JSONResponseBody_Items_Item struct { + union json.RawMessage +} + +// GetResponseRootAnyOf200JSONResponseBody defines parameters for GetResponseRootAnyOf. +type GetResponseRootAnyOf200JSONResponseBody struct { + union json.RawMessage +} + +// GetResponseRootOneOf200JSONResponseBody defines parameters for GetResponseRootOneOf. +type GetResponseRootOneOf200JSONResponseBody struct { + union json.RawMessage +} + +// PostBodyPropertyOneOfJSONRequestBody defines body for PostBodyPropertyOneOf for application/json ContentType. +type PostBodyPropertyOneOfJSONRequestBody PostBodyPropertyOneOfJSONBody + +// PostBodyRootOneOfJSONRequestBody defines body for PostBodyRootOneOf for application/json ContentType. +type PostBodyRootOneOfJSONRequestBody PostBodyRootOneOfJSONBody + +// AsCat returns the union data inside the PostBodyPropertyOneOfJSONBody_Pet as a Cat +func (t PostBodyPropertyOneOfJSONBody_Pet) AsCat() (Cat, error) { + var body Cat + err := json.Unmarshal(t.union, &body) + return body, err +} + +// FromCat overwrites any union data inside the PostBodyPropertyOneOfJSONBody_Pet as the provided Cat +func (t *PostBodyPropertyOneOfJSONBody_Pet) FromCat(v Cat) error { + b, err := json.Marshal(v) + t.union = b + return err +} + +// MergeCat performs a merge with any union data inside the PostBodyPropertyOneOfJSONBody_Pet, using the provided Cat +func (t *PostBodyPropertyOneOfJSONBody_Pet) MergeCat(v Cat) error { + b, err := json.Marshal(v) + if err != nil { + return err + } + + merged, err := runtime.JSONMerge(t.union, b) + t.union = merged + return err +} + +// AsDog returns the union data inside the PostBodyPropertyOneOfJSONBody_Pet as a Dog +func (t PostBodyPropertyOneOfJSONBody_Pet) AsDog() (Dog, error) { + var body Dog + err := json.Unmarshal(t.union, &body) + return body, err +} + +// FromDog overwrites any union data inside the PostBodyPropertyOneOfJSONBody_Pet as the provided Dog +func (t *PostBodyPropertyOneOfJSONBody_Pet) FromDog(v Dog) error { + b, err := json.Marshal(v) + t.union = b + return err +} + +// MergeDog performs a merge with any union data inside the PostBodyPropertyOneOfJSONBody_Pet, using the provided Dog +func (t *PostBodyPropertyOneOfJSONBody_Pet) MergeDog(v Dog) error { + b, err := json.Marshal(v) + if err != nil { + return err + } + + merged, err := runtime.JSONMerge(t.union, b) + t.union = merged + return err +} + +func (t PostBodyPropertyOneOfJSONBody_Pet) MarshalJSON() ([]byte, error) { + b, err := t.union.MarshalJSON() + return b, err +} + +func (t *PostBodyPropertyOneOfJSONBody_Pet) UnmarshalJSON(b []byte) error { + err := t.union.UnmarshalJSON(b) + return err +} + +// AsCat returns the union data inside the PostBodyRootOneOfJSONBody as a Cat +func (t PostBodyRootOneOfJSONBody) AsCat() (Cat, error) { + var body Cat + err := json.Unmarshal(t.union, &body) + return body, err +} + +// FromCat overwrites any union data inside the PostBodyRootOneOfJSONBody as the provided Cat +func (t *PostBodyRootOneOfJSONBody) FromCat(v Cat) error { + b, err := json.Marshal(v) + t.union = b + return err +} + +// MergeCat performs a merge with any union data inside the PostBodyRootOneOfJSONBody, using the provided Cat +func (t *PostBodyRootOneOfJSONBody) MergeCat(v Cat) error { + b, err := json.Marshal(v) + if err != nil { + return err + } + + merged, err := runtime.JSONMerge(t.union, b) + t.union = merged + return err +} + +// AsDog returns the union data inside the PostBodyRootOneOfJSONBody as a Dog +func (t PostBodyRootOneOfJSONBody) AsDog() (Dog, error) { + var body Dog + err := json.Unmarshal(t.union, &body) + return body, err +} + +// FromDog overwrites any union data inside the PostBodyRootOneOfJSONBody as the provided Dog +func (t *PostBodyRootOneOfJSONBody) FromDog(v Dog) error { + b, err := json.Marshal(v) + t.union = b + return err +} + +// MergeDog performs a merge with any union data inside the PostBodyRootOneOfJSONBody, using the provided Dog +func (t *PostBodyRootOneOfJSONBody) MergeDog(v Dog) error { + b, err := json.Marshal(v) + if err != nil { + return err + } + + merged, err := runtime.JSONMerge(t.union, b) + t.union = merged + return err +} + +func (t PostBodyRootOneOfJSONBody) MarshalJSON() ([]byte, error) { + b, err := t.union.MarshalJSON() + return b, err +} + +func (t *PostBodyRootOneOfJSONBody) UnmarshalJSON(b []byte) error { + err := t.union.UnmarshalJSON(b) + return err +} + +// AsCat returns the union data inside the GetResponseDeepNested200JSONResponseBody_Wrapper_Inner as a Cat +func (t GetResponseDeepNested200JSONResponseBody_Wrapper_Inner) AsCat() (Cat, error) { + var body Cat + err := json.Unmarshal(t.union, &body) + return body, err +} + +// FromCat overwrites any union data inside the GetResponseDeepNested200JSONResponseBody_Wrapper_Inner as the provided Cat +func (t *GetResponseDeepNested200JSONResponseBody_Wrapper_Inner) FromCat(v Cat) error { + b, err := json.Marshal(v) + t.union = b + return err +} + +// MergeCat performs a merge with any union data inside the GetResponseDeepNested200JSONResponseBody_Wrapper_Inner, using the provided Cat +func (t *GetResponseDeepNested200JSONResponseBody_Wrapper_Inner) MergeCat(v Cat) error { + b, err := json.Marshal(v) + if err != nil { + return err + } + + merged, err := runtime.JSONMerge(t.union, b) + t.union = merged + return err +} + +// AsDog returns the union data inside the GetResponseDeepNested200JSONResponseBody_Wrapper_Inner as a Dog +func (t GetResponseDeepNested200JSONResponseBody_Wrapper_Inner) AsDog() (Dog, error) { + var body Dog + err := json.Unmarshal(t.union, &body) + return body, err +} + +// FromDog overwrites any union data inside the GetResponseDeepNested200JSONResponseBody_Wrapper_Inner as the provided Dog +func (t *GetResponseDeepNested200JSONResponseBody_Wrapper_Inner) FromDog(v Dog) error { + b, err := json.Marshal(v) + t.union = b + return err +} + +// MergeDog performs a merge with any union data inside the GetResponseDeepNested200JSONResponseBody_Wrapper_Inner, using the provided Dog +func (t *GetResponseDeepNested200JSONResponseBody_Wrapper_Inner) MergeDog(v Dog) error { + b, err := json.Marshal(v) + if err != nil { + return err + } + + merged, err := runtime.JSONMerge(t.union, b) + t.union = merged + return err +} + +func (t GetResponseDeepNested200JSONResponseBody_Wrapper_Inner) MarshalJSON() ([]byte, error) { + b, err := t.union.MarshalJSON() + return b, err +} + +func (t *GetResponseDeepNested200JSONResponseBody_Wrapper_Inner) UnmarshalJSON(b []byte) error { + err := t.union.UnmarshalJSON(b) + return err +} + +// AsCat returns the union data inside the GetResponseItemsOneOf200JSONResponseBody_Items_Item as a Cat +func (t GetResponseItemsOneOf200JSONResponseBody_Items_Item) AsCat() (Cat, error) { + var body Cat + err := json.Unmarshal(t.union, &body) + return body, err +} + +// FromCat overwrites any union data inside the GetResponseItemsOneOf200JSONResponseBody_Items_Item as the provided Cat +func (t *GetResponseItemsOneOf200JSONResponseBody_Items_Item) FromCat(v Cat) error { + b, err := json.Marshal(v) + t.union = b + return err +} + +// MergeCat performs a merge with any union data inside the GetResponseItemsOneOf200JSONResponseBody_Items_Item, using the provided Cat +func (t *GetResponseItemsOneOf200JSONResponseBody_Items_Item) MergeCat(v Cat) error { + b, err := json.Marshal(v) + if err != nil { + return err + } + + merged, err := runtime.JSONMerge(t.union, b) + t.union = merged + return err +} + +// AsDog returns the union data inside the GetResponseItemsOneOf200JSONResponseBody_Items_Item as a Dog +func (t GetResponseItemsOneOf200JSONResponseBody_Items_Item) AsDog() (Dog, error) { + var body Dog + err := json.Unmarshal(t.union, &body) + return body, err +} + +// FromDog overwrites any union data inside the GetResponseItemsOneOf200JSONResponseBody_Items_Item as the provided Dog +func (t *GetResponseItemsOneOf200JSONResponseBody_Items_Item) FromDog(v Dog) error { + b, err := json.Marshal(v) + t.union = b + return err +} + +// MergeDog performs a merge with any union data inside the GetResponseItemsOneOf200JSONResponseBody_Items_Item, using the provided Dog +func (t *GetResponseItemsOneOf200JSONResponseBody_Items_Item) MergeDog(v Dog) error { + b, err := json.Marshal(v) + if err != nil { + return err + } + + merged, err := runtime.JSONMerge(t.union, b) + t.union = merged + return err +} + +func (t GetResponseItemsOneOf200JSONResponseBody_Items_Item) MarshalJSON() ([]byte, error) { + b, err := t.union.MarshalJSON() + return b, err +} + +func (t *GetResponseItemsOneOf200JSONResponseBody_Items_Item) UnmarshalJSON(b []byte) error { + err := t.union.UnmarshalJSON(b) + return err +} + +// AsCat returns the union data inside the GetResponseRootAnyOf200JSONResponseBody as a Cat +func (t GetResponseRootAnyOf200JSONResponseBody) AsCat() (Cat, error) { + var body Cat + err := json.Unmarshal(t.union, &body) + return body, err +} + +// FromCat overwrites any union data inside the GetResponseRootAnyOf200JSONResponseBody as the provided Cat +func (t *GetResponseRootAnyOf200JSONResponseBody) FromCat(v Cat) error { + b, err := json.Marshal(v) + t.union = b + return err +} + +// MergeCat performs a merge with any union data inside the GetResponseRootAnyOf200JSONResponseBody, using the provided Cat +func (t *GetResponseRootAnyOf200JSONResponseBody) MergeCat(v Cat) error { + b, err := json.Marshal(v) + if err != nil { + return err + } + + merged, err := runtime.JSONMerge(t.union, b) + t.union = merged + return err +} + +// AsDog returns the union data inside the GetResponseRootAnyOf200JSONResponseBody as a Dog +func (t GetResponseRootAnyOf200JSONResponseBody) AsDog() (Dog, error) { + var body Dog + err := json.Unmarshal(t.union, &body) + return body, err +} + +// FromDog overwrites any union data inside the GetResponseRootAnyOf200JSONResponseBody as the provided Dog +func (t *GetResponseRootAnyOf200JSONResponseBody) FromDog(v Dog) error { + b, err := json.Marshal(v) + t.union = b + return err +} + +// MergeDog performs a merge with any union data inside the GetResponseRootAnyOf200JSONResponseBody, using the provided Dog +func (t *GetResponseRootAnyOf200JSONResponseBody) MergeDog(v Dog) error { + b, err := json.Marshal(v) + if err != nil { + return err + } + + merged, err := runtime.JSONMerge(t.union, b) + t.union = merged + return err +} + +func (t GetResponseRootAnyOf200JSONResponseBody) MarshalJSON() ([]byte, error) { + b, err := t.union.MarshalJSON() + return b, err +} + +func (t *GetResponseRootAnyOf200JSONResponseBody) UnmarshalJSON(b []byte) error { + err := t.union.UnmarshalJSON(b) + return err +} + +// AsCat returns the union data inside the GetResponseRootOneOf200JSONResponseBody as a Cat +func (t GetResponseRootOneOf200JSONResponseBody) AsCat() (Cat, error) { + var body Cat + err := json.Unmarshal(t.union, &body) + return body, err +} + +// FromCat overwrites any union data inside the GetResponseRootOneOf200JSONResponseBody as the provided Cat +func (t *GetResponseRootOneOf200JSONResponseBody) FromCat(v Cat) error { + b, err := json.Marshal(v) + t.union = b + return err +} + +// MergeCat performs a merge with any union data inside the GetResponseRootOneOf200JSONResponseBody, using the provided Cat +func (t *GetResponseRootOneOf200JSONResponseBody) MergeCat(v Cat) error { + b, err := json.Marshal(v) + if err != nil { + return err + } + + merged, err := runtime.JSONMerge(t.union, b) + t.union = merged + return err +} + +// AsDog returns the union data inside the GetResponseRootOneOf200JSONResponseBody as a Dog +func (t GetResponseRootOneOf200JSONResponseBody) AsDog() (Dog, error) { + var body Dog + err := json.Unmarshal(t.union, &body) + return body, err +} + +// FromDog overwrites any union data inside the GetResponseRootOneOf200JSONResponseBody as the provided Dog +func (t *GetResponseRootOneOf200JSONResponseBody) FromDog(v Dog) error { + b, err := json.Marshal(v) + t.union = b + return err +} + +// MergeDog performs a merge with any union data inside the GetResponseRootOneOf200JSONResponseBody, using the provided Dog +func (t *GetResponseRootOneOf200JSONResponseBody) MergeDog(v Dog) error { + b, err := json.Marshal(v) + if err != nil { + return err + } + + merged, err := runtime.JSONMerge(t.union, b) + t.union = merged + return err +} + +func (t GetResponseRootOneOf200JSONResponseBody) MarshalJSON() ([]byte, error) { + b, err := t.union.MarshalJSON() + return b, err +} + +func (t *GetResponseRootOneOf200JSONResponseBody) UnmarshalJSON(b []byte) error { + err := t.union.UnmarshalJSON(b) + return err +} + +// RequestEditorFn is the function signature for the RequestEditor callback function +type RequestEditorFn func(ctx context.Context, req *http.Request) error + +// Doer performs HTTP requests. +// +// The standard http.Client implements this interface. +type HttpRequestDoer interface { + Do(req *http.Request) (*http.Response, error) +} + +// Client which conforms to the OpenAPI3 specification for this service. +type Client struct { + // The endpoint of the server conforming to this interface, with scheme, + // https://api.deepmap.com for example. This can contain a path relative + // to the server, such as https://api.deepmap.com/dev-test, and all the + // paths in the swagger spec will be appended to the server. + Server string + + // Doer for performing requests, typically a *http.Client with any + // customized settings, such as certificate chains. + Client HttpRequestDoer + + // A list of callbacks for modifying requests which are generated before sending over + // the network. + RequestEditors []RequestEditorFn +} + +// ClientOption allows setting custom parameters during construction +type ClientOption func(*Client) error + +// Creates a new Client, with reasonable defaults +func NewClient(server string, opts ...ClientOption) (*Client, error) { + // create a client with sane default values + client := Client{ + Server: server, + } + // mutate client and add all optional params + for _, o := range opts { + if err := o(&client); err != nil { + return nil, err + } + } + // ensure the server URL always has a trailing slash + if !strings.HasSuffix(client.Server, "/") { + client.Server += "/" + } + // create httpClient, if not already present + if client.Client == nil { + client.Client = &http.Client{} + } + return &client, nil +} + +// WithHTTPClient allows overriding the default Doer, which is +// automatically created using http.Client. This is useful for tests. +func WithHTTPClient(doer HttpRequestDoer) ClientOption { + return func(c *Client) error { + c.Client = doer + return nil + } +} + +// WithRequestEditorFn allows setting up a callback function, which will be +// called right before sending the request. This can be used to mutate the request. +func WithRequestEditorFn(fn RequestEditorFn) ClientOption { + return func(c *Client) error { + c.RequestEditors = append(c.RequestEditors, fn) + return nil + } +} + +// The interface specification for the client above. +type ClientInterface interface { + // PostBodyPropertyOneOfWithBody request with any body + PostBodyPropertyOneOfWithBody(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) + + PostBodyPropertyOneOf(ctx context.Context, body PostBodyPropertyOneOfJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) + + // PostBodyRootOneOfWithBody request with any body + PostBodyRootOneOfWithBody(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) + + PostBodyRootOneOf(ctx context.Context, body PostBodyRootOneOfJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) + + // GetResponseDeepNested request + GetResponseDeepNested(ctx context.Context, reqEditors ...RequestEditorFn) (*http.Response, error) + + // GetResponseItemsOneOf request + GetResponseItemsOneOf(ctx context.Context, reqEditors ...RequestEditorFn) (*http.Response, error) + + // GetResponseRootAnyOf request + GetResponseRootAnyOf(ctx context.Context, reqEditors ...RequestEditorFn) (*http.Response, error) + + // GetResponseRootOneOf request + GetResponseRootOneOf(ctx context.Context, reqEditors ...RequestEditorFn) (*http.Response, error) +} + +func (c *Client) PostBodyPropertyOneOfWithBody(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewPostBodyPropertyOneOfRequestWithBody(c.Server, contentType, body) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + if err := c.applyEditors(ctx, req, reqEditors); err != nil { + return nil, err + } + return c.Client.Do(req) +} + +func (c *Client) PostBodyPropertyOneOf(ctx context.Context, body PostBodyPropertyOneOfJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewPostBodyPropertyOneOfRequest(c.Server, body) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + if err := c.applyEditors(ctx, req, reqEditors); err != nil { + return nil, err + } + return c.Client.Do(req) +} + +func (c *Client) PostBodyRootOneOfWithBody(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewPostBodyRootOneOfRequestWithBody(c.Server, contentType, body) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + if err := c.applyEditors(ctx, req, reqEditors); err != nil { + return nil, err + } + return c.Client.Do(req) +} + +func (c *Client) PostBodyRootOneOf(ctx context.Context, body PostBodyRootOneOfJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewPostBodyRootOneOfRequest(c.Server, body) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + if err := c.applyEditors(ctx, req, reqEditors); err != nil { + return nil, err + } + return c.Client.Do(req) +} + +func (c *Client) GetResponseDeepNested(ctx context.Context, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewGetResponseDeepNestedRequest(c.Server) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + if err := c.applyEditors(ctx, req, reqEditors); err != nil { + return nil, err + } + return c.Client.Do(req) +} + +func (c *Client) GetResponseItemsOneOf(ctx context.Context, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewGetResponseItemsOneOfRequest(c.Server) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + if err := c.applyEditors(ctx, req, reqEditors); err != nil { + return nil, err + } + return c.Client.Do(req) +} + +func (c *Client) GetResponseRootAnyOf(ctx context.Context, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewGetResponseRootAnyOfRequest(c.Server) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + if err := c.applyEditors(ctx, req, reqEditors); err != nil { + return nil, err + } + return c.Client.Do(req) +} + +func (c *Client) GetResponseRootOneOf(ctx context.Context, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewGetResponseRootOneOfRequest(c.Server) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + if err := c.applyEditors(ctx, req, reqEditors); err != nil { + return nil, err + } + return c.Client.Do(req) +} + +// NewPostBodyPropertyOneOfRequest calls the generic PostBodyPropertyOneOf builder with application/json body +func NewPostBodyPropertyOneOfRequest(server string, body PostBodyPropertyOneOfJSONRequestBody) (*http.Request, error) { + var bodyReader io.Reader + buf, err := json.Marshal(body) + if err != nil { + return nil, err + } + bodyReader = bytes.NewReader(buf) + return NewPostBodyPropertyOneOfRequestWithBody(server, "application/json", bodyReader) +} + +// NewPostBodyPropertyOneOfRequestWithBody generates requests for PostBodyPropertyOneOf with any type of body +func NewPostBodyPropertyOneOfRequestWithBody(server string, contentType string, body io.Reader) (*http.Request, error) { + var err error + + serverURL, err := url.Parse(server) + if err != nil { + return nil, err + } + + operationPath := fmt.Sprintf("/body-property-oneof") + if operationPath[0] == '/' { + operationPath = "." + operationPath + } + + queryURL, err := serverURL.Parse(operationPath) + if err != nil { + return nil, err + } + + req, err := http.NewRequest(http.MethodPost, queryURL.String(), body) + if err != nil { + return nil, err + } + + req.Header.Add("Content-Type", contentType) + + return req, nil +} + +// NewPostBodyRootOneOfRequest calls the generic PostBodyRootOneOf builder with application/json body +func NewPostBodyRootOneOfRequest(server string, body PostBodyRootOneOfJSONRequestBody) (*http.Request, error) { + var bodyReader io.Reader + buf, err := json.Marshal(body) + if err != nil { + return nil, err + } + bodyReader = bytes.NewReader(buf) + return NewPostBodyRootOneOfRequestWithBody(server, "application/json", bodyReader) +} + +// NewPostBodyRootOneOfRequestWithBody generates requests for PostBodyRootOneOf with any type of body +func NewPostBodyRootOneOfRequestWithBody(server string, contentType string, body io.Reader) (*http.Request, error) { + var err error + + serverURL, err := url.Parse(server) + if err != nil { + return nil, err + } + + operationPath := fmt.Sprintf("/body-root-oneof") + if operationPath[0] == '/' { + operationPath = "." + operationPath + } + + queryURL, err := serverURL.Parse(operationPath) + if err != nil { + return nil, err + } + + req, err := http.NewRequest(http.MethodPost, queryURL.String(), body) + if err != nil { + return nil, err + } + + req.Header.Add("Content-Type", contentType) + + return req, nil +} + +// NewGetResponseDeepNestedRequest generates requests for GetResponseDeepNested +func NewGetResponseDeepNestedRequest(server string) (*http.Request, error) { + var err error + + serverURL, err := url.Parse(server) + if err != nil { + return nil, err + } + + operationPath := fmt.Sprintf("/response-deep-nested") + if operationPath[0] == '/' { + operationPath = "." + operationPath + } + + queryURL, err := serverURL.Parse(operationPath) + if err != nil { + return nil, err + } + + req, err := http.NewRequest(http.MethodGet, queryURL.String(), nil) + if err != nil { + return nil, err + } + + return req, nil +} + +// NewGetResponseItemsOneOfRequest generates requests for GetResponseItemsOneOf +func NewGetResponseItemsOneOfRequest(server string) (*http.Request, error) { + var err error + + serverURL, err := url.Parse(server) + if err != nil { + return nil, err + } + + operationPath := fmt.Sprintf("/response-items-oneof") + if operationPath[0] == '/' { + operationPath = "." + operationPath + } + + queryURL, err := serverURL.Parse(operationPath) + if err != nil { + return nil, err + } + + req, err := http.NewRequest(http.MethodGet, queryURL.String(), nil) + if err != nil { + return nil, err + } + + return req, nil +} + +// NewGetResponseRootAnyOfRequest generates requests for GetResponseRootAnyOf +func NewGetResponseRootAnyOfRequest(server string) (*http.Request, error) { + var err error + + serverURL, err := url.Parse(server) + if err != nil { + return nil, err + } + + operationPath := fmt.Sprintf("/response-root-anyof") + if operationPath[0] == '/' { + operationPath = "." + operationPath + } + + queryURL, err := serverURL.Parse(operationPath) + if err != nil { + return nil, err + } + + req, err := http.NewRequest(http.MethodGet, queryURL.String(), nil) + if err != nil { + return nil, err + } + + return req, nil +} + +// NewGetResponseRootOneOfRequest generates requests for GetResponseRootOneOf +func NewGetResponseRootOneOfRequest(server string) (*http.Request, error) { + var err error + + serverURL, err := url.Parse(server) + if err != nil { + return nil, err + } + + operationPath := fmt.Sprintf("/response-root-oneof") + if operationPath[0] == '/' { + operationPath = "." + operationPath + } + + queryURL, err := serverURL.Parse(operationPath) + if err != nil { + return nil, err + } + + req, err := http.NewRequest(http.MethodGet, queryURL.String(), nil) + if err != nil { + return nil, err + } + + return req, nil +} + +func (c *Client) applyEditors(ctx context.Context, req *http.Request, additionalEditors []RequestEditorFn) error { + for _, r := range c.RequestEditors { + if err := r(ctx, req); err != nil { + return err + } + } + for _, r := range additionalEditors { + if err := r(ctx, req); err != nil { + return err + } + } + return nil +} + +// ClientWithResponses builds on ClientInterface to offer response payloads +type ClientWithResponses struct { + ClientInterface +} + +// NewClientWithResponses creates a new ClientWithResponses, which wraps +// Client with return type handling +func NewClientWithResponses(server string, opts ...ClientOption) (*ClientWithResponses, error) { + client, err := NewClient(server, opts...) + if err != nil { + return nil, err + } + return &ClientWithResponses{client}, nil +} + +// WithBaseURL overrides the baseURL. +func WithBaseURL(baseURL string) ClientOption { + return func(c *Client) error { + newBaseURL, err := url.Parse(baseURL) + if err != nil { + return err + } + c.Server = newBaseURL.String() + return nil + } +} + +// ClientWithResponsesInterface is the interface specification for the client with responses above. +type ClientWithResponsesInterface interface { + // PostBodyPropertyOneOfWithBodyWithResponse request with any body + PostBodyPropertyOneOfWithBodyWithResponse(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*PostBodyPropertyOneOfResponse, error) + + PostBodyPropertyOneOfWithResponse(ctx context.Context, body PostBodyPropertyOneOfJSONRequestBody, reqEditors ...RequestEditorFn) (*PostBodyPropertyOneOfResponse, error) + + // PostBodyRootOneOfWithBodyWithResponse request with any body + PostBodyRootOneOfWithBodyWithResponse(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*PostBodyRootOneOfResponse, error) + + PostBodyRootOneOfWithResponse(ctx context.Context, body PostBodyRootOneOfJSONRequestBody, reqEditors ...RequestEditorFn) (*PostBodyRootOneOfResponse, error) + + // GetResponseDeepNestedWithResponse request + GetResponseDeepNestedWithResponse(ctx context.Context, reqEditors ...RequestEditorFn) (*GetResponseDeepNestedResponse, error) + + // GetResponseItemsOneOfWithResponse request + GetResponseItemsOneOfWithResponse(ctx context.Context, reqEditors ...RequestEditorFn) (*GetResponseItemsOneOfResponse, error) + + // GetResponseRootAnyOfWithResponse request + GetResponseRootAnyOfWithResponse(ctx context.Context, reqEditors ...RequestEditorFn) (*GetResponseRootAnyOfResponse, error) + + // GetResponseRootOneOfWithResponse request + GetResponseRootOneOfWithResponse(ctx context.Context, reqEditors ...RequestEditorFn) (*GetResponseRootOneOfResponse, error) +} + +type PostBodyPropertyOneOfResponse struct { + Body []byte + HTTPResponse *http.Response +} + +// Status returns HTTPResponse.Status +func (r PostBodyPropertyOneOfResponse) Status() string { + if r.HTTPResponse != nil { + return r.HTTPResponse.Status + } + return http.StatusText(0) +} + +// StatusCode returns HTTPResponse.StatusCode +func (r PostBodyPropertyOneOfResponse) StatusCode() int { + if r.HTTPResponse != nil { + return r.HTTPResponse.StatusCode + } + return 0 +} + +// ContentType is a convenience method to retrieve the Content-Type value from the HTTP response headers +func (r PostBodyPropertyOneOfResponse) ContentType() string { + if r.HTTPResponse != nil { + return r.HTTPResponse.Header.Get("Content-Type") + } + return "" +} + +type PostBodyRootOneOfResponse struct { + Body []byte + HTTPResponse *http.Response +} + +// Status returns HTTPResponse.Status +func (r PostBodyRootOneOfResponse) Status() string { + if r.HTTPResponse != nil { + return r.HTTPResponse.Status + } + return http.StatusText(0) +} + +// StatusCode returns HTTPResponse.StatusCode +func (r PostBodyRootOneOfResponse) StatusCode() int { + if r.HTTPResponse != nil { + return r.HTTPResponse.StatusCode + } + return 0 +} + +// ContentType is a convenience method to retrieve the Content-Type value from the HTTP response headers +func (r PostBodyRootOneOfResponse) ContentType() string { + if r.HTTPResponse != nil { + return r.HTTPResponse.Header.Get("Content-Type") + } + return "" +} + +type GetResponseDeepNestedResponse struct { + Body []byte + HTTPResponse *http.Response + JSON200 *struct { + Wrapper *struct { + Inner *GetResponseDeepNested200JSONResponseBody_Wrapper_Inner `json:"inner,omitempty"` + } `json:"wrapper,omitempty"` + } +} + +// Status returns HTTPResponse.Status +func (r GetResponseDeepNestedResponse) Status() string { + if r.HTTPResponse != nil { + return r.HTTPResponse.Status + } + return http.StatusText(0) +} + +// StatusCode returns HTTPResponse.StatusCode +func (r GetResponseDeepNestedResponse) StatusCode() int { + if r.HTTPResponse != nil { + return r.HTTPResponse.StatusCode + } + return 0 +} + +// ContentType is a convenience method to retrieve the Content-Type value from the HTTP response headers +func (r GetResponseDeepNestedResponse) ContentType() string { + if r.HTTPResponse != nil { + return r.HTTPResponse.Header.Get("Content-Type") + } + return "" +} + +type GetResponseItemsOneOfResponse struct { + Body []byte + HTTPResponse *http.Response + JSON200 *struct { + Items []GetResponseItemsOneOf200JSONResponseBody_Items_Item `json:"items"` + } +} + +// Status returns HTTPResponse.Status +func (r GetResponseItemsOneOfResponse) Status() string { + if r.HTTPResponse != nil { + return r.HTTPResponse.Status + } + return http.StatusText(0) +} + +// StatusCode returns HTTPResponse.StatusCode +func (r GetResponseItemsOneOfResponse) StatusCode() int { + if r.HTTPResponse != nil { + return r.HTTPResponse.StatusCode + } + return 0 +} + +// ContentType is a convenience method to retrieve the Content-Type value from the HTTP response headers +func (r GetResponseItemsOneOfResponse) ContentType() string { + if r.HTTPResponse != nil { + return r.HTTPResponse.Header.Get("Content-Type") + } + return "" +} + +type GetResponseRootAnyOfResponse struct { + Body []byte + HTTPResponse *http.Response + JSON200 *GetResponseRootAnyOf200JSONResponseBody +} + +// Status returns HTTPResponse.Status +func (r GetResponseRootAnyOfResponse) Status() string { + if r.HTTPResponse != nil { + return r.HTTPResponse.Status + } + return http.StatusText(0) +} + +// StatusCode returns HTTPResponse.StatusCode +func (r GetResponseRootAnyOfResponse) StatusCode() int { + if r.HTTPResponse != nil { + return r.HTTPResponse.StatusCode + } + return 0 +} + +// ContentType is a convenience method to retrieve the Content-Type value from the HTTP response headers +func (r GetResponseRootAnyOfResponse) ContentType() string { + if r.HTTPResponse != nil { + return r.HTTPResponse.Header.Get("Content-Type") + } + return "" +} + +type GetResponseRootOneOfResponse struct { + Body []byte + HTTPResponse *http.Response + JSON200 *GetResponseRootOneOf200JSONResponseBody +} + +// Status returns HTTPResponse.Status +func (r GetResponseRootOneOfResponse) Status() string { + if r.HTTPResponse != nil { + return r.HTTPResponse.Status + } + return http.StatusText(0) +} + +// StatusCode returns HTTPResponse.StatusCode +func (r GetResponseRootOneOfResponse) StatusCode() int { + if r.HTTPResponse != nil { + return r.HTTPResponse.StatusCode + } + return 0 +} + +// ContentType is a convenience method to retrieve the Content-Type value from the HTTP response headers +func (r GetResponseRootOneOfResponse) ContentType() string { + if r.HTTPResponse != nil { + return r.HTTPResponse.Header.Get("Content-Type") + } + return "" +} + +// PostBodyPropertyOneOfWithBodyWithResponse request with arbitrary body returning *PostBodyPropertyOneOfResponse +func (c *ClientWithResponses) PostBodyPropertyOneOfWithBodyWithResponse(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*PostBodyPropertyOneOfResponse, error) { + rsp, err := c.PostBodyPropertyOneOfWithBody(ctx, contentType, body, reqEditors...) + if err != nil { + return nil, err + } + return ParsePostBodyPropertyOneOfResponse(rsp) +} + +func (c *ClientWithResponses) PostBodyPropertyOneOfWithResponse(ctx context.Context, body PostBodyPropertyOneOfJSONRequestBody, reqEditors ...RequestEditorFn) (*PostBodyPropertyOneOfResponse, error) { + rsp, err := c.PostBodyPropertyOneOf(ctx, body, reqEditors...) + if err != nil { + return nil, err + } + return ParsePostBodyPropertyOneOfResponse(rsp) +} + +// PostBodyRootOneOfWithBodyWithResponse request with arbitrary body returning *PostBodyRootOneOfResponse +func (c *ClientWithResponses) PostBodyRootOneOfWithBodyWithResponse(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*PostBodyRootOneOfResponse, error) { + rsp, err := c.PostBodyRootOneOfWithBody(ctx, contentType, body, reqEditors...) + if err != nil { + return nil, err + } + return ParsePostBodyRootOneOfResponse(rsp) +} + +func (c *ClientWithResponses) PostBodyRootOneOfWithResponse(ctx context.Context, body PostBodyRootOneOfJSONRequestBody, reqEditors ...RequestEditorFn) (*PostBodyRootOneOfResponse, error) { + rsp, err := c.PostBodyRootOneOf(ctx, body, reqEditors...) + if err != nil { + return nil, err + } + return ParsePostBodyRootOneOfResponse(rsp) +} + +// GetResponseDeepNestedWithResponse request returning *GetResponseDeepNestedResponse +func (c *ClientWithResponses) GetResponseDeepNestedWithResponse(ctx context.Context, reqEditors ...RequestEditorFn) (*GetResponseDeepNestedResponse, error) { + rsp, err := c.GetResponseDeepNested(ctx, reqEditors...) + if err != nil { + return nil, err + } + return ParseGetResponseDeepNestedResponse(rsp) +} + +// GetResponseItemsOneOfWithResponse request returning *GetResponseItemsOneOfResponse +func (c *ClientWithResponses) GetResponseItemsOneOfWithResponse(ctx context.Context, reqEditors ...RequestEditorFn) (*GetResponseItemsOneOfResponse, error) { + rsp, err := c.GetResponseItemsOneOf(ctx, reqEditors...) + if err != nil { + return nil, err + } + return ParseGetResponseItemsOneOfResponse(rsp) +} + +// GetResponseRootAnyOfWithResponse request returning *GetResponseRootAnyOfResponse +func (c *ClientWithResponses) GetResponseRootAnyOfWithResponse(ctx context.Context, reqEditors ...RequestEditorFn) (*GetResponseRootAnyOfResponse, error) { + rsp, err := c.GetResponseRootAnyOf(ctx, reqEditors...) + if err != nil { + return nil, err + } + return ParseGetResponseRootAnyOfResponse(rsp) +} + +// GetResponseRootOneOfWithResponse request returning *GetResponseRootOneOfResponse +func (c *ClientWithResponses) GetResponseRootOneOfWithResponse(ctx context.Context, reqEditors ...RequestEditorFn) (*GetResponseRootOneOfResponse, error) { + rsp, err := c.GetResponseRootOneOf(ctx, reqEditors...) + if err != nil { + return nil, err + } + return ParseGetResponseRootOneOfResponse(rsp) +} + +// ParsePostBodyPropertyOneOfResponse parses an HTTP response from a PostBodyPropertyOneOfWithResponse call +func ParsePostBodyPropertyOneOfResponse(rsp *http.Response) (*PostBodyPropertyOneOfResponse, error) { + bodyBytes, err := io.ReadAll(rsp.Body) + defer func() { _ = rsp.Body.Close() }() + if err != nil { + return nil, err + } + + response := &PostBodyPropertyOneOfResponse{ + Body: bodyBytes, + HTTPResponse: rsp, + } + + return response, nil +} + +// ParsePostBodyRootOneOfResponse parses an HTTP response from a PostBodyRootOneOfWithResponse call +func ParsePostBodyRootOneOfResponse(rsp *http.Response) (*PostBodyRootOneOfResponse, error) { + bodyBytes, err := io.ReadAll(rsp.Body) + defer func() { _ = rsp.Body.Close() }() + if err != nil { + return nil, err + } + + response := &PostBodyRootOneOfResponse{ + Body: bodyBytes, + HTTPResponse: rsp, + } + + return response, nil +} + +// ParseGetResponseDeepNestedResponse parses an HTTP response from a GetResponseDeepNestedWithResponse call +func ParseGetResponseDeepNestedResponse(rsp *http.Response) (*GetResponseDeepNestedResponse, error) { + bodyBytes, err := io.ReadAll(rsp.Body) + defer func() { _ = rsp.Body.Close() }() + if err != nil { + return nil, err + } + + response := &GetResponseDeepNestedResponse{ + Body: bodyBytes, + HTTPResponse: rsp, + } + + switch { + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: + var dest struct { + Wrapper *struct { + Inner *GetResponseDeepNested200JSONResponseBody_Wrapper_Inner `json:"inner,omitempty"` + } `json:"wrapper,omitempty"` + } + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON200 = &dest + + } + + return response, nil +} + +// ParseGetResponseItemsOneOfResponse parses an HTTP response from a GetResponseItemsOneOfWithResponse call +func ParseGetResponseItemsOneOfResponse(rsp *http.Response) (*GetResponseItemsOneOfResponse, error) { + bodyBytes, err := io.ReadAll(rsp.Body) + defer func() { _ = rsp.Body.Close() }() + if err != nil { + return nil, err + } + + response := &GetResponseItemsOneOfResponse{ + Body: bodyBytes, + HTTPResponse: rsp, + } + + switch { + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: + var dest struct { + Items []GetResponseItemsOneOf200JSONResponseBody_Items_Item `json:"items"` + } + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON200 = &dest + + } + + return response, nil +} + +// ParseGetResponseRootAnyOfResponse parses an HTTP response from a GetResponseRootAnyOfWithResponse call +func ParseGetResponseRootAnyOfResponse(rsp *http.Response) (*GetResponseRootAnyOfResponse, error) { + bodyBytes, err := io.ReadAll(rsp.Body) + defer func() { _ = rsp.Body.Close() }() + if err != nil { + return nil, err + } + + response := &GetResponseRootAnyOfResponse{ + Body: bodyBytes, + HTTPResponse: rsp, + } + + switch { + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: + var dest GetResponseRootAnyOf200JSONResponseBody + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON200 = &dest + + } + + return response, nil +} + +// ParseGetResponseRootOneOfResponse parses an HTTP response from a GetResponseRootOneOfWithResponse call +func ParseGetResponseRootOneOfResponse(rsp *http.Response) (*GetResponseRootOneOfResponse, error) { + bodyBytes, err := io.ReadAll(rsp.Body) + defer func() { _ = rsp.Body.Close() }() + if err != nil { + return nil, err + } + + response := &GetResponseRootOneOfResponse{ + Body: bodyBytes, + HTTPResponse: rsp, + } + + switch { + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: + var dest GetResponseRootOneOf200JSONResponseBody + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON200 = &dest + + } + + return response, nil +} diff --git a/internal/test/anonymous_inner_hoisting/client_test.go b/internal/test/anonymous_inner_hoisting/client_test.go new file mode 100644 index 0000000000..abdf532440 --- /dev/null +++ b/internal/test/anonymous_inner_hoisting/client_test.go @@ -0,0 +1,141 @@ +package anonymous_inner_hoisting + +import ( + "encoding/json" + "testing" + + "github.com/stretchr/testify/require" +) + +func ptr[T any](v T) *T { return &v } + +func TestResponseRootOneOf_RoundTripCat(t *testing.T) { + cat := Cat{Kind: CatKindCat, Name: ptr("whiskers")} + + var u GetResponseRootOneOf200JSONResponseBody + require.NoError(t, u.FromCat(cat)) + + b, err := json.Marshal(u) + require.NoError(t, err) + + var decoded GetResponseRootOneOf200JSONResponseBody + require.NoError(t, json.Unmarshal(b, &decoded)) + + got, err := decoded.AsCat() + require.NoError(t, err) + require.Equal(t, cat, got) +} + +func TestResponseRootAnyOf_RoundTripDog(t *testing.T) { + dog := Dog{Kind: DogKindDog, Name: ptr("rex")} + + var u GetResponseRootAnyOf200JSONResponseBody + require.NoError(t, u.FromDog(dog)) + + b, err := json.Marshal(u) + require.NoError(t, err) + + var decoded GetResponseRootAnyOf200JSONResponseBody + require.NoError(t, json.Unmarshal(b, &decoded)) + + got, err := decoded.AsDog() + require.NoError(t, err) + require.Equal(t, dog, got) +} + +func TestResponseItemsOneOf_RoundTripBothBranches(t *testing.T) { + cat := Cat{Kind: CatKindCat, Name: ptr("whiskers")} + dog := Dog{Kind: DogKindDog, Name: ptr("rex")} + + var catItem GetResponseItemsOneOf200JSONResponseBody_Items_Item + require.NoError(t, catItem.FromCat(cat)) + var dogItem GetResponseItemsOneOf200JSONResponseBody_Items_Item + require.NoError(t, dogItem.FromDog(dog)) + + bCat, err := json.Marshal(catItem) + require.NoError(t, err) + bDog, err := json.Marshal(dogItem) + require.NoError(t, err) + + var decodedCat, decodedDog GetResponseItemsOneOf200JSONResponseBody_Items_Item + require.NoError(t, json.Unmarshal(bCat, &decodedCat)) + require.NoError(t, json.Unmarshal(bDog, &decodedDog)) + + gotCat, err := decodedCat.AsCat() + require.NoError(t, err) + require.Equal(t, cat, gotCat) + + gotDog, err := decodedDog.AsDog() + require.NoError(t, err) + require.Equal(t, dog, gotDog) +} + +func TestResponseDeepNested_RoundTrip(t *testing.T) { + cat := Cat{Kind: CatKindCat, Name: ptr("whiskers")} + + var inner GetResponseDeepNested200JSONResponseBody_Wrapper_Inner + require.NoError(t, inner.FromCat(cat)) + + b, err := json.Marshal(inner) + require.NoError(t, err) + + var decoded GetResponseDeepNested200JSONResponseBody_Wrapper_Inner + require.NoError(t, json.Unmarshal(b, &decoded)) + + got, err := decoded.AsCat() + require.NoError(t, err) + require.Equal(t, cat, got) +} + +func TestBodyRootOneOf_RoundTripCat(t *testing.T) { + cat := Cat{Kind: CatKindCat, Name: ptr("whiskers")} + + var body PostBodyRootOneOfJSONBody + require.NoError(t, body.FromCat(cat)) + + b, err := json.Marshal(body) + require.NoError(t, err) + + var decoded PostBodyRootOneOfJSONBody + require.NoError(t, json.Unmarshal(b, &decoded)) + + got, err := decoded.AsCat() + require.NoError(t, err) + require.Equal(t, cat, got) +} + +func TestBodyPropertyOneOf_RoundTripDog(t *testing.T) { + dog := Dog{Kind: DogKindDog, Name: ptr("rex")} + + var pet PostBodyPropertyOneOfJSONBody_Pet + require.NoError(t, pet.FromDog(dog)) + + b, err := json.Marshal(pet) + require.NoError(t, err) + + var decoded PostBodyPropertyOneOfJSONBody_Pet + require.NoError(t, json.Unmarshal(b, &decoded)) + + got, err := decoded.AsDog() + require.NoError(t, err) + require.Equal(t, dog, got) +} + +func TestMergeOverwritesPriorBranch(t *testing.T) { + cat := Cat{Kind: CatKindCat, Name: ptr("whiskers")} + dog := Dog{Kind: DogKindDog, Name: ptr("rex")} + + var u GetResponseRootOneOf200JSONResponseBody + require.NoError(t, u.FromCat(cat)) + require.NoError(t, u.MergeDog(dog)) + + b, err := json.Marshal(u) + require.NoError(t, err) + + var decoded GetResponseRootOneOf200JSONResponseBody + require.NoError(t, json.Unmarshal(b, &decoded)) + + gotDog, err := decoded.AsDog() + require.NoError(t, err) + require.Equal(t, dog, gotDog) +} diff --git a/internal/test/anonymous_inner_hoisting/generate.go b/internal/test/anonymous_inner_hoisting/generate.go new file mode 100644 index 0000000000..19ff037202 --- /dev/null +++ b/internal/test/anonymous_inner_hoisting/generate.go @@ -0,0 +1,3 @@ +package anonymous_inner_hoisting + +//go:generate go run github.com/oapi-codegen/oapi-codegen/v2/cmd/oapi-codegen --config=cfg.yaml spec.yaml diff --git a/internal/test/anonymous_inner_hoisting/spec.yaml b/internal/test/anonymous_inner_hoisting/spec.yaml new file mode 100644 index 0000000000..a6febff103 --- /dev/null +++ b/internal/test/anonymous_inner_hoisting/spec.yaml @@ -0,0 +1,132 @@ +openapi: 3.0.3 +info: + version: 1.0.0 + title: Anonymous Inner Hoisting + description: | + Exercises inline oneOf / anyOf schemas at every operation root and at + nested positions. Each generated wrapper type should receive + As() / From() / Merge() accessor methods. +paths: + + /response-root-oneof: + get: + operationId: getResponseRootOneOf + responses: + '200': + description: inline oneOf as the response body root + content: + application/json: + schema: + oneOf: + - $ref: '#/components/schemas/Cat' + - $ref: '#/components/schemas/Dog' + + /response-root-anyof: + get: + operationId: getResponseRootAnyOf + responses: + '200': + description: inline anyOf as the response body root + content: + application/json: + schema: + anyOf: + - $ref: '#/components/schemas/Cat' + - $ref: '#/components/schemas/Dog' + + /response-items-oneof: + get: + operationId: getResponseItemsOneOf + responses: + '200': + description: inline oneOf as items of an array property + content: + application/json: + schema: + type: object + required: + - items + properties: + items: + type: array + items: + oneOf: + - $ref: '#/components/schemas/Cat' + - $ref: '#/components/schemas/Dog' + + /response-deep-nested: + get: + operationId: getResponseDeepNested + responses: + '200': + description: inline oneOf nested inside a property of a property + content: + application/json: + schema: + type: object + properties: + wrapper: + type: object + properties: + inner: + oneOf: + - $ref: '#/components/schemas/Cat' + - $ref: '#/components/schemas/Dog' + + /body-root-oneof: + post: + operationId: postBodyRootOneOf + requestBody: + required: true + content: + application/json: + schema: + oneOf: + - $ref: '#/components/schemas/Cat' + - $ref: '#/components/schemas/Dog' + responses: + '200': + description: ok + + /body-property-oneof: + post: + operationId: postBodyPropertyOneOf + requestBody: + required: true + content: + application/json: + schema: + type: object + properties: + pet: + oneOf: + - $ref: '#/components/schemas/Cat' + - $ref: '#/components/schemas/Dog' + responses: + '200': + description: ok + +components: + schemas: + Cat: + type: object + required: + - kind + properties: + kind: + type: string + enum: + - cat + name: + type: string + Dog: + type: object + required: + - kind + properties: + kind: + type: string + enum: + - dog + name: + type: string diff --git a/internal/test/any_of/codegen/inline/openapi.gen.go b/internal/test/any_of/codegen/inline/openapi.gen.go index 3e9467cb7f..fff0ef0f89 100644 --- a/internal/test/any_of/codegen/inline/openapi.gen.go +++ b/internal/test/any_of/codegen/inline/openapi.gen.go @@ -13,6 +13,7 @@ import ( "strings" "github.com/labstack/echo/v4" + "github.com/oapi-codegen/runtime" ) const ( @@ -48,11 +49,99 @@ type Rat struct { // apiKeyAuthContextKey is the context key for ApiKeyAuth security scheme type apiKeyAuthContextKey string -// GetPets200JSONResponse_Data_Item defines parameters for GetPets. -type GetPets200JSONResponse_Data_Item struct { +// GetPets200JSONResponseBody_Data_Item defines parameters for GetPets. +type GetPets200JSONResponseBody_Data_Item struct { union json.RawMessage } +// AsCat returns the union data inside the GetPets200JSONResponseBody_Data_Item as a Cat +func (t GetPets200JSONResponseBody_Data_Item) AsCat() (Cat, error) { + var body Cat + err := json.Unmarshal(t.union, &body) + return body, err +} + +// FromCat overwrites any union data inside the GetPets200JSONResponseBody_Data_Item as the provided Cat +func (t *GetPets200JSONResponseBody_Data_Item) FromCat(v Cat) error { + b, err := json.Marshal(v) + t.union = b + return err +} + +// MergeCat performs a merge with any union data inside the GetPets200JSONResponseBody_Data_Item, using the provided Cat +func (t *GetPets200JSONResponseBody_Data_Item) MergeCat(v Cat) error { + b, err := json.Marshal(v) + if err != nil { + return err + } + + merged, err := runtime.JSONMerge(t.union, b) + t.union = merged + return err +} + +// AsDog returns the union data inside the GetPets200JSONResponseBody_Data_Item as a Dog +func (t GetPets200JSONResponseBody_Data_Item) AsDog() (Dog, error) { + var body Dog + err := json.Unmarshal(t.union, &body) + return body, err +} + +// FromDog overwrites any union data inside the GetPets200JSONResponseBody_Data_Item as the provided Dog +func (t *GetPets200JSONResponseBody_Data_Item) FromDog(v Dog) error { + b, err := json.Marshal(v) + t.union = b + return err +} + +// MergeDog performs a merge with any union data inside the GetPets200JSONResponseBody_Data_Item, using the provided Dog +func (t *GetPets200JSONResponseBody_Data_Item) MergeDog(v Dog) error { + b, err := json.Marshal(v) + if err != nil { + return err + } + + merged, err := runtime.JSONMerge(t.union, b) + t.union = merged + return err +} + +// AsRat returns the union data inside the GetPets200JSONResponseBody_Data_Item as a Rat +func (t GetPets200JSONResponseBody_Data_Item) AsRat() (Rat, error) { + var body Rat + err := json.Unmarshal(t.union, &body) + return body, err +} + +// FromRat overwrites any union data inside the GetPets200JSONResponseBody_Data_Item as the provided Rat +func (t *GetPets200JSONResponseBody_Data_Item) FromRat(v Rat) error { + b, err := json.Marshal(v) + t.union = b + return err +} + +// MergeRat performs a merge with any union data inside the GetPets200JSONResponseBody_Data_Item, using the provided Rat +func (t *GetPets200JSONResponseBody_Data_Item) MergeRat(v Rat) error { + b, err := json.Marshal(v) + if err != nil { + return err + } + + merged, err := runtime.JSONMerge(t.union, b) + t.union = merged + return err +} + +func (t GetPets200JSONResponseBody_Data_Item) MarshalJSON() ([]byte, error) { + b, err := t.union.MarshalJSON() + return b, err +} + +func (t *GetPets200JSONResponseBody_Data_Item) UnmarshalJSON(b []byte) error { + err := t.union.UnmarshalJSON(b) + return err +} + // RequestEditorFn is the function signature for the RequestEditor callback function type RequestEditorFn func(ctx context.Context, req *http.Request) error @@ -220,12 +309,9 @@ type GetPetsResponse struct { Body []byte HTTPResponse *http.Response JSON200 *struct { - Data *[]GetPets_200_Data_Item `json:"data,omitempty"` + Data *[]GetPets200JSONResponseBody_Data_Item `json:"data,omitempty"` } } -type GetPets_200_Data_Item struct { - union json.RawMessage -} // Status returns HTTPResponse.Status func (r GetPetsResponse) Status() string { @@ -276,7 +362,7 @@ func ParseGetPetsResponse(rsp *http.Response) (*GetPetsResponse, error) { switch { case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: var dest struct { - Data *[]GetPets_200_Data_Item `json:"data,omitempty"` + Data *[]GetPets200JSONResponseBody_Data_Item `json:"data,omitempty"` } if err := json.Unmarshal(bodyBytes, &dest); err != nil { return nil, err diff --git a/internal/test/components/components.gen.go b/internal/test/components/components.gen.go index e8550c87b3..c80b28dc4d 100644 --- a/internal/test/components/components.gen.go +++ b/internal/test/components/components.gen.go @@ -587,87 +587,6 @@ type EnsureEverythingIsReferencedTextRequestBody = EnsureEverythingIsReferencedT // BodyWithAddPropsJSONRequestBody defines body for BodyWithAddProps for application/json ContentType. type BodyWithAddPropsJSONRequestBody BodyWithAddPropsJSONBody -// Getter for additional properties for BodyWithAddPropsJSONBody. Returns the specified -// element and whether it was found -func (a BodyWithAddPropsJSONBody) Get(fieldName string) (value interface{}, found bool) { - if a.AdditionalProperties != nil { - value, found = a.AdditionalProperties[fieldName] - } - return -} - -// Setter for additional properties for BodyWithAddPropsJSONBody -func (a *BodyWithAddPropsJSONBody) Set(fieldName string, value interface{}) { - if a.AdditionalProperties == nil { - a.AdditionalProperties = make(map[string]interface{}) - } - a.AdditionalProperties[fieldName] = value -} - -// Override default JSON handling for BodyWithAddPropsJSONBody to handle AdditionalProperties -func (a *BodyWithAddPropsJSONBody) UnmarshalJSON(b []byte) error { - object := make(map[string]json.RawMessage) - err := json.Unmarshal(b, &object) - if err != nil { - return err - } - - if raw, found := object["inner"]; found { - err = json.Unmarshal(raw, &a.Inner) - if err != nil { - return fmt.Errorf("error reading 'inner': %w", err) - } - delete(object, "inner") - } - - if raw, found := object["name"]; found { - err = json.Unmarshal(raw, &a.Name) - if err != nil { - return fmt.Errorf("error reading 'name': %w", err) - } - delete(object, "name") - } - - if len(object) != 0 { - a.AdditionalProperties = make(map[string]interface{}) - for fieldName, fieldBuf := range object { - var fieldVal interface{} - err := json.Unmarshal(fieldBuf, &fieldVal) - if err != nil { - return fmt.Errorf("error unmarshaling field %s: %w", fieldName, err) - } - a.AdditionalProperties[fieldName] = fieldVal - } - } - return nil -} - -// Override default JSON handling for BodyWithAddPropsJSONBody to handle AdditionalProperties -func (a BodyWithAddPropsJSONBody) MarshalJSON() ([]byte, error) { - var err error - object := make(map[string]json.RawMessage) - - if a.Inner != nil { - object["inner"], err = json.Marshal(a.Inner) - if err != nil { - return nil, fmt.Errorf("error marshaling 'inner': %w", err) - } - } - - object["name"], err = json.Marshal(a.Name) - if err != nil { - return nil, fmt.Errorf("error marshaling 'name': %w", err) - } - - for fieldName, field := range a.AdditionalProperties { - object[fieldName], err = json.Marshal(field) - if err != nil { - return nil, fmt.Errorf("error marshaling '%s': %w", fieldName, err) - } - } - return json.Marshal(object) -} - // Getter for additional properties for AdditionalPropertiesObject1. Returns the specified // element and whether it was found func (a AdditionalPropertiesObject1) Get(fieldName string) (value int, found bool) { @@ -990,6 +909,87 @@ func (a *OneOfObject13) Set(fieldName string, value interface{}) { a.AdditionalProperties[fieldName] = value } +// Getter for additional properties for BodyWithAddPropsJSONBody. Returns the specified +// element and whether it was found +func (a BodyWithAddPropsJSONBody) Get(fieldName string) (value interface{}, found bool) { + if a.AdditionalProperties != nil { + value, found = a.AdditionalProperties[fieldName] + } + return +} + +// Setter for additional properties for BodyWithAddPropsJSONBody +func (a *BodyWithAddPropsJSONBody) Set(fieldName string, value interface{}) { + if a.AdditionalProperties == nil { + a.AdditionalProperties = make(map[string]interface{}) + } + a.AdditionalProperties[fieldName] = value +} + +// Override default JSON handling for BodyWithAddPropsJSONBody to handle AdditionalProperties +func (a *BodyWithAddPropsJSONBody) UnmarshalJSON(b []byte) error { + object := make(map[string]json.RawMessage) + err := json.Unmarshal(b, &object) + if err != nil { + return err + } + + if raw, found := object["inner"]; found { + err = json.Unmarshal(raw, &a.Inner) + if err != nil { + return fmt.Errorf("error reading 'inner': %w", err) + } + delete(object, "inner") + } + + if raw, found := object["name"]; found { + err = json.Unmarshal(raw, &a.Name) + if err != nil { + return fmt.Errorf("error reading 'name': %w", err) + } + delete(object, "name") + } + + if len(object) != 0 { + a.AdditionalProperties = make(map[string]interface{}) + for fieldName, fieldBuf := range object { + var fieldVal interface{} + err := json.Unmarshal(fieldBuf, &fieldVal) + if err != nil { + return fmt.Errorf("error unmarshaling field %s: %w", fieldName, err) + } + a.AdditionalProperties[fieldName] = fieldVal + } + } + return nil +} + +// Override default JSON handling for BodyWithAddPropsJSONBody to handle AdditionalProperties +func (a BodyWithAddPropsJSONBody) MarshalJSON() ([]byte, error) { + var err error + object := make(map[string]json.RawMessage) + + if a.Inner != nil { + object["inner"], err = json.Marshal(a.Inner) + if err != nil { + return nil, fmt.Errorf("error marshaling 'inner': %w", err) + } + } + + object["name"], err = json.Marshal(a.Name) + if err != nil { + return nil, fmt.Errorf("error marshaling 'name': %w", err) + } + + for fieldName, field := range a.AdditionalProperties { + object[fieldName], err = json.Marshal(field) + if err != nil { + return nil, fmt.Errorf("error marshaling '%s': %w", fieldName, err) + } + } + return json.Marshal(object) +} + // AsOneOfVariant4 returns the union data inside the AnyOfObject1 as a OneOfVariant4 func (t AnyOfObject1) AsOneOfVariant4() (OneOfVariant4, error) { var body OneOfVariant4 diff --git a/internal/test/issues/issue-1277/content-array.gen.go b/internal/test/issues/issue-1277/content-array.gen.go index fee7e80abb..4e13565987 100644 --- a/internal/test/issues/issue-1277/content-array.gen.go +++ b/internal/test/issues/issue-1277/content-array.gen.go @@ -16,32 +16,32 @@ import ( "github.com/go-chi/chi/v5" ) -// Test200JSONResponse_Item defines parameters for Test. -type Test200JSONResponse_Item struct { +// Test200JSONResponseBody_Item defines parameters for Test. +type Test200JSONResponseBody_Item struct { Field1 *string `json:"field1,omitempty"` Field2 *string `json:"field2,omitempty"` AdditionalProperties map[string]interface{} `json:"-"` } -// Getter for additional properties for Test200JSONResponse_Item. Returns the specified +// Getter for additional properties for Test200JSONResponseBody_Item. Returns the specified // element and whether it was found -func (a Test200JSONResponse_Item) Get(fieldName string) (value interface{}, found bool) { +func (a Test200JSONResponseBody_Item) Get(fieldName string) (value interface{}, found bool) { if a.AdditionalProperties != nil { value, found = a.AdditionalProperties[fieldName] } return } -// Setter for additional properties for Test200JSONResponse_Item -func (a *Test200JSONResponse_Item) Set(fieldName string, value interface{}) { +// Setter for additional properties for Test200JSONResponseBody_Item +func (a *Test200JSONResponseBody_Item) Set(fieldName string, value interface{}) { if a.AdditionalProperties == nil { a.AdditionalProperties = make(map[string]interface{}) } a.AdditionalProperties[fieldName] = value } -// Override default JSON handling for Test200JSONResponse_Item to handle AdditionalProperties -func (a *Test200JSONResponse_Item) UnmarshalJSON(b []byte) error { +// Override default JSON handling for Test200JSONResponseBody_Item to handle AdditionalProperties +func (a *Test200JSONResponseBody_Item) UnmarshalJSON(b []byte) error { object := make(map[string]json.RawMessage) err := json.Unmarshal(b, &object) if err != nil { @@ -78,8 +78,8 @@ func (a *Test200JSONResponse_Item) UnmarshalJSON(b []byte) error { return nil } -// Override default JSON handling for Test200JSONResponse_Item to handle AdditionalProperties -func (a Test200JSONResponse_Item) MarshalJSON() ([]byte, error) { +// Override default JSON handling for Test200JSONResponseBody_Item to handle AdditionalProperties +func (a Test200JSONResponseBody_Item) MarshalJSON() ([]byte, error) { var err error object := make(map[string]json.RawMessage) @@ -272,12 +272,7 @@ type ClientWithResponsesInterface interface { type TestResponse struct { Body []byte HTTPResponse *http.Response - JSON200 *[]Test_200_Item -} -type Test_200_Item struct { - Field1 *string `json:"field1,omitempty"` - Field2 *string `json:"field2,omitempty"` - AdditionalProperties map[string]interface{} `json:"-"` + JSON200 *[]Test200JSONResponseBody_Item } // Status returns HTTPResponse.Status @@ -328,7 +323,7 @@ func ParseTestResponse(rsp *http.Response) (*TestResponse, error) { switch { case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: - var dest []Test_200_Item + var dest []Test200JSONResponseBody_Item if err := json.Unmarshal(bodyBytes, &dest); err != nil { return nil, err } @@ -505,7 +500,7 @@ type TestResponseObject interface { VisitTestResponse(w http.ResponseWriter) error } -type Test200JSONResponse []Test200JSONResponse_Item +type Test200JSONResponse []Test200JSONResponseBody_Item func (response Test200JSONResponse) VisitTestResponse(w http.ResponseWriter) error { diff --git a/internal/test/issues/issue-1378/bionicle/bionicle.gen.go b/internal/test/issues/issue-1378/bionicle/bionicle.gen.go index f708015ae6..c9c57df5fb 100644 --- a/internal/test/issues/issue-1378/bionicle/bionicle.gen.go +++ b/internal/test/issues/issue-1378/bionicle/bionicle.gen.go @@ -29,6 +29,47 @@ type Bionicle struct { // BionicleName defines model for bionicleName. type BionicleName = string +// GetBionicleName400JSONResponseBody defines parameters for GetBionicleName. +type GetBionicleName400JSONResponseBody struct { + union json.RawMessage +} + +// AsBionicle returns the union data inside the GetBionicleName400JSONResponseBody as a Bionicle +func (t GetBionicleName400JSONResponseBody) AsBionicle() (Bionicle, error) { + var body Bionicle + err := json.Unmarshal(t.union, &body) + return body, err +} + +// FromBionicle overwrites any union data inside the GetBionicleName400JSONResponseBody as the provided Bionicle +func (t *GetBionicleName400JSONResponseBody) FromBionicle(v Bionicle) error { + b, err := json.Marshal(v) + t.union = b + return err +} + +// MergeBionicle performs a merge with any union data inside the GetBionicleName400JSONResponseBody, using the provided Bionicle +func (t *GetBionicleName400JSONResponseBody) MergeBionicle(v Bionicle) error { + b, err := json.Marshal(v) + if err != nil { + return err + } + + merged, err := runtime.JSONMerge(t.union, b) + t.union = merged + return err +} + +func (t GetBionicleName400JSONResponseBody) MarshalJSON() ([]byte, error) { + b, err := t.union.MarshalJSON() + return b, err +} + +func (t *GetBionicleName400JSONResponseBody) UnmarshalJSON(b []byte) error { + err := t.union.UnmarshalJSON(b) + return err +} + // ServerInterface represents all server handlers. type ServerInterface interface { @@ -211,9 +252,7 @@ func (response GetBionicleName200JSONResponse) VisitGetBionicleNameResponse(w ht return err } -type GetBionicleName400JSONResponse struct { - union json.RawMessage -} +type GetBionicleName400JSONResponse = GetBionicleName400JSONResponseBody func (response GetBionicleName400JSONResponse) VisitGetBionicleNameResponse(w http.ResponseWriter) error { diff --git a/internal/test/issues/issue-1378/fooservice/fooservice.gen.go b/internal/test/issues/issue-1378/fooservice/fooservice.gen.go index 9c856f981b..b11772fd3c 100644 --- a/internal/test/issues/issue-1378/fooservice/fooservice.gen.go +++ b/internal/test/issues/issue-1378/fooservice/fooservice.gen.go @@ -204,14 +204,20 @@ func (response GetBionicleName200JSONResponse) VisitGetBionicleNameResponse(w ht return err } -type GetBionicleName400JSONResponse struct { - union json.RawMessage +type GetBionicleName400JSONResponse externalRef0.GetBionicleName400JSONResponseBody + +func (t GetBionicleName400JSONResponse) MarshalJSON() ([]byte, error) { + return externalRef0.GetBionicleName400JSONResponseBody(t).MarshalJSON() +} + +func (t *GetBionicleName400JSONResponse) UnmarshalJSON(b []byte) error { + return (*externalRef0.GetBionicleName400JSONResponseBody)(t).UnmarshalJSON(b) } func (response GetBionicleName400JSONResponse) VisitGetBionicleNameResponse(w http.ResponseWriter) error { var buf bytes.Buffer - if err := json.NewEncoder(&buf).Encode(response.union); err != nil { + if err := json.NewEncoder(&buf).Encode(response); err != nil { return err } w.Header().Set("Content-Type", "application/json") diff --git a/internal/test/issues/issue-1378/fooservice/fooservice_test.go b/internal/test/issues/issue-1378/fooservice/fooservice_test.go new file mode 100644 index 0000000000..0d3319ea6b --- /dev/null +++ b/internal/test/issues/issue-1378/fooservice/fooservice_test.go @@ -0,0 +1,27 @@ +package fooservice + +import ( + "encoding/json" + "testing" + + "github.com/stretchr/testify/require" + + bionicle "github.com/oapi-codegen/oapi-codegen/v2/internal/test/issues/issue-1378/bionicle" +) + +// TestExternalRefUnionResponseSerialization locks in the fix for the +// case where a strict-server response envelope wraps an external union +// type. Because the strict envelope is a defined type (`type X +// externalRef0.Y`), methods on Y don't transfer; without an explicit +// MarshalJSON delegator the encode falls back to struct-field +// serialization on the unexported `union` field and produces `{}`. +func TestExternalRefUnionResponseSerialization(t *testing.T) { + var body bionicle.GetBionicleName400JSONResponseBody + require.NoError(t, body.FromBionicle(bionicle.Bionicle{Name: "tahu"})) + + resp := GetBionicleName400JSONResponse(body) + + got, err := json.Marshal(resp) + require.NoError(t, err) + require.JSONEq(t, `{"name":"tahu"}`, string(got)) +} diff --git a/internal/test/name_conflict_resolution/name_conflict_resolution.gen.go b/internal/test/name_conflict_resolution/name_conflict_resolution.gen.go index 348bcff460..b97ae67fa3 100644 --- a/internal/test/name_conflict_resolution/name_conflict_resolution.gen.go +++ b/internal/test/name_conflict_resolution/name_conflict_resolution.gen.go @@ -258,17 +258,17 @@ type QueryJSONBody struct { Q *string `json:"q,omitempty"` } -// PatchResource200ApplicationJSONPatchPlusJSONResponse1 defines parameters for PatchResource. -type PatchResource200ApplicationJSONPatchPlusJSONResponse1 = []Resource +// PatchResource200ApplicationJSONPatchPlusJSONResponseBody1 defines parameters for PatchResource. +type PatchResource200ApplicationJSONPatchPlusJSONResponseBody1 = []Resource -// PatchResource200ApplicationJSONPatchPlusJSONResponse2 defines parameters for PatchResource. -type PatchResource200ApplicationJSONPatchPlusJSONResponse2 = string +// PatchResource200ApplicationJSONPatchPlusJSONResponseBody2 defines parameters for PatchResource. +type PatchResource200ApplicationJSONPatchPlusJSONResponseBody2 = string -// PatchResource200ApplicationJSONPatchQueryPlusJSONResponse1 defines parameters for PatchResource. -type PatchResource200ApplicationJSONPatchQueryPlusJSONResponse1 = []Resource +// PatchResource200ApplicationJSONPatchQueryPlusJSONResponseBody1 defines parameters for PatchResource. +type PatchResource200ApplicationJSONPatchQueryPlusJSONResponseBody1 = []Resource -// PatchResource200ApplicationJSONPatchQueryPlusJSONResponse2 defines parameters for PatchResource. -type PatchResource200ApplicationJSONPatchQueryPlusJSONResponse2 = string +// PatchResource200ApplicationJSONPatchQueryPlusJSONResponseBody2 defines parameters for PatchResource. +type PatchResource200ApplicationJSONPatchQueryPlusJSONResponseBody2 = string // PostFooJSONRequestBody defines body for PostFoo for application/json ContentType. type PostFooJSONRequestBody PostFooJSONBody @@ -2308,10 +2308,6 @@ type PatchResourceResponse struct { ApplicationjsonPatchQueryJSON200 *N200ResourcePatchResponseJSON3ApplicationJSONPatchQueryPlusJSON ApplicationmergePatchJSON200 *N200ResourcePatchResponseJSON4ApplicationMergePatchPlusJSON } -type PatchResource200ApplicationJSONPatchPlusJSON1 = []Resource -type PatchResource200ApplicationJSONPatchPlusJSON2 = string -type PatchResource200ApplicationJSONPatchQueryPlusJSON1 = []Resource -type PatchResource200ApplicationJSONPatchQueryPlusJSON2 = string // Status returns HTTPResponse.Status func (r PatchResourceResponse) Status() string { diff --git a/internal/test/strict-server/chi/server.gen.go b/internal/test/strict-server/chi/server.gen.go index caf622b4b2..24563e498f 100644 --- a/internal/test/strict-server/chi/server.gen.go +++ b/internal/test/strict-server/chi/server.gen.go @@ -1184,9 +1184,7 @@ func (response UnionExample200ApplicationAlternativePlusJSONResponse) VisitUnion } type UnionExample200JSONResponse struct { - Body struct { - union json.RawMessage - } + Body UnionExample200JSONResponseBody Headers UnionExample200ResponseHeaders } diff --git a/internal/test/strict-server/chi/types.gen.go b/internal/test/strict-server/chi/types.gen.go index 532c5ebae3..08ae8548e1 100644 --- a/internal/test/strict-server/chi/types.gen.go +++ b/internal/test/strict-server/chi/types.gen.go @@ -3,6 +3,12 @@ // Code generated by github.com/oapi-codegen/oapi-codegen/v2 version v2.0.0-00010101000000-000000000000 DO NOT EDIT. package api +import ( + "encoding/json" + + "github.com/oapi-codegen/runtime" +) + // Example defines model for example. type Example struct { Value *string `json:"value,omitempty"` @@ -26,8 +32,13 @@ type HeadersExampleParams struct { Header2 *int `json:"header2,omitempty"` } -// UnionExample200JSONResponse0 defines parameters for UnionExample. -type UnionExample200JSONResponse0 = string +// UnionExample200JSONResponseBody0 defines parameters for UnionExample. +type UnionExample200JSONResponseBody0 = string + +// UnionExample200JSONResponseBody defines parameters for UnionExample. +type UnionExample200JSONResponseBody struct { + union json.RawMessage +} // JSONExampleJSONRequestBody defines body for JSONExample for application/json ContentType. type JSONExampleJSONRequestBody = Example @@ -70,3 +81,65 @@ type HeadersExampleJSONRequestBody = Example // UnionExampleJSONRequestBody defines body for UnionExample for application/json ContentType. type UnionExampleJSONRequestBody = Example + +// AsUnionExample200JSONResponseBody0 returns the union data inside the UnionExample200JSONResponseBody as a UnionExample200JSONResponseBody0 +func (t UnionExample200JSONResponseBody) AsUnionExample200JSONResponseBody0() (UnionExample200JSONResponseBody0, error) { + var body UnionExample200JSONResponseBody0 + err := json.Unmarshal(t.union, &body) + return body, err +} + +// FromUnionExample200JSONResponseBody0 overwrites any union data inside the UnionExample200JSONResponseBody as the provided UnionExample200JSONResponseBody0 +func (t *UnionExample200JSONResponseBody) FromUnionExample200JSONResponseBody0(v UnionExample200JSONResponseBody0) error { + b, err := json.Marshal(v) + t.union = b + return err +} + +// MergeUnionExample200JSONResponseBody0 performs a merge with any union data inside the UnionExample200JSONResponseBody, using the provided UnionExample200JSONResponseBody0 +func (t *UnionExample200JSONResponseBody) MergeUnionExample200JSONResponseBody0(v UnionExample200JSONResponseBody0) error { + b, err := json.Marshal(v) + if err != nil { + return err + } + + merged, err := runtime.JSONMerge(t.union, b) + t.union = merged + return err +} + +// AsExample returns the union data inside the UnionExample200JSONResponseBody as a Example +func (t UnionExample200JSONResponseBody) AsExample() (Example, error) { + var body Example + err := json.Unmarshal(t.union, &body) + return body, err +} + +// FromExample overwrites any union data inside the UnionExample200JSONResponseBody as the provided Example +func (t *UnionExample200JSONResponseBody) FromExample(v Example) error { + b, err := json.Marshal(v) + t.union = b + return err +} + +// MergeExample performs a merge with any union data inside the UnionExample200JSONResponseBody, using the provided Example +func (t *UnionExample200JSONResponseBody) MergeExample(v Example) error { + b, err := json.Marshal(v) + if err != nil { + return err + } + + merged, err := runtime.JSONMerge(t.union, b) + t.union = merged + return err +} + +func (t UnionExample200JSONResponseBody) MarshalJSON() ([]byte, error) { + b, err := t.union.MarshalJSON() + return b, err +} + +func (t *UnionExample200JSONResponseBody) UnmarshalJSON(b []byte) error { + err := t.union.UnmarshalJSON(b) + return err +} diff --git a/internal/test/strict-server/client/client.gen.go b/internal/test/strict-server/client/client.gen.go index 5efe7502b1..d4cdfbc074 100644 --- a/internal/test/strict-server/client/client.gen.go +++ b/internal/test/strict-server/client/client.gen.go @@ -39,8 +39,13 @@ type HeadersExampleParams struct { Header2 *int `json:"header2,omitempty"` } -// UnionExample200JSONResponse0 defines parameters for UnionExample. -type UnionExample200JSONResponse0 = string +// UnionExample200JSONResponseBody0 defines parameters for UnionExample. +type UnionExample200JSONResponseBody0 = string + +// UnionExample200JSONResponseBody defines parameters for UnionExample. +type UnionExample200JSONResponseBody struct { + union json.RawMessage +} // JSONExampleJSONRequestBody defines body for JSONExample for application/json ContentType. type JSONExampleJSONRequestBody = Example @@ -84,6 +89,68 @@ type HeadersExampleJSONRequestBody = Example // UnionExampleJSONRequestBody defines body for UnionExample for application/json ContentType. type UnionExampleJSONRequestBody = Example +// AsUnionExample200JSONResponseBody0 returns the union data inside the UnionExample200JSONResponseBody as a UnionExample200JSONResponseBody0 +func (t UnionExample200JSONResponseBody) AsUnionExample200JSONResponseBody0() (UnionExample200JSONResponseBody0, error) { + var body UnionExample200JSONResponseBody0 + err := json.Unmarshal(t.union, &body) + return body, err +} + +// FromUnionExample200JSONResponseBody0 overwrites any union data inside the UnionExample200JSONResponseBody as the provided UnionExample200JSONResponseBody0 +func (t *UnionExample200JSONResponseBody) FromUnionExample200JSONResponseBody0(v UnionExample200JSONResponseBody0) error { + b, err := json.Marshal(v) + t.union = b + return err +} + +// MergeUnionExample200JSONResponseBody0 performs a merge with any union data inside the UnionExample200JSONResponseBody, using the provided UnionExample200JSONResponseBody0 +func (t *UnionExample200JSONResponseBody) MergeUnionExample200JSONResponseBody0(v UnionExample200JSONResponseBody0) error { + b, err := json.Marshal(v) + if err != nil { + return err + } + + merged, err := runtime.JSONMerge(t.union, b) + t.union = merged + return err +} + +// AsExample returns the union data inside the UnionExample200JSONResponseBody as a Example +func (t UnionExample200JSONResponseBody) AsExample() (Example, error) { + var body Example + err := json.Unmarshal(t.union, &body) + return body, err +} + +// FromExample overwrites any union data inside the UnionExample200JSONResponseBody as the provided Example +func (t *UnionExample200JSONResponseBody) FromExample(v Example) error { + b, err := json.Marshal(v) + t.union = b + return err +} + +// MergeExample performs a merge with any union data inside the UnionExample200JSONResponseBody, using the provided Example +func (t *UnionExample200JSONResponseBody) MergeExample(v Example) error { + b, err := json.Marshal(v) + if err != nil { + return err + } + + merged, err := runtime.JSONMerge(t.union, b) + t.union = merged + return err +} + +func (t UnionExample200JSONResponseBody) MarshalJSON() ([]byte, error) { + b, err := t.union.MarshalJSON() + return b, err +} + +func (t *UnionExample200JSONResponseBody) UnmarshalJSON(b []byte) error { + err := t.union.UnmarshalJSON(b) + return err +} + // RequestEditorFn is the function signature for the RequestEditor callback function type RequestEditorFn func(ctx context.Context, req *http.Request) error @@ -1572,11 +1639,8 @@ type UnionExampleResponse struct { Body []byte HTTPResponse *http.Response ApplicationalternativeJSON200 *Example - JSON200 *struct { - union json.RawMessage - } + JSON200 *UnionExample200JSONResponseBody } -type UnionExample2000 = string // Status returns HTTPResponse.Status func (r UnionExampleResponse) Status() string { @@ -2099,9 +2163,7 @@ func ParseUnionExampleResponse(rsp *http.Response) (*UnionExampleResponse, error response.ApplicationalternativeJSON200 = &dest case rsp.Header.Get("Content-Type") == "application/json" && rsp.StatusCode == 200: - var dest struct { - union json.RawMessage - } + var dest UnionExample200JSONResponseBody if err := json.Unmarshal(bodyBytes, &dest); err != nil { return nil, err } diff --git a/internal/test/strict-server/echo/server.gen.go b/internal/test/strict-server/echo/server.gen.go index 3a314fb909..041e8f75d1 100644 --- a/internal/test/strict-server/echo/server.gen.go +++ b/internal/test/strict-server/echo/server.gen.go @@ -904,9 +904,7 @@ func (response UnionExample200ApplicationAlternativePlusJSONResponse) VisitUnion } type UnionExample200JSONResponse struct { - Body struct { - union json.RawMessage - } + Body UnionExample200JSONResponseBody Headers UnionExample200ResponseHeaders } diff --git a/internal/test/strict-server/echo/types.gen.go b/internal/test/strict-server/echo/types.gen.go index 532c5ebae3..08ae8548e1 100644 --- a/internal/test/strict-server/echo/types.gen.go +++ b/internal/test/strict-server/echo/types.gen.go @@ -3,6 +3,12 @@ // Code generated by github.com/oapi-codegen/oapi-codegen/v2 version v2.0.0-00010101000000-000000000000 DO NOT EDIT. package api +import ( + "encoding/json" + + "github.com/oapi-codegen/runtime" +) + // Example defines model for example. type Example struct { Value *string `json:"value,omitempty"` @@ -26,8 +32,13 @@ type HeadersExampleParams struct { Header2 *int `json:"header2,omitempty"` } -// UnionExample200JSONResponse0 defines parameters for UnionExample. -type UnionExample200JSONResponse0 = string +// UnionExample200JSONResponseBody0 defines parameters for UnionExample. +type UnionExample200JSONResponseBody0 = string + +// UnionExample200JSONResponseBody defines parameters for UnionExample. +type UnionExample200JSONResponseBody struct { + union json.RawMessage +} // JSONExampleJSONRequestBody defines body for JSONExample for application/json ContentType. type JSONExampleJSONRequestBody = Example @@ -70,3 +81,65 @@ type HeadersExampleJSONRequestBody = Example // UnionExampleJSONRequestBody defines body for UnionExample for application/json ContentType. type UnionExampleJSONRequestBody = Example + +// AsUnionExample200JSONResponseBody0 returns the union data inside the UnionExample200JSONResponseBody as a UnionExample200JSONResponseBody0 +func (t UnionExample200JSONResponseBody) AsUnionExample200JSONResponseBody0() (UnionExample200JSONResponseBody0, error) { + var body UnionExample200JSONResponseBody0 + err := json.Unmarshal(t.union, &body) + return body, err +} + +// FromUnionExample200JSONResponseBody0 overwrites any union data inside the UnionExample200JSONResponseBody as the provided UnionExample200JSONResponseBody0 +func (t *UnionExample200JSONResponseBody) FromUnionExample200JSONResponseBody0(v UnionExample200JSONResponseBody0) error { + b, err := json.Marshal(v) + t.union = b + return err +} + +// MergeUnionExample200JSONResponseBody0 performs a merge with any union data inside the UnionExample200JSONResponseBody, using the provided UnionExample200JSONResponseBody0 +func (t *UnionExample200JSONResponseBody) MergeUnionExample200JSONResponseBody0(v UnionExample200JSONResponseBody0) error { + b, err := json.Marshal(v) + if err != nil { + return err + } + + merged, err := runtime.JSONMerge(t.union, b) + t.union = merged + return err +} + +// AsExample returns the union data inside the UnionExample200JSONResponseBody as a Example +func (t UnionExample200JSONResponseBody) AsExample() (Example, error) { + var body Example + err := json.Unmarshal(t.union, &body) + return body, err +} + +// FromExample overwrites any union data inside the UnionExample200JSONResponseBody as the provided Example +func (t *UnionExample200JSONResponseBody) FromExample(v Example) error { + b, err := json.Marshal(v) + t.union = b + return err +} + +// MergeExample performs a merge with any union data inside the UnionExample200JSONResponseBody, using the provided Example +func (t *UnionExample200JSONResponseBody) MergeExample(v Example) error { + b, err := json.Marshal(v) + if err != nil { + return err + } + + merged, err := runtime.JSONMerge(t.union, b) + t.union = merged + return err +} + +func (t UnionExample200JSONResponseBody) MarshalJSON() ([]byte, error) { + b, err := t.union.MarshalJSON() + return b, err +} + +func (t *UnionExample200JSONResponseBody) UnmarshalJSON(b []byte) error { + err := t.union.UnmarshalJSON(b) + return err +} diff --git a/internal/test/strict-server/fiber/server.gen.go b/internal/test/strict-server/fiber/server.gen.go index bdb00bfcdb..caea58b285 100644 --- a/internal/test/strict-server/fiber/server.gen.go +++ b/internal/test/strict-server/fiber/server.gen.go @@ -8,7 +8,6 @@ import ( "compress/gzip" "context" "encoding/base64" - "encoding/json" "errors" "fmt" "io" @@ -1010,9 +1009,7 @@ func (response UnionExample200ApplicationAlternativePlusJSONResponse) VisitUnion } type UnionExample200JSONResponse struct { - Body struct { - union json.RawMessage - } + Body UnionExample200JSONResponseBody Headers UnionExample200ResponseHeaders } diff --git a/internal/test/strict-server/fiber/types.gen.go b/internal/test/strict-server/fiber/types.gen.go index 532c5ebae3..08ae8548e1 100644 --- a/internal/test/strict-server/fiber/types.gen.go +++ b/internal/test/strict-server/fiber/types.gen.go @@ -3,6 +3,12 @@ // Code generated by github.com/oapi-codegen/oapi-codegen/v2 version v2.0.0-00010101000000-000000000000 DO NOT EDIT. package api +import ( + "encoding/json" + + "github.com/oapi-codegen/runtime" +) + // Example defines model for example. type Example struct { Value *string `json:"value,omitempty"` @@ -26,8 +32,13 @@ type HeadersExampleParams struct { Header2 *int `json:"header2,omitempty"` } -// UnionExample200JSONResponse0 defines parameters for UnionExample. -type UnionExample200JSONResponse0 = string +// UnionExample200JSONResponseBody0 defines parameters for UnionExample. +type UnionExample200JSONResponseBody0 = string + +// UnionExample200JSONResponseBody defines parameters for UnionExample. +type UnionExample200JSONResponseBody struct { + union json.RawMessage +} // JSONExampleJSONRequestBody defines body for JSONExample for application/json ContentType. type JSONExampleJSONRequestBody = Example @@ -70,3 +81,65 @@ type HeadersExampleJSONRequestBody = Example // UnionExampleJSONRequestBody defines body for UnionExample for application/json ContentType. type UnionExampleJSONRequestBody = Example + +// AsUnionExample200JSONResponseBody0 returns the union data inside the UnionExample200JSONResponseBody as a UnionExample200JSONResponseBody0 +func (t UnionExample200JSONResponseBody) AsUnionExample200JSONResponseBody0() (UnionExample200JSONResponseBody0, error) { + var body UnionExample200JSONResponseBody0 + err := json.Unmarshal(t.union, &body) + return body, err +} + +// FromUnionExample200JSONResponseBody0 overwrites any union data inside the UnionExample200JSONResponseBody as the provided UnionExample200JSONResponseBody0 +func (t *UnionExample200JSONResponseBody) FromUnionExample200JSONResponseBody0(v UnionExample200JSONResponseBody0) error { + b, err := json.Marshal(v) + t.union = b + return err +} + +// MergeUnionExample200JSONResponseBody0 performs a merge with any union data inside the UnionExample200JSONResponseBody, using the provided UnionExample200JSONResponseBody0 +func (t *UnionExample200JSONResponseBody) MergeUnionExample200JSONResponseBody0(v UnionExample200JSONResponseBody0) error { + b, err := json.Marshal(v) + if err != nil { + return err + } + + merged, err := runtime.JSONMerge(t.union, b) + t.union = merged + return err +} + +// AsExample returns the union data inside the UnionExample200JSONResponseBody as a Example +func (t UnionExample200JSONResponseBody) AsExample() (Example, error) { + var body Example + err := json.Unmarshal(t.union, &body) + return body, err +} + +// FromExample overwrites any union data inside the UnionExample200JSONResponseBody as the provided Example +func (t *UnionExample200JSONResponseBody) FromExample(v Example) error { + b, err := json.Marshal(v) + t.union = b + return err +} + +// MergeExample performs a merge with any union data inside the UnionExample200JSONResponseBody, using the provided Example +func (t *UnionExample200JSONResponseBody) MergeExample(v Example) error { + b, err := json.Marshal(v) + if err != nil { + return err + } + + merged, err := runtime.JSONMerge(t.union, b) + t.union = merged + return err +} + +func (t UnionExample200JSONResponseBody) MarshalJSON() ([]byte, error) { + b, err := t.union.MarshalJSON() + return b, err +} + +func (t *UnionExample200JSONResponseBody) UnmarshalJSON(b []byte) error { + err := t.union.UnmarshalJSON(b) + return err +} diff --git a/internal/test/strict-server/gin/server.gen.go b/internal/test/strict-server/gin/server.gen.go index 00ea144ab2..0642639e37 100644 --- a/internal/test/strict-server/gin/server.gen.go +++ b/internal/test/strict-server/gin/server.gen.go @@ -979,9 +979,7 @@ func (response UnionExample200ApplicationAlternativePlusJSONResponse) VisitUnion } type UnionExample200JSONResponse struct { - Body struct { - union json.RawMessage - } + Body UnionExample200JSONResponseBody Headers UnionExample200ResponseHeaders } diff --git a/internal/test/strict-server/gin/types.gen.go b/internal/test/strict-server/gin/types.gen.go index 532c5ebae3..08ae8548e1 100644 --- a/internal/test/strict-server/gin/types.gen.go +++ b/internal/test/strict-server/gin/types.gen.go @@ -3,6 +3,12 @@ // Code generated by github.com/oapi-codegen/oapi-codegen/v2 version v2.0.0-00010101000000-000000000000 DO NOT EDIT. package api +import ( + "encoding/json" + + "github.com/oapi-codegen/runtime" +) + // Example defines model for example. type Example struct { Value *string `json:"value,omitempty"` @@ -26,8 +32,13 @@ type HeadersExampleParams struct { Header2 *int `json:"header2,omitempty"` } -// UnionExample200JSONResponse0 defines parameters for UnionExample. -type UnionExample200JSONResponse0 = string +// UnionExample200JSONResponseBody0 defines parameters for UnionExample. +type UnionExample200JSONResponseBody0 = string + +// UnionExample200JSONResponseBody defines parameters for UnionExample. +type UnionExample200JSONResponseBody struct { + union json.RawMessage +} // JSONExampleJSONRequestBody defines body for JSONExample for application/json ContentType. type JSONExampleJSONRequestBody = Example @@ -70,3 +81,65 @@ type HeadersExampleJSONRequestBody = Example // UnionExampleJSONRequestBody defines body for UnionExample for application/json ContentType. type UnionExampleJSONRequestBody = Example + +// AsUnionExample200JSONResponseBody0 returns the union data inside the UnionExample200JSONResponseBody as a UnionExample200JSONResponseBody0 +func (t UnionExample200JSONResponseBody) AsUnionExample200JSONResponseBody0() (UnionExample200JSONResponseBody0, error) { + var body UnionExample200JSONResponseBody0 + err := json.Unmarshal(t.union, &body) + return body, err +} + +// FromUnionExample200JSONResponseBody0 overwrites any union data inside the UnionExample200JSONResponseBody as the provided UnionExample200JSONResponseBody0 +func (t *UnionExample200JSONResponseBody) FromUnionExample200JSONResponseBody0(v UnionExample200JSONResponseBody0) error { + b, err := json.Marshal(v) + t.union = b + return err +} + +// MergeUnionExample200JSONResponseBody0 performs a merge with any union data inside the UnionExample200JSONResponseBody, using the provided UnionExample200JSONResponseBody0 +func (t *UnionExample200JSONResponseBody) MergeUnionExample200JSONResponseBody0(v UnionExample200JSONResponseBody0) error { + b, err := json.Marshal(v) + if err != nil { + return err + } + + merged, err := runtime.JSONMerge(t.union, b) + t.union = merged + return err +} + +// AsExample returns the union data inside the UnionExample200JSONResponseBody as a Example +func (t UnionExample200JSONResponseBody) AsExample() (Example, error) { + var body Example + err := json.Unmarshal(t.union, &body) + return body, err +} + +// FromExample overwrites any union data inside the UnionExample200JSONResponseBody as the provided Example +func (t *UnionExample200JSONResponseBody) FromExample(v Example) error { + b, err := json.Marshal(v) + t.union = b + return err +} + +// MergeExample performs a merge with any union data inside the UnionExample200JSONResponseBody, using the provided Example +func (t *UnionExample200JSONResponseBody) MergeExample(v Example) error { + b, err := json.Marshal(v) + if err != nil { + return err + } + + merged, err := runtime.JSONMerge(t.union, b) + t.union = merged + return err +} + +func (t UnionExample200JSONResponseBody) MarshalJSON() ([]byte, error) { + b, err := t.union.MarshalJSON() + return b, err +} + +func (t *UnionExample200JSONResponseBody) UnmarshalJSON(b []byte) error { + err := t.union.UnmarshalJSON(b) + return err +} diff --git a/internal/test/strict-server/gorilla/server.gen.go b/internal/test/strict-server/gorilla/server.gen.go index 2c28c12eb4..85fc6fe3a9 100644 --- a/internal/test/strict-server/gorilla/server.gen.go +++ b/internal/test/strict-server/gorilla/server.gen.go @@ -1095,9 +1095,7 @@ func (response UnionExample200ApplicationAlternativePlusJSONResponse) VisitUnion } type UnionExample200JSONResponse struct { - Body struct { - union json.RawMessage - } + Body UnionExample200JSONResponseBody Headers UnionExample200ResponseHeaders } diff --git a/internal/test/strict-server/gorilla/types.gen.go b/internal/test/strict-server/gorilla/types.gen.go index 532c5ebae3..08ae8548e1 100644 --- a/internal/test/strict-server/gorilla/types.gen.go +++ b/internal/test/strict-server/gorilla/types.gen.go @@ -3,6 +3,12 @@ // Code generated by github.com/oapi-codegen/oapi-codegen/v2 version v2.0.0-00010101000000-000000000000 DO NOT EDIT. package api +import ( + "encoding/json" + + "github.com/oapi-codegen/runtime" +) + // Example defines model for example. type Example struct { Value *string `json:"value,omitempty"` @@ -26,8 +32,13 @@ type HeadersExampleParams struct { Header2 *int `json:"header2,omitempty"` } -// UnionExample200JSONResponse0 defines parameters for UnionExample. -type UnionExample200JSONResponse0 = string +// UnionExample200JSONResponseBody0 defines parameters for UnionExample. +type UnionExample200JSONResponseBody0 = string + +// UnionExample200JSONResponseBody defines parameters for UnionExample. +type UnionExample200JSONResponseBody struct { + union json.RawMessage +} // JSONExampleJSONRequestBody defines body for JSONExample for application/json ContentType. type JSONExampleJSONRequestBody = Example @@ -70,3 +81,65 @@ type HeadersExampleJSONRequestBody = Example // UnionExampleJSONRequestBody defines body for UnionExample for application/json ContentType. type UnionExampleJSONRequestBody = Example + +// AsUnionExample200JSONResponseBody0 returns the union data inside the UnionExample200JSONResponseBody as a UnionExample200JSONResponseBody0 +func (t UnionExample200JSONResponseBody) AsUnionExample200JSONResponseBody0() (UnionExample200JSONResponseBody0, error) { + var body UnionExample200JSONResponseBody0 + err := json.Unmarshal(t.union, &body) + return body, err +} + +// FromUnionExample200JSONResponseBody0 overwrites any union data inside the UnionExample200JSONResponseBody as the provided UnionExample200JSONResponseBody0 +func (t *UnionExample200JSONResponseBody) FromUnionExample200JSONResponseBody0(v UnionExample200JSONResponseBody0) error { + b, err := json.Marshal(v) + t.union = b + return err +} + +// MergeUnionExample200JSONResponseBody0 performs a merge with any union data inside the UnionExample200JSONResponseBody, using the provided UnionExample200JSONResponseBody0 +func (t *UnionExample200JSONResponseBody) MergeUnionExample200JSONResponseBody0(v UnionExample200JSONResponseBody0) error { + b, err := json.Marshal(v) + if err != nil { + return err + } + + merged, err := runtime.JSONMerge(t.union, b) + t.union = merged + return err +} + +// AsExample returns the union data inside the UnionExample200JSONResponseBody as a Example +func (t UnionExample200JSONResponseBody) AsExample() (Example, error) { + var body Example + err := json.Unmarshal(t.union, &body) + return body, err +} + +// FromExample overwrites any union data inside the UnionExample200JSONResponseBody as the provided Example +func (t *UnionExample200JSONResponseBody) FromExample(v Example) error { + b, err := json.Marshal(v) + t.union = b + return err +} + +// MergeExample performs a merge with any union data inside the UnionExample200JSONResponseBody, using the provided Example +func (t *UnionExample200JSONResponseBody) MergeExample(v Example) error { + b, err := json.Marshal(v) + if err != nil { + return err + } + + merged, err := runtime.JSONMerge(t.union, b) + t.union = merged + return err +} + +func (t UnionExample200JSONResponseBody) MarshalJSON() ([]byte, error) { + b, err := t.union.MarshalJSON() + return b, err +} + +func (t *UnionExample200JSONResponseBody) UnmarshalJSON(b []byte) error { + err := t.union.UnmarshalJSON(b) + return err +} diff --git a/internal/test/strict-server/iris/server.gen.go b/internal/test/strict-server/iris/server.gen.go index 66d0dce0bb..6bef496890 100644 --- a/internal/test/strict-server/iris/server.gen.go +++ b/internal/test/strict-server/iris/server.gen.go @@ -8,7 +8,6 @@ import ( "compress/gzip" "context" "encoding/base64" - "encoding/json" "errors" "fmt" "io" @@ -844,9 +843,7 @@ func (response UnionExample200ApplicationAlternativePlusJSONResponse) VisitUnion } type UnionExample200JSONResponse struct { - Body struct { - union json.RawMessage - } + Body UnionExample200JSONResponseBody Headers UnionExample200ResponseHeaders } diff --git a/internal/test/strict-server/iris/types.gen.go b/internal/test/strict-server/iris/types.gen.go index 532c5ebae3..08ae8548e1 100644 --- a/internal/test/strict-server/iris/types.gen.go +++ b/internal/test/strict-server/iris/types.gen.go @@ -3,6 +3,12 @@ // Code generated by github.com/oapi-codegen/oapi-codegen/v2 version v2.0.0-00010101000000-000000000000 DO NOT EDIT. package api +import ( + "encoding/json" + + "github.com/oapi-codegen/runtime" +) + // Example defines model for example. type Example struct { Value *string `json:"value,omitempty"` @@ -26,8 +32,13 @@ type HeadersExampleParams struct { Header2 *int `json:"header2,omitempty"` } -// UnionExample200JSONResponse0 defines parameters for UnionExample. -type UnionExample200JSONResponse0 = string +// UnionExample200JSONResponseBody0 defines parameters for UnionExample. +type UnionExample200JSONResponseBody0 = string + +// UnionExample200JSONResponseBody defines parameters for UnionExample. +type UnionExample200JSONResponseBody struct { + union json.RawMessage +} // JSONExampleJSONRequestBody defines body for JSONExample for application/json ContentType. type JSONExampleJSONRequestBody = Example @@ -70,3 +81,65 @@ type HeadersExampleJSONRequestBody = Example // UnionExampleJSONRequestBody defines body for UnionExample for application/json ContentType. type UnionExampleJSONRequestBody = Example + +// AsUnionExample200JSONResponseBody0 returns the union data inside the UnionExample200JSONResponseBody as a UnionExample200JSONResponseBody0 +func (t UnionExample200JSONResponseBody) AsUnionExample200JSONResponseBody0() (UnionExample200JSONResponseBody0, error) { + var body UnionExample200JSONResponseBody0 + err := json.Unmarshal(t.union, &body) + return body, err +} + +// FromUnionExample200JSONResponseBody0 overwrites any union data inside the UnionExample200JSONResponseBody as the provided UnionExample200JSONResponseBody0 +func (t *UnionExample200JSONResponseBody) FromUnionExample200JSONResponseBody0(v UnionExample200JSONResponseBody0) error { + b, err := json.Marshal(v) + t.union = b + return err +} + +// MergeUnionExample200JSONResponseBody0 performs a merge with any union data inside the UnionExample200JSONResponseBody, using the provided UnionExample200JSONResponseBody0 +func (t *UnionExample200JSONResponseBody) MergeUnionExample200JSONResponseBody0(v UnionExample200JSONResponseBody0) error { + b, err := json.Marshal(v) + if err != nil { + return err + } + + merged, err := runtime.JSONMerge(t.union, b) + t.union = merged + return err +} + +// AsExample returns the union data inside the UnionExample200JSONResponseBody as a Example +func (t UnionExample200JSONResponseBody) AsExample() (Example, error) { + var body Example + err := json.Unmarshal(t.union, &body) + return body, err +} + +// FromExample overwrites any union data inside the UnionExample200JSONResponseBody as the provided Example +func (t *UnionExample200JSONResponseBody) FromExample(v Example) error { + b, err := json.Marshal(v) + t.union = b + return err +} + +// MergeExample performs a merge with any union data inside the UnionExample200JSONResponseBody, using the provided Example +func (t *UnionExample200JSONResponseBody) MergeExample(v Example) error { + b, err := json.Marshal(v) + if err != nil { + return err + } + + merged, err := runtime.JSONMerge(t.union, b) + t.union = merged + return err +} + +func (t UnionExample200JSONResponseBody) MarshalJSON() ([]byte, error) { + b, err := t.union.MarshalJSON() + return b, err +} + +func (t *UnionExample200JSONResponseBody) UnmarshalJSON(b []byte) error { + err := t.union.UnmarshalJSON(b) + return err +} diff --git a/internal/test/strict-server/stdhttp/server.gen.go b/internal/test/strict-server/stdhttp/server.gen.go index ac8d245c8d..adca621779 100644 --- a/internal/test/strict-server/stdhttp/server.gen.go +++ b/internal/test/strict-server/stdhttp/server.gen.go @@ -1090,9 +1090,7 @@ func (response UnionExample200ApplicationAlternativePlusJSONResponse) VisitUnion } type UnionExample200JSONResponse struct { - Body struct { - union json.RawMessage - } + Body UnionExample200JSONResponseBody Headers UnionExample200ResponseHeaders } diff --git a/internal/test/strict-server/stdhttp/types.gen.go b/internal/test/strict-server/stdhttp/types.gen.go index 532c5ebae3..08ae8548e1 100644 --- a/internal/test/strict-server/stdhttp/types.gen.go +++ b/internal/test/strict-server/stdhttp/types.gen.go @@ -3,6 +3,12 @@ // Code generated by github.com/oapi-codegen/oapi-codegen/v2 version v2.0.0-00010101000000-000000000000 DO NOT EDIT. package api +import ( + "encoding/json" + + "github.com/oapi-codegen/runtime" +) + // Example defines model for example. type Example struct { Value *string `json:"value,omitempty"` @@ -26,8 +32,13 @@ type HeadersExampleParams struct { Header2 *int `json:"header2,omitempty"` } -// UnionExample200JSONResponse0 defines parameters for UnionExample. -type UnionExample200JSONResponse0 = string +// UnionExample200JSONResponseBody0 defines parameters for UnionExample. +type UnionExample200JSONResponseBody0 = string + +// UnionExample200JSONResponseBody defines parameters for UnionExample. +type UnionExample200JSONResponseBody struct { + union json.RawMessage +} // JSONExampleJSONRequestBody defines body for JSONExample for application/json ContentType. type JSONExampleJSONRequestBody = Example @@ -70,3 +81,65 @@ type HeadersExampleJSONRequestBody = Example // UnionExampleJSONRequestBody defines body for UnionExample for application/json ContentType. type UnionExampleJSONRequestBody = Example + +// AsUnionExample200JSONResponseBody0 returns the union data inside the UnionExample200JSONResponseBody as a UnionExample200JSONResponseBody0 +func (t UnionExample200JSONResponseBody) AsUnionExample200JSONResponseBody0() (UnionExample200JSONResponseBody0, error) { + var body UnionExample200JSONResponseBody0 + err := json.Unmarshal(t.union, &body) + return body, err +} + +// FromUnionExample200JSONResponseBody0 overwrites any union data inside the UnionExample200JSONResponseBody as the provided UnionExample200JSONResponseBody0 +func (t *UnionExample200JSONResponseBody) FromUnionExample200JSONResponseBody0(v UnionExample200JSONResponseBody0) error { + b, err := json.Marshal(v) + t.union = b + return err +} + +// MergeUnionExample200JSONResponseBody0 performs a merge with any union data inside the UnionExample200JSONResponseBody, using the provided UnionExample200JSONResponseBody0 +func (t *UnionExample200JSONResponseBody) MergeUnionExample200JSONResponseBody0(v UnionExample200JSONResponseBody0) error { + b, err := json.Marshal(v) + if err != nil { + return err + } + + merged, err := runtime.JSONMerge(t.union, b) + t.union = merged + return err +} + +// AsExample returns the union data inside the UnionExample200JSONResponseBody as a Example +func (t UnionExample200JSONResponseBody) AsExample() (Example, error) { + var body Example + err := json.Unmarshal(t.union, &body) + return body, err +} + +// FromExample overwrites any union data inside the UnionExample200JSONResponseBody as the provided Example +func (t *UnionExample200JSONResponseBody) FromExample(v Example) error { + b, err := json.Marshal(v) + t.union = b + return err +} + +// MergeExample performs a merge with any union data inside the UnionExample200JSONResponseBody, using the provided Example +func (t *UnionExample200JSONResponseBody) MergeExample(v Example) error { + b, err := json.Marshal(v) + if err != nil { + return err + } + + merged, err := runtime.JSONMerge(t.union, b) + t.union = merged + return err +} + +func (t UnionExample200JSONResponseBody) MarshalJSON() ([]byte, error) { + b, err := t.union.MarshalJSON() + return b, err +} + +func (t *UnionExample200JSONResponseBody) UnmarshalJSON(b []byte) error { + err := t.union.UnmarshalJSON(b) + return err +} diff --git a/pkg/codegen/codegen.go b/pkg/codegen/codegen.go index 8514ef83e1..fb588dcfa5 100644 --- a/pkg/codegen/codegen.go +++ b/pkg/codegen/codegen.go @@ -345,7 +345,7 @@ func Generate(spec *openapi3.T, opts Configuration) (string, error) { if opts.Generate.Strict { var responses []ResponseDefinition if spec.Components != nil { - responses, err = GenerateResponseDefinitions("", spec.Components.Responses) + responses, err = GenerateResponseDefinitions("", spec.Components.Responses, "") if err != nil { return "", fmt.Errorf("error generation response definitions for schema: %w", err) } @@ -580,13 +580,28 @@ func GenerateTypeDefinitions(t *template.Template, swagger *openapi3.T, ops []Op allTypes = append(allTypes, securitySchemeTypes...) } - // Go through all operations, and add their types to allTypes, so that we can - // scan all of them for enums. Operation definitions are handled differently - // from the rest, so let's keep track of enumTypes separately, which will contain - // all types needed to be scanned for enums, which includes those within operations. - enumTypes := allTypes + // allTypes stays components-only — it's the slice passed to + // GenerateTypes (typedef.tmpl) for top-level type declarations. + // Op-derived types are declared by their per-context templates + // (param-types.tmpl, request-bodies.tmpl, client-with-responses.tmpl). + // + // boilerplateTypes is the wider universe for the method-emitting + // passes (enum scanning, additionalProperties marshalers, union + // accessors). Inline union/additionalProperties types living + // inside operations (params, request bodies, response schemas + // and any nested AdditionalTypeDefinitions) historically never + // reached these passes, so accessor methods were silently + // missing. Folding them in here is what fixes that. + boilerplateTypes := slices.Clone(allTypes) for _, op := range ops { - enumTypes = append(enumTypes, op.TypeDefinitions...) + boilerplateTypes = append(boilerplateTypes, op.TypeDefinitions...) + respDefs, err := op.GetResponseTypeDefinitions() + if err != nil { + return "", fmt.Errorf("error collecting response type definitions for %s: %w", op.OperationId, err) + } + for _, rd := range respDefs { + boilerplateTypes = append(boilerplateTypes, rd.AdditionalTypeDefinitions...) + } } operationsOut, err := GenerateTypesForOperations(t, ops) @@ -594,7 +609,7 @@ func GenerateTypeDefinitions(t *template.Template, swagger *openapi3.T, ops []Op return "", fmt.Errorf("error generating Go types for component request bodies: %w", err) } - enumsOut, err := GenerateEnums(t, enumTypes) + enumsOut, err := GenerateEnums(t, boilerplateTypes) if err != nil { return "", fmt.Errorf("error generating code for type enums: %w", err) } @@ -604,17 +619,17 @@ func GenerateTypeDefinitions(t *template.Template, swagger *openapi3.T, ops []Op return "", fmt.Errorf("error generating code for type definitions: %w", err) } - allOfBoilerplate, err := GenerateAdditionalPropertyBoilerplate(t, allTypes) + allOfBoilerplate, err := GenerateAdditionalPropertyBoilerplate(t, boilerplateTypes) if err != nil { return "", fmt.Errorf("error generating allOf boilerplate: %w", err) } - unionBoilerplate, err := GenerateUnionBoilerplate(t, allTypes) + unionBoilerplate, err := GenerateUnionBoilerplate(t, boilerplateTypes) if err != nil { return "", fmt.Errorf("error generating union boilerplate: %w", err) } - unionAndAdditionalBoilerplate, err := GenerateUnionAndAdditionalProopertiesBoilerplate(t, allTypes) + unionAndAdditionalBoilerplate, err := GenerateUnionAndAdditionalProopertiesBoilerplate(t, boilerplateTypes) if err != nil { return "", fmt.Errorf("error generating boilerplate for union types with additionalProperties: %w", err) } @@ -1120,7 +1135,12 @@ func GenerateAdditionalPropertyBoilerplate(t *template.Template, typeDefs []Type func GenerateUnionBoilerplate(t *template.Template, typeDefs []TypeDefinition) (string, error) { var filteredTypes []TypeDefinition + seen := map[string]bool{} for _, t := range typeDefs { + if seen[t.TypeName] { + continue + } + seen[t.TypeName] = true if len(t.Schema.UnionElements) != 0 { filteredTypes = append(filteredTypes, t) } @@ -1141,7 +1161,12 @@ func GenerateUnionBoilerplate(t *template.Template, typeDefs []TypeDefinition) ( func GenerateUnionAndAdditionalProopertiesBoilerplate(t *template.Template, typeDefs []TypeDefinition) (string, error) { var filteredTypes []TypeDefinition + seen := map[string]bool{} for _, t := range typeDefs { + if seen[t.TypeName] { + continue + } + seen[t.TypeName] = true if len(t.Schema.UnionElements) != 0 && t.Schema.HasAdditionalProperties { filteredTypes = append(filteredTypes, t) } diff --git a/pkg/codegen/externalref.go b/pkg/codegen/externalref.go index a48b3d4e1e..829885a5b3 100644 --- a/pkg/codegen/externalref.go +++ b/pkg/codegen/externalref.go @@ -58,6 +58,20 @@ func ensureExternalRefsInParameterDefinitions(defs *[]ParameterDefinition, ref s } } +// externalPackageFor returns the imported Go package name for a `$ref` that +// targets a file outside the current spec. Returns an empty string when the +// ref is empty, points within the current spec, or has no import-mapping. +func externalPackageFor(ref string) string { + if ref == "" { + return "" + } + parts := strings.SplitN(ref, "#", 2) + if pack, ok := globalState.importMapping[parts[0]]; ok { + return pack.Name + } + return "" +} + // ensureExternalRefsInSchema ensures that when an externalRef (`$ref` that points to a file that isn't the current spec) is encountered, we make sure we update our underlying `RefType` to make sure that we point to that type. // // This only happens if we have a non-empty `ref` passed in, and that `ref` isn't pointing to something in our file @@ -68,13 +82,26 @@ func ensureExternalRefsInSchema(schema *Schema, ref string) { return } - // if this is already defined as the start of a struct, we shouldn't inject **??** - if strings.HasPrefix(schema.GoType, "struct {") { + parts := strings.SplitN(ref, "#", 2) + pack, ok := globalState.importMapping[parts[0]] + if !ok { return } - parts := strings.SplitN(ref, "#", 2) - if pack, ok := globalState.importMapping[parts[0]]; ok { + // if this is already defined as the start of a struct, we shouldn't inject **??** + if !strings.HasPrefix(schema.GoType, "struct {") { schema.RefType = fmt.Sprintf("%s.%s", pack.Name, schema.GoType) } + + // Qualify union branch types. Each UnionElement was resolved as a + // local reference during generateUnion (e.g. "Bionicle"); when the + // enclosing schema came from an externally-ref'd file the branches + // actually live in the imported package, so the As/From/Merge + // methods generated by union.tmpl must use ".Bionicle" instead. + for i, elem := range schema.UnionElements { + s := string(elem) + if !strings.Contains(s, ".") { + schema.UnionElements[i] = UnionElement(fmt.Sprintf("%s.%s", pack.Name, s)) + } + } } diff --git a/pkg/codegen/operations.go b/pkg/codegen/operations.go index 0fedc32be2..4087d30fec 100644 --- a/pkg/codegen/operations.go +++ b/pkg/codegen/operations.go @@ -329,6 +329,7 @@ type OperationDefinition struct { Spec *openapi3.Operation IsAlias bool // True when this path is a $ref alias of another path item AliasTarget string // When IsAlias is true, this is the OperationId of the canonical operation (for route registration to reference the correct wrapper) + PathItemRef string // The path item's $ref (if any); used to qualify externally-loaded schemas referenced from this operation's responses } // HandlerName returns the OperationId to use when referencing the server-side @@ -401,10 +402,10 @@ func (o *OperationDefinition) GetResponseTypeDefinitions() ([]ResponseTypeDefini // We can only generate a type if we have a value: if responseRef.Value != nil { - jsonCount := 0 + supportedCount := 0 for mediaType := range responseRef.Value.Content { - if util.IsMediaTypeJson(mediaType) { - jsonCount++ + if isMediaTypeSupported(mediaType) { + supportedCount++ } } @@ -413,57 +414,64 @@ func (o *OperationDefinition) GetResponseTypeDefinitions() ([]ResponseTypeDefini contentType := responseRef.Value.Content[contentTypeName] // We can only generate a type if we have a schema: if contentType.Schema != nil { - // When a response has multiple JSON content types (e.g., - // application/json, application/json-patch+json, and - // application/merge-patch+json), we include a content-type-derived - // segment in the schema path. This is necessary because - // GenerateGoSchema uses the path to name any inline types it - // creates (e.g., oneOf union members). Without the content type - // in the path, all content types for the same response produce - // identically-named inline types. If those content types have - // different schemas, the result is conflicting type declarations; - // if they have the same schema, the result is duplicate - // declarations. Both cases produce code that won't compile. - // - // We only add the content type segment when collision resolution - // is enabled (resolve-type-name-collisions) and jsonCount > 1, - // to avoid changing type names for existing users. Ideally the - // media type would always be part of the path for consistency. - // TODO: revisit this at the next major version change — - // always include the media type in the schema path. - schemaPath := []string{o.OperationId, responseName} - if jsonCount > 1 && util.IsMediaTypeJson(contentTypeName) && globalState.options.OutputOptions.ResolveTypeNameCollisions { - schemaPath = append(schemaPath, mediaTypeToCamelCase(contentTypeName)) - } - responseSchema, err := GenerateGoSchema(contentType.Schema, schemaPath) - if err != nil { - return nil, fmt.Errorf("unable to determine Go type for %s.%s: %w", o.OperationId, contentTypeName, err) - } - - var typeName string + var typeName, tag string switch { // HAL+JSON: case slices.Contains(contentTypesHalJSON, contentTypeName): typeName = fmt.Sprintf("HALJSON%s", nameNormalizer(responseName)) + tag = "HALJSON" case contentTypeName == "application/json": // if it's the standard application/json typeName = fmt.Sprintf("JSON%s", nameNormalizer(responseName)) + tag = "JSON" // Vendored JSON case slices.Contains(contentTypesJSON, contentTypeName) || util.IsMediaTypeJson(contentTypeName): baseTypeName := fmt.Sprintf("%s%s", nameNormalizer(contentTypeName), nameNormalizer(responseName)) typeName = strings.ReplaceAll(baseTypeName, "Json", "JSON") + tag = strings.ReplaceAll(nameNormalizer(contentTypeName), "Json", "JSON") // YAML: case slices.Contains(contentTypesYAML, contentTypeName): typeName = fmt.Sprintf("YAML%s", nameNormalizer(responseName)) + tag = "YAML" // XML: case slices.Contains(contentTypesXML, contentTypeName): typeName = fmt.Sprintf("XML%s", nameNormalizer(responseName)) + tag = "XML" default: continue } + // Use the same body-type name as the server-side + // GenerateResponseDefinitions ("Body" suffixed so it + // doesn't collide with the strict envelope's struct + // wrapper) as the schema-path root. The canonical + // declaration happens server-side; here we just point + // RefType at the same name so the JSON field + // renders as a pointer to it. + responseBodyTypeName := o.OperationId + responseName + tag + "ResponseBody" + schemaPath := []string{responseBodyTypeName} + responseSchema, err := GenerateGoSchema(contentType.Schema, schemaPath) + if err != nil { + return nil, fmt.Errorf("unable to determine Go type for %s.%s: %w", o.OperationId, contentTypeName, err) + } + + // Hoist inline response-root schemas that need + // method-emitting boilerplate (UnionElements / + // AdditionalProperties). For external path items, + // qualify with the imported package — see the + // equivalent block in GenerateResponseDefinitions for + // rationale. + if !IsGoTypeReference(responseRef.Ref) && responseSchema.RefType == "" && + (len(responseSchema.UnionElements) != 0 || responseSchema.HasAdditionalProperties) { + if externalPkg := externalPackageFor(o.PathItemRef); externalPkg != "" { + responseSchema.RefType = fmt.Sprintf("%s.%s", externalPkg, responseBodyTypeName) + } else { + responseSchema.RefType = responseBodyTypeName + } + } + td := ResponseTypeDefinition{ TypeDefinition: TypeDefinition{ TypeName: typeName, @@ -478,7 +486,7 @@ func (o *OperationDefinition) GetResponseTypeDefinitions() ([]ResponseTypeDefini if err != nil { return nil, fmt.Errorf("error dereferencing response Ref: %w", err) } - if jsonCount > 1 && util.IsMediaTypeJson(contentTypeName) { + if supportedCount > 1 { if resolved := resolvedNameForRefPath(responseRef.Ref, contentTypeName); resolved != "" { refType = resolved + mediaTypeToCamelCase(contentTypeName) } else { @@ -808,14 +816,14 @@ func OperationDefinitions(swagger *openapi3.T) ([]OperationDefinition, error) { return nil, err } - bodyDefinitions, typeDefinitions, err := GenerateBodyDefinitions(operationId, op.RequestBody) + bodyDefinitions, typeDefinitions, err := GenerateBodyDefinitions(operationId, op.RequestBody, pathItem.Ref) if err != nil { return nil, fmt.Errorf("error generating body definitions: %w", err) } ensureExternalRefsInRequestBodyDefinitions(&bodyDefinitions, pathItem.Ref) - responseDefinitions, err := GenerateResponseDefinitions(operationId, op.Responses.Map()) + responseDefinitions, err := GenerateResponseDefinitions(operationId, op.Responses.Map(), pathItem.Ref) if err != nil { return nil, fmt.Errorf("error generating response definitions: %w", err) } @@ -838,6 +846,7 @@ func OperationDefinitions(swagger *openapi3.T) ([]OperationDefinition, error) { TypeDefinitions: typeDefinitions, IsAlias: isAlias, AliasTarget: aliasTarget, + PathItemRef: pathItem.Ref, } // check for overrides of SecurityDefinitions. @@ -887,7 +896,14 @@ func generateDefaultOperationID(opName string, requestPath string) (string, erro // GenerateBodyDefinitions turns the Swagger body definitions into a list of our body // definitions which will be used for code generation. -func GenerateBodyDefinitions(operationID string, bodyOrRef *openapi3.RequestBodyRef) ([]RequestBodyDefinition, []TypeDefinition, error) { +// +// pathItemRef is the path item's $ref (if any). When non-empty and pointing at +// an external file, the body type that would otherwise be hoisted locally is +// replaced by a reference to the imported package's same-named type — the +// imported package already declares it (with any As/From/Merge methods), so +// redeclaring locally would just produce an awkward duplicate with +// package-qualified union elements. +func GenerateBodyDefinitions(operationID string, bodyOrRef *openapi3.RequestBodyRef, pathItemRef string) ([]RequestBodyDefinition, []TypeDefinition, error) { if bodyOrRef == nil { return nil, nil, nil } @@ -942,24 +958,31 @@ func GenerateBodyDefinitions(operationID string, bodyOrRef *openapi3.RequestBody // type under #/components, we'll define a type for it, so // that we have an easy to use type for marshaling. if bodySchema.RefType == "" { - if contentType == "application/x-www-form-urlencoded" { - // Apply the appropriate structure tag if the request - // schema was defined under the operations' section. - for i := range bodySchema.Properties { - bodySchema.Properties[i].NeedsFormTag = true - } + if externalPkg := externalPackageFor(pathItemRef); externalPkg != "" { + // The operation's path item came from an external file; the + // imported package already declares this body type with the + // matching name. Reference it instead of redeclaring. + bodySchema.RefType = fmt.Sprintf("%s.%s", externalPkg, bodyTypeName) + } else { + if contentType == "application/x-www-form-urlencoded" { + // Apply the appropriate structure tag if the request + // schema was defined under the operations' section. + for i := range bodySchema.Properties { + bodySchema.Properties[i].NeedsFormTag = true + } - // Regenerate the Golang struct adding the new form tag. - bodySchema.GoType = GenStructFromSchema(bodySchema) - } + // Regenerate the Golang struct adding the new form tag. + bodySchema.GoType = GenStructFromSchema(bodySchema) + } - td := TypeDefinition{ - TypeName: bodyTypeName, - Schema: bodySchema, + td := TypeDefinition{ + TypeName: bodyTypeName, + Schema: bodySchema, + } + typeDefinitions = append(typeDefinitions, td) + // The body schema now is a reference to a type + bodySchema.RefType = bodyTypeName } - typeDefinitions = append(typeDefinitions, td) - // The body schema now is a reference to a type - bodySchema.RefType = bodyTypeName } bd := RequestBodyDefinition{ @@ -986,7 +1009,9 @@ func GenerateBodyDefinitions(operationID string, bodyOrRef *openapi3.RequestBody return bodyDefinitions, typeDefinitions, nil } -func GenerateResponseDefinitions(operationID string, responses map[string]*openapi3.ResponseRef) ([]ResponseDefinition, error) { +func GenerateResponseDefinitions(operationID string, responses map[string]*openapi3.ResponseRef, pathItemRef string) ([]ResponseDefinition, error) { + externalPkg := externalPackageFor(pathItemRef) + var responseDefinitions []ResponseDefinition // do not let multiple status codes ref to same response, it will break the type switch refSet := make(map[string]struct{}) @@ -1023,11 +1048,44 @@ func GenerateResponseDefinitions(operationID string, responses map[string]*opena } responseTypeName := operationID + statusCode + tag + "Response" - contentSchema, err := GenerateGoSchema(content.Schema, []string{responseTypeName}) + // The strict-server envelope keeps the bare ...Response name + // (e.g. "GetPing200JSONResponse"); the hoisted body type is + // suffixed so the envelope can reference it without colliding + // (the strict envelope is sometimes a struct that wraps the + // body in a Body field, which would self-reference if the + // names matched). + responseBodyTypeName := responseTypeName + "Body" + contentSchema, err := GenerateGoSchema(content.Schema, []string{responseBodyTypeName}) if err != nil { return nil, fmt.Errorf("error generating request body definition: %w", err) } + // Hoist inline response-root schemas that need method-emitting + // boilerplate (UnionElements / AdditionalProperties) to a + // synthetic top-level TypeDefinition. The hoisted typedef flows + // via op.TypeDefinitions (collected in + // GenerateTypeDefsForOperation) and gets declared once via + // typedef.tmpl with full union/additionalProperties methods. + // The strict-server template references it as the body type + // from the envelope. + // + // When the operation came from an externally-ref'd path item, + // the imported package generated the same hoisted name, so we + // reference it instead of redeclaring locally. + if !IsGoTypeReference(responseOrRef.Ref) && contentSchema.RefType == "" && + (len(contentSchema.UnionElements) != 0 || contentSchema.HasAdditionalProperties) { + if externalPkg != "" { + contentSchema.RefType = fmt.Sprintf("%s.%s", externalPkg, responseBodyTypeName) + } else { + contentSchema.AdditionalTypes = append(contentSchema.AdditionalTypes, TypeDefinition{ + TypeName: responseBodyTypeName, + JsonName: responseBodyTypeName, + Schema: contentSchema, + }) + contentSchema.RefType = responseBodyTypeName + } + } + rcd := ResponseContentDefinition{ ContentType: contentType, NameTag: tag, @@ -1157,7 +1215,6 @@ func GenerateParamsTypes(op OperationDefinition) []TypeDefinition { s.Properties = append(s.Properties, prop) } - s.Description = op.Spec.Description s.GoType = GenStructFromSchema(s) td := TypeDefinition{ @@ -1180,25 +1237,6 @@ func GenerateTypesForOperations(t *template.Template, ops []OperationDefinition) return "", fmt.Errorf("error writing boilerplate to buffer: %w", err) } - // Generate boiler plate for all additional types. - var td []TypeDefinition - for _, op := range ops { - td = append(td, op.TypeDefinitions...) - } - - addProps, err := GenerateAdditionalPropertyBoilerplate(t, td) - if err != nil { - return "", fmt.Errorf("error generating additional properties boilerplate for operations: %w", err) - } - - if _, err := w.WriteString("\n"); err != nil { - return "", fmt.Errorf("error generating additional properties boilerplate for operations: %w", err) - } - - if _, err := w.WriteString(addProps); err != nil { - return "", fmt.Errorf("error generating additional properties boilerplate for operations: %w", err) - } - if err = w.Flush(); err != nil { return "", fmt.Errorf("error flushing output buffer for server interface: %w", err) } diff --git a/pkg/codegen/schema.go b/pkg/codegen/schema.go index 4c6fc4517d..bf46a67a63 100644 --- a/pkg/codegen/schema.go +++ b/pkg/codegen/schema.go @@ -71,16 +71,22 @@ func (s Schema) IsExternalRef() bool { // MarshalJSON/UnmarshalJSON that we need to delegate to. This is true when // the schema is a $ref to a oneOf/anyOf union defined elsewhere. // -// It is deliberately false for inline unions: those are generated by emitting -// the union struct at this schema position, with its own MarshalJSON. The -// template's existing $hasUnionElements branch handles encoding by writing -// .union directly, so no delegation is needed. +// For *local* inline unions it is deliberately false: those are generated by +// emitting the union struct at this schema position, with its own +// MarshalJSON. The template's existing $hasUnionElements branch handles +// encoding by writing .union directly, so no delegation is needed. +// +// For *external* inline unions (the response-root hoist set RefType to a +// type living in an imported package), the strict envelope is rendered as a +// defined type — `type X externalRef0.Y` — and methods on Y don't transfer. +// The .union shortcut also can't reach across packages. So we still need the +// MarshalJSON delegator here, even though UnionElements is non-empty. func (s Schema) HasCustomMarshalJSON() bool { if s.OAPISchema == nil { return false } if len(s.UnionElements) > 0 { - return false + return s.IsExternalRef() } return len(s.OAPISchema.OneOf) > 0 || len(s.OAPISchema.AnyOf) > 0 } diff --git a/pkg/codegen/template_helpers.go b/pkg/codegen/template_helpers.go index 3ea88718bb..1beaba0b3f 100644 --- a/pkg/codegen/template_helpers.go +++ b/pkg/codegen/template_helpers.go @@ -47,6 +47,28 @@ var ( titleCaser = cases.Title(language.English) ) +// isMediaTypeSupported reports whether code generation produces a typed +// body for this media type. Today this is the closed set of JSON / YAML / +// XML variants the response and request templates know how to handle — +// see the typeName switch in GetResponseTypeDefinitions and the body +// definition switch in GenerateBodyDefinitions. A future configuration +// option is intended to let users extend this list. +func isMediaTypeSupported(mediaType string) bool { + switch { + case slices.Contains(contentTypesHalJSON, mediaType): + return true + case slices.Contains(contentTypesJSON, mediaType): + return true + case util.IsMediaTypeJson(mediaType): + return true + case slices.Contains(contentTypesYAML, mediaType): + return true + case slices.Contains(contentTypesXML, mediaType): + return true + } + return false +} + // genParamArgs takes an array of Parameter definition, and generates a valid // Go parameter declaration from them, eg: // ", foo int, bar string, baz float32". The preceding comma is there to save diff --git a/pkg/codegen/templates/client-with-responses.tmpl b/pkg/codegen/templates/client-with-responses.tmpl index 8b5b670be6..68ad3d4355 100644 --- a/pkg/codegen/templates/client-with-responses.tmpl +++ b/pkg/codegen/templates/client-with-responses.tmpl @@ -53,12 +53,6 @@ type {{genResponseTypeName $opid | ucFirst}} struct { {{- end}} } -{{- range $responseTypeDefinitions}} - {{- range .AdditionalTypeDefinitions}} - type {{.TypeName}} {{if .IsAlias }}={{end}} {{.Schema.TypeDecl}} - {{- end}} -{{- end}} - // Status returns HTTPResponse.Status func (r {{genResponseTypeName $opid | ucFirst}}) Status() string { if r.HTTPResponse != nil { diff --git a/pkg/codegen/templates/strict/strict-fiber-interface.tmpl b/pkg/codegen/templates/strict/strict-fiber-interface.tmpl index bcf372a35c..5f79c84348 100644 --- a/pkg/codegen/templates/strict/strict-fiber-interface.tmpl +++ b/pkg/codegen/templates/strict/strict-fiber-interface.tmpl @@ -97,7 +97,7 @@ {{$hasBodyVar := or ($hasHeaders) (not $fixedStatusCode) (not .IsSupported)}} {{if .IsJSON }} {{$hasUnionElements := ne 0 (len .Schema.UnionElements)}} - return ctx.JSON(&{{if $hasBodyVar}}response.Body{{else}}response{{end}}{{if $hasUnionElements}}.union{{end}}) + return ctx.JSON(&{{if $hasBodyVar}}response.Body{{else}}response{{end}}{{if and $hasUnionElements (not .Schema.IsExternalRef)}}.union{{end}}) {{else if eq .NameTag "Text" -}} _, err := ctx.WriteString(string({{if $hasBodyVar}}response.Body{{else}}response{{end}})) return err diff --git a/pkg/codegen/templates/strict/strict-interface.tmpl b/pkg/codegen/templates/strict/strict-interface.tmpl index f722f51a91..25e7455081 100644 --- a/pkg/codegen/templates/strict/strict-interface.tmpl +++ b/pkg/codegen/templates/strict/strict-interface.tmpl @@ -88,7 +88,7 @@ {{if .IsJSON -}} {{$hasUnionElements := ne 0 (len .Schema.UnionElements) -}} var buf bytes.Buffer - if err := json.NewEncoder(&buf).Encode(response{{if $hasBodyVar}}.Body{{end}}{{if $hasUnionElements}}.union{{end}}); err != nil { + if err := json.NewEncoder(&buf).Encode(response{{if $hasBodyVar}}.Body{{end}}{{if and $hasUnionElements (not .Schema.IsExternalRef)}}.union{{end}}); err != nil { return err } w.Header().Set("Content-Type", {{if .HasFixedContentType }}{{.ContentType | toGoString}}{{else}}response.ContentType{{end}}) diff --git a/pkg/codegen/templates/strict/strict-iris-interface.tmpl b/pkg/codegen/templates/strict/strict-iris-interface.tmpl index 75b1d792ec..32d319ed8d 100644 --- a/pkg/codegen/templates/strict/strict-iris-interface.tmpl +++ b/pkg/codegen/templates/strict/strict-iris-interface.tmpl @@ -99,7 +99,7 @@ {{$hasBodyVar := or ($hasHeaders) (not $fixedStatusCode) (not .IsSupported)}} {{if .IsJSON -}} {{$hasUnionElements := ne 0 (len .Schema.UnionElements)}} - return ctx.JSON(&{{if $hasBodyVar}}response.Body{{else}}response{{end}}{{if $hasUnionElements}}.union{{end}}) + return ctx.JSON(&{{if $hasBodyVar}}response.Body{{else}}response{{end}}{{if and $hasUnionElements (not .Schema.IsExternalRef)}}.union{{end}}) {{else if eq .NameTag "Text" -}} _, err := ctx.WriteString(string({{if $hasBodyVar}}response.Body{{else}}response{{end}})) return err