diff --git a/openapi2/marsh.go b/openapi2/marsh.go new file mode 100644 index 000000000..6a9df466c --- /dev/null +++ b/openapi2/marsh.go @@ -0,0 +1,26 @@ +package openapi2 + +import ( + "encoding/json" + "fmt" + "strings" + + "github.com/invopop/yaml" +) + +func unmarshalError(jsonUnmarshalErr error) error { + if before, after, found := strings.Cut(jsonUnmarshalErr.Error(), "Bis."); found && before != "" && after != "" { + before = strings.ReplaceAll(before, " Go struct ", " ") + return fmt.Errorf("%s.%s", before, after) + } + return jsonUnmarshalErr +} + +func unmarshal(data []byte, v interface{}) error { + // See https://github.com/getkin/kin-openapi/issues/680 + if err := json.Unmarshal(data, v); err != nil { + // UnmarshalStrict(data, v) TODO: investigate how ymlv3 handles duplicate map keys + return yaml.Unmarshal(data, v) + } + return nil +} diff --git a/openapi2/marsh_test.go b/openapi2/marsh_test.go new file mode 100644 index 000000000..9c1500a15 --- /dev/null +++ b/openapi2/marsh_test.go @@ -0,0 +1,61 @@ +package openapi2 + +import ( + "testing" + + "github.com/stretchr/testify/require" +) + +func TestUnmarshalError(t *testing.T) { + { + v2 := []byte(` +openapi: '2.0' +info: + version: '1.10' + title: title +paths: + "/ping": + post: + consumes: + - multipart/form-data + parameters: + name: file # <-- Missing dash + in: formData + description: file + required: true + type: file + responses: + '200': + description: OK +`[1:]) + + var doc T + err := unmarshal(v2, &doc) + require.ErrorContains(t, err, `json: cannot unmarshal object into field Operation.parameters of type openapi2.Parameters`) + } + + v2 := []byte(` +openapi: '2.0' +info: + version: '1.10' + title: title +paths: + "/ping": + post: + consumes: + - multipart/form-data + parameters: + - name: file # <-- + in: formData + description: file + required: true + type: file + responses: + '200': + description: OK +`[1:]) + + var doc T + err := unmarshal(v2, &doc) + require.NoError(t, err) +} diff --git a/openapi2/openapi2.go b/openapi2/openapi2.go index 69f909414..03a72cb73 100644 --- a/openapi2/openapi2.go +++ b/openapi2/openapi2.go @@ -82,7 +82,7 @@ func (doc *T) UnmarshalJSON(data []byte) error { type TBis T var x TBis if err := json.Unmarshal(data, &x); err != nil { - return err + return unmarshalError(err) } _ = json.Unmarshal(data, &x.Extensions) delete(x.Extensions, "swagger") diff --git a/openapi2/operation.go b/openapi2/operation.go index 0bc3db6c4..e50f21eb0 100644 --- a/openapi2/operation.go +++ b/openapi2/operation.go @@ -71,7 +71,7 @@ func (operation *Operation) UnmarshalJSON(data []byte) error { type OperationBis Operation var x OperationBis if err := json.Unmarshal(data, &x); err != nil { - return err + return unmarshalError(err) } _ = json.Unmarshal(data, &x.Extensions) delete(x.Extensions, "summary") diff --git a/openapi2/parameter.go b/openapi2/parameter.go index 36bc299a8..025509871 100644 --- a/openapi2/parameter.go +++ b/openapi2/parameter.go @@ -142,7 +142,7 @@ func (parameter *Parameter) UnmarshalJSON(data []byte) error { type ParameterBis Parameter var x ParameterBis if err := json.Unmarshal(data, &x); err != nil { - return err + return unmarshalError(err) } _ = json.Unmarshal(data, &x.Extensions) delete(x.Extensions, "$ref") diff --git a/openapi2/path_item.go b/openapi2/path_item.go index 15be87262..a365f0c50 100644 --- a/openapi2/path_item.go +++ b/openapi2/path_item.go @@ -65,7 +65,7 @@ func (pathItem *PathItem) UnmarshalJSON(data []byte) error { type PathItemBis PathItem var x PathItemBis if err := json.Unmarshal(data, &x); err != nil { - return err + return unmarshalError(err) } _ = json.Unmarshal(data, &x.Extensions) delete(x.Extensions, "$ref") diff --git a/openapi2/response.go b/openapi2/response.go index e6b22e1ee..b4aa7fe85 100644 --- a/openapi2/response.go +++ b/openapi2/response.go @@ -47,7 +47,7 @@ func (response *Response) UnmarshalJSON(data []byte) error { type ResponseBis Response var x ResponseBis if err := json.Unmarshal(data, &x); err != nil { - return err + return unmarshalError(err) } _ = json.Unmarshal(data, &x.Extensions) delete(x.Extensions, "$ref") diff --git a/openapi2/security_scheme.go b/openapi2/security_scheme.go index 6ce9beed3..d81d8e7c4 100644 --- a/openapi2/security_scheme.go +++ b/openapi2/security_scheme.go @@ -69,7 +69,7 @@ func (securityScheme *SecurityScheme) UnmarshalJSON(data []byte) error { type SecuritySchemeBis SecurityScheme var x SecuritySchemeBis if err := json.Unmarshal(data, &x); err != nil { - return err + return unmarshalError(err) } _ = json.Unmarshal(data, &x.Extensions) delete(x.Extensions, "$ref") diff --git a/openapi3/components.go b/openapi3/components.go index 0678ca7de..a645b77a0 100644 --- a/openapi3/components.go +++ b/openapi3/components.go @@ -68,7 +68,7 @@ func (components *Components) UnmarshalJSON(data []byte) error { type ComponentsBis Components var x ComponentsBis if err := json.Unmarshal(data, &x); err != nil { - return err + return unmarshalError(err) } _ = json.Unmarshal(data, &x.Extensions) delete(x.Extensions, "schemas") diff --git a/openapi3/contact.go b/openapi3/contact.go index a025803d0..e60d2818a 100644 --- a/openapi3/contact.go +++ b/openapi3/contact.go @@ -38,7 +38,7 @@ func (contact *Contact) UnmarshalJSON(data []byte) error { type ContactBis Contact var x ContactBis if err := json.Unmarshal(data, &x); err != nil { - return err + return unmarshalError(err) } _ = json.Unmarshal(data, &x.Extensions) delete(x.Extensions, "name") diff --git a/openapi3/discriminator.go b/openapi3/discriminator.go index d45e28b44..abb480741 100644 --- a/openapi3/discriminator.go +++ b/openapi3/discriminator.go @@ -32,7 +32,7 @@ func (discriminator *Discriminator) UnmarshalJSON(data []byte) error { type DiscriminatorBis Discriminator var x DiscriminatorBis if err := json.Unmarshal(data, &x); err != nil { - return err + return unmarshalError(err) } _ = json.Unmarshal(data, &x.Extensions) delete(x.Extensions, "propertyName") diff --git a/openapi3/encoding.go b/openapi3/encoding.go index 96c08ea70..8e810279c 100644 --- a/openapi3/encoding.go +++ b/openapi3/encoding.go @@ -68,7 +68,7 @@ func (encoding *Encoding) UnmarshalJSON(data []byte) error { type EncodingBis Encoding var x EncodingBis if err := json.Unmarshal(data, &x); err != nil { - return err + return unmarshalError(err) } _ = json.Unmarshal(data, &x.Extensions) delete(x.Extensions, "contentType") diff --git a/openapi3/example.go b/openapi3/example.go index dc9c46a92..67039a6c3 100644 --- a/openapi3/example.go +++ b/openapi3/example.go @@ -67,7 +67,7 @@ func (example *Example) UnmarshalJSON(data []byte) error { type ExampleBis Example var x ExampleBis if err := json.Unmarshal(data, &x); err != nil { - return err + return unmarshalError(err) } _ = json.Unmarshal(data, &x.Extensions) delete(x.Extensions, "summary") diff --git a/openapi3/external_docs.go b/openapi3/external_docs.go index 318892d9c..7190be4b0 100644 --- a/openapi3/external_docs.go +++ b/openapi3/external_docs.go @@ -37,7 +37,7 @@ func (e *ExternalDocs) UnmarshalJSON(data []byte) error { type ExternalDocsBis ExternalDocs var x ExternalDocsBis if err := json.Unmarshal(data, &x); err != nil { - return err + return unmarshalError(err) } _ = json.Unmarshal(data, &x.Extensions) delete(x.Extensions, "description") diff --git a/openapi3/info.go b/openapi3/info.go index bb3003b8e..ffcd3b0e3 100644 --- a/openapi3/info.go +++ b/openapi3/info.go @@ -47,7 +47,7 @@ func (info *Info) UnmarshalJSON(data []byte) error { type InfoBis Info var x InfoBis if err := json.Unmarshal(data, &x); err != nil { - return err + return unmarshalError(err) } _ = json.Unmarshal(data, &x.Extensions) delete(x.Extensions, "title") diff --git a/openapi3/license.go b/openapi3/license.go index 32f75872d..3d2d2f06d 100644 --- a/openapi3/license.go +++ b/openapi3/license.go @@ -33,7 +33,7 @@ func (license *License) UnmarshalJSON(data []byte) error { type LicenseBis License var x LicenseBis if err := json.Unmarshal(data, &x); err != nil { - return err + return unmarshalError(err) } _ = json.Unmarshal(data, &x.Extensions) delete(x.Extensions, "name") diff --git a/openapi3/link.go b/openapi3/link.go index c77e23805..394afa8a9 100644 --- a/openapi3/link.go +++ b/openapi3/link.go @@ -73,7 +73,7 @@ func (link *Link) UnmarshalJSON(data []byte) error { type LinkBis Link var x LinkBis if err := json.Unmarshal(data, &x); err != nil { - return err + return unmarshalError(err) } _ = json.Unmarshal(data, &x.Extensions) delete(x.Extensions, "operationRef") diff --git a/openapi3/loader.go b/openapi3/loader.go index ffa17e8b6..7d059541a 100644 --- a/openapi3/loader.go +++ b/openapi3/loader.go @@ -14,8 +14,6 @@ import ( "sort" "strconv" "strings" - - "github.com/invopop/yaml" ) var CircularReferenceError = "kin-openapi bug found: circular schema reference not handled" @@ -178,15 +176,6 @@ func (loader *Loader) loadFromDataWithPathInternal(data []byte, location *url.UR return doc, nil } -func unmarshal(data []byte, v interface{}) error { - // See https://github.com/getkin/kin-openapi/issues/680 - if err := json.Unmarshal(data, v); err != nil { - // UnmarshalStrict(data, v) TODO: investigate how ymlv3 handles duplicate map keys - return yaml.Unmarshal(data, v) - } - return nil -} - // ResolveRefsIn expands references if for instance spec was just unmarshaled func (loader *Loader) ResolveRefsIn(doc *T, location *url.URL) (err error) { if loader.Context == nil { diff --git a/openapi3/marsh.go b/openapi3/marsh.go new file mode 100644 index 000000000..5ef69ae8b --- /dev/null +++ b/openapi3/marsh.go @@ -0,0 +1,26 @@ +package openapi3 + +import ( + "encoding/json" + "fmt" + "strings" + + "github.com/invopop/yaml" +) + +func unmarshalError(jsonUnmarshalErr error) error { + if before, after, found := strings.Cut(jsonUnmarshalErr.Error(), "Bis."); found && before != "" && after != "" { + before = strings.ReplaceAll(before, " Go struct ", " ") + return fmt.Errorf("%s.%s", before, after) + } + return jsonUnmarshalErr +} + +func unmarshal(data []byte, v interface{}) error { + // See https://github.com/getkin/kin-openapi/issues/680 + if err := json.Unmarshal(data, v); err != nil { + // UnmarshalStrict(data, v) TODO: investigate how ymlv3 handles duplicate map keys + return yaml.Unmarshal(data, v) + } + return nil +} diff --git a/openapi3/marsh_test.go b/openapi3/marsh_test.go new file mode 100644 index 000000000..4ddc4fa93 --- /dev/null +++ b/openapi3/marsh_test.go @@ -0,0 +1,78 @@ +package openapi3 + +import ( + "testing" + + "github.com/stretchr/testify/require" +) + +func TestUnmarshalError(t *testing.T) { + { + spec := []byte(` +openapi: 3.0.1 +info: + version: v1 + title: Products api +components: + schemas: + someSchema: + type: object + schemaArray: + type: array + minItems: 1 + items: + $ref: '#/components/schemas/someSchema' +paths: + /categories: + get: + responses: + '200': + description: '' + content: + application/json: + schema: + allOf: + $ref: '#/components/schemas/schemaArray' # <- Should have been a list +`[1:]) + + sl := NewLoader() + + _, err := sl.LoadFromData(spec) + require.ErrorContains(t, err, `json: cannot unmarshal object into field Schema.allOf of type openapi3.SchemaRefs`) + } + + spec := []byte(` +openapi: 3.0.1 +info: + version: v1 + title: Products api +components: + schemas: + someSchema: + type: object + schemaArray: + type: array + minItems: 1 + items: + $ref: '#/components/schemas/someSchema' +paths: + /categories: + get: + responses: + '200': + description: '' + content: + application/json: + schema: + allOf: + - $ref: '#/components/schemas/schemaArray' # <- +`[1:]) + + sl := NewLoader() + + doc, err := sl.LoadFromData(spec) + require.NoError(t, err) + + err = doc.Validate(sl.Context) + require.NoError(t, err) +} diff --git a/openapi3/media_type.go b/openapi3/media_type.go index 88e61d59a..e043a7c95 100644 --- a/openapi3/media_type.go +++ b/openapi3/media_type.go @@ -89,7 +89,7 @@ func (mediaType *MediaType) UnmarshalJSON(data []byte) error { type MediaTypeBis MediaType var x MediaTypeBis if err := json.Unmarshal(data, &x); err != nil { - return err + return unmarshalError(err) } _ = json.Unmarshal(data, &x.Extensions) delete(x.Extensions, "schema") diff --git a/openapi3/openapi3.go b/openapi3/openapi3.go index d0d99bdea..52125b1b3 100644 --- a/openapi3/openapi3.go +++ b/openapi3/openapi3.go @@ -56,7 +56,7 @@ func (doc *T) UnmarshalJSON(data []byte) error { type TBis T var x TBis if err := json.Unmarshal(data, &x); err != nil { - return err + return unmarshalError(err) } _ = json.Unmarshal(data, &x.Extensions) delete(x.Extensions, "openapi") diff --git a/openapi3/operation.go b/openapi3/operation.go index 99c64567d..02df2d278 100644 --- a/openapi3/operation.go +++ b/openapi3/operation.go @@ -104,7 +104,7 @@ func (operation *Operation) UnmarshalJSON(data []byte) error { type OperationBis Operation var x OperationBis if err := json.Unmarshal(data, &x); err != nil { - return err + return unmarshalError(err) } _ = json.Unmarshal(data, &x.Extensions) delete(x.Extensions, "tags") diff --git a/openapi3/parameter.go b/openapi3/parameter.go index c85fa09dc..8a7546989 100644 --- a/openapi3/parameter.go +++ b/openapi3/parameter.go @@ -222,7 +222,7 @@ func (parameter *Parameter) UnmarshalJSON(data []byte) error { type ParameterBis Parameter var x ParameterBis if err := json.Unmarshal(data, &x); err != nil { - return err + return unmarshalError(err) } _ = json.Unmarshal(data, &x.Extensions) diff --git a/openapi3/path_item.go b/openapi3/path_item.go index c1112288f..e5dd0fb63 100644 --- a/openapi3/path_item.go +++ b/openapi3/path_item.go @@ -86,7 +86,7 @@ func (pathItem *PathItem) UnmarshalJSON(data []byte) error { type PathItemBis PathItem var x PathItemBis if err := json.Unmarshal(data, &x); err != nil { - return err + return unmarshalError(err) } _ = json.Unmarshal(data, &x.Extensions) delete(x.Extensions, "$ref") diff --git a/openapi3/request_body.go b/openapi3/request_body.go index 6ab99b24e..8f5c0d6f8 100644 --- a/openapi3/request_body.go +++ b/openapi3/request_body.go @@ -116,7 +116,7 @@ func (requestBody *RequestBody) UnmarshalJSON(data []byte) error { type RequestBodyBis RequestBody var x RequestBodyBis if err := json.Unmarshal(data, &x); err != nil { - return err + return unmarshalError(err) } _ = json.Unmarshal(data, &x.Extensions) delete(x.Extensions, "description") diff --git a/openapi3/response.go b/openapi3/response.go index 4707c6b80..61d859e0e 100644 --- a/openapi3/response.go +++ b/openapi3/response.go @@ -149,7 +149,7 @@ func (response *Response) UnmarshalJSON(data []byte) error { type ResponseBis Response var x ResponseBis if err := json.Unmarshal(data, &x); err != nil { - return err + return unmarshalError(err) } _ = json.Unmarshal(data, &x.Extensions) delete(x.Extensions, "description") diff --git a/openapi3/schema.go b/openapi3/schema.go index f941a1114..07f569616 100644 --- a/openapi3/schema.go +++ b/openapi3/schema.go @@ -178,7 +178,7 @@ func (addProps AdditionalProperties) MarshalJSON() ([]byte, error) { func (addProps *AdditionalProperties) UnmarshalJSON(data []byte) error { var x interface{} if err := json.Unmarshal(data, &x); err != nil { - return err + return unmarshalError(err) } switch y := x.(type) { case nil: @@ -342,7 +342,7 @@ func (schema *Schema) UnmarshalJSON(data []byte) error { type SchemaBis Schema var x SchemaBis if err := json.Unmarshal(data, &x); err != nil { - return err + return unmarshalError(err) } _ = json.Unmarshal(data, &x.Extensions) diff --git a/openapi3/security_scheme.go b/openapi3/security_scheme.go index fe607a186..42df94d5b 100644 --- a/openapi3/security_scheme.go +++ b/openapi3/security_scheme.go @@ -107,7 +107,7 @@ func (ss *SecurityScheme) UnmarshalJSON(data []byte) error { type SecuritySchemeBis SecurityScheme var x SecuritySchemeBis if err := json.Unmarshal(data, &x); err != nil { - return err + return unmarshalError(err) } _ = json.Unmarshal(data, &x.Extensions) delete(x.Extensions, "type") @@ -268,7 +268,7 @@ func (flows *OAuthFlows) UnmarshalJSON(data []byte) error { type OAuthFlowsBis OAuthFlows var x OAuthFlowsBis if err := json.Unmarshal(data, &x); err != nil { - return err + return unmarshalError(err) } _ = json.Unmarshal(data, &x.Extensions) delete(x.Extensions, "implicit") @@ -348,7 +348,7 @@ func (flow *OAuthFlow) UnmarshalJSON(data []byte) error { type OAuthFlowBis OAuthFlow var x OAuthFlowBis if err := json.Unmarshal(data, &x); err != nil { - return err + return unmarshalError(err) } _ = json.Unmarshal(data, &x.Extensions) delete(x.Extensions, "authorizationUrl") diff --git a/openapi3/server.go b/openapi3/server.go index a10e96027..04e233d51 100644 --- a/openapi3/server.go +++ b/openapi3/server.go @@ -103,7 +103,7 @@ func (server *Server) UnmarshalJSON(data []byte) error { type ServerBis Server var x ServerBis if err := json.Unmarshal(data, &x); err != nil { - return err + return unmarshalError(err) } _ = json.Unmarshal(data, &x.Extensions) delete(x.Extensions, "url") @@ -255,7 +255,7 @@ func (serverVariable *ServerVariable) UnmarshalJSON(data []byte) error { type ServerVariableBis ServerVariable var x ServerVariableBis if err := json.Unmarshal(data, &x); err != nil { - return err + return unmarshalError(err) } _ = json.Unmarshal(data, &x.Extensions) delete(x.Extensions, "enum") diff --git a/openapi3/tag.go b/openapi3/tag.go index a2cd50969..eea6462f5 100644 --- a/openapi3/tag.go +++ b/openapi3/tag.go @@ -63,7 +63,7 @@ func (t *Tag) UnmarshalJSON(data []byte) error { type TagBis Tag var x TagBis if err := json.Unmarshal(data, &x); err != nil { - return err + return unmarshalError(err) } _ = json.Unmarshal(data, &x.Extensions) delete(x.Extensions, "name") diff --git a/openapi3/xml.go b/openapi3/xml.go index 444117d91..604b607dc 100644 --- a/openapi3/xml.go +++ b/openapi3/xml.go @@ -46,7 +46,7 @@ func (xml *XML) UnmarshalJSON(data []byte) error { type XMLBis XML var x XMLBis if err := json.Unmarshal(data, &x); err != nil { - return err + return unmarshalError(err) } _ = json.Unmarshal(data, &x.Extensions) delete(x.Extensions, "name")