Skip to content

Commit 1eeb41c

Browse files
authored
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 2db2b39. * v2 schema with discriminator field set as string * update ref link and comment * run docs.sh
1 parent c606b55 commit 1eeb41c

11 files changed

+838
-136
lines changed

.github/docs/openapi2.txt

+149-42
Original file line numberDiff line numberDiff line change
@@ -47,29 +47,29 @@ type Parameter struct {
4747

4848
Ref string `json:"$ref,omitempty" yaml:"$ref,omitempty"`
4949

50-
In string `json:"in,omitempty" yaml:"in,omitempty"`
51-
Name string `json:"name,omitempty" yaml:"name,omitempty"`
52-
Description string `json:"description,omitempty" yaml:"description,omitempty"`
53-
CollectionFormat string `json:"collectionFormat,omitempty" yaml:"collectionFormat,omitempty"`
54-
Type *openapi3.Types `json:"type,omitempty" yaml:"type,omitempty"`
55-
Format string `json:"format,omitempty" yaml:"format,omitempty"`
56-
Pattern string `json:"pattern,omitempty" yaml:"pattern,omitempty"`
57-
AllowEmptyValue bool `json:"allowEmptyValue,omitempty" yaml:"allowEmptyValue,omitempty"`
58-
Required bool `json:"required,omitempty" yaml:"required,omitempty"`
59-
UniqueItems bool `json:"uniqueItems,omitempty" yaml:"uniqueItems,omitempty"`
60-
ExclusiveMin bool `json:"exclusiveMinimum,omitempty" yaml:"exclusiveMinimum,omitempty"`
61-
ExclusiveMax bool `json:"exclusiveMaximum,omitempty" yaml:"exclusiveMaximum,omitempty"`
62-
Schema *openapi3.SchemaRef `json:"schema,omitempty" yaml:"schema,omitempty"`
63-
Items *openapi3.SchemaRef `json:"items,omitempty" yaml:"items,omitempty"`
64-
Enum []any `json:"enum,omitempty" yaml:"enum,omitempty"`
65-
MultipleOf *float64 `json:"multipleOf,omitempty" yaml:"multipleOf,omitempty"`
66-
Minimum *float64 `json:"minimum,omitempty" yaml:"minimum,omitempty"`
67-
Maximum *float64 `json:"maximum,omitempty" yaml:"maximum,omitempty"`
68-
MaxLength *uint64 `json:"maxLength,omitempty" yaml:"maxLength,omitempty"`
69-
MaxItems *uint64 `json:"maxItems,omitempty" yaml:"maxItems,omitempty"`
70-
MinLength uint64 `json:"minLength,omitempty" yaml:"minLength,omitempty"`
71-
MinItems uint64 `json:"minItems,omitempty" yaml:"minItems,omitempty"`
72-
Default any `json:"default,omitempty" yaml:"default,omitempty"`
50+
In string `json:"in,omitempty" yaml:"in,omitempty"`
51+
Name string `json:"name,omitempty" yaml:"name,omitempty"`
52+
Description string `json:"description,omitempty" yaml:"description,omitempty"`
53+
CollectionFormat string `json:"collectionFormat,omitempty" yaml:"collectionFormat,omitempty"`
54+
Type *openapi3.Types `json:"type,omitempty" yaml:"type,omitempty"`
55+
Format string `json:"format,omitempty" yaml:"format,omitempty"`
56+
Pattern string `json:"pattern,omitempty" yaml:"pattern,omitempty"`
57+
AllowEmptyValue bool `json:"allowEmptyValue,omitempty" yaml:"allowEmptyValue,omitempty"`
58+
Required bool `json:"required,omitempty" yaml:"required,omitempty"`
59+
UniqueItems bool `json:"uniqueItems,omitempty" yaml:"uniqueItems,omitempty"`
60+
ExclusiveMin bool `json:"exclusiveMinimum,omitempty" yaml:"exclusiveMinimum,omitempty"`
61+
ExclusiveMax bool `json:"exclusiveMaximum,omitempty" yaml:"exclusiveMaximum,omitempty"`
62+
Schema *SchemaRef `json:"schema,omitempty" yaml:"schema,omitempty"`
63+
Items *SchemaRef `json:"items,omitempty" yaml:"items,omitempty"`
64+
Enum []any `json:"enum,omitempty" yaml:"enum,omitempty"`
65+
MultipleOf *float64 `json:"multipleOf,omitempty" yaml:"multipleOf,omitempty"`
66+
Minimum *float64 `json:"minimum,omitempty" yaml:"minimum,omitempty"`
67+
Maximum *float64 `json:"maximum,omitempty" yaml:"maximum,omitempty"`
68+
MaxLength *uint64 `json:"maxLength,omitempty" yaml:"maxLength,omitempty"`
69+
MaxItems *uint64 `json:"maxItems,omitempty" yaml:"maxItems,omitempty"`
70+
MinLength uint64 `json:"minLength,omitempty" yaml:"minLength,omitempty"`
71+
MinItems uint64 `json:"minItems,omitempty" yaml:"minItems,omitempty"`
72+
Default any `json:"default,omitempty" yaml:"default,omitempty"`
7373
}
7474

