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 < pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy