diff --git a/.github/docs/openapi3.txt b/.github/docs/openapi3.txt
index 993d2da17..7c950937b 100644
--- a/.github/docs/openapi3.txt
+++ b/.github/docs/openapi3.txt
@@ -4,6 +4,8 @@ Package openapi3 parses and writes OpenAPI 3 specification documents.
See https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.3.md
+Code generated by go generate; DO NOT EDIT.
+
CONSTANTS
const (
@@ -170,6 +172,9 @@ func (callback *Callback) Map() (m map[string]*PathItem)
func (callback *Callback) MarshalJSON() ([]byte, error)
MarshalJSON returns the JSON encoding of Callback.
+func (callback *Callback) MarshalYAML() (interface{}, error)
+ MarshalYAML returns the YAML encoding of Callback.
+
func (callback *Callback) Set(key string, value *PathItem)
Set adds or replaces key 'key' of 'callback' with 'value'. Note: 'callback'
MUST be non-nil
@@ -235,6 +240,9 @@ func NewComponents() Components
func (components Components) MarshalJSON() ([]byte, error)
MarshalJSON returns the JSON encoding of Components.
+func (components Components) MarshalYAML() (interface{}, error)
+ MarshalYAML returns the YAML encoding of Components.
+
func (components *Components) UnmarshalJSON(data []byte) error
UnmarshalJSON sets Components to a copy of data.
@@ -255,6 +263,9 @@ type Contact struct {
func (contact Contact) MarshalJSON() ([]byte, error)
MarshalJSON returns the JSON encoding of Contact.
+func (contact Contact) MarshalYAML() (interface{}, error)
+ MarshalYAML returns the YAML encoding of Contact.
+
func (contact *Contact) UnmarshalJSON(data []byte) error
UnmarshalJSON sets Contact to a copy of data.
@@ -295,6 +306,9 @@ type Discriminator struct {
func (discriminator Discriminator) MarshalJSON() ([]byte, error)
MarshalJSON returns the JSON encoding of Discriminator.
+func (discriminator Discriminator) MarshalYAML() (interface{}, error)
+ MarshalYAML returns the YAML encoding of Discriminator.
+
func (discriminator *Discriminator) UnmarshalJSON(data []byte) error
UnmarshalJSON sets Discriminator to a copy of data.
@@ -319,6 +333,9 @@ func NewEncoding() *Encoding
func (encoding Encoding) MarshalJSON() ([]byte, error)
MarshalJSON returns the JSON encoding of Encoding.
+func (encoding Encoding) MarshalYAML() (interface{}, error)
+ MarshalYAML returns the YAML encoding of Encoding.
+
func (encoding *Encoding) SerializationMethod() *SerializationMethod
SerializationMethod returns a serialization method of request body.
When serialization method is not defined the method returns the default
@@ -350,6 +367,9 @@ func NewExample(value interface{}) *Example
func (example Example) MarshalJSON() ([]byte, error)
MarshalJSON returns the JSON encoding of Example.
+func (example Example) MarshalYAML() (interface{}, error)
+ MarshalYAML returns the YAML encoding of Example.
+
func (example *Example) UnmarshalJSON(data []byte) error
UnmarshalJSON sets Example to a copy of data.
@@ -399,6 +419,9 @@ type ExternalDocs struct {
func (e ExternalDocs) MarshalJSON() ([]byte, error)
MarshalJSON returns the JSON encoding of ExternalDocs.
+func (e ExternalDocs) MarshalYAML() (interface{}, error)
+ MarshalYAML returns the YAML encoding of ExternalDocs.
+
func (e *ExternalDocs) UnmarshalJSON(data []byte) error
UnmarshalJSON sets ExternalDocs to a copy of data.
@@ -487,6 +510,9 @@ type Info struct {
func (info Info) MarshalJSON() ([]byte, error)
MarshalJSON returns the JSON encoding of Info.
+func (info Info) MarshalYAML() (interface{}, error)
+ MarshalYAML returns the YAML encoding of Info.
+
func (info *Info) UnmarshalJSON(data []byte) error
UnmarshalJSON sets Info to a copy of data.
@@ -505,6 +531,9 @@ type License struct {
func (license License) MarshalJSON() ([]byte, error)
MarshalJSON returns the JSON encoding of License.
+func (license License) MarshalYAML() (interface{}, error)
+ MarshalYAML returns the YAML encoding of License.
+
func (license *License) UnmarshalJSON(data []byte) error
UnmarshalJSON sets License to a copy of data.
@@ -527,6 +556,9 @@ type Link struct {
func (link Link) MarshalJSON() ([]byte, error)
MarshalJSON returns the JSON encoding of Link.
+func (link Link) MarshalYAML() (interface{}, error)
+ MarshalYAML returns the YAML encoding of Link.
+
func (link *Link) UnmarshalJSON(data []byte) error
UnmarshalJSON sets Link to a copy of data.
@@ -622,6 +654,9 @@ func (mediaType MediaType) JSONLookup(token string) (interface{}, error)
func (mediaType MediaType) MarshalJSON() ([]byte, error)
MarshalJSON returns the JSON encoding of MediaType.
+func (mediaType MediaType) MarshalYAML() (interface{}, error)
+ MarshalYAML returns the YAML encoding of MediaType.
+
func (mediaType *MediaType) UnmarshalJSON(data []byte) error
UnmarshalJSON sets MediaType to a copy of data.
@@ -687,6 +722,9 @@ type OAuthFlow struct {
func (flow OAuthFlow) MarshalJSON() ([]byte, error)
MarshalJSON returns the JSON encoding of OAuthFlow.
+func (flow OAuthFlow) MarshalYAML() (interface{}, error)
+ MarshalYAML returns the YAML encoding of OAuthFlow.
+
func (flow *OAuthFlow) UnmarshalJSON(data []byte) error
UnmarshalJSON sets OAuthFlow to a copy of data.
@@ -708,6 +746,9 @@ type OAuthFlows struct {
func (flows OAuthFlows) MarshalJSON() ([]byte, error)
MarshalJSON returns the JSON encoding of OAuthFlows.
+func (flows OAuthFlows) MarshalYAML() (interface{}, error)
+ MarshalYAML returns the YAML encoding of OAuthFlows.
+
func (flows *OAuthFlows) UnmarshalJSON(data []byte) error
UnmarshalJSON sets OAuthFlows to a copy of data.
@@ -769,6 +810,9 @@ func (operation Operation) JSONLookup(token string) (interface{}, error)
func (operation Operation) MarshalJSON() ([]byte, error)
MarshalJSON returns the JSON encoding of Operation.
+func (operation Operation) MarshalYAML() (interface{}, error)
+ MarshalYAML returns the YAML encoding of Operation.
+
func (operation *Operation) UnmarshalJSON(data []byte) error
UnmarshalJSON sets Operation to a copy of data.
@@ -811,6 +855,9 @@ func (parameter Parameter) JSONLookup(token string) (interface{}, error)
func (parameter Parameter) MarshalJSON() ([]byte, error)
MarshalJSON returns the JSON encoding of Parameter.
+func (parameter Parameter) MarshalYAML() (interface{}, error)
+ MarshalYAML returns the YAML encoding of Parameter.
+
func (parameter *Parameter) SerializationMethod() (*SerializationMethod, error)
SerializationMethod returns a parameter's serialization method. When a
parameter's serialization method is not defined the method returns the
@@ -901,6 +948,9 @@ func (pathItem *PathItem) GetOperation(method string) *Operation
func (pathItem PathItem) MarshalJSON() ([]byte, error)
MarshalJSON returns the JSON encoding of PathItem.
+func (pathItem PathItem) MarshalYAML() (interface{}, error)
+ MarshalYAML returns the YAML encoding of PathItem.
+
func (pathItem *PathItem) Operations() map[string]*Operation
func (pathItem *PathItem) SetOperation(method string, operation *Operation)
@@ -963,8 +1013,8 @@ func (paths *Paths) Map() (m map[string]*PathItem)
func (paths *Paths) MarshalJSON() ([]byte, error)
MarshalJSON returns the JSON encoding of Paths.
-func (paths *Paths) MarshalYAML() (any, error)
- Support YAML Marshaler interface for gopkg.in/yaml
+func (paths *Paths) MarshalYAML() (interface{}, error)
+ MarshalYAML returns the YAML encoding of Paths.
func (paths *Paths) Set(key string, value *PathItem)
Set adds or replaces key 'key' of 'paths' with 'value'. Note: 'paths' MUST
@@ -1031,6 +1081,9 @@ func (requestBody *RequestBody) GetMediaType(mediaType string) *MediaType
func (requestBody RequestBody) MarshalJSON() ([]byte, error)
MarshalJSON returns the JSON encoding of RequestBody.
+func (requestBody RequestBody) MarshalYAML() (interface{}, error)
+ MarshalYAML returns the YAML encoding of RequestBody.
+
func (requestBody *RequestBody) UnmarshalJSON(data []byte) error
UnmarshalJSON sets RequestBody to a copy of data.
@@ -1097,6 +1150,9 @@ func NewResponse() *Response
func (response Response) MarshalJSON() ([]byte, error)
MarshalJSON returns the JSON encoding of Response.
+func (response Response) MarshalYAML() (interface{}, error)
+ MarshalYAML returns the YAML encoding of Response.
+
func (response *Response) UnmarshalJSON(data []byte) error
UnmarshalJSON sets Response to a copy of data.
@@ -1177,8 +1233,8 @@ func (responses *Responses) Map() (m map[string]*ResponseRef)
func (responses *Responses) MarshalJSON() ([]byte, error)
MarshalJSON returns the JSON encoding of Responses.
-func (responses *Responses) MarshalYAML() (any, error)
- Support YAML Marshaler interface for gopkg.in/yaml
+func (responses *Responses) MarshalYAML() (interface{}, error)
+ MarshalYAML returns the YAML encoding of Responses.
func (responses *Responses) Set(key string, value *ResponseRef)
Set adds or replaces key 'key' of 'responses' with 'value'. Note:
@@ -1307,6 +1363,9 @@ func (schema Schema) JSONLookup(token string) (interface{}, error)
func (schema Schema) MarshalJSON() ([]byte, error)
MarshalJSON returns the JSON encoding of Schema.
+func (schema Schema) MarshalYAML() (interface{}, error)
+ MarshalYAML returns the YAML encoding of Schema.
+
func (schema *Schema) NewRef() *SchemaRef
func (schema *Schema) PermitsNull() bool
@@ -1536,6 +1595,9 @@ func NewSecurityScheme() *SecurityScheme
func (ss SecurityScheme) MarshalJSON() ([]byte, error)
MarshalJSON returns the JSON encoding of SecurityScheme.
+func (ss SecurityScheme) MarshalYAML() (interface{}, error)
+ MarshalYAML returns the YAML encoding of SecurityScheme.
+
func (ss *SecurityScheme) UnmarshalJSON(data []byte) error
UnmarshalJSON sets SecurityScheme to a copy of data.
@@ -1611,6 +1673,9 @@ func (server *Server) BasePath() (string, error)
func (server Server) MarshalJSON() ([]byte, error)
MarshalJSON returns the JSON encoding of Server.
+func (server Server) MarshalYAML() (interface{}, error)
+ MarshalYAML returns the YAML encoding of Server.
+
func (server Server) MatchRawURL(input string) ([]string, string, bool)
func (server Server) ParameterNames() ([]string, error)
@@ -1634,6 +1699,9 @@ type ServerVariable struct {
func (serverVariable ServerVariable) MarshalJSON() ([]byte, error)
MarshalJSON returns the JSON encoding of ServerVariable.
+func (serverVariable ServerVariable) MarshalYAML() (interface{}, error)
+ MarshalYAML returns the YAML encoding of ServerVariable.
+
func (serverVariable *ServerVariable) UnmarshalJSON(data []byte) error
UnmarshalJSON sets ServerVariable to a copy of data.
@@ -1699,6 +1767,9 @@ func (doc *T) JSONLookup(token string) (interface{}, error)
func (doc *T) MarshalJSON() ([]byte, error)
MarshalJSON returns the JSON encoding of T.
+func (doc *T) MarshalYAML() (interface{}, error)
+ MarshalYAML returns the YAML encoding of T.
+
func (doc *T) UnmarshalJSON(data []byte) error
UnmarshalJSON sets T to a copy of data.
@@ -1719,6 +1790,9 @@ type Tag struct {
func (t Tag) MarshalJSON() ([]byte, error)
MarshalJSON returns the JSON encoding of Tag.
+func (t Tag) MarshalYAML() (interface{}, error)
+ MarshalYAML returns the YAML encoding of Tag.
+
func (t *Tag) UnmarshalJSON(data []byte) error
UnmarshalJSON sets Tag to a copy of data.
@@ -1813,6 +1887,9 @@ type XML struct {
func (xml XML) MarshalJSON() ([]byte, error)
MarshalJSON returns the JSON encoding of XML.
+func (xml XML) MarshalYAML() (interface{}, error)
+ MarshalYAML returns the YAML encoding of XML.
+
func (xml *XML) UnmarshalJSON(data []byte) error
UnmarshalJSON sets XML to a copy of data.
diff --git a/.github/docs/openapi3filter.txt b/.github/docs/openapi3filter.txt
index 0fd7027c8..43540c416 100644
--- a/.github/docs/openapi3filter.txt
+++ b/.github/docs/openapi3filter.txt
@@ -182,7 +182,7 @@ type ErrCode int
occur during validation. These may be used to write an appropriate response
in ErrFunc.
-type ErrFunc func(w http.ResponseWriter, status int, code ErrCode, err error)
+type ErrFunc func(ctx context.Context, w http.ResponseWriter, status int, code ErrCode, err error)
ErrFunc handles errors that may occur during validation.
type ErrorEncoder func(ctx context.Context, err error, w http.ResponseWriter)
@@ -198,7 +198,7 @@ type Headerer interface {
Headerer, the provided headers will be applied to the response writer,
after the Content-Type is set.
-type LogFunc func(message string, err error)
+type LogFunc func(ctx context.Context, message string, err error)
LogFunc handles log messages that may occur during validation.
type Options struct {
diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml
deleted file mode 100644
index a09546a40..000000000
--- a/.github/workflows/codeql.yml
+++ /dev/null
@@ -1,41 +0,0 @@
-name: "CodeQL"
-
-on:
- push:
- branches: [ "master" ]
- pull_request:
- branches: [ "master" ]
- schedule:
- - cron: "4 8 * * 4"
-
-jobs:
- analyze:
- name: Analyze
- runs-on: ubuntu-latest
- permissions:
- actions: read
- contents: read
- security-events: write
-
- strategy:
- fail-fast: false
- matrix:
- language: [ go ]
-
- steps:
- - name: Checkout
- uses: actions/checkout@v3
-
- - name: Initialize CodeQL
- uses: github/codeql-action/init@v2
- with:
- languages: ${{ matrix.language }}
- queries: +security-and-quality
-
- - name: Autobuild
- uses: github/codeql-action/autobuild@v2
-
- - name: Perform CodeQL Analysis
- uses: github/codeql-action/analyze@v2
- with:
- category: "/language:${{ matrix.language }}"
diff --git a/.github/workflows/go.yml b/.github/workflows/go.yml
index acb3a0f0d..fa32c1b59 100644
--- a/.github/workflows/go.yml
+++ b/.github/workflows/go.yml
@@ -52,7 +52,7 @@ jobs:
- uses: actions/checkout@v2
- - run: ./refs.sh | tee openapi3/refs.go
+ - run: go generate openapi3/refsgenerator.go
- run: git --no-pager diff --exit-code
- run: ./maps.sh
diff --git a/README.md b/README.md
index dfd1781ad..aea156c0d 100644
--- a/README.md
+++ b/README.md
@@ -27,6 +27,7 @@ Be sure to [give back to this project](https://github.com/sponsors/fenollp) like
Here's some projects that depend on _kin-openapi_:
+ * [github.com/a-h/rest](https://github.com/a-h/rest) - "Generate OpenAPI 3.0 specifications from Go code without annotations or magic comments"
* [github.com/Tufin/oasdiff](https://github.com/Tufin/oasdiff) - "A diff tool for OpenAPI Specification 3"
* [github.com/danielgtaylor/apisprout](https://github.com/danielgtaylor/apisprout) - "Lightweight, blazing fast, cross-platform OpenAPI 3 mock server with validation"
* [github.com/deepmap/oapi-codegen](https://github.com/deepmap/oapi-codegen) - "Generate Go client and server boilerplate from OpenAPI 3 specifications"
@@ -277,6 +278,9 @@ This will change the schema validation errors to return only the `Reason` field,
## CHANGELOG: Sub-v1 breaking API changes
+### v0.125.0
+* The `openapi3filter.ErrFunc` and `openapi3filter.LogFunc` func types now take the validated request's context as first argument.
+
### v0.122.0
* `Paths` field of `openapi3.T` is now a pointer
* `Responses` field of `openapi3.Operation` is now a pointer
diff --git a/maps.sh b/maps.sh
index 0f7e14ca5..7e335b01c 100755
--- a/maps.sh
+++ b/maps.sh
@@ -156,8 +156,8 @@ EOF
maplike_UnMarsh() {
cat <>"$maplike"
-// MarshalJSON returns the JSON encoding of ${type#'*'}.
-func (${name} ${type}) MarshalJSON() ([]byte, error) {
+// MarshalYAML returns the YAML encoding of ${type#'*'}.
+func (${name} ${type}) MarshalYAML() (interface{}, error) {
m := make(map[string]interface{}, ${name}.Len()+len(${name}.Extensions))
for k, v := range ${name}.Extensions {
m[k] = v
@@ -165,7 +165,16 @@ func (${name} ${type}) MarshalJSON() ([]byte, error) {
for k, v := range ${name}.Map() {
m[k] = v
}
- return json.Marshal(m)
+ return m, nil
+}
+
+// MarshalJSON returns the JSON encoding of ${type#'*'}.
+func (${name} ${type}) MarshalJSON() ([]byte, error) {
+ ${name}Yaml, err := ${name}.MarshalYAML()
+ if err != nil {
+ return nil, err
+ }
+ return json.Marshal(${name}Yaml)
}
// UnmarshalJSON sets ${type#'*'} to a copy of data.
diff --git a/openapi2/marsh.go b/openapi2/marsh.go
index b15f3b82c..5aa162a72 100644
--- a/openapi2/marsh.go
+++ b/openapi2/marsh.go
@@ -17,10 +17,18 @@ func unmarshalError(jsonUnmarshalErr error) error {
}
func unmarshal(data []byte, v interface{}) error {
+ var jsonErr, yamlErr 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)
+ if jsonErr = json.Unmarshal(data, v); jsonErr == nil {
+ return nil
+ }
+
+ // UnmarshalStrict(data, v) TODO: investigate how ymlv3 handles duplicate map keys
+ if yamlErr = yaml.Unmarshal(data, v); yamlErr == nil {
+ return nil
}
- return nil
+
+ // If both unmarshaling attempts fail, return a new error that includes both errors
+ return fmt.Errorf("failed to unmarshal data: json error: %v, yaml error: %v", jsonErr, yamlErr)
}
diff --git a/openapi3/components.go b/openapi3/components.go
index 656ea1936..c46663273 100644
--- a/openapi3/components.go
+++ b/openapi3/components.go
@@ -43,6 +43,15 @@ func NewComponents() Components {
// MarshalJSON returns the JSON encoding of Components.
func (components Components) MarshalJSON() ([]byte, error) {
+ x, err := components.MarshalYAML()
+ if err != nil {
+ return nil, err
+ }
+ return json.Marshal(x)
+}
+
+// MarshalYAML returns the YAML encoding of Components.
+func (components Components) MarshalYAML() (interface{}, error) {
m := make(map[string]interface{}, 9+len(components.Extensions))
for k, v := range components.Extensions {
m[k] = v
@@ -74,7 +83,7 @@ func (components Components) MarshalJSON() ([]byte, error) {
if x := components.Callbacks; len(x) != 0 {
m["callbacks"] = x
}
- return json.Marshal(m)
+ return m, nil
}
// UnmarshalJSON sets Components to a copy of data.
diff --git a/openapi3/contact.go b/openapi3/contact.go
index e60d2818a..7b707ce39 100644
--- a/openapi3/contact.go
+++ b/openapi3/contact.go
@@ -17,6 +17,15 @@ type Contact struct {
// MarshalJSON returns the JSON encoding of Contact.
func (contact Contact) MarshalJSON() ([]byte, error) {
+ x, err := contact.MarshalYAML()
+ if err != nil {
+ return nil, err
+ }
+ return json.Marshal(x)
+}
+
+// MarshalYAML returns the YAML encoding of Contact.
+func (contact Contact) MarshalYAML() (interface{}, error) {
m := make(map[string]interface{}, 3+len(contact.Extensions))
for k, v := range contact.Extensions {
m[k] = v
@@ -30,7 +39,7 @@ func (contact Contact) MarshalJSON() ([]byte, error) {
if x := contact.Email; x != "" {
m["email"] = x
}
- return json.Marshal(m)
+ return m, nil
}
// UnmarshalJSON sets Contact to a copy of data.
diff --git a/openapi3/discriminator.go b/openapi3/discriminator.go
index abb480741..ae36f416d 100644
--- a/openapi3/discriminator.go
+++ b/openapi3/discriminator.go
@@ -16,6 +16,15 @@ type Discriminator struct {
// MarshalJSON returns the JSON encoding of Discriminator.
func (discriminator Discriminator) MarshalJSON() ([]byte, error) {
+ x, err := discriminator.MarshalYAML()
+ if err != nil {
+ return nil, err
+ }
+ return json.Marshal(x)
+}
+
+// MarshalYAML returns the YAML encoding of Discriminator.
+func (discriminator Discriminator) MarshalYAML() (interface{}, error) {
m := make(map[string]interface{}, 2+len(discriminator.Extensions))
for k, v := range discriminator.Extensions {
m[k] = v
@@ -24,7 +33,7 @@ func (discriminator Discriminator) MarshalJSON() ([]byte, error) {
if x := discriminator.Mapping; len(x) != 0 {
m["mapping"] = x
}
- return json.Marshal(m)
+ return m, nil
}
// UnmarshalJSON sets Discriminator to a copy of data.
diff --git a/openapi3/encoding.go b/openapi3/encoding.go
index 8e810279c..57d20ee3d 100644
--- a/openapi3/encoding.go
+++ b/openapi3/encoding.go
@@ -41,6 +41,15 @@ func (encoding *Encoding) WithHeaderRef(name string, ref *HeaderRef) *Encoding {
// MarshalJSON returns the JSON encoding of Encoding.
func (encoding Encoding) MarshalJSON() ([]byte, error) {
+ x, err := encoding.MarshalYAML()
+ if err != nil {
+ return nil, err
+ }
+ return json.Marshal(x)
+}
+
+// MarshalYAML returns the YAML encoding of Encoding.
+func (encoding Encoding) MarshalYAML() (interface{}, error) {
m := make(map[string]interface{}, 5+len(encoding.Extensions))
for k, v := range encoding.Extensions {
m[k] = v
@@ -60,7 +69,7 @@ func (encoding Encoding) MarshalJSON() ([]byte, error) {
if x := encoding.AllowReserved; x {
m["allowReserved"] = x
}
- return json.Marshal(m)
+ return m, nil
}
// UnmarshalJSON sets Encoding to a copy of data.
diff --git a/openapi3/example.go b/openapi3/example.go
index 44e71d827..a1a5a2b35 100644
--- a/openapi3/example.go
+++ b/openapi3/example.go
@@ -23,6 +23,15 @@ func NewExample(value interface{}) *Example {
// MarshalJSON returns the JSON encoding of Example.
func (example Example) MarshalJSON() ([]byte, error) {
+ x, err := example.MarshalYAML()
+ if err != nil {
+ return nil, err
+ }
+ return json.Marshal(x)
+}
+
+// MarshalYAML returns the YAML encoding of Example.
+func (example Example) MarshalYAML() (interface{}, error) {
m := make(map[string]interface{}, 4+len(example.Extensions))
for k, v := range example.Extensions {
m[k] = v
@@ -39,7 +48,7 @@ func (example Example) MarshalJSON() ([]byte, error) {
if x := example.ExternalValue; x != "" {
m["externalValue"] = x
}
- return json.Marshal(m)
+ return m, nil
}
// UnmarshalJSON sets Example to a copy of data.
diff --git a/openapi3/external_docs.go b/openapi3/external_docs.go
index 7190be4b0..40e9f3db0 100644
--- a/openapi3/external_docs.go
+++ b/openapi3/external_docs.go
@@ -19,6 +19,15 @@ type ExternalDocs struct {
// MarshalJSON returns the JSON encoding of ExternalDocs.
func (e ExternalDocs) MarshalJSON() ([]byte, error) {
+ x, err := e.MarshalYAML()
+ if err != nil {
+ return nil, err
+ }
+ return json.Marshal(x)
+}
+
+// MarshalYAML returns the YAML encoding of ExternalDocs.
+func (e ExternalDocs) MarshalYAML() (interface{}, error) {
m := make(map[string]interface{}, 2+len(e.Extensions))
for k, v := range e.Extensions {
m[k] = v
@@ -29,7 +38,7 @@ func (e ExternalDocs) MarshalJSON() ([]byte, error) {
if x := e.URL; x != "" {
m["url"] = x
}
- return json.Marshal(m)
+ return m, nil
}
// UnmarshalJSON sets ExternalDocs to a copy of data.
diff --git a/openapi3/info.go b/openapi3/info.go
index ffcd3b0e3..51707f852 100644
--- a/openapi3/info.go
+++ b/openapi3/info.go
@@ -21,6 +21,15 @@ type Info struct {
// MarshalJSON returns the JSON encoding of Info.
func (info Info) MarshalJSON() ([]byte, error) {
+ x, err := info.MarshalYAML()
+ if err != nil {
+ return nil, err
+ }
+ return json.Marshal(x)
+}
+
+// MarshalYAML returns the YAML encoding of Info.
+func (info Info) MarshalYAML() (interface{}, error) {
m := make(map[string]interface{}, 6+len(info.Extensions))
for k, v := range info.Extensions {
m[k] = v
@@ -39,7 +48,7 @@ func (info Info) MarshalJSON() ([]byte, error) {
m["license"] = x
}
m["version"] = info.Version
- return json.Marshal(m)
+ return m, nil
}
// UnmarshalJSON sets Info to a copy of data.
diff --git a/openapi3/internalize_refs.go b/openapi3/internalize_refs.go
index e313e5535..191c14f7e 100644
--- a/openapi3/internalize_refs.go
+++ b/openapi3/internalize_refs.go
@@ -351,7 +351,10 @@ func (doc *T) derefPaths(paths map[string]*PathItem, refNameResolver RefNameReso
ops.Ref = ""
for _, param := range ops.Parameters {
- doc.addParameterToSpec(param, refNameResolver, pathIsExternal)
+ isExternal := doc.addParameterToSpec(param, refNameResolver, pathIsExternal)
+ if param.Value != nil {
+ doc.derefParameter(*param.Value, refNameResolver, pathIsExternal || isExternal)
+ }
}
for _, op := range ops.Operations() {
diff --git a/openapi3/internalize_refs_test.go b/openapi3/internalize_refs_test.go
index b5ceb6905..90e73f234 100644
--- a/openapi3/internalize_refs_test.go
+++ b/openapi3/internalize_refs_test.go
@@ -23,6 +23,7 @@ func TestInternalizeRefs(t *testing.T) {
{"testdata/spec.yaml"},
{"testdata/callbacks.yml"},
{"testdata/issue831/testref.internalizepath.openapi.yml"},
+ {"testdata/issue959/openapi.yml"},
}
for _, test := range tests {
diff --git a/openapi3/license.go b/openapi3/license.go
index 3d2d2f06d..e845ed832 100644
--- a/openapi3/license.go
+++ b/openapi3/license.go
@@ -17,6 +17,15 @@ type License struct {
// MarshalJSON returns the JSON encoding of License.
func (license License) MarshalJSON() ([]byte, error) {
+ x, err := license.MarshalYAML()
+ if err != nil {
+ return nil, err
+ }
+ return json.Marshal(x)
+}
+
+// MarshalYAML returns the YAML encoding of License.
+func (license License) MarshalYAML() (interface{}, error) {
m := make(map[string]interface{}, 2+len(license.Extensions))
for k, v := range license.Extensions {
m[k] = v
@@ -25,7 +34,7 @@ func (license License) MarshalJSON() ([]byte, error) {
if x := license.URL; x != "" {
m["url"] = x
}
- return json.Marshal(m)
+ return m, nil
}
// UnmarshalJSON sets License to a copy of data.
diff --git a/openapi3/link.go b/openapi3/link.go
index 23a8df41b..961566538 100644
--- a/openapi3/link.go
+++ b/openapi3/link.go
@@ -22,6 +22,15 @@ type Link struct {
// MarshalJSON returns the JSON encoding of Link.
func (link Link) MarshalJSON() ([]byte, error) {
+ x, err := link.MarshalYAML()
+ if err != nil {
+ return nil, err
+ }
+ return json.Marshal(x)
+}
+
+// MarshalYAML returns the YAML encoding of Link.
+func (link Link) MarshalYAML() (interface{}, error) {
m := make(map[string]interface{}, 6+len(link.Extensions))
for k, v := range link.Extensions {
m[k] = v
@@ -46,7 +55,7 @@ func (link Link) MarshalJSON() ([]byte, error) {
m["requestBody"] = x
}
- return json.Marshal(m)
+ return m, nil
}
// UnmarshalJSON sets Link to a copy of data.
diff --git a/openapi3/maplike.go b/openapi3/maplike.go
index b27cbf6c5..8829b8db5 100644
--- a/openapi3/maplike.go
+++ b/openapi3/maplike.go
@@ -76,8 +76,8 @@ func (responses Responses) JSONLookup(token string) (interface{}, error) {
}
}
-// MarshalJSON returns the JSON encoding of Responses.
-func (responses *Responses) MarshalJSON() ([]byte, error) {
+// MarshalYAML returns the YAML encoding of Responses.
+func (responses *Responses) MarshalYAML() (interface{}, error) {
m := make(map[string]interface{}, responses.Len()+len(responses.Extensions))
for k, v := range responses.Extensions {
m[k] = v
@@ -85,7 +85,16 @@ func (responses *Responses) MarshalJSON() ([]byte, error) {
for k, v := range responses.Map() {
m[k] = v
}
- return json.Marshal(m)
+ return m, nil
+}
+
+// MarshalJSON returns the JSON encoding of Responses.
+func (responses *Responses) MarshalJSON() ([]byte, error) {
+ responsesYaml, err := responses.MarshalYAML()
+ if err != nil {
+ return nil, err
+ }
+ return json.Marshal(responsesYaml)
}
// UnmarshalJSON sets Responses to a copy of data.
@@ -195,8 +204,8 @@ func (callback Callback) JSONLookup(token string) (interface{}, error) {
}
}
-// MarshalJSON returns the JSON encoding of Callback.
-func (callback *Callback) MarshalJSON() ([]byte, error) {
+// MarshalYAML returns the YAML encoding of Callback.
+func (callback *Callback) MarshalYAML() (interface{}, error) {
m := make(map[string]interface{}, callback.Len()+len(callback.Extensions))
for k, v := range callback.Extensions {
m[k] = v
@@ -204,7 +213,16 @@ func (callback *Callback) MarshalJSON() ([]byte, error) {
for k, v := range callback.Map() {
m[k] = v
}
- return json.Marshal(m)
+ return m, nil
+}
+
+// MarshalJSON returns the JSON encoding of Callback.
+func (callback *Callback) MarshalJSON() ([]byte, error) {
+ callbackYaml, err := callback.MarshalYAML()
+ if err != nil {
+ return nil, err
+ }
+ return json.Marshal(callbackYaml)
}
// UnmarshalJSON sets Callback to a copy of data.
@@ -314,8 +332,8 @@ func (paths Paths) JSONLookup(token string) (interface{}, error) {
}
}
-// MarshalJSON returns the JSON encoding of Paths.
-func (paths *Paths) MarshalJSON() ([]byte, error) {
+// MarshalYAML returns the YAML encoding of Paths.
+func (paths *Paths) MarshalYAML() (interface{}, error) {
m := make(map[string]interface{}, paths.Len()+len(paths.Extensions))
for k, v := range paths.Extensions {
m[k] = v
@@ -323,7 +341,16 @@ func (paths *Paths) MarshalJSON() ([]byte, error) {
for k, v := range paths.Map() {
m[k] = v
}
- return json.Marshal(m)
+ return m, nil
+}
+
+// MarshalJSON returns the JSON encoding of Paths.
+func (paths *Paths) MarshalJSON() ([]byte, error) {
+ pathsYaml, err := paths.MarshalYAML()
+ if err != nil {
+ return nil, err
+ }
+ return json.Marshal(pathsYaml)
}
// UnmarshalJSON sets Paths to a copy of data.
diff --git a/openapi3/marsh.go b/openapi3/marsh.go
index 18036ae78..9be7bb44c 100644
--- a/openapi3/marsh.go
+++ b/openapi3/marsh.go
@@ -17,10 +17,18 @@ func unmarshalError(jsonUnmarshalErr error) error {
}
func unmarshal(data []byte, v interface{}) error {
+ var jsonErr, yamlErr 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)
+ if jsonErr = json.Unmarshal(data, v); jsonErr == nil {
+ return nil
+ }
+
+ // UnmarshalStrict(data, v) TODO: investigate how ymlv3 handles duplicate map keys
+ if yamlErr = yaml.Unmarshal(data, v); yamlErr == nil {
+ return nil
}
- return nil
+
+ // If both unmarshaling attempts fail, return a new error that includes both errors
+ return fmt.Errorf("failed to unmarshal data: json error: %v, yaml error: %v", jsonErr, yamlErr)
}
diff --git a/openapi3/media_type.go b/openapi3/media_type.go
index e043a7c95..02de1dbc5 100644
--- a/openapi3/media_type.go
+++ b/openapi3/media_type.go
@@ -65,6 +65,15 @@ func (mediaType *MediaType) WithEncoding(name string, enc *Encoding) *MediaType
// MarshalJSON returns the JSON encoding of MediaType.
func (mediaType MediaType) MarshalJSON() ([]byte, error) {
+ x, err := mediaType.MarshalYAML()
+ if err != nil {
+ return nil, err
+ }
+ return json.Marshal(x)
+}
+
+// MarshalYAML returns the YAML encoding of MediaType.
+func (mediaType MediaType) MarshalYAML() (interface{}, error) {
m := make(map[string]interface{}, 4+len(mediaType.Extensions))
for k, v := range mediaType.Extensions {
m[k] = v
@@ -81,7 +90,7 @@ func (mediaType MediaType) MarshalJSON() ([]byte, error) {
if x := mediaType.Encoding; len(x) != 0 {
m["encoding"] = x
}
- return json.Marshal(m)
+ return m, nil
}
// UnmarshalJSON sets MediaType to a copy of data.
diff --git a/openapi3/openapi3.go b/openapi3/openapi3.go
index 04df3505b..04bac8ff7 100644
--- a/openapi3/openapi3.go
+++ b/openapi3/openapi3.go
@@ -55,6 +55,15 @@ func (doc *T) JSONLookup(token string) (interface{}, error) {
// MarshalJSON returns the JSON encoding of T.
func (doc *T) MarshalJSON() ([]byte, error) {
+ x, err := doc.MarshalYAML()
+ if err != nil {
+ return nil, err
+ }
+ return json.Marshal(x)
+}
+
+// MarshalYAML returns the YAML encoding of T.
+func (doc *T) MarshalYAML() (interface{}, error) {
m := make(map[string]interface{}, 4+len(doc.Extensions))
for k, v := range doc.Extensions {
m[k] = v
@@ -77,7 +86,7 @@ func (doc *T) MarshalJSON() ([]byte, error) {
if x := doc.ExternalDocs; x != nil {
m["externalDocs"] = x
}
- return json.Marshal(m)
+ return m, nil
}
// UnmarshalJSON sets T to a copy of data.
diff --git a/openapi3/openapi3_test.go b/openapi3/openapi3_test.go
index 989294823..3b68c1693 100644
--- a/openapi3/openapi3_test.go
+++ b/openapi3/openapi3_test.go
@@ -82,18 +82,9 @@ func TestRefsYAML(t *testing.T) {
require.NoError(t, err)
dataB, err := yaml.Marshal(docB)
require.NoError(t, err)
- eqYAML(t, data, specYAML)
- eqYAML(t, data, dataA)
- eqYAML(t, data, dataB)
-}
-
-func eqYAML(t *testing.T, expected, actual []byte) {
- var e, a interface{}
- err := yaml.Unmarshal(expected, &e)
- require.NoError(t, err)
- err = yaml.Unmarshal(actual, &a)
- require.NoError(t, err)
- require.Equal(t, e, a)
+ require.YAMLEq(t, string(data), string(specYAML))
+ require.YAMLEq(t, string(data), string(dataA))
+ require.YAMLEq(t, string(data), string(dataB))
}
var specYAML = []byte(`
diff --git a/openapi3/operation.go b/openapi3/operation.go
index d859a437c..41e3c9b99 100644
--- a/openapi3/operation.go
+++ b/openapi3/operation.go
@@ -58,6 +58,15 @@ func NewOperation() *Operation {
// MarshalJSON returns the JSON encoding of Operation.
func (operation Operation) MarshalJSON() ([]byte, error) {
+ x, err := operation.MarshalYAML()
+ if err != nil {
+ return nil, err
+ }
+ return json.Marshal(x)
+}
+
+// MarshalYAML returns the YAML encoding of Operation.
+func (operation Operation) MarshalYAML() (interface{}, error) {
m := make(map[string]interface{}, 12+len(operation.Extensions))
for k, v := range operation.Extensions {
m[k] = v
@@ -96,7 +105,7 @@ func (operation Operation) MarshalJSON() ([]byte, error) {
if x := operation.ExternalDocs; x != nil {
m["externalDocs"] = x
}
- return json.Marshal(m)
+ return m, nil
}
// UnmarshalJSON sets Operation to a copy of data.
diff --git a/openapi3/parameter.go b/openapi3/parameter.go
index f5a157de2..a13c1121b 100644
--- a/openapi3/parameter.go
+++ b/openapi3/parameter.go
@@ -150,6 +150,15 @@ func (parameter *Parameter) WithSchema(value *Schema) *Parameter {
// MarshalJSON returns the JSON encoding of Parameter.
func (parameter Parameter) MarshalJSON() ([]byte, error) {
+ x, err := parameter.MarshalYAML()
+ if err != nil {
+ return nil, err
+ }
+ return json.Marshal(x)
+}
+
+// MarshalYAML returns the YAML encoding of Parameter.
+func (parameter Parameter) MarshalYAML() (interface{}, error) {
m := make(map[string]interface{}, 13+len(parameter.Extensions))
for k, v := range parameter.Extensions {
m[k] = v
@@ -195,7 +204,7 @@ func (parameter Parameter) MarshalJSON() ([]byte, error) {
m["content"] = x
}
- return json.Marshal(m)
+ return m, nil
}
// UnmarshalJSON sets Parameter to a copy of data.
diff --git a/openapi3/path_item.go b/openapi3/path_item.go
index e5dd0fb63..0a6493a1f 100644
--- a/openapi3/path_item.go
+++ b/openapi3/path_item.go
@@ -31,8 +31,17 @@ type PathItem struct {
// MarshalJSON returns the JSON encoding of PathItem.
func (pathItem PathItem) MarshalJSON() ([]byte, error) {
+ x, err := pathItem.MarshalYAML()
+ if err != nil {
+ return nil, err
+ }
+ return json.Marshal(x)
+}
+
+// MarshalYAML returns the YAML encoding of PathItem.
+func (pathItem PathItem) MarshalYAML() (interface{}, error) {
if ref := pathItem.Ref; ref != "" {
- return json.Marshal(Ref{Ref: ref})
+ return Ref{Ref: ref}, nil
}
m := make(map[string]interface{}, 13+len(pathItem.Extensions))
@@ -78,7 +87,7 @@ func (pathItem PathItem) MarshalJSON() ([]byte, error) {
if x := pathItem.Parameters; len(x) != 0 {
m["parameters"] = x
}
- return json.Marshal(m)
+ return m, nil
}
// UnmarshalJSON sets PathItem to a copy of data.
diff --git a/openapi3/paths.go b/openapi3/paths.go
index daafe71cc..ac4f58bbb 100644
--- a/openapi3/paths.go
+++ b/openapi3/paths.go
@@ -218,21 +218,6 @@ func (paths *Paths) validateUniqueOperationIDs() error {
return nil
}
-// Support YAML Marshaler interface for gopkg.in/yaml
-func (paths *Paths) MarshalYAML() (any, error) {
- res := make(map[string]any, len(paths.Extensions)+len(paths.m))
-
- for k, v := range paths.Extensions {
- res[k] = v
- }
-
- for k, v := range paths.m {
- res[k] = v
- }
-
- return res, nil
-}
-
func normalizeTemplatedPath(path string) (string, uint, map[string]struct{}) {
if strings.IndexByte(path, '{') < 0 {
return path, 0, nil
diff --git a/openapi3/ref.go b/openapi3/ref.go
index a937de4a5..07060731f 100644
--- a/openapi3/ref.go
+++ b/openapi3/ref.go
@@ -1,5 +1,7 @@
package openapi3
+//go:generate go run refsgenerator.go
+
// Ref is specified by OpenAPI/Swagger 3.0 standard.
// See https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#reference-object
type Ref struct {
diff --git a/openapi3/refs.go b/openapi3/refs.go
index a7e1e3680..087e5abfe 100644
--- a/openapi3/refs.go
+++ b/openapi3/refs.go
@@ -1,3 +1,4 @@
+// Code generated by go generate; DO NOT EDIT.
package openapi3
import (
@@ -27,15 +28,16 @@ func (x CallbackRef) MarshalYAML() (interface{}, error) {
if ref := x.Ref; ref != "" {
return &Ref{Ref: ref}, nil
}
- return x.Value, nil
+ return x.Value.MarshalYAML()
}
// MarshalJSON returns the JSON encoding of CallbackRef.
func (x CallbackRef) MarshalJSON() ([]byte, error) {
- if ref := x.Ref; ref != "" {
- return json.Marshal(Ref{Ref: ref})
+ y, err := x.MarshalYAML()
+ if err != nil {
+ return nil, err
}
- return json.Marshal(x.Value)
+ return json.Marshal(y)
}
// UnmarshalJSON sets CallbackRef to a copy of data.
@@ -105,15 +107,16 @@ func (x ExampleRef) MarshalYAML() (interface{}, error) {
if ref := x.Ref; ref != "" {
return &Ref{Ref: ref}, nil
}
- return x.Value, nil
+ return x.Value.MarshalYAML()
}
// MarshalJSON returns the JSON encoding of ExampleRef.
func (x ExampleRef) MarshalJSON() ([]byte, error) {
- if ref := x.Ref; ref != "" {
- return json.Marshal(Ref{Ref: ref})
+ y, err := x.MarshalYAML()
+ if err != nil {
+ return nil, err
}
- return x.Value.MarshalJSON()
+ return json.Marshal(y)
}
// UnmarshalJSON sets ExampleRef to a copy of data.
@@ -183,15 +186,16 @@ func (x HeaderRef) MarshalYAML() (interface{}, error) {
if ref := x.Ref; ref != "" {
return &Ref{Ref: ref}, nil
}
- return x.Value, nil
+ return x.Value.MarshalYAML()
}
// MarshalJSON returns the JSON encoding of HeaderRef.
func (x HeaderRef) MarshalJSON() ([]byte, error) {
- if ref := x.Ref; ref != "" {
- return json.Marshal(Ref{Ref: ref})
+ y, err := x.MarshalYAML()
+ if err != nil {
+ return nil, err
}
- return x.Value.MarshalJSON()
+ return json.Marshal(y)
}
// UnmarshalJSON sets HeaderRef to a copy of data.
@@ -261,15 +265,16 @@ func (x LinkRef) MarshalYAML() (interface{}, error) {
if ref := x.Ref; ref != "" {
return &Ref{Ref: ref}, nil
}
- return x.Value, nil
+ return x.Value.MarshalYAML()
}
// MarshalJSON returns the JSON encoding of LinkRef.
func (x LinkRef) MarshalJSON() ([]byte, error) {
- if ref := x.Ref; ref != "" {
- return json.Marshal(Ref{Ref: ref})
+ y, err := x.MarshalYAML()
+ if err != nil {
+ return nil, err
}
- return x.Value.MarshalJSON()
+ return json.Marshal(y)
}
// UnmarshalJSON sets LinkRef to a copy of data.
@@ -339,15 +344,16 @@ func (x ParameterRef) MarshalYAML() (interface{}, error) {
if ref := x.Ref; ref != "" {
return &Ref{Ref: ref}, nil
}
- return x.Value, nil
+ return x.Value.MarshalYAML()
}
// MarshalJSON returns the JSON encoding of ParameterRef.
func (x ParameterRef) MarshalJSON() ([]byte, error) {
- if ref := x.Ref; ref != "" {
- return json.Marshal(Ref{Ref: ref})
+ y, err := x.MarshalYAML()
+ if err != nil {
+ return nil, err
}
- return x.Value.MarshalJSON()
+ return json.Marshal(y)
}
// UnmarshalJSON sets ParameterRef to a copy of data.
@@ -417,15 +423,16 @@ func (x RequestBodyRef) MarshalYAML() (interface{}, error) {
if ref := x.Ref; ref != "" {
return &Ref{Ref: ref}, nil
}
- return x.Value, nil
+ return x.Value.MarshalYAML()
}
// MarshalJSON returns the JSON encoding of RequestBodyRef.
func (x RequestBodyRef) MarshalJSON() ([]byte, error) {
- if ref := x.Ref; ref != "" {
- return json.Marshal(Ref{Ref: ref})
+ y, err := x.MarshalYAML()
+ if err != nil {
+ return nil, err
}
- return x.Value.MarshalJSON()
+ return json.Marshal(y)
}
// UnmarshalJSON sets RequestBodyRef to a copy of data.
@@ -495,15 +502,16 @@ func (x ResponseRef) MarshalYAML() (interface{}, error) {
if ref := x.Ref; ref != "" {
return &Ref{Ref: ref}, nil
}
- return x.Value, nil
+ return x.Value.MarshalYAML()
}
// MarshalJSON returns the JSON encoding of ResponseRef.
func (x ResponseRef) MarshalJSON() ([]byte, error) {
- if ref := x.Ref; ref != "" {
- return json.Marshal(Ref{Ref: ref})
+ y, err := x.MarshalYAML()
+ if err != nil {
+ return nil, err
}
- return x.Value.MarshalJSON()
+ return json.Marshal(y)
}
// UnmarshalJSON sets ResponseRef to a copy of data.
@@ -573,15 +581,16 @@ func (x SchemaRef) MarshalYAML() (interface{}, error) {
if ref := x.Ref; ref != "" {
return &Ref{Ref: ref}, nil
}
- return x.Value, nil
+ return x.Value.MarshalYAML()
}
// MarshalJSON returns the JSON encoding of SchemaRef.
func (x SchemaRef) MarshalJSON() ([]byte, error) {
- if ref := x.Ref; ref != "" {
- return json.Marshal(Ref{Ref: ref})
+ y, err := x.MarshalYAML()
+ if err != nil {
+ return nil, err
}
- return x.Value.MarshalJSON()
+ return json.Marshal(y)
}
// UnmarshalJSON sets SchemaRef to a copy of data.
@@ -651,15 +660,16 @@ func (x SecuritySchemeRef) MarshalYAML() (interface{}, error) {
if ref := x.Ref; ref != "" {
return &Ref{Ref: ref}, nil
}
- return x.Value, nil
+ return x.Value.MarshalYAML()
}
// MarshalJSON returns the JSON encoding of SecuritySchemeRef.
func (x SecuritySchemeRef) MarshalJSON() ([]byte, error) {
- if ref := x.Ref; ref != "" {
- return json.Marshal(Ref{Ref: ref})
+ y, err := x.MarshalYAML()
+ if err != nil {
+ return nil, err
}
- return x.Value.MarshalJSON()
+ return json.Marshal(y)
}
// UnmarshalJSON sets SecuritySchemeRef to a copy of data.
diff --git a/openapi3/refs.tmpl b/openapi3/refs.tmpl
new file mode 100644
index 000000000..638d6469d
--- /dev/null
+++ b/openapi3/refs.tmpl
@@ -0,0 +1,92 @@
+// Code generated by go generate; DO NOT EDIT.
+package {{ .Package }}
+
+import (
+ "context"
+ "encoding/json"
+ "fmt"
+ "sort"
+
+ "github.com/go-openapi/jsonpointer"
+ "github.com/perimeterx/marshmallow"
+)
+{{ range $type := .Types }}
+// {{ $type }}Ref represents either a {{ $type }} or a $ref to a {{ $type }}.
+// When serializing and both fields are set, Ref is preferred over Value.
+type {{ $type }}Ref struct {
+ Ref string
+ Value *{{ $type }}
+ extra []string
+}
+
+var _ jsonpointer.JSONPointable = (*{{ $type }}Ref)(nil)
+
+func (x *{{ $type }}Ref) isEmpty() bool { return x == nil || x.Ref == "" && x.Value == nil }
+
+// MarshalYAML returns the YAML encoding of {{ $type }}Ref.
+func (x {{ $type }}Ref) MarshalYAML() (interface{}, error) {
+ if ref := x.Ref; ref != "" {
+ return &Ref{Ref: ref}, nil
+ }
+ return x.Value.MarshalYAML()
+}
+
+// MarshalJSON returns the JSON encoding of {{ $type }}Ref.
+func (x {{ $type }}Ref) MarshalJSON() ([]byte, error) {
+ y, err := x.MarshalYAML()
+ if err != nil {
+ return nil, err
+ }
+ return json.Marshal(y)
+}
+
+// UnmarshalJSON sets {{ $type }}Ref to a copy of data.
+func (x *{{ $type }}Ref) 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)
+ }
+ return nil
+ }
+ return json.Unmarshal(data, &x.Value)
+}
+
+// Validate returns an error if {{ $type }}Ref does not comply with the OpenAPI spec.
+func (x *{{ $type }}Ref) Validate(ctx context.Context, opts ...ValidationOption) error {
+ ctx = WithValidationOptions(ctx, opts...)
+ if extra := x.extra; len(extra) != 0 {
+ extras := make([]string, 0, len(extra))
+ allowed := getValidationOptions(ctx).extraSiblingFieldsAllowed
+ for _, ex := range extra {
+ if allowed != nil {
+ if _, ok := allowed[ex]; ok {
+ continue
+ }
+ }
+ extras = append(extras, ex)
+ }
+ if len(extras) != 0 {
+ return fmt.Errorf("extra sibling fields: %+v", extras)
+ }
+ }
+ if v := x.Value; v != nil {
+ return v.Validate(ctx)
+ }
+ return foundUnresolvedRef(x.Ref)
+}
+
+// JSONLookup implements https://pkg.go.dev/github.com/go-openapi/jsonpointer#JSONPointable
+func (x *{{ $type }}Ref) JSONLookup(token string) (interface{}, error) {
+ if token == "$ref" {
+ return x.Ref, nil
+ }
+ ptr, _, err := jsonpointer.GetForToken(x.Value, token)
+ return ptr, err
+}
+{{ end -}}
diff --git a/openapi3/refsgenerator.go b/openapi3/refsgenerator.go
new file mode 100644
index 000000000..5bddfe258
--- /dev/null
+++ b/openapi3/refsgenerator.go
@@ -0,0 +1,49 @@
+//go:build ignore
+// +build ignore
+
+// The program generates refs.go, invoke `go generate ./...` to run.
+package main
+
+import (
+ _ "embed"
+ "os"
+ "text/template"
+)
+
+//go:embed refs.tmpl
+var tmplData string
+
+func main() {
+ file, err := os.Create("refs.go")
+ if err != nil {
+ panic(err)
+ }
+
+ defer func() {
+ if err := file.Close(); err != nil {
+ panic(err)
+ }
+ }()
+
+ packageTemplate := template.Must(template.New("openapi3-refs").Parse(tmplData))
+
+ if err := packageTemplate.Execute(file, struct {
+ Package string
+ Types []string
+ }{
+ Package: os.Getenv("GOPACKAGE"), // set by the go:generate directive
+ Types: []string{
+ "Callback",
+ "Example",
+ "Header",
+ "Link",
+ "Parameter",
+ "RequestBody",
+ "Response",
+ "Schema",
+ "SecurityScheme",
+ },
+ }); err != nil {
+ panic(err)
+ }
+}
diff --git a/openapi3/request_body.go b/openapi3/request_body.go
index acd2d0e8c..7b8d35399 100644
--- a/openapi3/request_body.go
+++ b/openapi3/request_body.go
@@ -75,6 +75,15 @@ func (requestBody *RequestBody) GetMediaType(mediaType string) *MediaType {
// MarshalJSON returns the JSON encoding of RequestBody.
func (requestBody RequestBody) MarshalJSON() ([]byte, error) {
+ x, err := requestBody.MarshalYAML()
+ if err != nil {
+ return nil, err
+ }
+ return json.Marshal(x)
+}
+
+// MarshalYAML returns the YAML encoding of RequestBody.
+func (requestBody RequestBody) MarshalYAML() (interface{}, error) {
m := make(map[string]interface{}, 3+len(requestBody.Extensions))
for k, v := range requestBody.Extensions {
m[k] = v
@@ -88,7 +97,7 @@ func (requestBody RequestBody) MarshalJSON() ([]byte, error) {
if x := requestBody.Content; true {
m["content"] = x
}
- return json.Marshal(m)
+ return m, nil
}
// UnmarshalJSON sets RequestBody to a copy of data.
diff --git a/openapi3/response.go b/openapi3/response.go
index f69c237b3..6209b5810 100644
--- a/openapi3/response.go
+++ b/openapi3/response.go
@@ -98,21 +98,6 @@ func (responses *Responses) Validate(ctx context.Context, opts ...ValidationOpti
return validateExtensions(ctx, responses.Extensions)
}
-// Support YAML Marshaler interface for gopkg.in/yaml
-func (responses *Responses) MarshalYAML() (any, error) {
- res := make(map[string]any, len(responses.Extensions)+len(responses.m))
-
- for k, v := range responses.Extensions {
- res[k] = v
- }
-
- for k, v := range responses.m {
- res[k] = v
- }
-
- return res, nil
-}
-
// Response is specified by OpenAPI/Swagger 3.0 standard.
// See https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#response-object
type Response struct {
@@ -150,6 +135,15 @@ func (response *Response) WithJSONSchemaRef(schema *SchemaRef) *Response {
// MarshalJSON returns the JSON encoding of Response.
func (response Response) MarshalJSON() ([]byte, error) {
+ x, err := response.MarshalYAML()
+ if err != nil {
+ return nil, err
+ }
+ return json.Marshal(x)
+}
+
+// MarshalYAML returns the YAML encoding of Response.
+func (response Response) MarshalYAML() (interface{}, error) {
m := make(map[string]interface{}, 4+len(response.Extensions))
for k, v := range response.Extensions {
m[k] = v
@@ -166,7 +160,7 @@ func (response Response) MarshalJSON() ([]byte, error) {
if x := response.Links; len(x) != 0 {
m["links"] = x
}
- return json.Marshal(m)
+ return m, nil
}
// UnmarshalJSON sets Response to a copy of data.
diff --git a/openapi3/schema.go b/openapi3/schema.go
index ae28afef7..8bacf729d 100644
--- a/openapi3/schema.go
+++ b/openapi3/schema.go
@@ -222,23 +222,18 @@ func (addProps AdditionalProperties) MarshalYAML() (interface{}, error) {
return false, nil
}
if x := addProps.Schema; x != nil {
- return x.Value, nil
+ return x.MarshalYAML()
}
return nil, nil
}
// MarshalJSON returns the JSON encoding of AdditionalProperties.
func (addProps AdditionalProperties) MarshalJSON() ([]byte, error) {
- if x := addProps.Has; x != nil {
- if *x {
- return []byte("true"), nil
- }
- return []byte("false"), nil
- }
- if x := addProps.Schema; x != nil {
- return json.Marshal(x)
+ x, err := addProps.MarshalYAML()
+ if err != nil {
+ return nil, err
}
- return nil, nil
+ return json.Marshal(x)
}
// UnmarshalJSON sets AdditionalProperties to a copy of data.
@@ -275,6 +270,16 @@ func NewSchema() *Schema {
// 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() (interface{}, error) {
m := make(map[string]interface{}, 36+len(schema.Extensions))
for k, v := range schema.Extensions {
m[k] = v
@@ -401,7 +406,7 @@ func (schema Schema) MarshalJSON() ([]byte, error) {
m["discriminator"] = x
}
- return json.Marshal(m)
+ return m, nil
}
// UnmarshalJSON sets Schema to a copy of data.
@@ -2119,14 +2124,16 @@ type SchemaError struct {
var _ interface{ Unwrap() error } = SchemaError{}
func markSchemaErrorKey(err error, key string) error {
- var me multiErrorForOneOf
-
- if errors.As(err, &me) {
- err = me.Unwrap()
- }
if v, ok := err.(*SchemaError); ok {
v.reversePath = append(v.reversePath, key)
+ if v.Origin != nil {
+ if unwrapped := errors.Unwrap(v.Origin); unwrapped != nil {
+ if me, ok := unwrapped.(multiErrorForOneOf); ok {
+ _ = markSchemaErrorKey(MultiError(me), key)
+ }
+ }
+ }
return v
}
if v, ok := err.(MultiError); ok {
diff --git a/openapi3/schema_issue940_test.go b/openapi3/schema_issue940_test.go
new file mode 100644
index 000000000..95d3d7869
--- /dev/null
+++ b/openapi3/schema_issue940_test.go
@@ -0,0 +1,72 @@
+package openapi3
+
+import (
+ "context"
+ "encoding/json"
+ "errors"
+ "testing"
+
+ "github.com/stretchr/testify/require"
+)
+
+func TestOneOfErrorPreserved(t *testing.T) {
+
+ SchemaErrorDetailsDisabled = true
+ defer func() { SchemaErrorDetailsDisabled = false }()
+
+ // language=json
+ raw := `
+{
+ "foo": [ "bar" ]
+}
+`
+
+ // language=json
+ schema := `
+{
+ "type": "object",
+ "properties": {
+ "foo": {
+ "oneOf": [
+ {
+ "type": "number"
+ },
+ {
+ "type": "string"
+ }
+ ]
+ }
+ }
+}
+`
+
+ s := NewSchema()
+ err := s.UnmarshalJSON([]byte(schema))
+ require.NoError(t, err)
+ err = s.Validate(context.Background())
+ require.NoError(t, err)
+
+ obj := make(map[string]interface{})
+ err = json.Unmarshal([]byte(raw), &obj)
+ require.NoError(t, err)
+
+ err = s.VisitJSON(obj, MultiErrors())
+ require.Error(t, err)
+
+ var multiError MultiError
+ ok := errors.As(err, &multiError)
+ require.True(t, ok)
+ var schemaErr *SchemaError
+ ok = errors.As(multiError[0], &schemaErr)
+ require.True(t, ok)
+
+ require.Equal(t, "oneOf", schemaErr.SchemaField)
+ require.Equal(t, `value doesn't match any schema from "oneOf"`, schemaErr.Reason)
+ require.Equal(t, `Error at "/foo": doesn't match schema due to: value must be a number Or value must be a string`, schemaErr.Error())
+
+ var me multiErrorForOneOf
+ ok = errors.As(err, &me)
+ require.True(t, ok)
+ require.Equal(t, `Error at "/foo": value must be a number`, me[0].Error())
+ require.Equal(t, `Error at "/foo": value must be a string`, me[1].Error())
+}
diff --git a/openapi3/security_scheme.go b/openapi3/security_scheme.go
index c07bfb619..7b6662c4d 100644
--- a/openapi3/security_scheme.go
+++ b/openapi3/security_scheme.go
@@ -52,6 +52,15 @@ func NewJWTSecurityScheme() *SecurityScheme {
// MarshalJSON returns the JSON encoding of SecurityScheme.
func (ss SecurityScheme) MarshalJSON() ([]byte, error) {
+ x, err := ss.MarshalYAML()
+ if err != nil {
+ return nil, err
+ }
+ return json.Marshal(x)
+}
+
+// MarshalYAML returns the YAML encoding of SecurityScheme.
+func (ss SecurityScheme) MarshalYAML() (interface{}, error) {
m := make(map[string]interface{}, 8+len(ss.Extensions))
for k, v := range ss.Extensions {
m[k] = v
@@ -80,7 +89,7 @@ func (ss SecurityScheme) MarshalJSON() ([]byte, error) {
if x := ss.OpenIdConnectUrl; x != "" {
m["openIdConnectUrl"] = x
}
- return json.Marshal(m)
+ return m, nil
}
// UnmarshalJSON sets SecurityScheme to a copy of data.
@@ -225,6 +234,15 @@ const (
// MarshalJSON returns the JSON encoding of OAuthFlows.
func (flows OAuthFlows) MarshalJSON() ([]byte, error) {
+ x, err := flows.MarshalYAML()
+ if err != nil {
+ return nil, err
+ }
+ return json.Marshal(x)
+}
+
+// MarshalYAML returns the YAML encoding of OAuthFlows.
+func (flows OAuthFlows) MarshalYAML() (interface{}, error) {
m := make(map[string]interface{}, 4+len(flows.Extensions))
for k, v := range flows.Extensions {
m[k] = v
@@ -241,7 +259,7 @@ func (flows OAuthFlows) MarshalJSON() ([]byte, error) {
if x := flows.AuthorizationCode; x != nil {
m["authorizationCode"] = x
}
- return json.Marshal(m)
+ return m, nil
}
// UnmarshalJSON sets OAuthFlows to a copy of data.
@@ -307,6 +325,15 @@ type OAuthFlow struct {
// MarshalJSON returns the JSON encoding of OAuthFlow.
func (flow OAuthFlow) MarshalJSON() ([]byte, error) {
+ x, err := flow.MarshalYAML()
+ if err != nil {
+ return nil, err
+ }
+ return json.Marshal(x)
+}
+
+// MarshalYAML returns the YAML encoding of OAuthFlow.
+func (flow OAuthFlow) MarshalYAML() (interface{}, error) {
m := make(map[string]interface{}, 4+len(flow.Extensions))
for k, v := range flow.Extensions {
m[k] = v
@@ -321,7 +348,7 @@ func (flow OAuthFlow) MarshalJSON() ([]byte, error) {
m["refreshUrl"] = x
}
m["scopes"] = flow.Scopes
- return json.Marshal(m)
+ return m, nil
}
// UnmarshalJSON sets OAuthFlow to a copy of data.
diff --git a/openapi3/server.go b/openapi3/server.go
index 04e233d51..85b716fc9 100644
--- a/openapi3/server.go
+++ b/openapi3/server.go
@@ -84,6 +84,15 @@ func (server *Server) BasePath() (string, error) {
// MarshalJSON returns the JSON encoding of Server.
func (server Server) MarshalJSON() ([]byte, error) {
+ x, err := server.MarshalYAML()
+ if err != nil {
+ return nil, err
+ }
+ return json.Marshal(x)
+}
+
+// MarshalYAML returns the YAML encoding of Server.
+func (server Server) MarshalYAML() (interface{}, error) {
m := make(map[string]interface{}, 3+len(server.Extensions))
for k, v := range server.Extensions {
m[k] = v
@@ -95,7 +104,7 @@ func (server Server) MarshalJSON() ([]byte, error) {
if x := server.Variables; len(x) != 0 {
m["variables"] = x
}
- return json.Marshal(m)
+ return m, nil
}
// UnmarshalJSON sets Server to a copy of data.
@@ -234,6 +243,15 @@ type ServerVariable struct {
// MarshalJSON returns the JSON encoding of ServerVariable.
func (serverVariable ServerVariable) MarshalJSON() ([]byte, error) {
+ x, err := serverVariable.MarshalYAML()
+ if err != nil {
+ return nil, err
+ }
+ return json.Marshal(x)
+}
+
+// MarshalYAML returns the YAML encoding of ServerVariable.
+func (serverVariable ServerVariable) MarshalYAML() (interface{}, error) {
m := make(map[string]interface{}, 4+len(serverVariable.Extensions))
for k, v := range serverVariable.Extensions {
m[k] = v
@@ -247,7 +265,7 @@ func (serverVariable ServerVariable) MarshalJSON() ([]byte, error) {
if x := serverVariable.Description; x != "" {
m["description"] = x
}
- return json.Marshal(m)
+ return m, nil
}
// UnmarshalJSON sets ServerVariable to a copy of data.
diff --git a/openapi3/tag.go b/openapi3/tag.go
index eea6462f5..8dc996724 100644
--- a/openapi3/tag.go
+++ b/openapi3/tag.go
@@ -42,6 +42,15 @@ type Tag struct {
// MarshalJSON returns the JSON encoding of Tag.
func (t Tag) MarshalJSON() ([]byte, error) {
+ x, err := t.MarshalYAML()
+ if err != nil {
+ return nil, err
+ }
+ return json.Marshal(x)
+}
+
+// MarshalYAML returns the YAML encoding of Tag.
+func (t Tag) MarshalYAML() (interface{}, error) {
m := make(map[string]interface{}, 3+len(t.Extensions))
for k, v := range t.Extensions {
m[k] = v
@@ -55,7 +64,7 @@ func (t Tag) MarshalJSON() ([]byte, error) {
if x := t.ExternalDocs; x != nil {
m["externalDocs"] = x
}
- return json.Marshal(m)
+ return m, nil
}
// UnmarshalJSON sets Tag to a copy of data.
diff --git a/openapi3/testdata/issue241.yml b/openapi3/testdata/issue241.yml
index 07609c1d8..1bcd73dce 100644
--- a/openapi3/testdata/issue241.yml
+++ b/openapi3/testdata/issue241.yml
@@ -1,15 +1,15 @@
-openapi: 3.0.3
components:
schemas:
FooBar:
- type: object
properties:
type_url:
type: string
value:
- type: string
format: byte
+ type: string
+ type: object
info:
title: sample
version: version not set
+openapi: 3.0.3
paths: {}
diff --git a/openapi3/testdata/issue959/components.yml b/openapi3/testdata/issue959/components.yml
new file mode 100644
index 000000000..c7095e90d
--- /dev/null
+++ b/openapi3/testdata/issue959/components.yml
@@ -0,0 +1,4 @@
+components:
+ schemas:
+ External1:
+ type: string
\ No newline at end of file
diff --git a/openapi3/testdata/issue959/openapi.yml b/openapi3/testdata/issue959/openapi.yml
new file mode 100644
index 000000000..de5bbfe48
--- /dev/null
+++ b/openapi3/testdata/issue959/openapi.yml
@@ -0,0 +1,16 @@
+openapi: 3.0.0
+info:
+ title: foo
+ version: 0.0.0
+paths:
+ /{external1}:
+ parameters:
+ - in: path
+ name: external1
+ required: true
+ schema:
+ $ref: './components.yml#/components/schemas/External1'
+ get:
+ responses:
+ '204':
+ description: No content
\ No newline at end of file
diff --git a/openapi3/testdata/issue959/openapi.yml.internalized.yml b/openapi3/testdata/issue959/openapi.yml.internalized.yml
new file mode 100644
index 000000000..3cbe674d6
--- /dev/null
+++ b/openapi3/testdata/issue959/openapi.yml.internalized.yml
@@ -0,0 +1,35 @@
+{
+ "components": {
+ "schemas": {
+ "External1": {
+ "type": "string"
+ }
+ }
+ },
+ "info": {
+ "title": "foo",
+ "version": "0.0.0"
+ },
+ "openapi": "3.0.0",
+ "paths": {
+ "/{external1}": {
+ "get": {
+ "responses": {
+ "204": {
+ "description": "No content"
+ }
+ }
+ },
+ "parameters": [
+ {
+ "in": "path",
+ "name": "external1",
+ "required": true,
+ "schema": {
+ "$ref": "#/components/schemas/External1"
+ }
+ }
+ ]
+ }
+ }
+}
\ No newline at end of file
diff --git a/openapi3/xml.go b/openapi3/xml.go
index 604b607dc..e69c1fa6d 100644
--- a/openapi3/xml.go
+++ b/openapi3/xml.go
@@ -19,6 +19,15 @@ type XML struct {
// MarshalJSON returns the JSON encoding of XML.
func (xml XML) MarshalJSON() ([]byte, error) {
+ x, err := xml.MarshalYAML()
+ if err != nil {
+ return nil, err
+ }
+ return json.Marshal(x)
+}
+
+// MarshalYAML returns the YAML encoding of XML.
+func (xml XML) MarshalYAML() (interface{}, error) {
m := make(map[string]interface{}, 5+len(xml.Extensions))
for k, v := range xml.Extensions {
m[k] = v
@@ -38,7 +47,7 @@ func (xml XML) MarshalJSON() ([]byte, error) {
if x := xml.Wrapped; x {
m["wrapped"] = x
}
- return json.Marshal(m)
+ return m, nil
}
// UnmarshalJSON sets XML to a copy of data.
diff --git a/openapi3filter/middleware.go b/openapi3filter/middleware.go
index 0009d61c4..d20889ed9 100644
--- a/openapi3filter/middleware.go
+++ b/openapi3filter/middleware.go
@@ -2,6 +2,7 @@ package openapi3filter
import (
"bytes"
+ "context"
"io"
"log"
"net/http"
@@ -19,10 +20,10 @@ type Validator struct {
}
// ErrFunc handles errors that may occur during validation.
-type ErrFunc func(w http.ResponseWriter, status int, code ErrCode, err error)
+type ErrFunc func(ctx context.Context, w http.ResponseWriter, status int, code ErrCode, err error)
// LogFunc handles log messages that may occur during validation.
-type LogFunc func(message string, err error)
+type LogFunc func(ctx context.Context, message string, err error)
// ErrCode is used for classification of different types of errors that may
// occur during validation. These may be used to write an appropriate response
@@ -61,10 +62,10 @@ func (e ErrCode) responseText() string {
func NewValidator(router routers.Router, options ...ValidatorOption) *Validator {
v := &Validator{
router: router,
- errFunc: func(w http.ResponseWriter, status int, code ErrCode, _ error) {
+ errFunc: func(_ context.Context, w http.ResponseWriter, status int, code ErrCode, _ error) {
http.Error(w, code.responseText(), status)
},
- logFunc: func(message string, err error) {
+ logFunc: func(_ context.Context, message string, err error) {
log.Printf("%s: %v", message, err)
},
}
@@ -117,10 +118,11 @@ func ValidationOptions(options Options) ValidatorOption {
// request and response validation.
func (v *Validator) Middleware(h http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ ctx := r.Context()
route, pathParams, err := v.router.FindRoute(r)
if err != nil {
- v.logFunc("validation error: failed to find route for "+r.URL.String(), err)
- v.errFunc(w, http.StatusNotFound, ErrCodeCannotFindRoute, err)
+ v.logFunc(ctx, "validation error: failed to find route for "+r.URL.String(), err)
+ v.errFunc(ctx, w, http.StatusNotFound, ErrCodeCannotFindRoute, err)
return
}
requestValidationInput := &RequestValidationInput{
@@ -129,9 +131,9 @@ func (v *Validator) Middleware(h http.Handler) http.Handler {
Route: route,
Options: &v.options,
}
- if err = ValidateRequest(r.Context(), requestValidationInput); err != nil {
- v.logFunc("invalid request", err)
- v.errFunc(w, http.StatusBadRequest, ErrCodeRequestInvalid, err)
+ if err = ValidateRequest(ctx, requestValidationInput); err != nil {
+ v.logFunc(ctx, "invalid request", err)
+ v.errFunc(ctx, w, http.StatusBadRequest, ErrCodeRequestInvalid, err)
return
}
@@ -144,22 +146,22 @@ func (v *Validator) Middleware(h http.Handler) http.Handler {
h.ServeHTTP(wr, r)
- if err = ValidateResponse(r.Context(), &ResponseValidationInput{
+ if err = ValidateResponse(ctx, &ResponseValidationInput{
RequestValidationInput: requestValidationInput,
Status: wr.statusCode(),
Header: wr.Header(),
Body: io.NopCloser(bytes.NewBuffer(wr.bodyContents())),
Options: &v.options,
}); err != nil {
- v.logFunc("invalid response", err)
+ v.logFunc(ctx, "invalid response", err)
if v.strict {
- v.errFunc(w, http.StatusInternalServerError, ErrCodeResponseInvalid, err)
+ v.errFunc(ctx, w, http.StatusInternalServerError, ErrCodeResponseInvalid, err)
}
return
}
if err = wr.flushBodyContents(); err != nil {
- v.logFunc("failed to write response", err)
+ v.logFunc(ctx, "failed to write response", err)
}
})
}
diff --git a/openapi3filter/middleware_test.go b/openapi3filter/middleware_test.go
index 1260ac54c..137228cd1 100644
--- a/openapi3filter/middleware_test.go
+++ b/openapi3filter/middleware_test.go
@@ -2,6 +2,7 @@ package openapi3filter_test
import (
"bytes"
+ "context"
"encoding/json"
"fmt"
"io"
@@ -489,7 +490,7 @@ paths:
// testing a service against its spec in development and CI. In production,
// availability may be more important than strictness.
v := openapi3filter.NewValidator(router, openapi3filter.Strict(true),
- openapi3filter.OnErr(func(w http.ResponseWriter, status int, code openapi3filter.ErrCode, err error) {
+ openapi3filter.OnErr(func(_ context.Context, w http.ResponseWriter, status int, code openapi3filter.ErrCode, err error) {
// Customize validation error responses to use JSON
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(status)
diff --git a/openapi3filter/req_resp_decoder.go b/openapi3filter/req_resp_decoder.go
index 72e9d86d9..882d2d34d 100644
--- a/openapi3filter/req_resp_decoder.go
+++ b/openapi3filter/req_resp_decoder.go
@@ -650,6 +650,10 @@ func (d *urlValuesDecoder) DecodeObject(param string, sm *openapi3.Serialization
propsFn = func(params url.Values) (map[string]string, error) {
props := make(map[string]string)
for key, values := range params {
+ if !regexp.MustCompile(fmt.Sprintf(`^%s\[`, regexp.QuoteMeta(param))).MatchString(key) {
+ continue
+ }
+
matches := regexp.MustCompile(`\[(.*?)\]`).FindAllStringSubmatch(key, -1)
switch l := len(matches); {
case l == 0:
@@ -1562,7 +1566,7 @@ func zipFileBodyDecoder(body io.Reader, header http.Header, schema *openapi3.Sch
func csvBodyDecoder(body io.Reader, header http.Header, schema *openapi3.SchemaRef, encFn EncodingFn) (interface{}, error) {
r := csv.NewReader(body)
- var content string
+ var sb strings.Builder
for {
record, err := r.Read()
if err == io.EOF {
@@ -1572,8 +1576,9 @@ func csvBodyDecoder(body io.Reader, header http.Header, schema *openapi3.SchemaR
return nil, err
}
- content += strings.Join(record, ",") + "\n"
+ sb.WriteString(strings.Join(record, ","))
+ sb.WriteString("\n")
}
- return content, nil
+ return sb.String(), nil
}
diff --git a/openapi3filter/req_resp_decoder_test.go b/openapi3filter/req_resp_decoder_test.go
index f24b24549..662636b46 100644
--- a/openapi3filter/req_resp_decoder_test.go
+++ b/openapi3filter/req_resp_decoder_test.go
@@ -998,6 +998,17 @@ func TestDecodeParameter(t *testing.T) {
query: "anotherparam=bar",
want: map[string]interface{}(nil),
},
+ {
+ name: "deepObject explode nested object - extraneous deep object param ignored",
+ param: &openapi3.Parameter{
+ Name: "param", In: "query", Style: "deepObject", Explode: explode,
+ Schema: objectOf(
+ "obj", objectOf("nestedObjOne", stringSchema, "nestedObjTwo", stringSchema),
+ ),
+ },
+ query: "anotherparam[obj][nestedObjOne]=one&anotherparam[obj][nestedObjTwo]=two",
+ want: map[string]interface{}(nil),
+ },
{
name: "deepObject explode nested object - bad array item type",
param: &openapi3.Parameter{
diff --git a/refs.sh b/refs.sh
deleted file mode 100755
index 9ece26eaa..000000000
--- a/refs.sh
+++ /dev/null
@@ -1,126 +0,0 @@
-#!/bin/bash -eux
-set -o pipefail
-
-types=()
-types+=("Callback")
-types+=("Example")
-types+=("Header")
-types+=("Link")
-types+=("Parameter")
-types+=("RequestBody")
-types+=("Response")
-types+=("Schema")
-types+=("SecurityScheme")
-
-cat <