7575
func (parameter Parameter) MarshalJSON() ([]byte, error)
@@ -113,15 +113,21 @@ func (pathItem *PathItem) SetOperation(method string, operation *Operation)
113113
func (pathItem *PathItem) UnmarshalJSON(data []byte) error
114114
UnmarshalJSON sets PathItem to a copy of data.
115115

116+
type Ref struct {
117+
Ref string `json:"$ref" yaml:"$ref"`
118+
}
119+
Ref is specified by OpenAPI/Swagger 2.0 standard. See
120+
https://github.com/OAI/OpenAPI-Specification/blob/main/versions/2.0.md#reference-object
121+
116122
type Response struct {
117123
Extensions map[string]any `json:"-" yaml:"-"`
118124

119125
Ref string `json:"$ref,omitempty" yaml:"$ref,omitempty"`
120126

121-
Description string `json:"description,omitempty" yaml:"description,omitempty"`
122-
Schema *openapi3.SchemaRef `json:"schema,omitempty" yaml:"schema,omitempty"`
123-
Headers map[string]*Header `json:"headers,omitempty" yaml:"headers,omitempty"`
124-
Examples map[string]any `json:"examples,omitempty" yaml:"examples,omitempty"`
127+
Description string `json:"description,omitempty" yaml:"description,omitempty"`
128+
Schema *SchemaRef `json:"schema,omitempty" yaml:"schema,omitempty"`
129+
Headers map[string]*Header `json:"headers,omitempty" yaml:"headers,omitempty"`
130+
Examples map[string]any `json:"examples,omitempty" yaml:"examples,omitempty"`
125131
}
126132

127133
func (response Response) MarshalJSON() ([]byte, error)
@@ -130,6 +136,107 @@ func (response Response) MarshalJSON() ([]byte, error)
130136
func (response *Response) UnmarshalJSON(data []byte) error
131137
UnmarshalJSON sets Response to a copy of data.
132138

