From 1eeb41ca5a03ec81862d9135bcb0a09c6d7d7d2a Mon Sep 17 00:00:00 2001 From: Jay Shah Date: Tue, 1 Oct 2024 16:44:42 -0400 Subject: [PATCH] openapi2: fix un/marshalling discriminator field (#1011) * fix: issue unmarshalling when discriminator field is set in openapi2.0 * revert original approach * update with different approach * Revert "update with different approach" This reverts commit 2db2b3929adb6a9fc22c9b8300a5689ca4633d98. * v2 schema with discriminator field set as string * update ref link and comment * run docs.sh --- .github/docs/openapi2.txt | 191 ++++++++++++++++++------ .github/docs/openapi2conv.txt | 8 +- openapi2/helpers.go | 15 ++ openapi2/issues1010_test.go | 98 +++++++++++++ openapi2/openapi2.go | 30 ++-- openapi2/parameter.go | 46 +++--- openapi2/ref.go | 9 ++ openapi2/refs.go | 104 +++++++++++++ openapi2/response.go | 8 +- openapi2/schema.go | 269 ++++++++++++++++++++++++++++++++++ openapi2conv/openapi2_conv.go | 196 +++++++++++++++++++------ 11 files changed, 838 insertions(+), 136 deletions(-) create mode 100644 openapi2/helpers.go create mode 100644 openapi2/issues1010_test.go create mode 100644 openapi2/ref.go create mode 100644 openapi2/refs.go create mode 100644 openapi2/schema.go diff --git a/.github/docs/openapi2.txt b/.github/docs/openapi2.txt index aec6439de..9025f5a83 100644 --- a/.github/docs/openapi2.txt +++ b/.github/docs/openapi2.txt @@ -47,29 +47,29 @@ type Parameter struct { Ref string `json:"$ref,omitempty" yaml:"$ref,omitempty"` - In string `json:"in,omitempty" yaml:"in,omitempty"` - Name string `json:"name,omitempty" yaml:"name,omitempty"` - Description string `json:"description,omitempty" yaml:"description,omitempty"` - CollectionFormat string `json:"collectionFormat,omitempty" yaml:"collectionFormat,omitempty"` - Type *openapi3.Types `json:"type,omitempty" yaml:"type,omitempty"` - Format string `json:"format,omitempty" yaml:"format,omitempty"` - Pattern string `json:"pattern,omitempty" yaml:"pattern,omitempty"` - AllowEmptyValue bool `json:"allowEmptyValue,omitempty" yaml:"allowEmptyValue,omitempty"` - Required bool `json:"required,omitempty" yaml:"required,omitempty"` - UniqueItems bool `json:"uniqueItems,omitempty" yaml:"uniqueItems,omitempty"` - ExclusiveMin bool `json:"exclusiveMinimum,omitempty" yaml:"exclusiveMinimum,omitempty"` - ExclusiveMax bool `json:"exclusiveMaximum,omitempty" yaml:"exclusiveMaximum,omitempty"` - Schema *openapi3.SchemaRef `json:"schema,omitempty" yaml:"schema,omitempty"` - Items *openapi3.SchemaRef `json:"items,omitempty" yaml:"items,omitempty"` - Enum []any `json:"enum,omitempty" yaml:"enum,omitempty"` - MultipleOf *float64 `json:"multipleOf,omitempty" yaml:"multipleOf,omitempty"` - Minimum *float64 `json:"minimum,omitempty" yaml:"minimum,omitempty"` - Maximum *float64 `json:"maximum,omitempty" yaml:"maximum,omitempty"` - MaxLength *uint64 `json:"maxLength,omitempty" yaml:"maxLength,omitempty"` - MaxItems *uint64 `json:"maxItems,omitempty" yaml:"maxItems,omitempty"` - MinLength uint64 `json:"minLength,omitempty" yaml:"minLength,omitempty"` - MinItems uint64 `json:"minItems,omitempty" yaml:"minItems,omitempty"` - Default any `json:"default,omitempty" yaml:"default,omitempty"` + In string `json:"in,omitempty" yaml:"in,omitempty"` + Name string `json:"name,omitempty" yaml:"name,omitempty"` + Description string `json:"description,omitempty" yaml:"description,omitempty"` + CollectionFormat string `json:"collectionFormat,omitempty" yaml:"collectionFormat,omitempty"` + Type *openapi3.Types `json:"type,omitempty" yaml:"type,omitempty"` + Format string `json:"format,omitempty" yaml:"format,omitempty"` + Pattern string `json:"pattern,omitempty" yaml:"pattern,omitempty"` + AllowEmptyValue bool `json:"allowEmptyValue,omitempty" yaml:"allowEmptyValue,omitempty"` + Required bool `json:"required,omitempty" yaml:"required,omitempty"` + UniqueItems bool `json:"uniqueItems,omitempty" yaml:"uniqueItems,omitempty"` + ExclusiveMin bool `json:"exclusiveMinimum,omitempty" yaml:"exclusiveMinimum,omitempty"` + ExclusiveMax bool `json:"exclusiveMaximum,omitempty" yaml:"exclusiveMaximum,omitempty"` + Schema *SchemaRef `json:"schema,omitempty" yaml:"schema,omitempty"` + Items *SchemaRef `json:"items,omitempty" yaml:"items,omitempty"` + Enum []any `json:"enum,omitempty" yaml:"enum,omitempty"` + MultipleOf *float64 `json:"multipleOf,omitempty" yaml:"multipleOf,omitempty"` + Minimum *float64 `json:"minimum,omitempty" yaml:"minimum,omitempty"` + Maximum *float64 `json:"maximum,omitempty" yaml:"maximum,omitempty"` + MaxLength *uint64 `json:"maxLength,omitempty" yaml:"maxLength,omitempty"` + MaxItems *uint64 `json:"maxItems,omitempty" yaml:"maxItems,omitempty"` + MinLength uint64 `json:"minLength,omitempty" yaml:"minLength,omitempty"` + MinItems uint64 `json:"minItems,omitempty" yaml:"minItems,omitempty"` + Default any `json:"default,omitempty" yaml:"default,omitempty"` } func (parameter Parameter) MarshalJSON() ([]byte, error) @@ -113,15 +113,21 @@ func (pathItem *PathItem) SetOperation(method string, operation *Operation) func (pathItem *PathItem) UnmarshalJSON(data []byte) error UnmarshalJSON sets PathItem to a copy of data. +type Ref struct { + Ref string `json:"$ref" yaml:"$ref"` +} + Ref is specified by OpenAPI/Swagger 2.0 standard. See + https://github.com/OAI/OpenAPI-Specification/blob/main/versions/2.0.md#reference-object + type Response struct { Extensions map[string]any `json:"-" yaml:"-"` Ref string `json:"$ref,omitempty" yaml:"$ref,omitempty"` - Description string `json:"description,omitempty" yaml:"description,omitempty"` - Schema *openapi3.SchemaRef `json:"schema,omitempty" yaml:"schema,omitempty"` - Headers map[string]*Header `json:"headers,omitempty" yaml:"headers,omitempty"` - Examples map[string]any `json:"examples,omitempty" yaml:"examples,omitempty"` + Description string `json:"description,omitempty" yaml:"description,omitempty"` + Schema *SchemaRef `json:"schema,omitempty" yaml:"schema,omitempty"` + Headers map[string]*Header `json:"headers,omitempty" yaml:"headers,omitempty"` + Examples map[string]any `json:"examples,omitempty" yaml:"examples,omitempty"` } func (response Response) MarshalJSON() ([]byte, error) @@ -130,6 +136,107 @@ func (response Response) MarshalJSON() ([]byte, error) func (response *Response) UnmarshalJSON(data []byte) error UnmarshalJSON sets Response to a copy of data. +type Schema struct { + Extensions map[string]any `json:"-" yaml:"-"` + + AllOf SchemaRefs `json:"allOf,omitempty" yaml:"allOf,omitempty"` + Not *SchemaRef `json:"not,omitempty" yaml:"not,omitempty"` + Type *openapi3.Types `json:"type,omitempty" yaml:"type,omitempty"` + Title string `json:"title,omitempty" yaml:"title,omitempty"` + Format string `json:"format,omitempty" yaml:"format,omitempty"` + Description string `json:"description,omitempty" yaml:"description,omitempty"` + Enum []any `json:"enum,omitempty" yaml:"enum,omitempty"` + Default any `json:"default,omitempty" yaml:"default,omitempty"` + Example any `json:"example,omitempty" yaml:"example,omitempty"` + ExternalDocs *openapi3.ExternalDocs `json:"externalDocs,omitempty" yaml:"externalDocs,omitempty"` + + // Array-related, here for struct compactness + UniqueItems bool `json:"uniqueItems,omitempty" yaml:"uniqueItems,omitempty"` + // Number-related, here for struct compactness + ExclusiveMin bool `json:"exclusiveMinimum,omitempty" yaml:"exclusiveMinimum,omitempty"` + ExclusiveMax bool `json:"exclusiveMaximum,omitempty" yaml:"exclusiveMaximum,omitempty"` + // Properties + ReadOnly bool `json:"readOnly,omitempty" yaml:"readOnly,omitempty"` + WriteOnly bool `json:"writeOnly,omitempty" yaml:"writeOnly,omitempty"` + AllowEmptyValue bool `json:"allowEmptyValue,omitempty" yaml:"allowEmptyValue,omitempty"` + Deprecated bool `json:"deprecated,omitempty" yaml:"deprecated,omitempty"` + XML *openapi3.XML `json:"xml,omitempty" yaml:"xml,omitempty"` + + // Number + Min *float64 `json:"minimum,omitempty" yaml:"minimum,omitempty"` + Max *float64 `json:"maximum,omitempty" yaml:"maximum,omitempty"` + MultipleOf *float64 `json:"multipleOf,omitempty" yaml:"multipleOf,omitempty"` + + // String + MinLength uint64 `json:"minLength,omitempty" yaml:"minLength,omitempty"` + MaxLength *uint64 `json:"maxLength,omitempty" yaml:"maxLength,omitempty"` + Pattern string `json:"pattern,omitempty" yaml:"pattern,omitempty"` + + // Array + MinItems uint64 `json:"minItems,omitempty" yaml:"minItems,omitempty"` + MaxItems *uint64 `json:"maxItems,omitempty" yaml:"maxItems,omitempty"` + Items *SchemaRef `json:"items,omitempty" yaml:"items,omitempty"` + + // Object + Required []string `json:"required,omitempty" yaml:"required,omitempty"` + Properties Schemas `json:"properties,omitempty" yaml:"properties,omitempty"` + MinProps uint64 `json:"minProperties,omitempty" yaml:"minProperties,omitempty"` + MaxProps *uint64 `json:"maxProperties,omitempty" yaml:"maxProperties,omitempty"` + AdditionalProperties openapi3.AdditionalProperties `json:"additionalProperties,omitempty" yaml:"additionalProperties,omitempty"` + Discriminator string `json:"discriminator,omitempty" yaml:"discriminator,omitempty"` +} + Schema is specified by OpenAPI/Swagger 2.0 standard. See + https://swagger.io/specification/v2/#schema-object + +func (schema Schema) MarshalJSON() ([]byte, error) + MarshalJSON returns the JSON encoding of Schema. + +func (schema Schema) MarshalYAML() (any, error) + MarshalYAML returns the YAML encoding of Schema. + +func (schema *Schema) UnmarshalJSON(data []byte) error + UnmarshalJSON sets Schema to a copy of data. + +type SchemaRef struct { + // Extensions only captures fields starting with 'x-' as no other fields + // are allowed by the openapi spec. + Extensions map[string]any + + Ref string + Value *Schema + + // Has unexported fields. +} + SchemaRef represents either a Schema or a $ref to a Schema. When serializing + and both fields are set, Ref is preferred over Value. + +func (x *SchemaRef) CollectionName() string + CollectionName returns the JSON string used for a collection of these + components. + +func (x *SchemaRef) JSONLookup(token string) (any, error) + JSONLookup implements + https://pkg.go.dev/github.com/go-openapi/jsonpointer#JSONPointable + +func (x SchemaRef) MarshalJSON() ([]byte, error) + MarshalJSON returns the JSON encoding of SchemaRef. + +func (x SchemaRef) MarshalYAML() (any, error) + MarshalYAML returns the YAML encoding of SchemaRef. + +func (x *SchemaRef) RefPath() *url.URL + RefPath returns the path of the $ref relative to the root document. + +func (x *SchemaRef) RefString() string + RefString returns the $ref value. + +func (x *SchemaRef) UnmarshalJSON(data []byte) error + UnmarshalJSON sets SchemaRef to a copy of data. + +type SchemaRefs []*SchemaRef + +type Schemas map[string]*SchemaRef + type SecurityRequirements []map[string][]string type SecurityScheme struct { @@ -157,21 +264,21 @@ func (securityScheme *SecurityScheme) UnmarshalJSON(data []byte) error type T struct { Extensions map[string]any `json:"-" yaml:"-"` - Swagger string `json:"swagger" yaml:"swagger"` // required - Info openapi3.Info `json:"info" yaml:"info"` // required - ExternalDocs *openapi3.ExternalDocs `json:"externalDocs,omitempty" yaml:"externalDocs,omitempty"` - Schemes []string `json:"schemes,omitempty" yaml:"schemes,omitempty"` - Consumes []string `json:"consumes,omitempty" yaml:"consumes,omitempty"` - Produces []string `json:"produces,omitempty" yaml:"produces,omitempty"` - Host string `json:"host,omitempty" yaml:"host,omitempty"` - BasePath string `json:"basePath,omitempty" yaml:"basePath,omitempty"` - Paths map[string]*PathItem `json:"paths,omitempty" yaml:"paths,omitempty"` - Definitions map[string]*openapi3.SchemaRef `json:"definitions,omitempty" yaml:"definitions,omitempty"` - Parameters map[string]*Parameter `json:"parameters,omitempty" yaml:"parameters,omitempty"` - Responses map[string]*Response `json:"responses,omitempty" yaml:"responses,omitempty"` - SecurityDefinitions map[string]*SecurityScheme `json:"securityDefinitions,omitempty" yaml:"securityDefinitions,omitempty"` - Security SecurityRequirements `json:"security,omitempty" yaml:"security,omitempty"` - Tags openapi3.Tags `json:"tags,omitempty" yaml:"tags,omitempty"` + Swagger string `json:"swagger" yaml:"swagger"` // required + Info openapi3.Info `json:"info" yaml:"info"` // required + ExternalDocs *openapi3.ExternalDocs `json:"externalDocs,omitempty" yaml:"externalDocs,omitempty"` + Schemes []string `json:"schemes,omitempty" yaml:"schemes,omitempty"` + Consumes []string `json:"consumes,omitempty" yaml:"consumes,omitempty"` + Produces []string `json:"produces,omitempty" yaml:"produces,omitempty"` + Host string `json:"host,omitempty" yaml:"host,omitempty"` + BasePath string `json:"basePath,omitempty" yaml:"basePath,omitempty"` + Paths map[string]*PathItem `json:"paths,omitempty" yaml:"paths,omitempty"` + Definitions map[string]*SchemaRef `json:"definitions,omitempty" yaml:"definitions,omitempty"` + Parameters map[string]*Parameter `json:"parameters,omitempty" yaml:"parameters,omitempty"` + Responses map[string]*Response `json:"responses,omitempty" yaml:"responses,omitempty"` + SecurityDefinitions map[string]*SecurityScheme `json:"securityDefinitions,omitempty" yaml:"securityDefinitions,omitempty"` + Security SecurityRequirements `json:"security,omitempty" yaml:"security,omitempty"` + Tags openapi3.Tags `json:"tags,omitempty" yaml:"tags,omitempty"` } T is the root of an OpenAPI v2 document diff --git a/.github/docs/openapi2conv.txt b/.github/docs/openapi2conv.txt index 2f9f6deca..e24925aa3 100644 --- a/.github/docs/openapi2conv.txt +++ b/.github/docs/openapi2conv.txt @@ -16,8 +16,8 @@ func FromV3RequestBody(name string, requestBodyRef *openapi3.RequestBodyRef, med func FromV3RequestBodyFormData(mediaType *openapi3.MediaType) openapi2.Parameters func FromV3Response(ref *openapi3.ResponseRef, components *openapi3.Components) (*openapi2.Response, error) func FromV3Responses(responses map[string]*openapi3.ResponseRef, components *openapi3.Components) (map[string]*openapi2.Response, error) -func FromV3SchemaRef(schema *openapi3.SchemaRef, components *openapi3.Components) (*openapi3.SchemaRef, *openapi2.Parameter) -func FromV3Schemas(schemas map[string]*openapi3.SchemaRef, components *openapi3.Components) (map[string]*openapi3.SchemaRef, map[string]*openapi2.Parameter) +func FromV3SchemaRef(schema *openapi3.SchemaRef, components *openapi3.Components) (*openapi2.SchemaRef, *openapi2.Parameter) +func FromV3Schemas(schemas map[string]*openapi3.SchemaRef, components *openapi3.Components) (map[string]*openapi2.SchemaRef, map[string]*openapi2.Parameter) func FromV3SecurityRequirements(requirements openapi3.SecurityRequirements) openapi2.SecurityRequirements func FromV3SecurityScheme(doc3 *openapi3.T, ref *openapi3.SecuritySchemeRef) (*openapi2.SecurityScheme, error) func ToV3(doc2 *openapi2.T) (*openapi3.T, error) @@ -29,8 +29,8 @@ func ToV3Parameter(components *openapi3.Components, parameter *openapi2.Paramete func ToV3PathItem(doc2 *openapi2.T, components *openapi3.Components, pathItem *openapi2.PathItem, consumes []string) (*openapi3.PathItem, error) func ToV3Ref(ref string) string func ToV3Response(response *openapi2.Response, produces []string) (*openapi3.ResponseRef, error) -func ToV3SchemaRef(schema *openapi3.SchemaRef) *openapi3.SchemaRef -func ToV3Schemas(defs map[string]*openapi3.SchemaRef) map[string]*openapi3.SchemaRef +func ToV3SchemaRef(schema *openapi2.SchemaRef) *openapi3.SchemaRef +func ToV3Schemas(defs map[string]*openapi2.SchemaRef) map[string]*openapi3.SchemaRef func ToV3SecurityRequirements(requirements openapi2.SecurityRequirements) openapi3.SecurityRequirements func ToV3SecurityScheme(securityScheme *openapi2.SecurityScheme) (*openapi3.SecuritySchemeRef, error) func ToV3WithLoader(doc2 *openapi2.T, loader *openapi3.Loader, location *url.URL) (*openapi3.T, error) diff --git a/openapi2/helpers.go b/openapi2/helpers.go new file mode 100644 index 000000000..777a2f0bb --- /dev/null +++ b/openapi2/helpers.go @@ -0,0 +1,15 @@ +package openapi2 + +import ( + "net/url" +) + +// copyURI makes a copy of the pointer. +func copyURI(u *url.URL) *url.URL { + if u == nil { + return nil + } + + c := *u // shallow-copy + return &c +} diff --git a/openapi2/issues1010_test.go b/openapi2/issues1010_test.go new file mode 100644 index 000000000..2f84dc946 --- /dev/null +++ b/openapi2/issues1010_test.go @@ -0,0 +1,98 @@ +package openapi2 + +import ( + "encoding/json" + "testing" + + "github.com/stretchr/testify/require" +) + +func TestIssue1010(t *testing.T) { + v2 := []byte(` +{ + "basePath": "/v2", + "host": "test.example.com", + "info": { + "title": "MyAPI", + "version": "0.1", + "x-info": "info extension" + }, + "paths": { + "/foo": { + "get": { + "operationId": "getFoo", + "responses": { + "200": { + "description": "returns all information", + "schema": { + "$ref": "#/definitions/Pet" + } + }, + "default": { + "description": "OK" + } + }, + "summary": "get foo" + } + } + }, + "schemes": [ + "http" + ], + "swagger": "2.0", + "definitions": { + "Pet": { + "type": "object", + "required": ["petType"], + "properties": { + "petType": { + "type": "string" + }, + "name": { + "type": "string" + }, + "age": { + "type": "integer" + } + }, + "discriminator": "petType" + }, + "Dog": { + "allOf": [ + { + "$ref": "#/definitions/Pet" + }, + { + "type": "object", + "properties": { + "breed": { + "type": "string" + } + } + } + ] + }, + "Cat": { + "allOf": [ + { + "$ref": "#/definitions/Pet" + }, + { + "type": "object", + "properties": { + "color": { + "type": "string" + } + } + } + ] + } + } +} +`) + + var doc2 T + err := json.Unmarshal(v2, &doc2) + require.NoError(t, err) + require.Equal(t, "petType", doc2.Definitions["Pet"].Value.Discriminator) +} diff --git a/openapi2/openapi2.go b/openapi2/openapi2.go index 2d922c639..bd3375339 100644 --- a/openapi2/openapi2.go +++ b/openapi2/openapi2.go @@ -10,21 +10,21 @@ import ( type T struct { Extensions map[string]any `json:"-" yaml:"-"` - Swagger string `json:"swagger" yaml:"swagger"` // required - Info openapi3.Info `json:"info" yaml:"info"` // required - ExternalDocs *openapi3.ExternalDocs `json:"externalDocs,omitempty" yaml:"externalDocs,omitempty"` - Schemes []string `json:"schemes,omitempty" yaml:"schemes,omitempty"` - Consumes []string `json:"consumes,omitempty" yaml:"consumes,omitempty"` - Produces []string `json:"produces,omitempty" yaml:"produces,omitempty"` - Host string `json:"host,omitempty" yaml:"host,omitempty"` - BasePath string `json:"basePath,omitempty" yaml:"basePath,omitempty"` - Paths map[string]*PathItem `json:"paths,omitempty" yaml:"paths,omitempty"` - Definitions map[string]*openapi3.SchemaRef `json:"definitions,omitempty" yaml:"definitions,omitempty"` - Parameters map[string]*Parameter `json:"parameters,omitempty" yaml:"parameters,omitempty"` - Responses map[string]*Response `json:"responses,omitempty" yaml:"responses,omitempty"` - SecurityDefinitions map[string]*SecurityScheme `json:"securityDefinitions,omitempty" yaml:"securityDefinitions,omitempty"` - Security SecurityRequirements `json:"security,omitempty" yaml:"security,omitempty"` - Tags openapi3.Tags `json:"tags,omitempty" yaml:"tags,omitempty"` + Swagger string `json:"swagger" yaml:"swagger"` // required + Info openapi3.Info `json:"info" yaml:"info"` // required + ExternalDocs *openapi3.ExternalDocs `json:"externalDocs,omitempty" yaml:"externalDocs,omitempty"` + Schemes []string `json:"schemes,omitempty" yaml:"schemes,omitempty"` + Consumes []string `json:"consumes,omitempty" yaml:"consumes,omitempty"` + Produces []string `json:"produces,omitempty" yaml:"produces,omitempty"` + Host string `json:"host,omitempty" yaml:"host,omitempty"` + BasePath string `json:"basePath,omitempty" yaml:"basePath,omitempty"` + Paths map[string]*PathItem `json:"paths,omitempty" yaml:"paths,omitempty"` + Definitions map[string]*SchemaRef `json:"definitions,omitempty" yaml:"definitions,omitempty"` + Parameters map[string]*Parameter `json:"parameters,omitempty" yaml:"parameters,omitempty"` + Responses map[string]*Response `json:"responses,omitempty" yaml:"responses,omitempty"` + SecurityDefinitions map[string]*SecurityScheme `json:"securityDefinitions,omitempty" yaml:"securityDefinitions,omitempty"` + Security SecurityRequirements `json:"security,omitempty" yaml:"security,omitempty"` + Tags openapi3.Tags `json:"tags,omitempty" yaml:"tags,omitempty"` } // MarshalJSON returns the JSON encoding of T. diff --git a/openapi2/parameter.go b/openapi2/parameter.go index c701705bb..7f2bddb2c 100644 --- a/openapi2/parameter.go +++ b/openapi2/parameter.go @@ -28,29 +28,29 @@ type Parameter struct { Ref string `json:"$ref,omitempty" yaml:"$ref,omitempty"` - In string `json:"in,omitempty" yaml:"in,omitempty"` - Name string `json:"name,omitempty" yaml:"name,omitempty"` - Description string `json:"description,omitempty" yaml:"description,omitempty"` - CollectionFormat string `json:"collectionFormat,omitempty" yaml:"collectionFormat,omitempty"` - Type *openapi3.Types `json:"type,omitempty" yaml:"type,omitempty"` - Format string `json:"format,omitempty" yaml:"format,omitempty"` - Pattern string `json:"pattern,omitempty" yaml:"pattern,omitempty"` - AllowEmptyValue bool `json:"allowEmptyValue,omitempty" yaml:"allowEmptyValue,omitempty"` - Required bool `json:"required,omitempty" yaml:"required,omitempty"` - UniqueItems bool `json:"uniqueItems,omitempty" yaml:"uniqueItems,omitempty"` - ExclusiveMin bool `json:"exclusiveMinimum,omitempty" yaml:"exclusiveMinimum,omitempty"` - ExclusiveMax bool `json:"exclusiveMaximum,omitempty" yaml:"exclusiveMaximum,omitempty"` - Schema *openapi3.SchemaRef `json:"schema,omitempty" yaml:"schema,omitempty"` - Items *openapi3.SchemaRef `json:"items,omitempty" yaml:"items,omitempty"` - Enum []any `json:"enum,omitempty" yaml:"enum,omitempty"` - MultipleOf *float64 `json:"multipleOf,omitempty" yaml:"multipleOf,omitempty"` - Minimum *float64 `json:"minimum,omitempty" yaml:"minimum,omitempty"` - Maximum *float64 `json:"maximum,omitempty" yaml:"maximum,omitempty"` - MaxLength *uint64 `json:"maxLength,omitempty" yaml:"maxLength,omitempty"` - MaxItems *uint64 `json:"maxItems,omitempty" yaml:"maxItems,omitempty"` - MinLength uint64 `json:"minLength,omitempty" yaml:"minLength,omitempty"` - MinItems uint64 `json:"minItems,omitempty" yaml:"minItems,omitempty"` - Default any `json:"default,omitempty" yaml:"default,omitempty"` + In string `json:"in,omitempty" yaml:"in,omitempty"` + Name string `json:"name,omitempty" yaml:"name,omitempty"` + Description string `json:"description,omitempty" yaml:"description,omitempty"` + CollectionFormat string `json:"collectionFormat,omitempty" yaml:"collectionFormat,omitempty"` + Type *openapi3.Types `json:"type,omitempty" yaml:"type,omitempty"` + Format string `json:"format,omitempty" yaml:"format,omitempty"` + Pattern string `json:"pattern,omitempty" yaml:"pattern,omitempty"` + AllowEmptyValue bool `json:"allowEmptyValue,omitempty" yaml:"allowEmptyValue,omitempty"` + Required bool `json:"required,omitempty" yaml:"required,omitempty"` + UniqueItems bool `json:"uniqueItems,omitempty" yaml:"uniqueItems,omitempty"` + ExclusiveMin bool `json:"exclusiveMinimum,omitempty" yaml:"exclusiveMinimum,omitempty"` + ExclusiveMax bool `json:"exclusiveMaximum,omitempty" yaml:"exclusiveMaximum,omitempty"` + Schema *SchemaRef `json:"schema,omitempty" yaml:"schema,omitempty"` + Items *SchemaRef `json:"items,omitempty" yaml:"items,omitempty"` + Enum []any `json:"enum,omitempty" yaml:"enum,omitempty"` + MultipleOf *float64 `json:"multipleOf,omitempty" yaml:"multipleOf,omitempty"` + Minimum *float64 `json:"minimum,omitempty" yaml:"minimum,omitempty"` + Maximum *float64 `json:"maximum,omitempty" yaml:"maximum,omitempty"` + MaxLength *uint64 `json:"maxLength,omitempty" yaml:"maxLength,omitempty"` + MaxItems *uint64 `json:"maxItems,omitempty" yaml:"maxItems,omitempty"` + MinLength uint64 `json:"minLength,omitempty" yaml:"minLength,omitempty"` + MinItems uint64 `json:"minItems,omitempty" yaml:"minItems,omitempty"` + Default any `json:"default,omitempty" yaml:"default,omitempty"` } // MarshalJSON returns the JSON encoding of Parameter. diff --git a/openapi2/ref.go b/openapi2/ref.go new file mode 100644 index 000000000..e591d143e --- /dev/null +++ b/openapi2/ref.go @@ -0,0 +1,9 @@ +package openapi2 + +//go:generate go run refsgenerator.go + +// Ref is specified by OpenAPI/Swagger 2.0 standard. +// See https://github.com/OAI/OpenAPI-Specification/blob/main/versions/2.0.md#reference-object +type Ref struct { + Ref string `json:"$ref" yaml:"$ref"` +} diff --git a/openapi2/refs.go b/openapi2/refs.go new file mode 100644 index 000000000..5109e9883 --- /dev/null +++ b/openapi2/refs.go @@ -0,0 +1,104 @@ +package openapi2 + +import ( + "encoding/json" + "net/url" + "sort" + "strings" + + "github.com/go-openapi/jsonpointer" + "github.com/perimeterx/marshmallow" +) + +// SchemaRef represents either a Schema or a $ref to a Schema. +// When serializing and both fields are set, Ref is preferred over Value. +type SchemaRef struct { + // Extensions only captures fields starting with 'x-' as no other fields + // are allowed by the openapi spec. + Extensions map[string]any + + Ref string + Value *Schema + extra []string + + refPath *url.URL +} + +var _ jsonpointer.JSONPointable = (*SchemaRef)(nil) + +func (x *SchemaRef) isEmpty() bool { return x == nil || x.Ref == "" && x.Value == nil } + +// RefString returns the $ref value. +func (x *SchemaRef) RefString() string { return x.Ref } + +// CollectionName returns the JSON string used for a collection of these components. +func (x *SchemaRef) CollectionName() string { return "schemas" } + +// RefPath returns the path of the $ref relative to the root document. +func (x *SchemaRef) RefPath() *url.URL { return copyURI(x.refPath) } + +func (x *SchemaRef) setRefPath(u *url.URL) { + // Once the refPath is set don't override. References can be loaded + // multiple times not all with access to the correct path info. + if x.refPath != nil { + return + } + + x.refPath = copyURI(u) +} + +// MarshalYAML returns the YAML encoding of SchemaRef. +func (x SchemaRef) MarshalYAML() (any, error) { + if ref := x.Ref; ref != "" { + return &Ref{Ref: ref}, nil + } + return x.Value.MarshalYAML() +} + +// MarshalJSON returns the JSON encoding of SchemaRef. +func (x SchemaRef) MarshalJSON() ([]byte, error) { + y, err := x.MarshalYAML() + if err != nil { + return nil, err + } + return json.Marshal(y) +} + +// UnmarshalJSON sets SchemaRef to a copy of data. +func (x *SchemaRef) UnmarshalJSON(data []byte) error { + var refOnly Ref + if extra, err := marshmallow.Unmarshal(data, &refOnly, marshmallow.WithExcludeKnownFieldsFromMap(true)); err == nil && refOnly.Ref != "" { + x.Ref = refOnly.Ref + if len(extra) != 0 { + x.extra = make([]string, 0, len(extra)) + for key := range extra { + x.extra = append(x.extra, key) + } + sort.Strings(x.extra) + for k := range extra { + if !strings.HasPrefix(k, "x-") { + delete(extra, k) + } + } + if len(extra) != 0 { + x.Extensions = extra + } + } + return nil + } + return json.Unmarshal(data, &x.Value) +} + +// JSONLookup implements https://pkg.go.dev/github.com/go-openapi/jsonpointer#JSONPointable +func (x *SchemaRef) JSONLookup(token string) (any, error) { + if token == "$ref" { + return x.Ref, nil + } + + if v, ok := x.Extensions[token]; ok { + return v, nil + } + + ptr, _, err := jsonpointer.GetForToken(x.Value, token) + return ptr, err +} diff --git a/openapi2/response.go b/openapi2/response.go index 5306beb15..3a2983ce1 100644 --- a/openapi2/response.go +++ b/openapi2/response.go @@ -11,10 +11,10 @@ type Response struct { Ref string `json:"$ref,omitempty" yaml:"$ref,omitempty"` - Description string `json:"description,omitempty" yaml:"description,omitempty"` - Schema *openapi3.SchemaRef `json:"schema,omitempty" yaml:"schema,omitempty"` - Headers map[string]*Header `json:"headers,omitempty" yaml:"headers,omitempty"` - Examples map[string]any `json:"examples,omitempty" yaml:"examples,omitempty"` + Description string `json:"description,omitempty" yaml:"description,omitempty"` + Schema *SchemaRef `json:"schema,omitempty" yaml:"schema,omitempty"` + Headers map[string]*Header `json:"headers,omitempty" yaml:"headers,omitempty"` + Examples map[string]any `json:"examples,omitempty" yaml:"examples,omitempty"` } // MarshalJSON returns the JSON encoding of Response. diff --git a/openapi2/schema.go b/openapi2/schema.go new file mode 100644 index 000000000..64bed2751 --- /dev/null +++ b/openapi2/schema.go @@ -0,0 +1,269 @@ +package openapi2 + +import ( + "encoding/json" + "strings" + + "github.com/getkin/kin-openapi/openapi3" +) + +type ( + Schemas map[string]*SchemaRef + SchemaRefs []*SchemaRef +) + +// Schema is specified by OpenAPI/Swagger 2.0 standard. +// See https://swagger.io/specification/v2/#schema-object +type Schema struct { + Extensions map[string]any `json:"-" yaml:"-"` + + AllOf SchemaRefs `json:"allOf,omitempty" yaml:"allOf,omitempty"` + Not *SchemaRef `json:"not,omitempty" yaml:"not,omitempty"` + Type *openapi3.Types `json:"type,omitempty" yaml:"type,omitempty"` + Title string `json:"title,omitempty" yaml:"title,omitempty"` + Format string `json:"format,omitempty" yaml:"format,omitempty"` + Description string `json:"description,omitempty" yaml:"description,omitempty"` + Enum []any `json:"enum,omitempty" yaml:"enum,omitempty"` + Default any `json:"default,omitempty" yaml:"default,omitempty"` + Example any `json:"example,omitempty" yaml:"example,omitempty"` + ExternalDocs *openapi3.ExternalDocs `json:"externalDocs,omitempty" yaml:"externalDocs,omitempty"` + + // Array-related, here for struct compactness + UniqueItems bool `json:"uniqueItems,omitempty" yaml:"uniqueItems,omitempty"` + // Number-related, here for struct compactness + ExclusiveMin bool `json:"exclusiveMinimum,omitempty" yaml:"exclusiveMinimum,omitempty"` + ExclusiveMax bool `json:"exclusiveMaximum,omitempty" yaml:"exclusiveMaximum,omitempty"` + // Properties + ReadOnly bool `json:"readOnly,omitempty" yaml:"readOnly,omitempty"` + WriteOnly bool `json:"writeOnly,omitempty" yaml:"writeOnly,omitempty"` + AllowEmptyValue bool `json:"allowEmptyValue,omitempty" yaml:"allowEmptyValue,omitempty"` + Deprecated bool `json:"deprecated,omitempty" yaml:"deprecated,omitempty"` + XML *openapi3.XML `json:"xml,omitempty" yaml:"xml,omitempty"` + + // Number + Min *float64 `json:"minimum,omitempty" yaml:"minimum,omitempty"` + Max *float64 `json:"maximum,omitempty" yaml:"maximum,omitempty"` + MultipleOf *float64 `json:"multipleOf,omitempty" yaml:"multipleOf,omitempty"` + + // String + MinLength uint64 `json:"minLength,omitempty" yaml:"minLength,omitempty"` + MaxLength *uint64 `json:"maxLength,omitempty" yaml:"maxLength,omitempty"` + Pattern string `json:"pattern,omitempty" yaml:"pattern,omitempty"` + + // Array + MinItems uint64 `json:"minItems,omitempty" yaml:"minItems,omitempty"` + MaxItems *uint64 `json:"maxItems,omitempty" yaml:"maxItems,omitempty"` + Items *SchemaRef `json:"items,omitempty" yaml:"items,omitempty"` + + // Object + Required []string `json:"required,omitempty" yaml:"required,omitempty"` + Properties Schemas `json:"properties,omitempty" yaml:"properties,omitempty"` + MinProps uint64 `json:"minProperties,omitempty" yaml:"minProperties,omitempty"` + MaxProps *uint64 `json:"maxProperties,omitempty" yaml:"maxProperties,omitempty"` + AdditionalProperties openapi3.AdditionalProperties `json:"additionalProperties,omitempty" yaml:"additionalProperties,omitempty"` + Discriminator string `json:"discriminator,omitempty" yaml:"discriminator,omitempty"` +} + +// MarshalJSON returns the JSON encoding of Schema. +func (schema Schema) MarshalJSON() ([]byte, error) { + m, err := schema.MarshalYAML() + if err != nil { + return nil, err + } + + return json.Marshal(m) +} + +// MarshalYAML returns the YAML encoding of Schema. +func (schema Schema) MarshalYAML() (any, error) { + m := make(map[string]any, 36+len(schema.Extensions)) + for k, v := range schema.Extensions { + m[k] = v + } + + if x := schema.AllOf; len(x) != 0 { + m["allOf"] = x + } + if x := schema.Not; x != nil { + m["not"] = x + } + if x := schema.Type; x != nil { + m["type"] = x + } + if x := schema.Title; len(x) != 0 { + m["title"] = x + } + if x := schema.Format; len(x) != 0 { + m["format"] = x + } + if x := schema.Description; len(x) != 0 { + m["description"] = x + } + if x := schema.Enum; len(x) != 0 { + m["enum"] = x + } + if x := schema.Default; x != nil { + m["default"] = x + } + if x := schema.Example; x != nil { + m["example"] = x + } + if x := schema.ExternalDocs; x != nil { + m["externalDocs"] = x + } + + // Array-related + if x := schema.UniqueItems; x { + m["uniqueItems"] = x + } + // Number-related + if x := schema.ExclusiveMin; x { + m["exclusiveMinimum"] = x + } + if x := schema.ExclusiveMax; x { + m["exclusiveMaximum"] = x + } + if x := schema.ReadOnly; x { + m["readOnly"] = x + } + if x := schema.WriteOnly; x { + m["writeOnly"] = x + } + if x := schema.AllowEmptyValue; x { + m["allowEmptyValue"] = x + } + if x := schema.Deprecated; x { + m["deprecated"] = x + } + if x := schema.XML; x != nil { + m["xml"] = x + } + + // Number + if x := schema.Min; x != nil { + m["minimum"] = x + } + if x := schema.Max; x != nil { + m["maximum"] = x + } + if x := schema.MultipleOf; x != nil { + m["multipleOf"] = x + } + + // String + if x := schema.MinLength; x != 0 { + m["minLength"] = x + } + if x := schema.MaxLength; x != nil { + m["maxLength"] = x + } + if x := schema.Pattern; x != "" { + m["pattern"] = x + } + + // Array + if x := schema.MinItems; x != 0 { + m["minItems"] = x + } + if x := schema.MaxItems; x != nil { + m["maxItems"] = x + } + if x := schema.Items; x != nil { + m["items"] = x + } + + // Object + if x := schema.Required; len(x) != 0 { + m["required"] = x + } + if x := schema.Properties; len(x) != 0 { + m["properties"] = x + } + if x := schema.MinProps; x != 0 { + m["minProperties"] = x + } + if x := schema.MaxProps; x != nil { + m["maxProperties"] = x + } + if x := schema.AdditionalProperties; x.Has != nil || x.Schema != nil { + m["additionalProperties"] = &x + } + if x := schema.Discriminator; x != "" { + m["discriminator"] = x + } + + return m, nil +} + +// UnmarshalJSON sets Schema to a copy of data. +func (schema *Schema) UnmarshalJSON(data []byte) error { + type SchemaBis Schema + var x SchemaBis + if err := json.Unmarshal(data, &x); err != nil { + return unmarshalError(err) + } + _ = json.Unmarshal(data, &x.Extensions) + + delete(x.Extensions, "oneOf") + delete(x.Extensions, "anyOf") + delete(x.Extensions, "allOf") + delete(x.Extensions, "not") + delete(x.Extensions, "type") + delete(x.Extensions, "title") + delete(x.Extensions, "format") + delete(x.Extensions, "description") + delete(x.Extensions, "enum") + delete(x.Extensions, "default") + delete(x.Extensions, "example") + delete(x.Extensions, "externalDocs") + + // Array-related + delete(x.Extensions, "uniqueItems") + // Number-related + delete(x.Extensions, "exclusiveMinimum") + delete(x.Extensions, "exclusiveMaximum") + // Properties + delete(x.Extensions, "nullable") + delete(x.Extensions, "readOnly") + delete(x.Extensions, "writeOnly") + delete(x.Extensions, "allowEmptyValue") + delete(x.Extensions, "deprecated") + delete(x.Extensions, "xml") + + // Number + delete(x.Extensions, "minimum") + delete(x.Extensions, "maximum") + delete(x.Extensions, "multipleOf") + + // String + delete(x.Extensions, "minLength") + delete(x.Extensions, "maxLength") + delete(x.Extensions, "pattern") + + // Array + delete(x.Extensions, "minItems") + delete(x.Extensions, "maxItems") + delete(x.Extensions, "items") + + // Object + delete(x.Extensions, "required") + delete(x.Extensions, "properties") + delete(x.Extensions, "minProperties") + delete(x.Extensions, "maxProperties") + delete(x.Extensions, "additionalProperties") + delete(x.Extensions, "discriminator") + + if len(x.Extensions) == 0 { + x.Extensions = nil + } + + *schema = Schema(x) + + if schema.Format == "date" { + // This is a fix for: https://github.com/getkin/kin-openapi/issues/697 + if eg, ok := schema.Example.(string); ok { + schema.Example = strings.TrimSuffix(eg, "T00:00:00Z") + } + } + return nil +} diff --git a/openapi2conv/openapi2_conv.go b/openapi2conv/openapi2_conv.go index fa161bcad..ef0a5eddc 100644 --- a/openapi2conv/openapi2_conv.go +++ b/openapi2conv/openapi2_conv.go @@ -272,7 +272,6 @@ func ToV3Parameter(components *openapi3.Components, parameter *openapi2.Paramete MinLength: parameter.MinLength, MaxLength: parameter.MaxLength, Default: parameter.Default, - Items: parameter.Items, MinItems: parameter.MinItems, MaxItems: parameter.MaxItems, Pattern: parameter.Pattern, @@ -281,6 +280,10 @@ func ToV3Parameter(components *openapi3.Components, parameter *openapi2.Paramete MultipleOf: parameter.MultipleOf, Required: required, }} + if parameter.Items != nil { + schemaRef.Value.Items = ToV3SchemaRef(parameter.Items) + } + schemaRefMap := make(map[string]*openapi3.SchemaRef, 1) schemaRefMap[parameter.Name] = schemaRef return nil, nil, schemaRefMap, nil @@ -301,7 +304,7 @@ func ToV3Parameter(components *openapi3.Components, parameter *openapi2.Paramete Description: parameter.Description, Required: required, Extensions: stripNonExtensions(parameter.Extensions), - Schema: ToV3SchemaRef(&openapi3.SchemaRef{Value: &openapi3.Schema{ + Schema: ToV3SchemaRef(&openapi2.SchemaRef{Value: &openapi2.Schema{ Type: parameter.Type, Format: parameter.Format, Enum: parameter.Enum, @@ -349,7 +352,7 @@ func formDataBody(bodies map[string]*openapi3.SchemaRef, reqs map[string]bool, c sort.Strings(requireds) schema := &openapi3.Schema{ Type: &openapi3.Types{"object"}, - Properties: ToV3Schemas(bodies), + Properties: bodies, Required: requireds, } return &openapi3.RequestBodyRef{ @@ -456,7 +459,7 @@ func ToV3Headers(defs map[string]*openapi2.Header) openapi3.Headers { return headers } -func ToV3Schemas(defs map[string]*openapi3.SchemaRef) map[string]*openapi3.SchemaRef { +func ToV3Schemas(defs map[string]*openapi2.SchemaRef) map[string]*openapi3.SchemaRef { schemas := make(map[string]*openapi3.SchemaRef, len(defs)) for name, schema := range defs { schemas[name] = ToV3SchemaRef(schema) @@ -464,34 +467,84 @@ func ToV3Schemas(defs map[string]*openapi3.SchemaRef) map[string]*openapi3.Schem return schemas } -func ToV3SchemaRef(schema *openapi3.SchemaRef) *openapi3.SchemaRef { +func ToV3SchemaRef(schema *openapi2.SchemaRef) *openapi3.SchemaRef { + if schema == nil { + return &openapi3.SchemaRef{} + } + if ref := schema.Ref; ref != "" { return &openapi3.SchemaRef{Ref: ToV3Ref(ref)} } + if schema.Value == nil { - return schema + return &openapi3.SchemaRef{ + Extensions: schema.Extensions, + } + } + + v3Schema := &openapi3.Schema{ + Extensions: schema.Extensions, + Type: schema.Value.Type, + Title: schema.Value.Title, + Format: schema.Value.Format, + Description: schema.Value.Description, + Enum: schema.Value.Enum, + Default: schema.Value.Default, + Example: schema.Value.Example, + ExternalDocs: schema.Value.ExternalDocs, + UniqueItems: schema.Value.UniqueItems, + ExclusiveMin: schema.Value.ExclusiveMin, + ExclusiveMax: schema.Value.ExclusiveMax, + ReadOnly: schema.Value.ReadOnly, + WriteOnly: schema.Value.WriteOnly, + AllowEmptyValue: schema.Value.AllowEmptyValue, + Deprecated: schema.Value.Deprecated, + XML: schema.Value.XML, + Min: schema.Value.Min, + Max: schema.Value.Max, + MultipleOf: schema.Value.MultipleOf, + MinLength: schema.Value.MinLength, + MaxLength: schema.Value.MaxLength, + Pattern: schema.Value.Pattern, + MinItems: schema.Value.MinItems, + MaxItems: schema.Value.MaxItems, + Required: schema.Value.Required, + MinProps: schema.Value.MinProps, + MaxProps: schema.Value.MaxProps, + AllOf: make(openapi3.SchemaRefs, len(schema.Value.AllOf)), + Properties: make(openapi3.Schemas), + AdditionalProperties: schema.Value.AdditionalProperties, + } + + if schema.Value.Discriminator != "" { + v3Schema.Discriminator = &openapi3.Discriminator{ + PropertyName: schema.Value.Discriminator, + } } + if schema.Value.Items != nil { - schema.Value.Items = ToV3SchemaRef(schema.Value.Items) + v3Schema.Items = ToV3SchemaRef(schema.Value.Items) } if schema.Value.Type.Is("file") { - schema.Value.Format, schema.Value.Type = "binary", &openapi3.Types{"string"} + v3Schema.Format, v3Schema.Type = "binary", &openapi3.Types{"string"} } for k, v := range schema.Value.Properties { - schema.Value.Properties[k] = ToV3SchemaRef(v) - } - if v := schema.Value.AdditionalProperties.Schema; v != nil { - schema.Value.AdditionalProperties.Schema = ToV3SchemaRef(v) + v3Schema.Properties[k] = ToV3SchemaRef(v) } for i, v := range schema.Value.AllOf { - schema.Value.AllOf[i] = ToV3SchemaRef(v) + v3Schema.AllOf[i] = ToV3SchemaRef(v) } if val, ok := schema.Value.Extensions["x-nullable"]; ok { - schema.Value.Nullable, _ = val.(bool) - delete(schema.Value.Extensions, "x-nullable") + if nullable, valid := val.(bool); valid { + v3Schema.Nullable = nullable + delete(v3Schema.Extensions, "x-nullable") + } } - return schema + return &openapi3.SchemaRef{ + Extensions: schema.Extensions, + Value: v3Schema, + } } var ref2To3 = map[string]string{ @@ -742,8 +795,8 @@ func fromV3RequestBodies(name string, requestBodyRef *openapi3.RequestBodyRef, c return } -func FromV3Schemas(schemas map[string]*openapi3.SchemaRef, components *openapi3.Components) (map[string]*openapi3.SchemaRef, map[string]*openapi2.Parameter) { - v2Defs := make(map[string]*openapi3.SchemaRef) +func FromV3Schemas(schemas map[string]*openapi3.SchemaRef, components *openapi3.Components) (map[string]*openapi2.SchemaRef, map[string]*openapi2.Parameter) { + v2Defs := make(map[string]*openapi2.SchemaRef) v2Params := make(map[string]*openapi2.Parameter) for name, schema := range schemas { schemaConv, parameterConv := FromV3SchemaRef(schema, components) @@ -759,7 +812,7 @@ func FromV3Schemas(schemas map[string]*openapi3.SchemaRef, components *openapi3. return v2Defs, v2Params } -func FromV3SchemaRef(schema *openapi3.SchemaRef, components *openapi3.Components) (*openapi3.SchemaRef, *openapi2.Parameter) { +func FromV3SchemaRef(schema *openapi3.SchemaRef, components *openapi3.Components) (*openapi2.SchemaRef, *openapi2.Parameter) { if ref := schema.Ref; ref != "" { name := getParameterNameFromNewRef(ref) if val, ok := components.Schemas[name]; ok { @@ -769,10 +822,12 @@ func FromV3SchemaRef(schema *openapi3.SchemaRef, components *openapi3.Components } } - return &openapi3.SchemaRef{Ref: FromV3Ref(ref)}, nil + return &openapi2.SchemaRef{Ref: FromV3Ref(ref)}, nil } if schema.Value == nil { - return schema, nil + return &openapi2.SchemaRef{ + Extensions: schema.Extensions, + }, nil } if schema.Value != nil { @@ -789,19 +844,19 @@ func FromV3SchemaRef(schema *openapi3.SchemaRef, components *openapi3.Components } } return nil, &openapi2.Parameter{ - In: "formData", - Name: originalName, - Description: schema.Value.Description, - Type: paramType, - Enum: schema.Value.Enum, - Minimum: schema.Value.Min, - Maximum: schema.Value.Max, - ExclusiveMin: schema.Value.ExclusiveMin, - ExclusiveMax: schema.Value.ExclusiveMax, - MinLength: schema.Value.MinLength, - MaxLength: schema.Value.MaxLength, - Default: schema.Value.Default, - Items: schema.Value.Items, + In: "formData", + Name: originalName, + Description: schema.Value.Description, + Type: paramType, + Enum: schema.Value.Enum, + Minimum: schema.Value.Min, + Maximum: schema.Value.Max, + ExclusiveMin: schema.Value.ExclusiveMin, + ExclusiveMax: schema.Value.ExclusiveMax, + MinLength: schema.Value.MinLength, + MaxLength: schema.Value.MaxLength, + Default: schema.Value.Default, + // Items: schema.Value.Items, MinItems: schema.Value.MinItems, MaxItems: schema.Value.MaxItems, AllowEmptyValue: schema.Value.AllowEmptyValue, @@ -812,32 +867,72 @@ func FromV3SchemaRef(schema *openapi3.SchemaRef, components *openapi3.Components } } } + + v2Schema := &openapi2.Schema{ + Extensions: schema.Value.Extensions, + Type: schema.Value.Type, + Title: schema.Value.Title, + Format: schema.Value.Format, + Description: schema.Value.Description, + Enum: schema.Value.Enum, + Default: schema.Value.Default, + Example: schema.Value.Example, + ExternalDocs: schema.Value.ExternalDocs, + UniqueItems: schema.Value.UniqueItems, + ExclusiveMin: schema.Value.ExclusiveMin, + ExclusiveMax: schema.Value.ExclusiveMax, + ReadOnly: schema.Value.ReadOnly, + WriteOnly: schema.Value.WriteOnly, + AllowEmptyValue: schema.Value.AllowEmptyValue, + Deprecated: schema.Value.Deprecated, + XML: schema.Value.XML, + Min: schema.Value.Min, + Max: schema.Value.Max, + MultipleOf: schema.Value.MultipleOf, + MinLength: schema.Value.MinLength, + MaxLength: schema.Value.MaxLength, + Pattern: schema.Value.Pattern, + MinItems: schema.Value.MinItems, + MaxItems: schema.Value.MaxItems, + Required: schema.Value.Required, + MinProps: schema.Value.MinProps, + MaxProps: schema.Value.MaxProps, + Properties: make(openapi2.Schemas), + AllOf: make(openapi2.SchemaRefs, len(schema.Value.AllOf)), + AdditionalProperties: schema.Value.AdditionalProperties, + } + if v := schema.Value.Items; v != nil { - schema.Value.Items, _ = FromV3SchemaRef(v, components) + v2Schema.Items, _ = FromV3SchemaRef(v, components) } + keys := make([]string, 0, len(schema.Value.Properties)) for k := range schema.Value.Properties { keys = append(keys, k) } sort.Strings(keys) for _, key := range keys { - schema.Value.Properties[key], _ = FromV3SchemaRef(schema.Value.Properties[key], components) - } - if v := schema.Value.AdditionalProperties.Schema; v != nil { - schema.Value.AdditionalProperties.Schema, _ = FromV3SchemaRef(v, components) + property, _ := FromV3SchemaRef(schema.Value.Properties[key], components) + if property != nil { + v2Schema.Properties[key] = property + } } + for i, v := range schema.Value.AllOf { - schema.Value.AllOf[i], _ = FromV3SchemaRef(v, components) + v2Schema.AllOf[i], _ = FromV3SchemaRef(v, components) } if schema.Value.PermitsNull() { schema.Value.Nullable = false if schema.Value.Extensions == nil { - schema.Value.Extensions = make(map[string]any) + v2Schema.Extensions = make(map[string]any) } - schema.Value.Extensions["x-nullable"] = true + v2Schema.Extensions["x-nullable"] = true } - return schema, nil + return &openapi2.SchemaRef{ + Extensions: schema.Extensions, + Value: v2Schema, + }, nil } func FromV3SecurityRequirements(requirements openapi3.SecurityRequirements) openapi2.SecurityRequirements { @@ -906,6 +1001,11 @@ func FromV3RequestBodyFormData(mediaType *openapi3.MediaType) openapi2.Parameter break } } + + var v2Items *openapi2.SchemaRef + if val.Items != nil { + v2Items, _ = FromV3SchemaRef(val.Items, nil) + } parameter := &openapi2.Parameter{ Name: propName, Description: val.Description, @@ -918,7 +1018,7 @@ func FromV3RequestBodyFormData(mediaType *openapi3.MediaType) openapi2.Parameter MinLength: val.MinLength, MaxLength: val.MaxLength, Default: val.Default, - Items: val.Items, + Items: v2Items, MinItems: val.MinItems, MaxItems: val.MaxItems, Maximum: val.Max, @@ -1029,12 +1129,12 @@ func FromV3Parameter(ref *openapi3.ParameterRef, components *openapi3.Components Extensions: stripNonExtensions(parameter.Extensions), } if schemaRef := parameter.Schema; schemaRef != nil { - schemaRef, _ = FromV3SchemaRef(schemaRef, components) - if ref := schemaRef.Ref; ref != "" { - result.Schema = &openapi3.SchemaRef{Ref: FromV3Ref(ref)} + schemaRefV2, _ := FromV3SchemaRef(schemaRef, components) + if ref := schemaRefV2.Ref; ref != "" { + result.Schema = &openapi2.SchemaRef{Ref: FromV3Ref(ref)} return result, nil } - schema := schemaRef.Value + schema := schemaRefV2.Value result.Type = schema.Type result.Format = schema.Format result.Enum = schema.Enum