Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

openapi2: fix un/marshalling discriminator field #1011

Merged
merged 7 commits into from
Oct 1, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
191 changes: 149 additions & 42 deletions .github/docs/openapi2.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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)
Expand All @@ -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 {
Expand Down Expand Up @@ -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

Expand Down
8 changes: 4 additions & 4 deletions .github/docs/openapi2conv.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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)
15 changes: 15 additions & 0 deletions openapi2/helpers.go
Original file line number Diff line number Diff line change
@@ -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
}
98 changes: 98 additions & 0 deletions openapi2/issues1010_test.go
Original file line number Diff line number Diff line change
@@ -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)
}
Loading
Loading