139+
type Schema struct {
140+
Extensions map[string]any `json:"-" yaml:"-"`
141+
142+
AllOf SchemaRefs `json:"allOf,omitempty" yaml:"allOf,omitempty"`
143+
Not *SchemaRef `json:"not,omitempty" yaml:"not,omitempty"`
144+
Type *openapi3.Types `json:"type,omitempty" yaml:"type,omitempty"`
145+
Title string `json:"title,omitempty" yaml:"title,omitempty"`
146+
Format string `json:"format,omitempty" yaml:"format,omitempty"`
147+
Description string `json:"description,omitempty" yaml:"description,omitempty"`
148+
Enum []any `json:"enum,omitempty" yaml:"enum,omitempty"`
149+
Default any `json:"default,omitempty" yaml:"default,omitempty"`
150+
Example any `json:"example,omitempty" yaml:"example,omitempty"`
151+
ExternalDocs *openapi3.ExternalDocs `json:"externalDocs,omitempty" yaml:"externalDocs,omitempty"`
152+
153+
// Array-related, here for struct compactness
154+
UniqueItems bool `json:"uniqueItems,omitempty" yaml:"uniqueItems,omitempty"`
155+
// Number-related, here for struct compactness
156+
ExclusiveMin bool `json:"exclusiveMinimum,omitempty" yaml:"exclusiveMinimum,omitempty"`
157+
ExclusiveMax bool `json:"exclusiveMaximum,omitempty" yaml:"exclusiveMaximum,omitempty"`
158+
// Properties
159+
ReadOnly bool `json:"readOnly,omitempty" yaml:"readOnly,omitempty"`
160+
WriteOnly bool `json:"writeOnly,omitempty" yaml:"writeOnly,omitempty"`
161+
AllowEmptyValue bool `json:"allowEmptyValue,omitempty" yaml:"allowEmptyValue,omitempty"`
162+
Deprecated bool `json:"deprecated,omitempty" yaml:"deprecated,omitempty"`
163+
XML *openapi3.XML `json:"xml,omitempty" yaml:"xml,omitempty"`
164+
165+
// Number
166+
Min *float64 `json:"minimum,omitempty" yaml:"minimum,omitempty"`
167+
Max *float64 `json:"maximum,omitempty" yaml:"maximum,omitempty"`
168+
MultipleOf *float64 `json:"multipleOf,omitempty" yaml:"multipleOf,omitempty"`
169+
170+
// String
171+
MinLength uint64 `json:"minLength,omitempty" yaml:"minLength,omitempty"`
172+
MaxLength *uint64 `json:"maxLength,omitempty" yaml:"maxLength,omitempty"`
173+
Pattern string `json:"pattern,omitempty" yaml:"pattern,omitempty"`
174+
175+
// Array
176+
MinItems uint64 `json:"minItems,omitempty" yaml:"minItems,omitempty"`
177+
MaxItems *uint64 `json:"maxItems,omitempty" yaml:"maxItems,omitempty"`
178+
Items *SchemaRef `json:"items,omitempty" yaml:"items,omitempty"`
179+
180+
// Object
181+
Required []string `json:"required,omitempty" yaml:"required,omitempty"`
182+
Properties Schemas `json:"properties,omitempty" yaml:"properties,omitempty"`
183+
MinProps uint64 `json:"minProperties,omitempty" yaml:"minProperties,omitempty"`
184+
MaxProps *uint64 `json:"maxProperties,omitempty" yaml:"maxProperties,omitempty"`
185+
AdditionalProperties openapi3.AdditionalProperties `json:"additionalProperties,omitempty" yaml:"additionalProperties,omitempty"`
186+
Discriminator string `json:"discriminator,omitempty" yaml:"discriminator,omitempty"`
187+
}
188+
Schema is specified by OpenAPI/Swagger 2.0 standard. See
189+
https://swagger.io/specification/v2/#schema-object
190+
191+
func (schema Schema) MarshalJSON() ([]byte, error)
192+
MarshalJSON returns the JSON encoding of Schema.
193+
194+
func (schema Schema) MarshalYAML() (any, error)
195+
MarshalYAML returns the YAML encoding of Schema.
196+
197+
func (schema *Schema) UnmarshalJSON(data []byte) error
198+
UnmarshalJSON sets Schema to a copy of data.
199+
200+
type SchemaRef struct {
201+
// Extensions only captures fields starting with 'x-' as no other fields
202+
// are allowed by the openapi spec.
203+
Extensions map[string]any
204+
205+
Ref string
206+
Value *Schema
207+
208+
// Has unexported fields.
209+
}
210+
SchemaRef represents either a Schema or a $ref to a Schema. When serializing
211+
and both fields are set, Ref is preferred over Value.
212+
213+
func (x *SchemaRef) CollectionName() string
214+
CollectionName returns the JSON string used for a collection of these
215+
components.
216+
217+
func (x *SchemaRef) JSONLookup(token string) (any, error)
218+
JSONLookup implements
219+
https://pkg.go.dev/github.com/go-openapi/jsonpointer#JSONPointable
220+
221+
func (x SchemaRef) MarshalJSON() ([]byte, error)
222+
MarshalJSON returns the JSON encoding of SchemaRef.
223+
224+
func (x SchemaRef) MarshalYAML() (any, error)
225+
MarshalYAML returns the YAML encoding of SchemaRef.
226+
227+
func (x *SchemaRef) RefPath() *url.URL
228+
RefPath returns the path of the $ref relative to the root document.
229+
230+
func (x *SchemaRef) RefString() string
231+
RefString returns the $ref value.
232+
233+
func (x *SchemaRef) UnmarshalJSON(data []byte) error
234+
UnmarshalJSON sets SchemaRef to a copy of data.
235+
236+
type SchemaRefs []*SchemaRef
237+
238+
type Schemas map[string]*SchemaRef
239+
133240
type SecurityRequirements []map[string][]string
134241

