From cc974052ea3c27ef05448f731c6c200be69f887d Mon Sep 17 00:00:00 2001 From: Marcin Romaszewicz Date: Thu, 12 Feb 2026 13:39:22 -0800 Subject: [PATCH] Add a thorough name conflict test This tests whether name conflict resolution works properly --- .../codegen/name_conflict_resolution_test.go | 623 ++++++++ .../test/name_conflict_resolution/config.yaml | 11 + .../test/name_conflict_resolution/doc.go | 6 + .../output/types.gen.go | 1399 +++++++++++++++++ .../output/types_test.go | 266 ++++ .../test/name_conflict_resolution/spec.yaml | 337 ++++ 6 files changed, 2642 insertions(+) create mode 100644 experimental/internal/codegen/name_conflict_resolution_test.go create mode 100644 experimental/internal/codegen/test/name_conflict_resolution/config.yaml create mode 100644 experimental/internal/codegen/test/name_conflict_resolution/doc.go create mode 100644 experimental/internal/codegen/test/name_conflict_resolution/output/types.gen.go create mode 100644 experimental/internal/codegen/test/name_conflict_resolution/output/types_test.go create mode 100644 experimental/internal/codegen/test/name_conflict_resolution/spec.yaml diff --git a/experimental/internal/codegen/name_conflict_resolution_test.go b/experimental/internal/codegen/name_conflict_resolution_test.go new file mode 100644 index 000000000..a590f4c85 --- /dev/null +++ b/experimental/internal/codegen/name_conflict_resolution_test.go @@ -0,0 +1,623 @@ +package codegen + +import ( + "sort" + "testing" + + "github.com/pb33f/libopenapi" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +// nameConflictSpec is the comprehensive collision spec from PR #2213. +// It exercises all documented name collision patterns: +// +// Pattern A: Cross-section collision (#200, #254, #407, #1881, PR #292) +// Pattern B: Schema vs client wrapper (#1474, #1713, #1450) +// Pattern C: Schema alias vs client wrapper (#1357) +// Pattern D: Operation name = schema response name (#255) +// Pattern E: Schema matches op+Response (#2097, #899) +// Pattern F: x-oapi-codegen-type-name extension + cross-section collision +// Pattern G: x-go-type extension + cross-section collision +// Pattern H: Multiple JSON content types in requestBody (TMF622 scenario, PR #2213) +// +// Note: The experimental code gathers schemas at path-level positions since +// libopenapi resolves $refs. Component-level requestBodies, parameters, responses, +// and headers are NOT gathered separately — they appear at their path-level +// positions instead (e.g., #/paths//foo/post/requestBody/content/application/json/schema). +// This inherently avoids many cross-section collision patterns that affected V2. +const nameConflictSpec = `openapi: "3.1.0" +info: + title: "Comprehensive name collision resolution test" + version: "0.0.0" +paths: + # Pattern A: Cross-section collision + # "Bar" appears in schemas, parameters, requestBodies, responses, and headers. + /foo: + post: + operationId: postFoo + parameters: + - $ref: '#/components/parameters/Bar' + requestBody: + $ref: '#/components/requestBodies/Bar' + responses: + "200": + $ref: '#/components/responses/Bar' + + # Pattern B: Schema vs client wrapper + # Schema "CreateItemResponse" collides with createItem wrapper. + /items: + post: + operationId: createItem + requestBody: + required: true + content: + application/json: + schema: + type: object + properties: + name: + type: string + responses: + "200": + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/CreateItemResponse' + + # Pattern C: Schema alias vs client wrapper + # Schema "ListItemsResponse" (string alias) collides with listItems wrapper. + get: + operationId: listItems + responses: + "200": + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/ListItemsResponse' + + # Pattern D: Operation name = schema response name + # Schema "QueryResponse" collides with query wrapper. + /query: + post: + operationId: query + requestBody: + required: true + content: + application/json: + schema: + type: object + properties: + q: + type: string + responses: + "200": + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/QueryResponse' + + # Pattern E: Schema matches op+Response + # Schema "GetStatusResponse" collides with getStatus wrapper. + /status: + get: + operationId: getStatus + responses: + "200": + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/GetStatusResponse' + + # Pattern F: x-oapi-codegen-type-name extension + cross-section collision + /qux: + get: + operationId: getQux + responses: + "200": + $ref: '#/components/responses/Qux' + post: + operationId: postQux + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/Qux' + responses: + "200": + description: OK + + # Pattern G: x-go-type extension + cross-section collision + /zap: + get: + operationId: getZap + responses: + "200": + $ref: '#/components/responses/Zap' + post: + operationId: postZap + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/Zap' + responses: + "200": + description: OK + + # Pattern H: Multiple JSON content types in requestBody (TMF622 scenario) + /orders: + post: + operationId: createOrder + requestBody: + $ref: '#/components/requestBodies/Order' + responses: + "200": + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/Order' + + # Cross-section: requestBody vs schema + /pets: + post: + operationId: createPet + requestBody: + $ref: '#/components/requestBodies/Pet' + responses: + "200": + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/Pet' + +components: + schemas: + Bar: + type: object + properties: + value: + type: string + + Bar2: + type: object + properties: + value: + type: number + + CreateItemResponse: + type: object + properties: + id: + type: string + name: + type: string + + ListItemsResponse: + type: string + + QueryResponse: + type: object + properties: + results: + type: array + items: + type: string + + GetStatusResponse: + type: object + properties: + status: + type: string + timestamp: + type: string + + Order: + type: object + properties: + id: + type: string + product: + type: string + + Pet: + type: object + properties: + id: + type: integer + name: + type: string + + Qux: + type: object + x-oapi-codegen-type-name-override: CustomQux + properties: + label: + type: string + + Zap: + type: object + x-go-type: string + properties: + unused: + type: string + + parameters: + Bar: + name: bar + in: query + schema: + type: string + + requestBodies: + Bar: + content: + application/json: + schema: + type: object + properties: + value: + type: integer + + Order: + content: + application/json: + schema: + type: object + properties: + id: + type: string + product: + type: string + application/merge-patch+json: + schema: + type: object + properties: + product: + type: string + application/json-patch+json: + schema: + type: array + items: + type: object + properties: + op: + type: string + path: + type: string + value: + type: string + + Pet: + content: + application/json: + schema: + type: object + properties: + name: + type: string + species: + type: string + + headers: + Bar: + schema: + type: boolean + + responses: + Bar: + description: Bar response + headers: + X-Bar: + $ref: '#/components/headers/Bar' + content: + application/json: + schema: + type: object + properties: + value1: + $ref: '#/components/schemas/Bar' + value2: + $ref: '#/components/schemas/Bar2' + + Qux: + description: A Qux response + content: + application/json: + schema: + type: object + properties: + data: + type: string + + Zap: + description: A Zap response + content: + application/json: + schema: + type: object + properties: + result: + type: string +` + +// gatherAndComputeNames parses the spec, gathers schemas, and computes names. +// Returns a map of path string -> short name for easy assertions. +func gatherAndComputeNames(t *testing.T, spec string) map[string]string { + t.Helper() + + doc, err := libopenapi.NewDocument([]byte(spec)) + require.NoError(t, err) + + matcher := NewContentTypeMatcher(DefaultContentTypes()) + schemas, err := GatherSchemas(doc, matcher, OutputOptions{}) + require.NoError(t, err) + + converter := NewNameConverter(DefaultNameMangling(), NameSubstitutions{}) + contentTypeNamer := NewContentTypeShortNamer(DefaultContentTypeShortNames()) + ComputeSchemaNames(schemas, converter, contentTypeNamer) + + result := make(map[string]string) + for _, s := range schemas { + result[s.Path.String()] = s.ShortName + } + return result +} + +// assertUniqueShortNames verifies that all non-reference schemas have unique short names. +func assertUniqueShortNames(t *testing.T, spec string) map[string]string { + t.Helper() + + doc, err := libopenapi.NewDocument([]byte(spec)) + require.NoError(t, err) + + matcher := NewContentTypeMatcher(DefaultContentTypes()) + schemas, err := GatherSchemas(doc, matcher, OutputOptions{}) + require.NoError(t, err) + + converter := NewNameConverter(DefaultNameMangling(), NameSubstitutions{}) + contentTypeNamer := NewContentTypeShortNamer(DefaultContentTypeShortNames()) + ComputeSchemaNames(schemas, converter, contentTypeNamer) + + // Build map of short name -> paths, excluding references + nameToPath := make(map[string][]string) + result := make(map[string]string) + for _, s := range schemas { + result[s.Path.String()] = s.ShortName + if s.Ref == "" { + nameToPath[s.ShortName] = append(nameToPath[s.ShortName], s.Path.String()) + } + } + + for name, paths := range nameToPath { + if len(paths) > 1 { + t.Errorf("short name %q is not unique, used by: %v", name, paths) + } + } + + return result +} + +// TestNameConflictResolution_AllNamesUnique verifies that the collision resolver +// produces unique short names for all non-reference schemas in the comprehensive spec. +func TestNameConflictResolution_AllNamesUnique(t *testing.T) { + names := assertUniqueShortNames(t, nameConflictSpec) + + // Log all names sorted by path for readability + var paths []string + for path := range names { + paths = append(paths, path) + } + sort.Strings(paths) + + t.Log("All schema names:") + for _, path := range paths { + t.Logf(" %-80s -> %s", path, names[path]) + } +} + +// TestNameConflictResolution_PatternA_CrossSection verifies that when "Bar" appears in +// schemas, parameters, requestBodies, responses, and headers, each gets a unique name. +// +// In experimental, libopenapi resolves $refs so component requestBodies/parameters/ +// responses/headers appear at their path-level positions. The component schema keeps +// its bare name, and path-level schemas get operationId-based names. +// +// Covers issues: #200, #254, #407, #1881, PR #292 +func TestNameConflictResolution_PatternA_CrossSection(t *testing.T) { + names := gatherAndComputeNames(t, nameConflictSpec) + + // Component schema keeps bare name + schemaName := names["#/components/schemas/Bar"] + assert.Equal(t, "Bar", schemaName, "component schema should keep bare name") + + // The $ref to components/requestBodies/Bar is resolved by libopenapi and + // gathered at the path level. The operationId "postFoo" gives it a distinct name. + reqBodyName := names["#/paths//foo/post/requestBody/content/application/json/schema"] + assert.NotEmpty(t, reqBodyName, "requestBody schema should be gathered") + assert.NotEqual(t, "Bar", reqBodyName, "requestBody should not collide with schema") + t.Logf("RequestBody Bar (via path) -> %s", reqBodyName) + + // The $ref to components/responses/Bar is resolved at the path level. + respName := names["#/paths//foo/post/responses/200/content/application/json/schema"] + assert.NotEmpty(t, respName, "response schema should be gathered") + assert.NotEqual(t, "Bar", respName, "response should not collide with schema") + t.Logf("Response Bar (via path) -> %s", respName) + + // All three should be distinct + assert.NotEqual(t, reqBodyName, respName, + "requestBody and response should have different names") +} + +// TestNameConflictResolution_PatternB_SchemaVsOperationResponse verifies that +// schema "CreateItemResponse" does not collide with the inline operation response +// type generated for createItem. In experimental, the path response is a $ref to +// the component schema, so it's a reference schema and doesn't generate a new type. +// +// Covers issues: #1474, #1713, #1450 +func TestNameConflictResolution_PatternB_SchemaVsOperationResponse(t *testing.T) { + names := gatherAndComputeNames(t, nameConflictSpec) + + // Component schema keeps bare name + schemaName := names["#/components/schemas/CreateItemResponse"] + assert.Equal(t, "CreateItemResponse", schemaName, + "component schema should keep bare name 'CreateItemResponse'") + + // The inline request body for createItem should get a distinct name + reqBodyName := names["#/paths//items/post/requestBody/content/application/json/schema"] + assert.NotEmpty(t, reqBodyName, "inline request body should be gathered") + t.Logf("createItem requestBody -> %s", reqBodyName) + + // The response is a $ref to CreateItemResponse, so it's a reference and + // uses the component schema's name. Verify it's gathered as a reference. + opRespName := names["#/paths//items/post/responses/200/content/application/json/schema"] + t.Logf("createItem response (ref to schema) -> %s", opRespName) +} + +// TestNameConflictResolution_PatternD_OperationNameMatchesSchema verifies that +// schema "QueryResponse" does not collide with the response type generated +// for operation "query". In experimental, the path response is a $ref to the +// component schema, so no collision occurs. +// +// Covers issue: #255 +func TestNameConflictResolution_PatternD_OperationNameMatchesSchema(t *testing.T) { + names := gatherAndComputeNames(t, nameConflictSpec) + + schemaName := names["#/components/schemas/QueryResponse"] + assert.Equal(t, "QueryResponse", schemaName, + "component schema should keep bare name 'QueryResponse'") + + // The inline request body for query should get a distinct name + reqBodyName := names["#/paths//query/post/requestBody/content/application/json/schema"] + assert.NotEmpty(t, reqBodyName, "inline request body should be gathered") + t.Logf("query requestBody -> %s", reqBodyName) + + // The response is a $ref, check its name + opRespName := names["#/paths//query/post/responses/200/content/application/json/schema"] + t.Logf("query response (ref to schema) -> %s", opRespName) +} + +// TestNameConflictResolution_PatternE_SchemaMatchesOpResponse verifies that +// schema "GetStatusResponse" does not collide with the response type generated +// for operation "getStatus". +// +// Covers issues: #2097, #899 +func TestNameConflictResolution_PatternE_SchemaMatchesOpResponse(t *testing.T) { + names := gatherAndComputeNames(t, nameConflictSpec) + + schemaName := names["#/components/schemas/GetStatusResponse"] + assert.Equal(t, "GetStatusResponse", schemaName, + "component schema should keep bare name 'GetStatusResponse'") + + // The response is a $ref, check its name + opRespName := names["#/paths//status/get/responses/200/content/application/json/schema"] + t.Logf("getStatus response (ref to schema) -> %s", opRespName) +} + +// TestNameConflictResolution_PatternH_MultipleJsonContentTypes verifies that +// when a requestBody has 3 content types that all contain "json", the resolver +// produces unique names for each. The component $ref is resolved by libopenapi, +// so the schemas appear at the path level. +// +// Expected: all 3 content type schemas get unique names despite all mapping to +// the "JSON" content type short name. +// +// Covers: PR #2213 (TMF622 scenario) +func TestNameConflictResolution_PatternH_MultipleJsonContentTypes(t *testing.T) { + names := gatherAndComputeNames(t, nameConflictSpec) + + // Schema "Order" keeps bare name + schemaName := names["#/components/schemas/Order"] + assert.Equal(t, "Order", schemaName, "component schema should keep bare name") + + // The $ref to components/requestBodies/Order is resolved at path level. + // The 3 content types should each get unique names. + jsonName := names["#/paths//orders/post/requestBody/content/application/json/schema"] + mergePatchName := names["#/paths//orders/post/requestBody/content/application/merge-patch+json/schema"] + jsonPatchName := names["#/paths//orders/post/requestBody/content/application/json-patch+json/schema"] + + t.Logf("Order schema -> %s", schemaName) + t.Logf("Order reqBody application/json -> %s", jsonName) + t.Logf("Order reqBody merge-patch+json -> %s", mergePatchName) + t.Logf("Order reqBody json-patch+json -> %s", jsonPatchName) + + // All should be non-empty + assert.NotEmpty(t, jsonName, "application/json requestBody should have a name") + assert.NotEmpty(t, mergePatchName, "application/merge-patch+json requestBody should have a name") + assert.NotEmpty(t, jsonPatchName, "application/json-patch+json requestBody should have a name") + + // All should be different from each other + assert.NotEqual(t, jsonName, mergePatchName, "json and merge-patch+json should have different names") + assert.NotEqual(t, jsonName, jsonPatchName, "json and json-patch+json should have different names") + assert.NotEqual(t, mergePatchName, jsonPatchName, "merge-patch+json and json-patch+json should have different names") + + // None should collide with the schema name + assert.NotEqual(t, schemaName, jsonName, "requestBody json should not collide with schema") + assert.NotEqual(t, schemaName, mergePatchName, "requestBody merge-patch should not collide with schema") + assert.NotEqual(t, schemaName, jsonPatchName, "requestBody json-patch should not collide with schema") +} + +// TestNameConflictResolution_RequestBodyVsSchema verifies that "Pet" in schemas +// and requestBodies resolves correctly: the schema keeps "Pet", the requestBody +// (resolved via $ref at path level) gets a different name. +// +// Covers issues: #254, #407 +func TestNameConflictResolution_RequestBodyVsSchema(t *testing.T) { + names := gatherAndComputeNames(t, nameConflictSpec) + + schemaName := names["#/components/schemas/Pet"] + assert.Equal(t, "Pet", schemaName, "component schema should keep bare name") + + // The $ref to components/requestBodies/Pet is resolved at path level + reqBodyName := names["#/paths//pets/post/requestBody/content/application/json/schema"] + assert.NotEmpty(t, reqBodyName, "requestBody schema should be gathered") + t.Logf("Pet requestBody (via path) -> %s", reqBodyName) + assert.NotEqual(t, "Pet", reqBodyName, "requestBody should not collide with schema") +} + +// TestNameConflictResolution_PatternF_TypeNameOverride verifies that x-oapi-codegen-type-name +// interacts correctly with collision resolution. +func TestNameConflictResolution_PatternF_TypeNameOverride(t *testing.T) { + names := gatherAndComputeNames(t, nameConflictSpec) + + // Schema Qux has x-oapi-codegen-type-name: CustomQux + schemaName := names["#/components/schemas/Qux"] + assert.Equal(t, "CustomQux", schemaName, + "schema with x-oapi-codegen-type-name should use override name") + + // Response Qux (resolved at path level from $ref to components/responses/Qux) + respName := names["#/paths//qux/get/responses/200/content/application/json/schema"] + assert.NotEmpty(t, respName, "response schema should be gathered") + t.Logf("Qux schema -> %s", schemaName) + t.Logf("Qux response (via path) -> %s", respName) + + // They should not collide + assert.NotEqual(t, schemaName, respName, + "schema Qux (CustomQux) and response Qux should not collide") +} + +// TestNameConflictResolution_PatternG_GoTypeOverride verifies that x-go-type +// interacts correctly with collision resolution. +func TestNameConflictResolution_PatternG_GoTypeOverride(t *testing.T) { + names := gatherAndComputeNames(t, nameConflictSpec) + + schemaName := names["#/components/schemas/Zap"] + t.Logf("Zap schema -> %s", schemaName) + + // Response Zap (resolved at path level from $ref to components/responses/Zap) + respName := names["#/paths//zap/get/responses/200/content/application/json/schema"] + assert.NotEmpty(t, respName, "response schema should be gathered") + t.Logf("Zap response (via path) -> %s", respName) + + // They should not collide + assert.NotEqual(t, schemaName, respName, + "schema Zap and response Zap should not collide") +} diff --git a/experimental/internal/codegen/test/name_conflict_resolution/config.yaml b/experimental/internal/codegen/test/name_conflict_resolution/config.yaml new file mode 100644 index 000000000..cc1d4cc27 --- /dev/null +++ b/experimental/internal/codegen/test/name_conflict_resolution/config.yaml @@ -0,0 +1,11 @@ +package: output +output: output/types.gen.go + +generation: + client: true + simple-client: true + +content-types: + - "^application/json$" + - "^application/.*\\+json$" + - "^application/x-www-form-urlencoded$" diff --git a/experimental/internal/codegen/test/name_conflict_resolution/doc.go b/experimental/internal/codegen/test/name_conflict_resolution/doc.go new file mode 100644 index 000000000..54735ba79 --- /dev/null +++ b/experimental/internal/codegen/test/name_conflict_resolution/doc.go @@ -0,0 +1,6 @@ +// Package name_conflict_resolution tests comprehensive type name collision resolution. +// Exercises patterns from issues #200, #254, #255, #292, #407, #899, #1357, #1450, +// #1474, #1713, #1881, #2097, #2213. +package name_conflict_resolution + +//go:generate go run ../../../../cmd/oapi-codegen -config config.yaml spec.yaml diff --git a/experimental/internal/codegen/test/name_conflict_resolution/output/types.gen.go b/experimental/internal/codegen/test/name_conflict_resolution/output/types.gen.go new file mode 100644 index 000000000..bd9ebbef4 --- /dev/null +++ b/experimental/internal/codegen/test/name_conflict_resolution/output/types.gen.go @@ -0,0 +1,1399 @@ +// Code generated by oapi-codegen; DO NOT EDIT. + +package output + +import ( + "bytes" + "compress/gzip" + "context" + "encoding/base64" + "encoding/json" + "fmt" + "io" + "net/http" + "net/url" + "strings" + "sync" +) + +// #/components/schemas/Bar +type Bar struct { + Value *string `json:"value,omitempty" form:"value,omitempty"` +} + +// ApplyDefaults sets default values for fields that are nil. +func (s *Bar) ApplyDefaults() { +} + +// #/components/schemas/Bar2 +type Bar2 struct { + Value *float32 `json:"value,omitempty" form:"value,omitempty"` +} + +// ApplyDefaults sets default values for fields that are nil. +func (s *Bar2) ApplyDefaults() { +} + +// #/components/schemas/CreateItemResponse +type CreateItemResponse struct { + ID *string `json:"id,omitempty" form:"id,omitempty"` + Name *string `json:"name,omitempty" form:"name,omitempty"` +} + +// ApplyDefaults sets default values for fields that are nil. +func (s *CreateItemResponse) ApplyDefaults() { +} + +// #/components/schemas/ListItemsResponse +type ListItemsResponse = string + +// #/components/schemas/QueryResponse +type QueryResponse struct { + Results []string `json:"results,omitempty" form:"results,omitempty"` +} + +// ApplyDefaults sets default values for fields that are nil. +func (s *QueryResponse) ApplyDefaults() { +} + +// #/components/schemas/GetStatusResponse +type GetStatusResponse struct { + Status *string `json:"status,omitempty" form:"status,omitempty"` + Timestamp *string `json:"timestamp,omitempty" form:"timestamp,omitempty"` +} + +// ApplyDefaults sets default values for fields that are nil. +func (s *GetStatusResponse) ApplyDefaults() { +} + +// #/components/schemas/Order +type Order struct { + ID *string `json:"id,omitempty" form:"id,omitempty"` + Product *string `json:"product,omitempty" form:"product,omitempty"` +} + +// ApplyDefaults sets default values for fields that are nil. +func (s *Order) ApplyDefaults() { +} + +// #/components/schemas/Pet +type Pet struct { + ID *int `json:"id,omitempty" form:"id,omitempty"` + Name *string `json:"name,omitempty" form:"name,omitempty"` +} + +// ApplyDefaults sets default values for fields that are nil. +func (s *Pet) ApplyDefaults() { +} + +// #/components/schemas/Qux +type CustomQux struct { + Label *string `json:"label,omitempty" form:"label,omitempty"` +} + +// ApplyDefaults sets default values for fields that are nil. +func (s *CustomQux) ApplyDefaults() { +} + +type Zap = string + +// #/paths//foo/post/requestBody/content/application/json/schema +type PostFooJSONRequest struct { + Value *int `json:"value,omitempty" form:"value,omitempty"` +} + +// ApplyDefaults sets default values for fields that are nil. +func (s *PostFooJSONRequest) ApplyDefaults() { +} + +// #/paths//foo/post/responses/200/content/application/json/schema +type PostFooJSONResponse struct { + Value1 *Bar `json:"value1,omitempty" form:"value1,omitempty"` + Value2 *Bar2 `json:"value2,omitempty" form:"value2,omitempty"` +} + +// ApplyDefaults sets default values for fields that are nil. +func (s *PostFooJSONResponse) ApplyDefaults() { + if s.Value1 != nil { + s.Value1.ApplyDefaults() + } + if s.Value2 != nil { + s.Value2.ApplyDefaults() + } +} + +// #/paths//items/post/requestBody/content/application/json/schema +type CreateItemJSONRequest struct { + Name *string `json:"name,omitempty" form:"name,omitempty"` +} + +// ApplyDefaults sets default values for fields that are nil. +func (s *CreateItemJSONRequest) ApplyDefaults() { +} + +// #/paths//query/post/requestBody/content/application/json/schema +type QueryJSONRequest struct { + Q *string `json:"q,omitempty" form:"q,omitempty"` +} + +// ApplyDefaults sets default values for fields that are nil. +func (s *QueryJSONRequest) ApplyDefaults() { +} + +// #/paths//qux/get/responses/200/content/application/json/schema +type GetQuxJSONResponse struct { + Data *string `json:"data,omitempty" form:"data,omitempty"` +} + +// ApplyDefaults sets default values for fields that are nil. +func (s *GetQuxJSONResponse) ApplyDefaults() { +} + +// #/paths//zap/get/responses/200/content/application/json/schema +type GetZapJSONResponse struct { + Result *string `json:"result,omitempty" form:"result,omitempty"` +} + +// ApplyDefaults sets default values for fields that are nil. +func (s *GetZapJSONResponse) ApplyDefaults() { +} + +type PostZapJSONRequest = string + +// #/paths//orders/post/requestBody/content/application/json/schema +type CreateOrderJSONRequest1 struct { + ID *string `json:"id,omitempty" form:"id,omitempty"` + Product *string `json:"product,omitempty" form:"product,omitempty"` +} + +// ApplyDefaults sets default values for fields that are nil. +func (s *CreateOrderJSONRequest1) ApplyDefaults() { +} + +// #/paths//orders/post/requestBody/content/application/merge-patch+json/schema +type CreateOrderJSONRequest2 struct { + Product *string `json:"product,omitempty" form:"product,omitempty"` +} + +// ApplyDefaults sets default values for fields that are nil. +func (s *CreateOrderJSONRequest2) ApplyDefaults() { +} + +// #/paths//orders/post/requestBody/content/application/json-patch+json/schema +type CreateOrderJSONRequest3 = []PostOrdersRequest + +// #/paths//orders/post/requestBody/content/application/json-patch+json/schema/items +type PostOrdersRequest struct { + Op *string `json:"op,omitempty" form:"op,omitempty"` + Path *string `json:"path,omitempty" form:"path,omitempty"` + Value *string `json:"value,omitempty" form:"value,omitempty"` +} + +// ApplyDefaults sets default values for fields that are nil. +func (s *PostOrdersRequest) ApplyDefaults() { +} + +// #/paths//pets/post/requestBody/content/application/json/schema +type CreatePetJSONRequest struct { + Name *string `json:"name,omitempty" form:"name,omitempty"` + Species *string `json:"species,omitempty" form:"species,omitempty"` +} + +// ApplyDefaults sets default values for fields that are nil. +func (s *CreatePetJSONRequest) ApplyDefaults() { +} + +// Base64-encoded, gzip-compressed OpenAPI spec. +var openAPISpecJSON = []string{ + "H4sIAAAAAAAC/9xZS3PbthO/61PsSP8Z2RPLpmjrn5gzPcRukqZtaiXOoeMbTK5FZEgCAUBF7vTDdwA+", + "wZekxKnj6qAHgF3s47cvinFMCKcenB47x/PRiCZ3zBsBKKoi9GB8yWIuMMRE0jVCQmIEn0URlZQlIFCy", + "KFX6q0KpxiOAAKUvKNdrHvw9AgB4tUHhU4kSSBRBwPw0xkRh0OTGiVIoEgnEF0xKoFKmmigJYPlBeobX", + "xHWcI5i4izPzvtDv5+4RTM6c50cweXF+fgST+elC/5ifLRzz8Vwfnj+fn+qPFy/mmsg510dcd346Alij", + "kEZg59g5dkYjTlRoLpzAMhMKXnpwqaWaSfSNwpXcB7mglmyZPNltyw9GykPDcHxBxBgI50iEBJqA9EOM", + "iTwCTgSJUaGQRyDwc4pSXbCAovkpOUuk/qrNESIJUMjjEcDJHWOZaTiTKvsGwDgKosV8G3hm4zVj+VbF", + "+b44DfA/gXceTCcnPos5SzBR8sQS4eSCiGnJIRemoncdp/rRxy6nylhZxr3w4NpYAdYS/IhiouCL0DYS", + "lXUbfjxbOJlBc8rxpUCi8K3C+EN+1ThzUoASvlAVgl+eKLgbC1KFsdxiw4p0yIx6kQoMPFAixXLZZ4nC", + "RNUtRDiPqG/Yn3ySLKnvQQ4Jew1A3XP0gN1+Ql81trjQ0ipa90nx0lHWXi3YSSVostrZs1Z4X/1W2+lQ", + "cpuafYp2AyiPk5O2nw2a6ni6LPFEIkpkL6qyVHGYUxdA+p1KpdnLCkcHmZkydocNWEXF+TqqAFbYDaXy", + "+FMyessmjQj+2YOrQsksr/+U31Pqly0XhncXCzt836co7vsi97PetILWrGwJWnPmycXr5/9SsFpebWDm", + "VRmlMVF+iBIYf1acrVfV87y024B5g+paEZXKPtCsigMWcKRZ8gZDtKR8SqZu2aNh7tcebGaMcDrzWYAr", + "TGYaVDMdlTO2RiFogIAbpVs9lsAz8Lv7nUbUbsYQEmkAmkV4yUt3KrZHylRg6PI43mz1xft00++Iqes4", + "092bj/fpZrq9Xarf+K/njeF42kz3sEUTlhYg3mhArJiBwd6OvyE8c3zFYsjf+rjx91+Eb/X3DeEP5u8b", + "wnfwd/3GH8rfpfgP4O9fPHiXRoryCOHX66s/CvlN7JpRpKY8HHx89/r/rgvSx4QIyvIxxp2f5nPMlQiw", + "c5IxQLCmh2P4GKLFXSPntBDA8MuEUCFRZkzUW4QmMNZWHJfYIlwiKAYqRJA624y1JmOQIROqbHUnHZ44", + "slZiFCuccV13nrV39UptU+OWaW13mxKMYb5t2jIspk+p+uQSG+Nbg7Jn+X0ti7awqu/FvJzjaonKQtUt", + "U+EAtLRzOKrdXLNE9W2OWaJ6Um4x8o6qDU2d72WMLogoOHb0q1196ppEqTVQWr1pwdR9GK5JGt+iyLi2", + "B78976BBr9h9w3Jbt9YkZAtRP2o1v3vKKlCmkZJtWYgQ5L62Wnt60S90qzfcU5p60zxgPUVjlIrEfNiE", + "VkkqGoq8nDQbiCp1FCe+hNQPdQHJeZ026lhZQmLCi1LRqhLZiG44PjyIuGBB6qthIyyr/ucbbqaJwlVZ", + "bXbB73cZCDTWNwPqbL3Hg8tUKhZXjXeXBSJyi9Gu6n1Fezvc4BYX7dToAtwQPmiRBtcBtdMklRgM6V09", + "Om5ldYMIuCUFRmhiPxhpVpUma6v+tbi3CtlQEesqYL0PSfofkbRKRVdAtBNNZy7RzutNIHYPmnPc2omm", + "d3d0A2tKCrYf7zlem9WDw4688xgmtFPJQCrrTWgDNEN99nfQ5euFa7T5u8nWrL+dFfirH/4xvuPTvxo7", + "osK9iXpiaEuhegyodv99MaCc5Oh32rapWv4vWiundWfEW8YiJEmWEq3Ov0ZqdfwXRJRH833rSv36c1aj", + "7mvmc6r6f3CPlnfn+80fNZEtNu7ebNzpqNloWNZ+qXea9n4MMwVEke3gs/qDhiI3hP8IimQjyBZV/gkA", + "AP//w/EIckAgAAA=", +} + +// decodeOpenAPISpec decodes and decompresses the embedded spec. +func decodeOpenAPISpec() ([]byte, error) { + joined := strings.Join(openAPISpecJSON, "") + raw, err := base64.StdEncoding.DecodeString(joined) + if err != nil { + return nil, fmt.Errorf("decoding base64: %w", err) + } + r, err := gzip.NewReader(bytes.NewReader(raw)) + if err != nil { + return nil, fmt.Errorf("creating gzip reader: %w", err) + } + defer r.Close() + var out bytes.Buffer + if _, err := out.ReadFrom(r); err != nil { + return nil, fmt.Errorf("decompressing: %w", err) + } + return out.Bytes(), nil +} + +// decodeOpenAPISpecCached returns a closure that caches the decoded spec. +func decodeOpenAPISpecCached() func() ([]byte, error) { + var cached []byte + var cachedErr error + var once sync.Once + return func() ([]byte, error) { + once.Do(func() { + cached, cachedErr = decodeOpenAPISpec() + }) + return cached, cachedErr + } +} + +var openAPISpec = decodeOpenAPISpecCached() + +// GetOpenAPISpecJSON returns the raw OpenAPI spec as JSON bytes. +func GetOpenAPISpecJSON() ([]byte, error) { + return openAPISpec() +} + +type postFooJSONRequestBody = any + +type createItemJSONRequestBody = any + +type createOrderJSONRequestBody = any + +type createOrderApplicationJsonPatchJsonRequestBody = any + +type createOrderApplicationMergePatchJsonRequestBody = any + +type createPetJSONRequestBody = any + +type queryJSONRequestBody = any + +type postQuxJSONRequestBody = CustomQux + +type postZapJSONRequestBody = Zap + +// RequestEditorFn is the function signature for the RequestEditor callback function. +type RequestEditorFn func(ctx context.Context, req *http.Request) error + +// HttpRequestDoer 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 OpenAPI 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 + +// NewClient creates a new Client with reasonable defaults. +func NewClient(server string, opts ...ClientOption) (*Client, error) { + client := Client{ + Server: server, + } + 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 + } +} + +// 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 + } +} + +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 +} + +// ClientInterface is the interface specification for the client. +type ClientInterface interface { + // PostFooWithBody makes a POST request to /foo + PostFooWithBody(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) + PostFoo(ctx context.Context, body postFooJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) + // ListItems makes a GET request to /items + ListItems(ctx context.Context, reqEditors ...RequestEditorFn) (*http.Response, error) + // CreateItemWithBody makes a POST request to /items + CreateItemWithBody(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) + CreateItem(ctx context.Context, body createItemJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) + // CreateOrderWithBody makes a POST request to /orders + CreateOrderWithBody(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) + CreateOrder(ctx context.Context, body createOrderJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) + CreateOrderWithApplicationJsonPatchJsonBody(ctx context.Context, body createOrderApplicationJsonPatchJsonRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) + CreateOrderWithApplicationMergePatchJsonBody(ctx context.Context, body createOrderApplicationMergePatchJsonRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) + // CreatePetWithBody makes a POST request to /pets + CreatePetWithBody(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) + CreatePet(ctx context.Context, body createPetJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) + // QueryWithBody makes a POST request to /query + QueryWithBody(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) + Query(ctx context.Context, body queryJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) + // GetQux makes a GET request to /qux + GetQux(ctx context.Context, reqEditors ...RequestEditorFn) (*http.Response, error) + // PostQuxWithBody makes a POST request to /qux + PostQuxWithBody(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) + PostQux(ctx context.Context, body postQuxJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) + // GetStatus makes a GET request to /status + GetStatus(ctx context.Context, reqEditors ...RequestEditorFn) (*http.Response, error) + // GetZap makes a GET request to /zap + GetZap(ctx context.Context, reqEditors ...RequestEditorFn) (*http.Response, error) + // PostZapWithBody makes a POST request to /zap + PostZapWithBody(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) + PostZap(ctx context.Context, body postZapJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) +} + +// PostFooWithBody makes a POST request to /foo + +func (c *Client) PostFooWithBody(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewPostFooRequestWithBody(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) +} + +// PostFoo makes a POST request to /foo with application/json body +func (c *Client) PostFoo(ctx context.Context, body postFooJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewPostFooRequest(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) +} + +// ListItems makes a GET request to /items + +func (c *Client) ListItems(ctx context.Context, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewListItemsRequest(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) +} + +// CreateItemWithBody makes a POST request to /items + +func (c *Client) CreateItemWithBody(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewCreateItemRequestWithBody(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) +} + +// CreateItem makes a POST request to /items with application/json body +func (c *Client) CreateItem(ctx context.Context, body createItemJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewCreateItemRequest(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) +} + +// CreateOrderWithBody makes a POST request to /orders + +func (c *Client) CreateOrderWithBody(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewCreateOrderRequestWithBody(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) +} + +// CreateOrder makes a POST request to /orders with application/json body +func (c *Client) CreateOrder(ctx context.Context, body createOrderJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewCreateOrderRequest(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) +} + +// CreateOrderWithApplicationJsonPatchJsonBody makes a POST request to /orders with application/json-patch+json body +func (c *Client) CreateOrderWithApplicationJsonPatchJsonBody(ctx context.Context, body createOrderApplicationJsonPatchJsonRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewCreateOrderRequestWithApplicationJsonPatchJsonBody(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) +} + +// CreateOrderWithApplicationMergePatchJsonBody makes a POST request to /orders with application/merge-patch+json body +func (c *Client) CreateOrderWithApplicationMergePatchJsonBody(ctx context.Context, body createOrderApplicationMergePatchJsonRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewCreateOrderRequestWithApplicationMergePatchJsonBody(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) +} + +// CreatePetWithBody makes a POST request to /pets + +func (c *Client) CreatePetWithBody(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewCreatePetRequestWithBody(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) +} + +// CreatePet makes a POST request to /pets with application/json body +func (c *Client) CreatePet(ctx context.Context, body createPetJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewCreatePetRequest(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) +} + +// QueryWithBody makes a POST request to /query + +func (c *Client) QueryWithBody(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewQueryRequestWithBody(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) +} + +// Query makes a POST request to /query with application/json body +func (c *Client) Query(ctx context.Context, body queryJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewQueryRequest(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) +} + +// GetQux makes a GET request to /qux + +func (c *Client) GetQux(ctx context.Context, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewGetQuxRequest(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) +} + +// PostQuxWithBody makes a POST request to /qux + +func (c *Client) PostQuxWithBody(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewPostQuxRequestWithBody(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) +} + +// PostQux makes a POST request to /qux with application/json body +func (c *Client) PostQux(ctx context.Context, body postQuxJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewPostQuxRequest(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) +} + +// GetStatus makes a GET request to /status + +func (c *Client) GetStatus(ctx context.Context, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewGetStatusRequest(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) +} + +// GetZap makes a GET request to /zap + +func (c *Client) GetZap(ctx context.Context, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewGetZapRequest(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) +} + +// PostZapWithBody makes a POST request to /zap + +func (c *Client) PostZapWithBody(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewPostZapRequestWithBody(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) +} + +// PostZap makes a POST request to /zap with application/json body +func (c *Client) PostZap(ctx context.Context, body postZapJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewPostZapRequest(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) +} + +// NewPostFooRequest creates a POST request for /foo with application/json body +func NewPostFooRequest(server string, body postFooJSONRequestBody) (*http.Request, error) { + var bodyReader io.Reader + buf, err := json.Marshal(body) + if err != nil { + return nil, err + } + bodyReader = bytes.NewReader(buf) + return NewPostFooRequestWithBody(server, "application/json", bodyReader) +} + +// NewPostFooRequestWithBody creates a POST request for /foo with any body +func NewPostFooRequestWithBody(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("/foo") + if operationPath[0] == '/' { + operationPath = "." + operationPath + } + + queryURL, err := serverURL.Parse(operationPath) + if err != nil { + return nil, err + } + + req, err := http.NewRequest("POST", queryURL.String(), body) + if err != nil { + return nil, err + } + + req.Header.Add("Content-Type", contentType) + + return req, nil +} + +// NewListItemsRequest creates a GET request for /items +func NewListItemsRequest(server string) (*http.Request, error) { + var err error + + serverURL, err := url.Parse(server) + if err != nil { + return nil, err + } + + operationPath := fmt.Sprintf("/items") + if operationPath[0] == '/' { + operationPath = "." + operationPath + } + + queryURL, err := serverURL.Parse(operationPath) + if err != nil { + return nil, err + } + + req, err := http.NewRequest("GET", queryURL.String(), nil) + if err != nil { + return nil, err + } + + return req, nil +} + +// NewCreateItemRequest creates a POST request for /items with application/json body +func NewCreateItemRequest(server string, body createItemJSONRequestBody) (*http.Request, error) { + var bodyReader io.Reader + buf, err := json.Marshal(body) + if err != nil { + return nil, err + } + bodyReader = bytes.NewReader(buf) + return NewCreateItemRequestWithBody(server, "application/json", bodyReader) +} + +// NewCreateItemRequestWithBody creates a POST request for /items with any body +func NewCreateItemRequestWithBody(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("/items") + if operationPath[0] == '/' { + operationPath = "." + operationPath + } + + queryURL, err := serverURL.Parse(operationPath) + if err != nil { + return nil, err + } + + req, err := http.NewRequest("POST", queryURL.String(), body) + if err != nil { + return nil, err + } + + req.Header.Add("Content-Type", contentType) + + return req, nil +} + +// NewCreateOrderRequest creates a POST request for /orders with application/json body +func NewCreateOrderRequest(server string, body createOrderJSONRequestBody) (*http.Request, error) { + var bodyReader io.Reader + buf, err := json.Marshal(body) + if err != nil { + return nil, err + } + bodyReader = bytes.NewReader(buf) + return NewCreateOrderRequestWithBody(server, "application/json", bodyReader) +} + +// NewCreateOrderRequestWithApplicationJsonPatchJsonBody creates a POST request for /orders with application/json-patch+json body +func NewCreateOrderRequestWithApplicationJsonPatchJsonBody(server string, body createOrderApplicationJsonPatchJsonRequestBody) (*http.Request, error) { + var bodyReader io.Reader + buf, err := json.Marshal(body) + if err != nil { + return nil, err + } + bodyReader = bytes.NewReader(buf) + return NewCreateOrderRequestWithBody(server, "application/json-patch+json", bodyReader) +} + +// NewCreateOrderRequestWithApplicationMergePatchJsonBody creates a POST request for /orders with application/merge-patch+json body +func NewCreateOrderRequestWithApplicationMergePatchJsonBody(server string, body createOrderApplicationMergePatchJsonRequestBody) (*http.Request, error) { + var bodyReader io.Reader + buf, err := json.Marshal(body) + if err != nil { + return nil, err + } + bodyReader = bytes.NewReader(buf) + return NewCreateOrderRequestWithBody(server, "application/merge-patch+json", bodyReader) +} + +// NewCreateOrderRequestWithBody creates a POST request for /orders with any body +func NewCreateOrderRequestWithBody(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("/orders") + if operationPath[0] == '/' { + operationPath = "." + operationPath + } + + queryURL, err := serverURL.Parse(operationPath) + if err != nil { + return nil, err + } + + req, err := http.NewRequest("POST", queryURL.String(), body) + if err != nil { + return nil, err + } + + req.Header.Add("Content-Type", contentType) + + return req, nil +} + +// NewCreatePetRequest creates a POST request for /pets with application/json body +func NewCreatePetRequest(server string, body createPetJSONRequestBody) (*http.Request, error) { + var bodyReader io.Reader + buf, err := json.Marshal(body) + if err != nil { + return nil, err + } + bodyReader = bytes.NewReader(buf) + return NewCreatePetRequestWithBody(server, "application/json", bodyReader) +} + +// NewCreatePetRequestWithBody creates a POST request for /pets with any body +func NewCreatePetRequestWithBody(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("/pets") + if operationPath[0] == '/' { + operationPath = "." + operationPath + } + + queryURL, err := serverURL.Parse(operationPath) + if err != nil { + return nil, err + } + + req, err := http.NewRequest("POST", queryURL.String(), body) + if err != nil { + return nil, err + } + + req.Header.Add("Content-Type", contentType) + + return req, nil +} + +// NewQueryRequest creates a POST request for /query with application/json body +func NewQueryRequest(server string, body queryJSONRequestBody) (*http.Request, error) { + var bodyReader io.Reader + buf, err := json.Marshal(body) + if err != nil { + return nil, err + } + bodyReader = bytes.NewReader(buf) + return NewQueryRequestWithBody(server, "application/json", bodyReader) +} + +// NewQueryRequestWithBody creates a POST request for /query with any body +func NewQueryRequestWithBody(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("/query") + if operationPath[0] == '/' { + operationPath = "." + operationPath + } + + queryURL, err := serverURL.Parse(operationPath) + if err != nil { + return nil, err + } + + req, err := http.NewRequest("POST", queryURL.String(), body) + if err != nil { + return nil, err + } + + req.Header.Add("Content-Type", contentType) + + return req, nil +} + +// NewGetQuxRequest creates a GET request for /qux +func NewGetQuxRequest(server string) (*http.Request, error) { + var err error + + serverURL, err := url.Parse(server) + if err != nil { + return nil, err + } + + operationPath := fmt.Sprintf("/qux") + if operationPath[0] == '/' { + operationPath = "." + operationPath + } + + queryURL, err := serverURL.Parse(operationPath) + if err != nil { + return nil, err + } + + req, err := http.NewRequest("GET", queryURL.String(), nil) + if err != nil { + return nil, err + } + + return req, nil +} + +// NewPostQuxRequest creates a POST request for /qux with application/json body +func NewPostQuxRequest(server string, body postQuxJSONRequestBody) (*http.Request, error) { + var bodyReader io.Reader + buf, err := json.Marshal(body) + if err != nil { + return nil, err + } + bodyReader = bytes.NewReader(buf) + return NewPostQuxRequestWithBody(server, "application/json", bodyReader) +} + +// NewPostQuxRequestWithBody creates a POST request for /qux with any body +func NewPostQuxRequestWithBody(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("/qux") + if operationPath[0] == '/' { + operationPath = "." + operationPath + } + + queryURL, err := serverURL.Parse(operationPath) + if err != nil { + return nil, err + } + + req, err := http.NewRequest("POST", queryURL.String(), body) + if err != nil { + return nil, err + } + + req.Header.Add("Content-Type", contentType) + + return req, nil +} + +// NewGetStatusRequest creates a GET request for /status +func NewGetStatusRequest(server string) (*http.Request, error) { + var err error + + serverURL, err := url.Parse(server) + if err != nil { + return nil, err + } + + operationPath := fmt.Sprintf("/status") + if operationPath[0] == '/' { + operationPath = "." + operationPath + } + + queryURL, err := serverURL.Parse(operationPath) + if err != nil { + return nil, err + } + + req, err := http.NewRequest("GET", queryURL.String(), nil) + if err != nil { + return nil, err + } + + return req, nil +} + +// NewGetZapRequest creates a GET request for /zap +func NewGetZapRequest(server string) (*http.Request, error) { + var err error + + serverURL, err := url.Parse(server) + if err != nil { + return nil, err + } + + operationPath := fmt.Sprintf("/zap") + if operationPath[0] == '/' { + operationPath = "." + operationPath + } + + queryURL, err := serverURL.Parse(operationPath) + if err != nil { + return nil, err + } + + req, err := http.NewRequest("GET", queryURL.String(), nil) + if err != nil { + return nil, err + } + + return req, nil +} + +// NewPostZapRequest creates a POST request for /zap with application/json body +func NewPostZapRequest(server string, body postZapJSONRequestBody) (*http.Request, error) { + var bodyReader io.Reader + buf, err := json.Marshal(body) + if err != nil { + return nil, err + } + bodyReader = bytes.NewReader(buf) + return NewPostZapRequestWithBody(server, "application/json", bodyReader) +} + +// NewPostZapRequestWithBody creates a POST request for /zap with any body +func NewPostZapRequestWithBody(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("/zap") + if operationPath[0] == '/' { + operationPath = "." + operationPath + } + + queryURL, err := serverURL.Parse(operationPath) + if err != nil { + return nil, err + } + + req, err := http.NewRequest("POST", queryURL.String(), body) + if err != nil { + return nil, err + } + + req.Header.Add("Content-Type", contentType) + + return req, nil +} + +// ClientHttpError represents an HTTP error response from the server. +// The type parameter E is the type of the parsed error body. +type ClientHttpError[E any] struct { + StatusCode int + Body E + RawBody []byte +} + +func (e *ClientHttpError[E]) Error() string { + return fmt.Sprintf("HTTP %d", e.StatusCode) +} + +// SimpleClient wraps Client with typed responses for operations that have +// unambiguous response types. Methods return the success type directly, +// and HTTP errors are returned as *ClientHttpError[E] where E is the error type. +type SimpleClient struct { + *Client +} + +// NewSimpleClient creates a new SimpleClient which wraps a Client. +func NewSimpleClient(server string, opts ...ClientOption) (*SimpleClient, error) { + client, err := NewClient(server, opts...) + if err != nil { + return nil, err + } + return &SimpleClient{Client: client}, nil +} + +// PostFoo makes a POST request to /foo and returns the parsed response. + +// On success, returns the response body. On HTTP error, returns *ClientHttpError[struct{}]. +func (c *SimpleClient) PostFoo(ctx context.Context, body postFooJSONRequestBody, reqEditors ...RequestEditorFn) (map[string]any, error) { + var result map[string]any + resp, err := c.Client.PostFoo(ctx, body, reqEditors...) + if err != nil { + return result, err + } + defer resp.Body.Close() + + rawBody, err := io.ReadAll(resp.Body) + if err != nil { + return result, err + } + + if resp.StatusCode >= 200 && resp.StatusCode < 300 { + if err := json.Unmarshal(rawBody, &result); err != nil { + return result, err + } + return result, nil + } + + // No typed error response defined + return result, &ClientHttpError[struct{}]{ + StatusCode: resp.StatusCode, + RawBody: rawBody, + } +} + +// ListItems makes a GET request to /items and returns the parsed response. + +// On success, returns the response body. On HTTP error, returns *ClientHttpError[struct{}]. +func (c *SimpleClient) ListItems(ctx context.Context, reqEditors ...RequestEditorFn) (ListItemsResponse, error) { + var result ListItemsResponse + resp, err := c.Client.ListItems(ctx, reqEditors...) + if err != nil { + return result, err + } + defer resp.Body.Close() + + rawBody, err := io.ReadAll(resp.Body) + if err != nil { + return result, err + } + + if resp.StatusCode >= 200 && resp.StatusCode < 300 { + if err := json.Unmarshal(rawBody, &result); err != nil { + return result, err + } + return result, nil + } + + // No typed error response defined + return result, &ClientHttpError[struct{}]{ + StatusCode: resp.StatusCode, + RawBody: rawBody, + } +} + +// CreateItem makes a POST request to /items and returns the parsed response. + +// On success, returns the response body. On HTTP error, returns *ClientHttpError[struct{}]. +func (c *SimpleClient) CreateItem(ctx context.Context, body createItemJSONRequestBody, reqEditors ...RequestEditorFn) (CreateItemResponse, error) { + var result CreateItemResponse + resp, err := c.Client.CreateItem(ctx, body, reqEditors...) + if err != nil { + return result, err + } + defer resp.Body.Close() + + rawBody, err := io.ReadAll(resp.Body) + if err != nil { + return result, err + } + + if resp.StatusCode >= 200 && resp.StatusCode < 300 { + if err := json.Unmarshal(rawBody, &result); err != nil { + return result, err + } + return result, nil + } + + // No typed error response defined + return result, &ClientHttpError[struct{}]{ + StatusCode: resp.StatusCode, + RawBody: rawBody, + } +} + +// CreateOrder makes a POST request to /orders and returns the parsed response. + +// On success, returns the response body. On HTTP error, returns *ClientHttpError[struct{}]. +func (c *SimpleClient) CreateOrder(ctx context.Context, body createOrderJSONRequestBody, reqEditors ...RequestEditorFn) (Order, error) { + var result Order + resp, err := c.Client.CreateOrder(ctx, body, reqEditors...) + if err != nil { + return result, err + } + defer resp.Body.Close() + + rawBody, err := io.ReadAll(resp.Body) + if err != nil { + return result, err + } + + if resp.StatusCode >= 200 && resp.StatusCode < 300 { + if err := json.Unmarshal(rawBody, &result); err != nil { + return result, err + } + return result, nil + } + + // No typed error response defined + return result, &ClientHttpError[struct{}]{ + StatusCode: resp.StatusCode, + RawBody: rawBody, + } +} + +// CreatePet makes a POST request to /pets and returns the parsed response. + +// On success, returns the response body. On HTTP error, returns *ClientHttpError[struct{}]. +func (c *SimpleClient) CreatePet(ctx context.Context, body createPetJSONRequestBody, reqEditors ...RequestEditorFn) (Pet, error) { + var result Pet + resp, err := c.Client.CreatePet(ctx, body, reqEditors...) + if err != nil { + return result, err + } + defer resp.Body.Close() + + rawBody, err := io.ReadAll(resp.Body) + if err != nil { + return result, err + } + + if resp.StatusCode >= 200 && resp.StatusCode < 300 { + if err := json.Unmarshal(rawBody, &result); err != nil { + return result, err + } + return result, nil + } + + // No typed error response defined + return result, &ClientHttpError[struct{}]{ + StatusCode: resp.StatusCode, + RawBody: rawBody, + } +} + +// Query makes a POST request to /query and returns the parsed response. + +// On success, returns the response body. On HTTP error, returns *ClientHttpError[struct{}]. +func (c *SimpleClient) Query(ctx context.Context, body queryJSONRequestBody, reqEditors ...RequestEditorFn) (QueryResponse, error) { + var result QueryResponse + resp, err := c.Client.Query(ctx, body, reqEditors...) + if err != nil { + return result, err + } + defer resp.Body.Close() + + rawBody, err := io.ReadAll(resp.Body) + if err != nil { + return result, err + } + + if resp.StatusCode >= 200 && resp.StatusCode < 300 { + if err := json.Unmarshal(rawBody, &result); err != nil { + return result, err + } + return result, nil + } + + // No typed error response defined + return result, &ClientHttpError[struct{}]{ + StatusCode: resp.StatusCode, + RawBody: rawBody, + } +} + +// GetQux makes a GET request to /qux and returns the parsed response. + +// On success, returns the response body. On HTTP error, returns *ClientHttpError[struct{}]. +func (c *SimpleClient) GetQux(ctx context.Context, reqEditors ...RequestEditorFn) (map[string]any, error) { + var result map[string]any + resp, err := c.Client.GetQux(ctx, reqEditors...) + if err != nil { + return result, err + } + defer resp.Body.Close() + + rawBody, err := io.ReadAll(resp.Body) + if err != nil { + return result, err + } + + if resp.StatusCode >= 200 && resp.StatusCode < 300 { + if err := json.Unmarshal(rawBody, &result); err != nil { + return result, err + } + return result, nil + } + + // No typed error response defined + return result, &ClientHttpError[struct{}]{ + StatusCode: resp.StatusCode, + RawBody: rawBody, + } +} + +// GetStatus makes a GET request to /status and returns the parsed response. + +// On success, returns the response body. On HTTP error, returns *ClientHttpError[struct{}]. +func (c *SimpleClient) GetStatus(ctx context.Context, reqEditors ...RequestEditorFn) (GetStatusResponse, error) { + var result GetStatusResponse + resp, err := c.Client.GetStatus(ctx, reqEditors...) + if err != nil { + return result, err + } + defer resp.Body.Close() + + rawBody, err := io.ReadAll(resp.Body) + if err != nil { + return result, err + } + + if resp.StatusCode >= 200 && resp.StatusCode < 300 { + if err := json.Unmarshal(rawBody, &result); err != nil { + return result, err + } + return result, nil + } + + // No typed error response defined + return result, &ClientHttpError[struct{}]{ + StatusCode: resp.StatusCode, + RawBody: rawBody, + } +} + +// GetZap makes a GET request to /zap and returns the parsed response. + +// On success, returns the response body. On HTTP error, returns *ClientHttpError[struct{}]. +func (c *SimpleClient) GetZap(ctx context.Context, reqEditors ...RequestEditorFn) (map[string]any, error) { + var result map[string]any + resp, err := c.Client.GetZap(ctx, reqEditors...) + if err != nil { + return result, err + } + defer resp.Body.Close() + + rawBody, err := io.ReadAll(resp.Body) + if err != nil { + return result, err + } + + if resp.StatusCode >= 200 && resp.StatusCode < 300 { + if err := json.Unmarshal(rawBody, &result); err != nil { + return result, err + } + return result, nil + } + + // No typed error response defined + return result, &ClientHttpError[struct{}]{ + StatusCode: resp.StatusCode, + RawBody: rawBody, + } +} diff --git a/experimental/internal/codegen/test/name_conflict_resolution/output/types_test.go b/experimental/internal/codegen/test/name_conflict_resolution/output/types_test.go new file mode 100644 index 000000000..7b77c1ad7 --- /dev/null +++ b/experimental/internal/codegen/test/name_conflict_resolution/output/types_test.go @@ -0,0 +1,266 @@ +package output + +import ( + "encoding/json" + "testing" +) + +// TestCrossSectionCollisions verifies Pattern A: when the same name "Bar" +// appears in schemas, parameters, requestBodies, and responses, the resolver +// keeps the bare name for the component schema and path-level schemas get +// operationId-based names. +// +// In the experimental codegen, libopenapi resolves $refs, so component-level +// requestBodies/parameters/responses appear at their path-level positions +// with operationId-based names. This inherently avoids the cross-section +// collision that plagued V2. +// +// Covers issues: #200, #254, #407, #1881, PR #292 +func TestCrossSectionCollisions(t *testing.T) { + // Schema type keeps bare name "Bar" + bar := Bar{Value: ptr("hello")} + assertEqual(t, "hello", *bar.Value) + + // No collision for Bar2 + bar2 := Bar2{Value: ptr(float32(1.5))} + assertEqual(t, float32(1.5), *bar2.Value) + + // RequestBody type gets operationId-based name "PostFooJSONRequest" + reqBody := PostFooJSONRequest{Value: ptr(42)} + assertEqual(t, 42, *reqBody.Value) + + // Response type gets operationId-based name "PostFooJSONResponse" + resp := PostFooJSONResponse{ + Value1: &Bar{Value: ptr("v1")}, + Value2: &Bar2{Value: ptr(float32(2.0))}, + } + assertEqual(t, "v1", *resp.Value1.Value) + assertEqual(t, float32(2.0), *resp.Value2.Value) +} + +// TestSchemaVsOperationResponse verifies Pattern B: schema "CreateItemResponse" +// does not collide with the operation response type for "createItem". +// +// In experimental, the SimpleClient returns the schema type directly (no wrapper), +// so no collision occurs. The inline request body gets "CreateItemJSONRequest". +// +// Covers issues: #1474, #1713, #1450 +func TestSchemaVsOperationResponse(t *testing.T) { + // Schema type keeps bare name + schema := CreateItemResponse{ + ID: ptr("item-1"), + Name: ptr("Widget"), + } + assertEqual(t, "item-1", *schema.ID) + assertEqual(t, "Widget", *schema.Name) + + // Inline request body gets operationId-based name + reqBody := CreateItemJSONRequest{ + Name: ptr("Widget"), + } + assertEqual(t, "Widget", *reqBody.Name) +} + +// TestSchemaAliasVsOperationResponse verifies Pattern C: schema "ListItemsResponse" +// (a string alias) does not collide with the operation response for "listItems". +// +// Covers issue: #1357 +func TestSchemaAliasVsOperationResponse(t *testing.T) { + // Schema type is a string alias + var schema ListItemsResponse = "item-list" + assertEqual(t, "item-list", schema) +} + +// TestOperationNameMatchesSchema verifies Pattern D: schema "QueryResponse" +// does not collide with the response type for operation "query". +// +// In experimental, path-level schemas from $ref use the target type directly +// via the SimpleClient, so "QueryResponse" (the component schema name) is used. +// +// Covers issue: #255 +func TestOperationNameMatchesSchema(t *testing.T) { + // Schema type keeps bare name + schema := QueryResponse{ + Results: []string{"result1", "result2"}, + } + if len(schema.Results) != 2 { + t.Errorf("expected 2 results, got %d", len(schema.Results)) + } + + // Inline request body gets operationId-based name + reqBody := QueryJSONRequest{ + Q: ptr("search term"), + } + assertEqual(t, "search term", *reqBody.Q) +} + +// TestSchemaMatchesOpResponse verifies Pattern E: schema "GetStatusResponse" +// does not collide with the response type for operation "getStatus". +// +// Covers issues: #2097, #899 +func TestSchemaMatchesOpResponse(t *testing.T) { + // Schema type keeps bare name + schema := GetStatusResponse{ + Status: ptr("healthy"), + Timestamp: ptr("2025-01-01T00:00:00Z"), + } + assertEqual(t, "healthy", *schema.Status) + assertEqual(t, "2025-01-01T00:00:00Z", *schema.Timestamp) +} + +// TestMultipleJsonContentTypes verifies Pattern H: schema "Order" collides with +// requestBody "Order" which has 3 content types that all contain "json": +// - application/json +// - application/merge-patch+json +// - application/json-patch+json +// +// All three map to the same "JSON" short name via the content type namer, so +// the numeric fallback disambiguates them. +// +// Expected types: +// - Order struct (schema keeps bare name) +// - CreateOrderJSONRequest1 struct (application/json requestBody, numeric fallback) +// - CreateOrderJSONRequest2 struct (application/merge-patch+json, numeric fallback) +// - CreateOrderJSONRequest3 []PostOrdersRequest (application/json-patch+json, numeric fallback) +// +// Covers: PR #2213 (TMF622 scenario) +func TestMultipleJsonContentTypes(t *testing.T) { + // Schema type keeps bare name "Order" + order := Order{ + ID: ptr("order-1"), + Product: ptr("Widget"), + } + assertEqual(t, "order-1", *order.ID) + assertEqual(t, "Widget", *order.Product) + + // application/json requestBody (numeric fallback 1) + jsonBody := CreateOrderJSONRequest1{ + ID: ptr("order-2"), + Product: ptr("Gadget"), + } + assertEqual(t, "order-2", *jsonBody.ID) + + // application/merge-patch+json requestBody (numeric fallback 2) + mergePatch := CreateOrderJSONRequest2{ + Product: ptr("Gadget-patched"), + } + assertEqual(t, "Gadget-patched", *mergePatch.Product) + + // application/json-patch+json requestBody (numeric fallback 3, array type alias) + var jsonPatch CreateOrderJSONRequest3 + jsonPatch = append(jsonPatch, PostOrdersRequest{ + Op: ptr("replace"), + Path: ptr("/product"), + Value: ptr("Gadget-v2"), + }) + assertEqual(t, "replace", *jsonPatch[0].Op) + assertEqual(t, "/product", *jsonPatch[0].Path) + assertEqual(t, "Gadget-v2", *jsonPatch[0].Value) +} + +// TestRequestBodyVsSchema verifies that "Pet" in schemas and requestBodies +// resolves correctly: the schema keeps bare name "Pet", the requestBody gets +// "CreatePetJSONRequest" (operationId-based). +// +// Covers issues: #254, #407 +func TestRequestBodyVsSchema(t *testing.T) { + // Schema type keeps bare name + pet := Pet{ + ID: ptr(1), + Name: ptr("Fluffy"), + } + assertEqual(t, 1, *pet.ID) + assertEqual(t, "Fluffy", *pet.Name) + + // RequestBody type gets operationId-based name + petReqBody := CreatePetJSONRequest{ + Name: ptr("Fluffy"), + Species: ptr("cat"), + } + assertEqual(t, "Fluffy", *petReqBody.Name) + assertEqual(t, "cat", *petReqBody.Species) +} + +// TestExtTypeNameOverrideWithCollisionResolver verifies that when a component schema +// has x-oapi-codegen-type-name-override: CustomQux and collides with a response "Qux", +// the type name override controls the generated type name. +// +// Expected types: +// - CustomQux struct (schema type from x-oapi-codegen-type-name-override) +// - GetQuxJSONResponse struct (response gets operationId-based name) +func TestExtTypeNameOverrideWithCollisionResolver(t *testing.T) { + // CustomQux is the struct created by x-oapi-codegen-type-name-override + custom := CustomQux{Label: ptr("hello")} + assertEqual(t, "hello", *custom.Label) + + // GetQuxJSONResponse is the response type (operationId-based name) + quxResp := GetQuxJSONResponse{Data: ptr("response-data")} + assertEqual(t, "response-data", *quxResp.Data) +} + +// TestExtGoTypeWithCollisionResolver verifies that when a component schema has +// x-go-type: string and collides with a response "Zap", the type override +// controls the generated type name. +// +// Expected types: +// - Zap = string (schema keeps bare name, x-go-type controls target) +// - GetZapJSONResponse struct (response gets operationId-based name) +func TestExtGoTypeWithCollisionResolver(t *testing.T) { + // Zap is a string type alias (x-go-type controls the target) + var zap Zap = "test-value" + assertEqual(t, "test-value", zap) + + // GetZapJSONResponse is the response type (operationId-based name) + zapResp := GetZapJSONResponse{Result: ptr("response-result")} + assertEqual(t, "response-result", *zapResp.Result) +} + +// TestJSONRoundTrip verifies that the generated types marshal/unmarshal correctly. +func TestJSONRoundTrip(t *testing.T) { + // Bar + bar := Bar{Value: ptr("hello")} + data, err := json.Marshal(bar) + if err != nil { + t.Fatalf("marshal Bar failed: %v", err) + } + var decoded Bar + if err := json.Unmarshal(data, &decoded); err != nil { + t.Fatalf("unmarshal Bar failed: %v", err) + } + assertEqual(t, "hello", *decoded.Value) + + // Order + order := Order{ID: ptr("o1"), Product: ptr("Widget")} + data, err = json.Marshal(order) + if err != nil { + t.Fatalf("marshal Order failed: %v", err) + } + var decodedOrder Order + if err := json.Unmarshal(data, &decodedOrder); err != nil { + t.Fatalf("unmarshal Order failed: %v", err) + } + assertEqual(t, "o1", *decodedOrder.ID) + assertEqual(t, "Widget", *decodedOrder.Product) +} + +// TestGetOpenAPISpecJSON verifies the embedded spec can be decoded. +func TestGetOpenAPISpecJSON(t *testing.T) { + data, err := GetOpenAPISpecJSON() + if err != nil { + t.Fatalf("GetOpenAPISpecJSON failed: %v", err) + } + if len(data) == 0 { + t.Fatal("GetOpenAPISpecJSON returned empty data") + } +} + +func ptr[T any](v T) *T { + return &v +} + +func assertEqual[T comparable](t *testing.T, expected, actual T) { + t.Helper() + if expected != actual { + t.Errorf("expected %v, got %v", expected, actual) + } +} diff --git a/experimental/internal/codegen/test/name_conflict_resolution/spec.yaml b/experimental/internal/codegen/test/name_conflict_resolution/spec.yaml new file mode 100644 index 000000000..f57eb3dcc --- /dev/null +++ b/experimental/internal/codegen/test/name_conflict_resolution/spec.yaml @@ -0,0 +1,337 @@ +openapi: 3.0.1 + +info: + title: "Comprehensive name collision resolution test" + description: | + Exercises all documented name collision patterns across issues and PRs: + #200, #254, #255, #292, #407, #899, #1357, #1450, #1474, #1713, #1881, #2097, #2213 + version: 0.0.0 + +paths: + # Pattern A: Cross-section collision (issues #200, #254, #407, #1881, PR #292) + # "Bar" appears in schemas, parameters, requestBodies, responses, and headers. + /foo: + post: + operationId: postFoo + requestBody: + $ref: '#/components/requestBodies/Bar' + responses: + 200: + $ref: '#/components/responses/Bar' + + # Pattern B: Schema vs client wrapper (issues #1474, #1713, #1450) + # Schema "CreateItemResponse" collides with createItem wrapper. + /items: + post: + operationId: createItem + requestBody: + required: true + content: + application/json: + schema: + type: object + properties: + name: + type: string + responses: + 200: + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/CreateItemResponse' + + # Pattern C: Schema alias vs client wrapper (issue #1357) + # Schema "ListItemsResponse" (string alias) collides with listItems wrapper. + get: + operationId: listItems + responses: + 200: + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/ListItemsResponse' + + # Pattern D: Operation name = schema response name (issue #255) + # Schema "QueryResponse" collides with query wrapper. + /query: + post: + operationId: query + requestBody: + required: true + content: + application/json: + schema: + type: object + properties: + q: + type: string + responses: + 200: + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/QueryResponse' + + # Pattern E: Schema matches op+Response (issues #2097, #899) + # Schema "GetStatusResponse" collides with getStatus wrapper. + /status: + get: + operationId: getStatus + responses: + 200: + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/GetStatusResponse' + + # Pattern F: x-oapi-codegen-type-name-override extension + cross-section collision + # Schema "Qux" has type name override and collides with response "Qux". + /qux: + get: + operationId: getQux + responses: + '200': + $ref: '#/components/responses/Qux' + post: + operationId: postQux + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/Qux' + responses: + '200': + description: OK + + # Pattern G: x-go-type extension + cross-section collision + # Schema "Zap" has x-go-type and collides with response "Zap". + /zap: + get: + operationId: getZap + responses: + '200': + $ref: '#/components/responses/Zap' + post: + operationId: postZap + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/Zap' + responses: + '200': + description: OK + + # Pattern H: Multiple JSON content types in requestBody (TMF622 scenario, PR #2213) + # "Order" appears in schemas and requestBodies. The requestBody has 3 content + # types that all contain "json" and collapse to the same "JSON" short name: + # application/json, application/merge-patch+json, application/json-patch+json + /orders: + post: + operationId: createOrder + requestBody: + $ref: '#/components/requestBodies/Order' + responses: + 200: + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/Order' + + # Cross-section: requestBody vs schema (issues #254, #407) + # "Pet" appears in both schemas and requestBodies. + /pets: + post: + operationId: createPet + requestBody: + $ref: '#/components/requestBodies/Pet' + responses: + 200: + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/Pet' + +components: + schemas: + Bar: + type: object + properties: + value: + type: string + + Bar2: + type: object + properties: + value: + type: number + + CreateItemResponse: + type: object + properties: + id: + type: string + name: + type: string + + ListItemsResponse: + type: string + + QueryResponse: + type: object + properties: + results: + type: array + items: + type: string + + GetStatusResponse: + type: object + properties: + status: + type: string + timestamp: + type: string + + # Pattern H: Schema "Order" collides with requestBody "Order" which has + # 3 content types that all map to the "JSON" short name. + Order: + type: object + properties: + id: + type: string + product: + type: string + + Pet: + type: object + properties: + id: + type: integer + name: + type: string + + # Pattern F: x-oapi-codegen-type-name-override extension + cross-section collision + Qux: + type: object + x-oapi-codegen-type-name-override: CustomQux + properties: + label: + type: string + + # Pattern G: x-go-type extension + cross-section collision + # Schema "Zap" has x-go-type: string and collides with response "Zap". + Zap: + type: object + x-go-type: string + properties: + unused: + type: string + + parameters: + Bar: + name: bar + in: query + schema: + type: string + + requestBodies: + Bar: + content: + application/json: + schema: + type: object + properties: + value: + type: integer + + # Pattern H: requestBody "Order" with 3 content types that all contain "json" + # and collapse to the same "JSON" suffix via contentTypeSuffix(). + Order: + content: + application/json: + schema: + type: object + properties: + id: + type: string + product: + type: string + application/merge-patch+json: + schema: + type: object + properties: + product: + type: string + application/json-patch+json: + schema: + type: array + items: + type: object + properties: + op: + type: string + path: + type: string + value: + type: string + + Pet: + content: + application/json: + schema: + type: object + properties: + name: + type: string + species: + type: string + + headers: + Bar: + schema: + type: boolean + + responses: + Bar: + description: Bar response + headers: + X-Bar: + $ref: '#/components/headers/Bar' + content: + application/json: + schema: + type: object + properties: + value1: + $ref: '#/components/schemas/Bar' + value2: + $ref: '#/components/schemas/Bar2' + + Qux: + description: A Qux response + content: + application/json: + schema: + type: object + properties: + data: + type: string + + Zap: + description: A Zap response + content: + application/json: + schema: + type: object + properties: + result: + type: string