135242
type SecurityScheme struct {
@@ -157,21 +264,21 @@ func (securityScheme *SecurityScheme) UnmarshalJSON(data []byte) error
157264
type T struct {
158265
Extensions map[string]any `json:"-" yaml:"-"`
159266

160-
Swagger string `json:"swagger" yaml:"swagger"` // required
161-
Info openapi3.Info `json:"info" yaml:"info"` // required
162-
ExternalDocs *openapi3.ExternalDocs `json:"externalDocs,omitempty" yaml:"externalDocs,omitempty"`
163-
Schemes []string `json:"schemes,omitempty" yaml:"schemes,omitempty"`
164-
Consumes []string `json:"consumes,omitempty" yaml:"consumes,omitempty"`
165-
Produces []string `json:"produces,omitempty" yaml:"produces,omitempty"`
166-
Host string `json:"host,omitempty" yaml:"host,omitempty"`
167-
BasePath string `json:"basePath,omitempty" yaml:"basePath,omitempty"`
168-
Paths map[string]*PathItem `json:"paths,omitempty" yaml:"paths,omitempty"`
169-
Definitions map[string]*openapi3.SchemaRef `json:"definitions,omitempty" yaml:"definitions,omitempty"`
170-
Parameters map[string]*Parameter `json:"parameters,omitempty" yaml:"parameters,omitempty"`
171-
Responses map[string]*Response `json:"responses,omitempty" yaml:"responses,omitempty"`
172-
SecurityDefinitions map[string]*SecurityScheme `json:"securityDefinitions,omitempty" yaml:"securityDefinitions,omitempty"`
173-
Security SecurityRequirements `json:"security,omitempty" yaml:"security,omitempty"`
174-
Tags openapi3.Tags `json:"tags,omitempty" yaml:"tags,omitempty"`
267+
Swagger string `json:"swagger" yaml:"swagger"` // required
268+
Info openapi3.Info `json:"info" yaml:"info"` // required
269+
ExternalDocs *openapi3.ExternalDocs `json:"externalDocs,omitempty" yaml:"externalDocs,omitempty"`
270+
Schemes []string `json:"schemes,omitempty" yaml:"schemes,omitempty"`
271+
Consumes []string `json:"consumes,omitempty" yaml:"consumes,omitempty"`
272+
Produces []string `json:"produces,omitempty" yaml:"produces,omitempty"`
273+
Host string `json:"host,omitempty" yaml:"host,omitempty"`
274+
BasePath string `json:"basePath,omitempty" yaml:"basePath,omitempty"`
275+
Paths map[string]*PathItem `json:"paths,omitempty" yaml:"paths,omitempty"`
276+
Definitions map[string]*SchemaRef `json:"definitions,omitempty" yaml:"definitions,omitempty"`
277+
Parameters map[string]*Parameter `json:"parameters,omitempty" yaml:"parameters,omitempty"`
278+
Responses map[string]*Response `json:"responses,omitempty" yaml:"responses,omitempty"`
279+
SecurityDefinitions map[string]*SecurityScheme `json:"securityDefinitions,omitempty" yaml:"securityDefinitions,omitempty"`
280+
Security SecurityRequirements `json:"security,omitempty" yaml:"security,omitempty"`
281+
Tags openapi3.Tags `json:"tags,omitempty" yaml:"tags,omitempty"`
175282
}
176283
T is the root of an OpenAPI v2 document
177284

.github/docs/openapi2conv.txt

+4-4
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,8 @@ func FromV3RequestBody(name string, requestBodyRef *openapi3.RequestBodyRef, med
1616
func FromV3RequestBodyFormData(mediaType *openapi3.MediaType) openapi2.Parameters
1717
func FromV3Response(ref *openapi3.ResponseRef, components *openapi3.Components) (*openapi2.Response, error)
1818
func FromV3Responses(responses map[string]*openapi3.ResponseRef, components *openapi3.Components) (map[string]*openapi2.Response, error)
19-
func FromV3SchemaRef(schema *openapi3.SchemaRef, components *openapi3.Components) (*openapi3.SchemaRef, *openapi2.Parameter)
20-
func FromV3Schemas(schemas map[string]*openapi3.SchemaRef, components *openapi3.Components) (map[string]*openapi3.SchemaRef, map[string]*openapi2.Parameter)
19+
func FromV3SchemaRef(schema *openapi3.SchemaRef, components *openapi3.Components) (*openapi2.SchemaRef, *openapi2.Parameter)
20+
func FromV3Schemas(schemas map[string]*openapi3.SchemaRef, components *openapi3.Components) (map[string]*openapi2.SchemaRef, map[string]*openapi2.Parameter)
2121
func FromV3SecurityRequirements(requirements openapi3.SecurityRequirements) openapi2.SecurityRequirements
2222
func FromV3SecurityScheme(doc3 *openapi3.T, ref *openapi3.SecuritySchemeRef) (*openapi2.SecurityScheme, error)
2323
func ToV3(doc2 *openapi2.T) (*openapi3.T, error)
@@ -29,8 +29,8 @@ func ToV3Parameter(components *openapi3.Components, parameter *openapi2.Paramete
2929
func ToV3PathItem(doc2 *openapi2.T, components *openapi3.Components, pathItem *openapi2.PathItem, consumes []string) (*openapi3.PathItem, error)
3030
func ToV3Ref(ref string) string
3131
func ToV3Response(response *openapi2.Response, produces []string) (*openapi3.ResponseRef, error)
32-
func ToV3SchemaRef(schema *openapi3.SchemaRef) *openapi3.SchemaRef
33-
func ToV3Schemas(defs map[string]*openapi3.SchemaRef) map[string]*openapi3.SchemaRef
32+
func ToV3SchemaRef(schema *openapi2.SchemaRef) *openapi3.SchemaRef
33+
func ToV3Schemas(defs map[string]*openapi2.SchemaRef) map[string]*openapi3.SchemaRef
3434
func ToV3SecurityRequirements(requirements openapi2.SecurityRequirements) openapi3.SecurityRequirements
3535
func ToV3SecurityScheme(securityScheme *openapi2.SecurityScheme) (*openapi3.SecuritySchemeRef, error)
3636
func ToV3WithLoader(doc2 *openapi2.T, loader *openapi3.Loader, location *url.URL) (*openapi3.T, error)

openapi2/helpers.go

+15
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
package openapi2
2+
3+
import (
4+
"net/url"
5+
)
6+
7+
// copyURI makes a copy of the pointer.
8+
func copyURI(u *url.URL) *url.URL {
9+
if u == nil {
10+
return nil
11+
}
12+
13+
c := *u // shallow-copy
14+
return &c
15+
}

openapi2/issues1010_test.go

+98
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
package openapi2
2+
3+
import (
4+
"encoding/json"
5+
"testing"
6+
7+
"github.com/stretchr/testify/require"
8+
)
9+
10+
func TestIssue1010(t *testing.T) {
11+
v2 := []byte(`
12+
{
13+
"basePath": "/v2",
14+
"host": "test.example.com",
15+
"info": {
16+
"title": "MyAPI",
17+
"version": "0.1",
18+
"x-info": "info extension"
19+
},
20+
"paths": {
21+
"/foo": {
22+
"get": {
23+
"operationId": "getFoo",
24+
"responses": {
25+
"200": {
26+
"description": "returns all information",
27+
"schema": {
28+
"$ref": "#/definitions/Pet"
29+
}
30+
},
31+
"default": {
32+
"description": "OK"
33+
}
34+
},
35+
"summary": "get foo"
36+
}
37+
}
38+
},
39+
"schemes": [
40+
"http"
41+
],
42+
"swagger": "2.0",
43+
"definitions": {
44+
"Pet": {
45+
"type": "object",
46+
"required": ["petType"],
47+
"properties": {
48+
"petType": {
49+
"type": "string"
50+
},
51+
"name": {
52+
"type": "string"
53+
},
54+
"age": {
55+
"type": "integer"
56+
}
57+
},
58+
"discriminator": "petType"
59+
},
60+
"Dog": {
61+
"allOf": [
62+
{
63+
"$ref": "#/definitions/Pet"
64+
},
65+
{
66+
"type": "object",
67+
"properties": {
68+
"breed": {
69+
"type": "string"
70+
}
71+
}
72+
}
73+
]
74+
},
75+
"Cat": {
76+
"allOf": [
77+
{
78+
"$ref": "#/definitions/Pet"
79+
},
80+
{
81+
"type": "object",
82+
"properties": {
83+
"color": {
84+
"type": "string"
85+
}
86+
}
87+
}
88+
]
89+
}
90+
}
91+
}
92+
`)
93+
94+
var doc2 T
95+
err := json.Unmarshal(v2, &doc2)
96+
require.NoError(t, err)
97+
require.Equal(t, "petType", doc2.Definitions["Pet"].Value.Discriminator)
98+
}

0 commit comments

Comments
 (0)