From 13c79294b6233a61380c0fb78430170eecec9c4a Mon Sep 17 00:00:00 2001 From: Chloe Kaubisch Date: Thu, 10 Mar 2022 14:16:36 +0000 Subject: [PATCH] cloudapi: validate input MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Validate incoming requests with openapi3. Remove unsupported uuid format from the openapi spec. Similarly, change url to uri as uri is a supported format and url is not. Co-authored-by: Ondřej Budai Signed-off-by: Ondřej Budai --- go.mod | 3 +- go.sum | 14 +- internal/cloudapi/v2/errors.go | 2 + internal/cloudapi/v2/openapi.v2.gen.go | 206 ++-- internal/cloudapi/v2/openapi.v2.yml | 29 +- internal/cloudapi/v2/server.go | 51 +- internal/cloudapi/v2/v2_test.go | 20 +- .../getkin/kin-openapi/jsoninfo/field_info.go | 3 + .../kin-openapi/jsoninfo/marshal_ref.go | 6 +- .../getkin/kin-openapi/openapi3/callback.go | 3 +- .../getkin/kin-openapi/openapi3/components.go | 4 +- .../kin-openapi/openapi3/discriminator.go | 4 +- .../getkin/kin-openapi/openapi3/encoding.go | 1 + .../openapi3/{examples.go => example.go} | 1 + .../kin-openapi/openapi3/external_docs.go | 22 +- .../getkin/kin-openapi/openapi3/header.go | 74 +- .../getkin/kin-openapi/openapi3/info.go | 12 +- .../kin-openapi/openapi3/internalize_refs.go | 369 ++++++ .../getkin/kin-openapi/openapi3/link.go | 6 +- .../getkin/kin-openapi/openapi3/loader.go | 62 +- .../kin-openapi/openapi3/loader_uri_reader.go | 104 ++ .../getkin/kin-openapi/openapi3/media_type.go | 1 + .../getkin/kin-openapi/openapi3/openapi3.go | 20 + .../getkin/kin-openapi/openapi3/operation.go | 7 + .../getkin/kin-openapi/openapi3/parameter.go | 43 +- .../getkin/kin-openapi/openapi3/path_item.go | 3 + .../getkin/kin-openapi/openapi3/paths.go | 109 +- .../getkin/kin-openapi/openapi3/refs.go | 19 + .../kin-openapi/openapi3/request_body.go | 13 +- .../getkin/kin-openapi/openapi3/response.go | 14 + .../getkin/kin-openapi/openapi3/schema.go | 263 ++-- .../kin-openapi/openapi3/schema_formats.go | 22 +- .../openapi3/security_requirements.go | 2 + .../kin-openapi/openapi3/security_scheme.go | 8 + .../getkin/kin-openapi/openapi3/server.go | 10 +- .../getkin/kin-openapi/openapi3/tag.go | 27 +- .../testdata/recursiveRef/components/Bar.yml | 2 + .../testdata/recursiveRef/components/Foo.yml | 4 + .../recursiveRef/components/Foo/Foo2.yml | 4 + .../testdata/recursiveRef/openapi.yml | 15 + .../testdata/recursiveRef/paths/foo.yml | 11 + .../getkin/kin-openapi/openapi3/xml.go | 31 + .../openapi3filter/authentication_input.go | 29 + .../kin-openapi/openapi3filter/errors.go | 82 ++ .../kin-openapi/openapi3filter/internal.go | 25 + .../kin-openapi/openapi3filter/middleware.go | 273 +++++ .../kin-openapi/openapi3filter/options.go | 24 + .../openapi3filter/req_resp_decoder.go | 1081 +++++++++++++++++ .../openapi3filter/validate_request.go | 332 +++++ .../openapi3filter/validate_request_input.go | 38 + .../openapi3filter/validate_response.go | 138 +++ .../openapi3filter/validate_response_input.go | 42 + .../openapi3filter/validation_error.go | 85 ++ .../validation_error_encoder.go | 185 +++ .../openapi3filter/validation_handler.go | 103 ++ .../openapi3filter/validation_kit.go | 85 ++ .../routers/legacy/pathpattern/node.go | 328 +++++ .../kin-openapi/routers/legacy/router.go | 167 +++ .../getkin/kin-openapi/routers/types.go | 42 + .../github.com/go-openapi/swag/.gitattributes | 2 + .../github.com/go-openapi/swag/.golangci.yml | 28 + vendor/github.com/go-openapi/swag/.travis.yml | 15 - vendor/github.com/go-openapi/swag/README.md | 1 - vendor/github.com/go-openapi/swag/convert.go | 16 +- .../go-openapi/swag/convert_types.go | 195 ++- vendor/github.com/go-openapi/swag/file.go | 33 + vendor/github.com/go-openapi/swag/go.mod | 14 +- vendor/github.com/go-openapi/swag/go.sum | 29 +- vendor/github.com/go-openapi/swag/json.go | 8 +- vendor/github.com/go-openapi/swag/loading.go | 42 +- .../github.com/go-openapi/swag/post_go18.go | 1 + .../github.com/go-openapi/swag/post_go19.go | 1 + vendor/github.com/go-openapi/swag/pre_go18.go | 1 + vendor/github.com/go-openapi/swag/pre_go19.go | 1 + vendor/github.com/go-openapi/swag/util.go | 6 +- vendor/github.com/josharian/intern/README.md | 5 + vendor/github.com/josharian/intern/go.mod | 3 + vendor/github.com/josharian/intern/intern.go | 44 + vendor/github.com/josharian/intern/license.md | 21 + .../github.com/mailru/easyjson/buffer/pool.go | 72 +- .../mailru/easyjson/jlexer/lexer.go | 216 ++-- .../mailru/easyjson/jwriter/writer.go | 41 +- vendor/modules.txt | 13 +- 83 files changed, 4942 insertions(+), 549 deletions(-) rename vendor/github.com/getkin/kin-openapi/openapi3/{examples.go => example.go} (93%) create mode 100644 vendor/github.com/getkin/kin-openapi/openapi3/internalize_refs.go create mode 100644 vendor/github.com/getkin/kin-openapi/openapi3/loader_uri_reader.go create mode 100644 vendor/github.com/getkin/kin-openapi/openapi3/testdata/recursiveRef/components/Bar.yml create mode 100644 vendor/github.com/getkin/kin-openapi/openapi3/testdata/recursiveRef/components/Foo.yml create mode 100644 vendor/github.com/getkin/kin-openapi/openapi3/testdata/recursiveRef/components/Foo/Foo2.yml create mode 100644 vendor/github.com/getkin/kin-openapi/openapi3/testdata/recursiveRef/openapi.yml create mode 100644 vendor/github.com/getkin/kin-openapi/openapi3/testdata/recursiveRef/paths/foo.yml create mode 100644 vendor/github.com/getkin/kin-openapi/openapi3/xml.go create mode 100644 vendor/github.com/getkin/kin-openapi/openapi3filter/authentication_input.go create mode 100644 vendor/github.com/getkin/kin-openapi/openapi3filter/errors.go create mode 100644 vendor/github.com/getkin/kin-openapi/openapi3filter/internal.go create mode 100644 vendor/github.com/getkin/kin-openapi/openapi3filter/middleware.go create mode 100644 vendor/github.com/getkin/kin-openapi/openapi3filter/options.go create mode 100644 vendor/github.com/getkin/kin-openapi/openapi3filter/req_resp_decoder.go create mode 100644 vendor/github.com/getkin/kin-openapi/openapi3filter/validate_request.go create mode 100644 vendor/github.com/getkin/kin-openapi/openapi3filter/validate_request_input.go create mode 100644 vendor/github.com/getkin/kin-openapi/openapi3filter/validate_response.go create mode 100644 vendor/github.com/getkin/kin-openapi/openapi3filter/validate_response_input.go create mode 100644 vendor/github.com/getkin/kin-openapi/openapi3filter/validation_error.go create mode 100644 vendor/github.com/getkin/kin-openapi/openapi3filter/validation_error_encoder.go create mode 100644 vendor/github.com/getkin/kin-openapi/openapi3filter/validation_handler.go create mode 100644 vendor/github.com/getkin/kin-openapi/openapi3filter/validation_kit.go create mode 100644 vendor/github.com/getkin/kin-openapi/routers/legacy/pathpattern/node.go create mode 100644 vendor/github.com/getkin/kin-openapi/routers/legacy/router.go create mode 100644 vendor/github.com/getkin/kin-openapi/routers/types.go create mode 100644 vendor/github.com/go-openapi/swag/.gitattributes delete mode 100644 vendor/github.com/go-openapi/swag/.travis.yml create mode 100644 vendor/github.com/go-openapi/swag/file.go create mode 100644 vendor/github.com/josharian/intern/README.md create mode 100644 vendor/github.com/josharian/intern/go.mod create mode 100644 vendor/github.com/josharian/intern/intern.go create mode 100644 vendor/github.com/josharian/intern/license.md diff --git a/go.mod b/go.mod index 32ec8954a..19c0cd7d6 100644 --- a/go.mod +++ b/go.mod @@ -19,7 +19,8 @@ require ( github.com/coreos/go-semver v0.3.0 github.com/coreos/go-systemd v0.0.0-20190719114852-fd7a80b32e1f github.com/deepmap/oapi-codegen v1.8.2 - github.com/getkin/kin-openapi v0.61.0 + github.com/getkin/kin-openapi v0.93.0 + github.com/go-openapi/swag v0.21.1 // indirect github.com/gobwas/glob v0.2.3 github.com/golang-jwt/jwt v3.2.2+incompatible github.com/golang/glog v1.0.0 // indirect diff --git a/go.sum b/go.sum index aa9d2ee54..52ef0c099 100644 --- a/go.sum +++ b/go.sum @@ -200,8 +200,9 @@ github.com/franela/goreq v0.0.0-20171204163338-bcd34c9993f8/go.mod h1:ZhphrRTfi2 github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4= github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= -github.com/getkin/kin-openapi v0.61.0 h1:6awGqF5nG5zkVpMsAih1QH4VgzS8phTxECUWIFo7zko= github.com/getkin/kin-openapi v0.61.0/go.mod h1:7Yn5whZr5kJi6t+kShccXS8ae1APpYTW6yheSwk8Yi4= +github.com/getkin/kin-openapi v0.93.0 h1:fc9z+9VADQla6bEb7V+dtZmr9Gj4qB6ZsD8c3pqEK0E= +github.com/getkin/kin-openapi v0.93.0/go.mod h1:LWZfzOd7PRy8GJ1dJ6mCU6tNdSfOwRac1BUPam4aw6Q= github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/go-chi/chi/v5 v5.0.0/go.mod h1:BBug9lr0cqtdAhsu6R4AAdvufI0/XBzAQSsUqJpoZOs= @@ -217,8 +218,9 @@ github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= github.com/go-openapi/jsonpointer v0.19.5 h1:gZr+CIYByUqjcgeLXnQu2gHYQC9o73G2XUeOFYEICuY= github.com/go-openapi/jsonpointer v0.19.5/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= -github.com/go-openapi/swag v0.19.5 h1:lTz6Ys4CmqqCQmZPBlbQENR1/GucA2bzYTE12Pw4tFY= github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= +github.com/go-openapi/swag v0.21.1 h1:wm0rhTb5z7qpJRHBdPOMuY4QjVUMbF6/kwoYeRAOrKU= +github.com/go-openapi/swag v0.21.1/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ= github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= @@ -337,6 +339,7 @@ github.com/gorilla/css v1.0.0 h1:BQqNyPTi50JCFMTw/b67hByjMVXZRwGha6wxVGkeihY= github.com/gorilla/css v1.0.0/go.mod h1:Dn721qIggHpt4+EFCcTLTU/vk5ySda2ReITrtgBl60c= github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= github.com/gorilla/mux v1.7.3/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= +github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI= github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= github.com/gorilla/websocket v0.0.0-20170926233335-4201258b820c/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= github.com/grpc-ecosystem/go-grpc-middleware v1.0.1-0.20190118093823-f849b5445de4/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= @@ -449,6 +452,8 @@ github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHW github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8= github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U= github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= +github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= +github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4= github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= @@ -496,8 +501,9 @@ github.com/lightstep/lightstep-tracer-common/golang/gogo v0.0.0-20190605223551-b github.com/lightstep/lightstep-tracer-go v0.18.1/go.mod h1:jlF1pusYV4pidLvZ+XD0UBX0ZE6WURAspgAczcDHrL4= github.com/lyft/protoc-gen-validate v0.0.13/go.mod h1:XbGvPuh87YZc5TdIa2/I4pLk0QoUACkjt2znoq26NVQ= github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= -github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e h1:hB2xlXdHp/pmPZq0y3QnmWAArdw9PqbmotexnWx/FU8= github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= +github.com/mailru/easyjson v0.7.6 h1:8yTIVnZgCoiM1TgqoeTl+LfU5Jg6/xL3QhGQnimLYnA= +github.com/mailru/easyjson v0.7.6/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= github.com/matryer/moq v0.0.0-20190312154309-6cfb0558e1bd/go.mod h1:9ELz6aaclSIGnZBoaSLZ3NAl1VTufbOrXBPvtcy6WiQ= github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ= @@ -549,6 +555,7 @@ github.com/nats-io/nats.go v1.9.1/go.mod h1:ZjDU1L/7fJ09jvUSRVBR2e7+RnLiiIQyqyzE github.com/nats-io/nkeys v0.1.0/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxziKVo7w= github.com/nats-io/nkeys v0.1.3/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxziKVo7w= github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c= +github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE= github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= @@ -1224,6 +1231,7 @@ gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLks gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/cheggaaa/pb.v1 v1.0.25/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qStrOgw= diff --git a/internal/cloudapi/v2/errors.go b/internal/cloudapi/v2/errors.go index bb39ddaa2..2e6b8df00 100644 --- a/internal/cloudapi/v2/errors.go +++ b/internal/cloudapi/v2/errors.go @@ -43,6 +43,7 @@ const ( ErrorInvalidOSTreeParams ServiceErrorCode = 27 ErrorTenantNotFound ServiceErrorCode = 28 ErrorNoGPGKey ServiceErrorCode = 29 + ErrorInvalidRequest ServiceErrorCode = 30 // Internal errors, these are bugs ErrorFailedToInitializeBlueprint ServiceErrorCode = 1000 @@ -110,6 +111,7 @@ func getServiceErrors() serviceErrors { serviceError{ErrorInvalidOSTreeParams, http.StatusBadRequest, "Invalid OSTree parameters or parameter combination"}, serviceError{ErrorTenantNotFound, http.StatusBadRequest, "Tenant not found in JWT claims"}, serviceError{ErrorNoGPGKey, http.StatusBadRequest, "Invalid repository, when check_gpg is set, gpgkey must be specified"}, + serviceError{ErrorInvalidRequest, http.StatusBadRequest, "Request could not be validated"}, serviceError{ErrorFailedToInitializeBlueprint, http.StatusInternalServerError, "Failed to initialize blueprint"}, serviceError{ErrorFailedToGenerateManifestSeed, http.StatusInternalServerError, "Failed to generate manifest seed"}, diff --git a/internal/cloudapi/v2/openapi.v2.gen.go b/internal/cloudapi/v2/openapi.v2.gen.go index 92b05a862..200d1ae07 100644 --- a/internal/cloudapi/v2/openapi.v2.gen.go +++ b/internal/cloudapi/v2/openapi.v2.gen.go @@ -351,6 +351,7 @@ type List struct { // OSTree defines model for OSTree. type OSTree struct { + // Can be either a commit (example: 02604b2da6e954bd34b8b82a835e5a77d2b60ffa), or a branch-like reference (example: rhel/8/x86_64/edge) Parent *string `json:"parent,omitempty"` Ref *string `json:"ref,omitempty"` Url *string `json:"url,omitempty"` @@ -661,108 +662,109 @@ func RegisterHandlersWithBaseURL(router EchoRouter, si ServerInterface, baseURL // Base64 encoded, gzipped, json marshaled Swagger object var swaggerSpec = []string{ - "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", + "w1eRWQAA", } // GetSwagger returns the content of the embedded swagger specification file diff --git a/internal/cloudapi/v2/openapi.v2.yml b/internal/cloudapi/v2/openapi.v2.yml index c168cb689..b488e1f84 100644 --- a/internal/cloudapi/v2/openapi.v2.yml +++ b/internal/cloudapi/v2/openapi.v2.yml @@ -279,6 +279,7 @@ paths: description: ID of the error responses: '200': + description: Error description content: application/json: schema: @@ -445,6 +446,9 @@ components: properties: image_builds: type: array + items: + type: object + x-go-type: interface{} koji: $ref: '#/components/schemas/KojiLogs' KojiLogs: @@ -464,6 +468,9 @@ components: properties: manifests: type: array + items: + type: object + x-go-type: interface{} ImageStatus: required: - status @@ -659,15 +666,15 @@ components: description: 'Determines whether a valid subscription is required to access this repository.' baseurl: type: string - format: url + format: uri example: 'https://cdn.redhat.com/content/dist/rhel8/8/x86_64/baseos/os/' mirrorlist: type: string - format: url + format: uri example: 'http://mirrorlist.centos.org/?release=8-stream&arch=aarch64&repo=BaseOS' metalink: type: string - format: url + format: uri example: 'https://mirrors.fedoraproject.org/metalink?repo=fedora-32&arch=x86_64' gpgkey: type: string @@ -865,13 +872,11 @@ components: example: 'rhel/8/x86_64/edge' parent: type: string - examples: - commit_id: - value: '02604b2da6e954bd34b8b82a835e5a77d2b60ffa' - summary: A commit ID - ref: - value: 'rhel/8/x86_64/edge' - summary: A branch-like ref + description: > + Can be either a commit (example: + 02604b2da6e954bd34b8b82a835e5a77d2b60ffa), or a branch-like + reference (example: rhel/8/x86_64/edge) + example: 'rhel/8/x86_64/edge' Subscription: type: object required: @@ -890,10 +895,10 @@ components: example: 'my-secret-key' server_url: type: string + format: uri example: 'subscription.rhsm.redhat.com' base_url: type: string - format: url example: 'http://cdn.redhat.com/' insights: type: boolean @@ -925,7 +930,7 @@ components: properties: server: type: string - format: url + format: uri example: 'https://koji.fedoraproject.org/kojihub' task_id: type: integer diff --git a/internal/cloudapi/v2/server.go b/internal/cloudapi/v2/server.go index a948362c3..859361c6d 100644 --- a/internal/cloudapi/v2/server.go +++ b/internal/cloudapi/v2/server.go @@ -8,6 +8,10 @@ import ( "sync" "time" + "github.com/getkin/kin-openapi/openapi3" + "github.com/getkin/kin-openapi/openapi3filter" + "github.com/getkin/kin-openapi/routers" + legacyrouter "github.com/getkin/kin-openapi/routers/legacy" "github.com/google/uuid" "github.com/labstack/echo/v4" "github.com/labstack/echo/v4/middleware" @@ -30,6 +34,7 @@ type Server struct { workers *worker.Server distros *distroregistry.Registry config ServerConfig + router routers.Router goroutinesCtx context.Context goroutinesCtxCancel context.CancelFunc @@ -44,10 +49,26 @@ type ServerConfig struct { func NewServer(workers *worker.Server, distros *distroregistry.Registry, config ServerConfig) *Server { ctx, cancel := context.WithCancel(context.Background()) + spec, err := GetSwagger() + if err != nil { + panic(err) + } + + loader := openapi3.NewLoader() + if err := spec.Validate(loader.Context); err != nil { + panic(err) + } + + router, err := legacyrouter.NewRouter(spec) + if err != nil { + panic(err) + } + server := &Server{ workers: workers, distros: distros, config: config, + router: router, goroutinesCtx: ctx, goroutinesCtxCancel: cancel, @@ -66,11 +87,39 @@ func (s *Server) Handler(path string) http.Handler { handler := apiHandlers{ server: s, } - RegisterHandlers(e.Group(path, prometheus.MetricsMiddleware), &handler) + RegisterHandlers(e.Group(path, prometheus.MetricsMiddleware, s.ValidateRequest), &handler) return e } +func (s *Server) ValidateRequest(next echo.HandlerFunc) echo.HandlerFunc { + return func(c echo.Context) error { + request := c.Request() + + // extract route and parameters from request + route, params, err := s.router.FindRoute(request) + if err != nil { + return HTTPErrorWithInternal(ErrorResourceNotFound, err) + } + + input := &openapi3filter.RequestValidationInput{ + Request: request, + PathParams: params, + Route: route, + Options: &openapi3filter.Options{ + AuthenticationFunc: openapi3filter.NoopAuthenticationFunc, + }, + } + + ctx := request.Context() + if err := openapi3filter.ValidateRequest(ctx, input); err != nil { + return HTTPErrorWithInternal(ErrorInvalidRequest, err) + } + + return next(c) + } +} + func (s *Server) Shutdown() { s.goroutinesCtxCancel() s.goroutinesGroup.Wait() diff --git a/internal/cloudapi/v2/v2_test.go b/internal/cloudapi/v2/v2_test.go index 81a748b1a..769b33ef8 100644 --- a/internal/cloudapi/v2/v2_test.go +++ b/internal/cloudapi/v2/v2_test.go @@ -159,11 +159,11 @@ func TestCompose(t *testing.T) { } }`, test_distro.TestArch3Name), http.StatusBadRequest, ` { - "href": "/api/image-builder-composer/v2/errors/4", - "id": "4", + "href": "/api/image-builder-composer/v2/errors/30", + "id": "30", "kind": "Error", - "code": "IMAGE-BUILDER-COMPOSER-4", - "reason": "Unsupported distribution" + "code": "IMAGE-BUILDER-COMPOSER-30", + "reason": "Request could not be validated" }`, "operation_id") // unsupported architecture @@ -207,11 +207,11 @@ func TestCompose(t *testing.T) { } }`, test_distro.TestDistroName, test_distro.TestArch3Name), http.StatusBadRequest, ` { - "href": "/api/image-builder-composer/v2/errors/6", - "id": "6", + "href": "/api/image-builder-composer/v2/errors/30", + "id": "30", "kind": "Error", - "code": "IMAGE-BUILDER-COMPOSER-6", - "reason": "Unsupported image type" + "code": "IMAGE-BUILDER-COMPOSER-30", + "reason": "Request could not be validated" }`, "operation_id") // Returns 404, but should be 405; see https://github.com/labstack/echo/issues/1981 @@ -945,7 +945,9 @@ func TestImageTypes(t *testing.T) { "rhsm": false }], "upload_options": { - "region": "eu-central-1" + "region": "eu-central-1", + "snapshot_name": "name", + "share_with_accounts": ["123456789012","234567890123"] } } }`, test_distro.TestDistroName, test_distro.TestArch3Name, string(v2.ImageTypesAws)), http.StatusCreated, ` diff --git a/vendor/github.com/getkin/kin-openapi/jsoninfo/field_info.go b/vendor/github.com/getkin/kin-openapi/jsoninfo/field_info.go index d2ad505bd..2382b731c 100644 --- a/vendor/github.com/getkin/kin-openapi/jsoninfo/field_info.go +++ b/vendor/github.com/getkin/kin-openapi/jsoninfo/field_info.go @@ -21,6 +21,9 @@ type FieldInfo struct { } func AppendFields(fields []FieldInfo, parentIndex []int, t reflect.Type) []FieldInfo { + if t.Kind() == reflect.Ptr { + t = t.Elem() + } // For each field numField := t.NumField() iteration: diff --git a/vendor/github.com/getkin/kin-openapi/jsoninfo/marshal_ref.go b/vendor/github.com/getkin/kin-openapi/jsoninfo/marshal_ref.go index 9738bf08f..29575e9e9 100644 --- a/vendor/github.com/getkin/kin-openapi/jsoninfo/marshal_ref.go +++ b/vendor/github.com/getkin/kin-openapi/jsoninfo/marshal_ref.go @@ -5,7 +5,7 @@ import ( ) func MarshalRef(value string, otherwise interface{}) ([]byte, error) { - if len(value) > 0 { + if value != "" { return json.Marshal(&refProps{ Ref: value, }) @@ -17,7 +17,7 @@ func UnmarshalRef(data []byte, destRef *string, destOtherwise interface{}) error refProps := &refProps{} if err := json.Unmarshal(data, refProps); err == nil { ref := refProps.Ref - if len(ref) > 0 { + if ref != "" { *destRef = ref return nil } @@ -26,5 +26,5 @@ func UnmarshalRef(data []byte, destRef *string, destOtherwise interface{}) error } type refProps struct { - Ref string `json:"$ref,omitempty"` + Ref string `json:"$ref,omitempty" yaml:"$ref,omitempty"` } diff --git a/vendor/github.com/getkin/kin-openapi/openapi3/callback.go b/vendor/github.com/getkin/kin-openapi/openapi3/callback.go index 8995e4792..5f883c1c9 100644 --- a/vendor/github.com/getkin/kin-openapi/openapi3/callback.go +++ b/vendor/github.com/getkin/kin-openapi/openapi3/callback.go @@ -23,7 +23,8 @@ func (c Callbacks) JSONLookup(token string) (interface{}, error) { return ref.Value, nil } -// Callback is specified by OpenAPI/Swagger standard version 3.0. +// Callback is specified by OpenAPI/Swagger standard version 3. +// See https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#callbackObject type Callback map[string]*PathItem func (value Callback) Validate(ctx context.Context) error { diff --git a/vendor/github.com/getkin/kin-openapi/openapi3/components.go b/vendor/github.com/getkin/kin-openapi/openapi3/components.go index 7acafabf9..42af634d6 100644 --- a/vendor/github.com/getkin/kin-openapi/openapi3/components.go +++ b/vendor/github.com/getkin/kin-openapi/openapi3/components.go @@ -8,9 +8,11 @@ import ( "github.com/getkin/kin-openapi/jsoninfo" ) -// Components is specified by OpenAPI/Swagger standard version 3.0. +// Components is specified by OpenAPI/Swagger standard version 3. +// See https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#componentsObject type Components struct { ExtensionProps + Schemas Schemas `json:"schemas,omitempty" yaml:"schemas,omitempty"` Parameters ParametersMap `json:"parameters,omitempty" yaml:"parameters,omitempty"` Headers Headers `json:"headers,omitempty" yaml:"headers,omitempty"` diff --git a/vendor/github.com/getkin/kin-openapi/openapi3/discriminator.go b/vendor/github.com/getkin/kin-openapi/openapi3/discriminator.go index 82ad7040b..5e181a291 100644 --- a/vendor/github.com/getkin/kin-openapi/openapi3/discriminator.go +++ b/vendor/github.com/getkin/kin-openapi/openapi3/discriminator.go @@ -6,9 +6,11 @@ import ( "github.com/getkin/kin-openapi/jsoninfo" ) -// Discriminator is specified by OpenAPI/Swagger standard version 3.0. +// Discriminator is specified by OpenAPI/Swagger standard version 3. +// See https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#discriminatorObject type Discriminator struct { ExtensionProps + PropertyName string `json:"propertyName" yaml:"propertyName"` Mapping map[string]string `json:"mapping,omitempty" yaml:"mapping,omitempty"` } diff --git a/vendor/github.com/getkin/kin-openapi/openapi3/encoding.go b/vendor/github.com/getkin/kin-openapi/openapi3/encoding.go index ad48b9160..b0dab7be0 100644 --- a/vendor/github.com/getkin/kin-openapi/openapi3/encoding.go +++ b/vendor/github.com/getkin/kin-openapi/openapi3/encoding.go @@ -8,6 +8,7 @@ import ( ) // Encoding is specified by OpenAPI/Swagger 3.0 standard. +// See https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#encodingObject type Encoding struct { ExtensionProps diff --git a/vendor/github.com/getkin/kin-openapi/openapi3/examples.go b/vendor/github.com/getkin/kin-openapi/openapi3/example.go similarity index 93% rename from vendor/github.com/getkin/kin-openapi/openapi3/examples.go rename to vendor/github.com/getkin/kin-openapi/openapi3/example.go index f7f90ce54..19cceb4d9 100644 --- a/vendor/github.com/getkin/kin-openapi/openapi3/examples.go +++ b/vendor/github.com/getkin/kin-openapi/openapi3/example.go @@ -25,6 +25,7 @@ func (e Examples) JSONLookup(token string) (interface{}, error) { } // Example is specified by OpenAPI/Swagger 3.0 standard. +// See https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#exampleObject type Example struct { ExtensionProps diff --git a/vendor/github.com/getkin/kin-openapi/openapi3/external_docs.go b/vendor/github.com/getkin/kin-openapi/openapi3/external_docs.go index 5a1476bde..bb9dd5a89 100644 --- a/vendor/github.com/getkin/kin-openapi/openapi3/external_docs.go +++ b/vendor/github.com/getkin/kin-openapi/openapi3/external_docs.go @@ -1,15 +1,21 @@ package openapi3 import ( + "context" + "errors" + "fmt" + "net/url" + "github.com/getkin/kin-openapi/jsoninfo" ) -// ExternalDocs is specified by OpenAPI/Swagger standard version 3.0. +// ExternalDocs is specified by OpenAPI/Swagger standard version 3. +// See https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#external-documentation-object type ExternalDocs struct { ExtensionProps - Description string `json:"description,omitempty"` - URL string `json:"url,omitempty"` + Description string `json:"description,omitempty" yaml:"description,omitempty"` + URL string `json:"url,omitempty" yaml:"url,omitempty"` } func (e *ExternalDocs) MarshalJSON() ([]byte, error) { @@ -19,3 +25,13 @@ func (e *ExternalDocs) MarshalJSON() ([]byte, error) { func (e *ExternalDocs) UnmarshalJSON(data []byte) error { return jsoninfo.UnmarshalStrictStruct(data, e) } + +func (e *ExternalDocs) Validate(ctx context.Context) error { + if e.URL == "" { + return errors.New("url is required") + } + if _, err := url.Parse(e.URL); err != nil { + return fmt.Errorf("url is incorrect: %w", err) + } + return nil +} diff --git a/vendor/github.com/getkin/kin-openapi/openapi3/header.go b/vendor/github.com/getkin/kin-openapi/openapi3/header.go index 7cf61f8c6..9adb5ac35 100644 --- a/vendor/github.com/getkin/kin-openapi/openapi3/header.go +++ b/vendor/github.com/getkin/kin-openapi/openapi3/header.go @@ -2,6 +2,7 @@ package openapi3 import ( "context" + "errors" "fmt" "github.com/getkin/kin-openapi/jsoninfo" @@ -24,17 +25,10 @@ func (h Headers) JSONLookup(token string) (interface{}, error) { return ref.Value, nil } +// Header is specified by OpenAPI/Swagger 3.0 standard. +// See https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#headerObject type Header struct { - ExtensionProps - - // Optional description. Should use CommonMark syntax. - Description string `json:"description,omitempty" yaml:"description,omitempty"` - Deprecated bool `json:"deprecated,omitempty" yaml:"deprecated,omitempty"` - Required bool `json:"required,omitempty" yaml:"required,omitempty"` - Schema *SchemaRef `json:"schema,omitempty" yaml:"schema,omitempty"` - Example interface{} `json:"example,omitempty" yaml:"example,omitempty"` - Examples Examples `json:"examples,omitempty" yaml:"examples,omitempty"` - Content Content `json:"content,omitempty" yaml:"content,omitempty"` + Parameter } var _ jsonpointer.JSONPointable = (*Header)(nil) @@ -43,10 +37,52 @@ func (value *Header) UnmarshalJSON(data []byte) error { return jsoninfo.UnmarshalStrictStruct(data, value) } +// SerializationMethod returns a header's serialization method. +func (value *Header) SerializationMethod() (*SerializationMethod, error) { + style := value.Style + if style == "" { + style = SerializationSimple + } + explode := false + if value.Explode != nil { + explode = *value.Explode + } + return &SerializationMethod{Style: style, Explode: explode}, nil +} + func (value *Header) Validate(ctx context.Context) error { - if v := value.Schema; v != nil { - if err := v.Validate(ctx); err != nil { - return err + if value.Name != "" { + return errors.New("header 'name' MUST NOT be specified, it is given in the corresponding headers map") + } + if value.In != "" { + return errors.New("header 'in' MUST NOT be specified, it is implicitly in header") + } + + // Validate a parameter's serialization method. + sm, err := value.SerializationMethod() + if err != nil { + return err + } + if smSupported := false || + sm.Style == SerializationSimple && !sm.Explode || + sm.Style == SerializationSimple && sm.Explode; !smSupported { + e := fmt.Errorf("serialization method with style=%q and explode=%v is not supported by a header parameter", sm.Style, sm.Explode) + return fmt.Errorf("header schema is invalid: %v", e) + } + + if (value.Schema == nil) == (value.Content == nil) { + e := fmt.Errorf("parameter must contain exactly one of content and schema: %v", value) + return fmt.Errorf("header schema is invalid: %v", e) + } + if schema := value.Schema; schema != nil { + if err := schema.Validate(ctx); err != nil { + return fmt.Errorf("header schema is invalid: %v", err) + } + } + + if content := value.Content; content != nil { + if err := content.Validate(ctx); err != nil { + return fmt.Errorf("header content is invalid: %v", err) } } return nil @@ -61,8 +97,20 @@ func (value Header) JSONLookup(token string) (interface{}, error) { } return value.Schema.Value, nil } + case "name": + return value.Name, nil + case "in": + return value.In, nil case "description": return value.Description, nil + case "style": + return value.Style, nil + case "explode": + return value.Explode, nil + case "allowEmptyValue": + return value.AllowEmptyValue, nil + case "allowReserved": + return value.AllowReserved, nil case "deprecated": return value.Deprecated, nil case "required": diff --git a/vendor/github.com/getkin/kin-openapi/openapi3/info.go b/vendor/github.com/getkin/kin-openapi/openapi3/info.go index 2adffff1a..6b41589b5 100644 --- a/vendor/github.com/getkin/kin-openapi/openapi3/info.go +++ b/vendor/github.com/getkin/kin-openapi/openapi3/info.go @@ -7,9 +7,11 @@ import ( "github.com/getkin/kin-openapi/jsoninfo" ) -// Info is specified by OpenAPI/Swagger standard version 3.0. +// Info is specified by OpenAPI/Swagger standard version 3. +// See https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#infoObject type Info struct { ExtensionProps + Title string `json:"title" yaml:"title"` // Required Description string `json:"description,omitempty" yaml:"description,omitempty"` TermsOfService string `json:"termsOfService,omitempty" yaml:"termsOfService,omitempty"` @@ -50,9 +52,11 @@ func (value *Info) Validate(ctx context.Context) error { return nil } -// Contact is specified by OpenAPI/Swagger standard version 3.0. +// Contact is specified by OpenAPI/Swagger standard version 3. +// See https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#contactObject type Contact struct { ExtensionProps + Name string `json:"name,omitempty" yaml:"name,omitempty"` URL string `json:"url,omitempty" yaml:"url,omitempty"` Email string `json:"email,omitempty" yaml:"email,omitempty"` @@ -70,9 +74,11 @@ func (value *Contact) Validate(ctx context.Context) error { return nil } -// License is specified by OpenAPI/Swagger standard version 3.0. +// License is specified by OpenAPI/Swagger standard version 3. +// See https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#licenseObject type License struct { ExtensionProps + Name string `json:"name" yaml:"name"` // Required URL string `json:"url,omitempty" yaml:"url,omitempty"` } diff --git a/vendor/github.com/getkin/kin-openapi/openapi3/internalize_refs.go b/vendor/github.com/getkin/kin-openapi/openapi3/internalize_refs.go new file mode 100644 index 000000000..3a993bfb4 --- /dev/null +++ b/vendor/github.com/getkin/kin-openapi/openapi3/internalize_refs.go @@ -0,0 +1,369 @@ +package openapi3 + +import ( + "context" + "path/filepath" + "strings" +) + +type RefNameResolver func(string) string + +// DefaultRefResolver is a default implementation of refNameResolver for the +// InternalizeRefs function. +// +// If a reference points to an element inside a document, it returns the last +// element in the reference using filepath.Base. Otherwise if the reference points +// to a file, it returns the file name trimmed of all extensions. +func DefaultRefNameResolver(ref string) string { + if ref == "" { + return "" + } + split := strings.SplitN(ref, "#", 2) + if len(split) == 2 { + return filepath.Base(split[1]) + } + ref = split[0] + for ext := filepath.Ext(ref); len(ext) > 0; ext = filepath.Ext(ref) { + ref = strings.TrimSuffix(ref, ext) + } + return filepath.Base(ref) +} + +func schemaNames(s Schemas) []string { + out := make([]string, 0, len(s)) + for i := range s { + out = append(out, i) + } + return out +} + +func parametersMapNames(s ParametersMap) []string { + out := make([]string, 0, len(s)) + for i := range s { + out = append(out, i) + } + return out +} + +func isExternalRef(ref string) bool { + return ref != "" && !strings.HasPrefix(ref, "#/components/") +} + +func (doc *T) addSchemaToSpec(s *SchemaRef, refNameResolver RefNameResolver) { + if s == nil || !isExternalRef(s.Ref) { + return + } + + name := refNameResolver(s.Ref) + if _, ok := doc.Components.Schemas[name]; ok { + s.Ref = "#/components/schemas/" + name + return + } + + if doc.Components.Schemas == nil { + doc.Components.Schemas = make(Schemas) + } + doc.Components.Schemas[name] = s.Value.NewRef() + s.Ref = "#/components/schemas/" + name +} + +func (doc *T) addParameterToSpec(p *ParameterRef, refNameResolver RefNameResolver) { + if p == nil || !isExternalRef(p.Ref) { + return + } + name := refNameResolver(p.Ref) + if _, ok := doc.Components.Parameters[name]; ok { + p.Ref = "#/components/parameters/" + name + return + } + + if doc.Components.Parameters == nil { + doc.Components.Parameters = make(ParametersMap) + } + doc.Components.Parameters[name] = &ParameterRef{Value: p.Value} + p.Ref = "#/components/parameters/" + name +} + +func (doc *T) addHeaderToSpec(h *HeaderRef, refNameResolver RefNameResolver) { + if h == nil || !isExternalRef(h.Ref) { + return + } + name := refNameResolver(h.Ref) + if _, ok := doc.Components.Headers[name]; ok { + h.Ref = "#/components/headers/" + name + return + } + if doc.Components.Headers == nil { + doc.Components.Headers = make(Headers) + } + doc.Components.Headers[name] = &HeaderRef{Value: h.Value} + h.Ref = "#/components/headers/" + name +} + +func (doc *T) addRequestBodyToSpec(r *RequestBodyRef, refNameResolver RefNameResolver) { + if r == nil || !isExternalRef(r.Ref) { + return + } + name := refNameResolver(r.Ref) + if _, ok := doc.Components.RequestBodies[name]; ok { + r.Ref = "#/components/requestBodies/" + name + return + } + if doc.Components.RequestBodies == nil { + doc.Components.RequestBodies = make(RequestBodies) + } + doc.Components.RequestBodies[name] = &RequestBodyRef{Value: r.Value} + r.Ref = "#/components/requestBodies/" + name +} + +func (doc *T) addResponseToSpec(r *ResponseRef, refNameResolver RefNameResolver) { + if r == nil || !isExternalRef(r.Ref) { + return + } + name := refNameResolver(r.Ref) + if _, ok := doc.Components.Responses[name]; ok { + r.Ref = "#/components/responses/" + name + return + } + if doc.Components.Responses == nil { + doc.Components.Responses = make(Responses) + } + doc.Components.Responses[name] = &ResponseRef{Value: r.Value} + r.Ref = "#/components/responses/" + name + +} + +func (doc *T) addSecuritySchemeToSpec(ss *SecuritySchemeRef, refNameResolver RefNameResolver) { + if ss == nil || !isExternalRef(ss.Ref) { + return + } + name := refNameResolver(ss.Ref) + if _, ok := doc.Components.SecuritySchemes[name]; ok { + ss.Ref = "#/components/securitySchemes/" + name + return + } + if doc.Components.SecuritySchemes == nil { + doc.Components.SecuritySchemes = make(SecuritySchemes) + } + doc.Components.SecuritySchemes[name] = &SecuritySchemeRef{Value: ss.Value} + ss.Ref = "#/components/securitySchemes/" + name + +} + +func (doc *T) addExampleToSpec(e *ExampleRef, refNameResolver RefNameResolver) { + if e == nil || !isExternalRef(e.Ref) { + return + } + name := refNameResolver(e.Ref) + if _, ok := doc.Components.Examples[name]; ok { + e.Ref = "#/components/examples/" + name + return + } + if doc.Components.Examples == nil { + doc.Components.Examples = make(Examples) + } + doc.Components.Examples[name] = &ExampleRef{Value: e.Value} + e.Ref = "#/components/examples/" + name + +} + +func (doc *T) addLinkToSpec(l *LinkRef, refNameResolver RefNameResolver) { + if l == nil || !isExternalRef(l.Ref) { + return + } + name := refNameResolver(l.Ref) + if _, ok := doc.Components.Links[name]; ok { + l.Ref = "#/components/links/" + name + return + } + if doc.Components.Links == nil { + doc.Components.Links = make(Links) + } + doc.Components.Links[name] = &LinkRef{Value: l.Value} + l.Ref = "#/components/links/" + name + +} + +func (doc *T) addCallbackToSpec(c *CallbackRef, refNameResolver RefNameResolver) { + if c == nil || !isExternalRef(c.Ref) { + return + } + name := refNameResolver(c.Ref) + if _, ok := doc.Components.Callbacks[name]; ok { + c.Ref = "#/components/callbacks/" + name + } + if doc.Components.Callbacks == nil { + doc.Components.Callbacks = make(Callbacks) + } + doc.Components.Callbacks[name] = &CallbackRef{Value: c.Value} + c.Ref = "#/components/callbacks/" + name +} + +func (doc *T) derefSchema(s *Schema, refNameResolver RefNameResolver) { + if s == nil { + return + } + + for _, list := range []SchemaRefs{s.AllOf, s.AnyOf, s.OneOf} { + for _, s2 := range list { + doc.addSchemaToSpec(s2, refNameResolver) + if s2 != nil { + doc.derefSchema(s2.Value, refNameResolver) + } + } + } + for _, s2 := range s.Properties { + doc.addSchemaToSpec(s2, refNameResolver) + if s2 != nil { + doc.derefSchema(s2.Value, refNameResolver) + } + } + for _, ref := range []*SchemaRef{s.Not, s.AdditionalProperties, s.Items} { + doc.addSchemaToSpec(ref, refNameResolver) + if ref != nil { + doc.derefSchema(ref.Value, refNameResolver) + } + } +} + +func (doc *T) derefHeaders(hs Headers, refNameResolver RefNameResolver) { + for _, h := range hs { + doc.addHeaderToSpec(h, refNameResolver) + doc.derefParameter(h.Value.Parameter, refNameResolver) + } +} + +func (doc *T) derefExamples(es Examples, refNameResolver RefNameResolver) { + for _, e := range es { + doc.addExampleToSpec(e, refNameResolver) + } +} + +func (doc *T) derefContent(c Content, refNameResolver RefNameResolver) { + for _, mediatype := range c { + doc.addSchemaToSpec(mediatype.Schema, refNameResolver) + if mediatype.Schema != nil { + doc.derefSchema(mediatype.Schema.Value, refNameResolver) + } + doc.derefExamples(mediatype.Examples, refNameResolver) + for _, e := range mediatype.Encoding { + doc.derefHeaders(e.Headers, refNameResolver) + } + } +} + +func (doc *T) derefLinks(ls Links, refNameResolver RefNameResolver) { + for _, l := range ls { + doc.addLinkToSpec(l, refNameResolver) + } +} + +func (doc *T) derefResponses(es Responses, refNameResolver RefNameResolver) { + for _, e := range es { + doc.addResponseToSpec(e, refNameResolver) + if e.Value != nil { + doc.derefHeaders(e.Value.Headers, refNameResolver) + doc.derefContent(e.Value.Content, refNameResolver) + doc.derefLinks(e.Value.Links, refNameResolver) + } + } +} + +func (doc *T) derefParameter(p Parameter, refNameResolver RefNameResolver) { + doc.addSchemaToSpec(p.Schema, refNameResolver) + doc.derefContent(p.Content, refNameResolver) + if p.Schema != nil { + doc.derefSchema(p.Schema.Value, refNameResolver) + } +} + +func (doc *T) derefRequestBody(r RequestBody, refNameResolver RefNameResolver) { + doc.derefContent(r.Content, refNameResolver) +} + +func (doc *T) derefPaths(paths map[string]*PathItem, refNameResolver RefNameResolver) { + for _, ops := range paths { + // inline full operations + ops.Ref = "" + + for _, op := range ops.Operations() { + doc.addRequestBodyToSpec(op.RequestBody, refNameResolver) + if op.RequestBody != nil && op.RequestBody.Value != nil { + doc.derefRequestBody(*op.RequestBody.Value, refNameResolver) + } + for _, cb := range op.Callbacks { + doc.addCallbackToSpec(cb, refNameResolver) + if cb.Value != nil { + doc.derefPaths(*cb.Value, refNameResolver) + } + } + doc.derefResponses(op.Responses, refNameResolver) + for _, param := range op.Parameters { + doc.addParameterToSpec(param, refNameResolver) + if param.Value != nil { + doc.derefParameter(*param.Value, refNameResolver) + } + } + } + } +} + +// InternalizeRefs removes all references to external files from the spec and moves them +// to the components section. +// +// refNameResolver takes in references to returns a name to store the reference under locally. +// It MUST return a unique name for each reference type. +// A default implementation is provided that will suffice for most use cases. See the function +// documention for more details. +// +// Example: +// +// doc.InternalizeRefs(context.Background(), nil) +func (doc *T) InternalizeRefs(ctx context.Context, refNameResolver func(ref string) string) { + if refNameResolver == nil { + refNameResolver = DefaultRefNameResolver + } + + // Handle components section + names := schemaNames(doc.Components.Schemas) + for _, name := range names { + schema := doc.Components.Schemas[name] + doc.addSchemaToSpec(schema, refNameResolver) + if schema != nil { + schema.Ref = "" // always dereference the top level + doc.derefSchema(schema.Value, refNameResolver) + } + } + names = parametersMapNames(doc.Components.Parameters) + for _, name := range names { + p := doc.Components.Parameters[name] + doc.addParameterToSpec(p, refNameResolver) + if p != nil && p.Value != nil { + p.Ref = "" // always dereference the top level + doc.derefParameter(*p.Value, refNameResolver) + } + } + doc.derefHeaders(doc.Components.Headers, refNameResolver) + for _, req := range doc.Components.RequestBodies { + doc.addRequestBodyToSpec(req, refNameResolver) + if req != nil && req.Value != nil { + req.Ref = "" // always dereference the top level + doc.derefRequestBody(*req.Value, refNameResolver) + } + } + doc.derefResponses(doc.Components.Responses, refNameResolver) + for _, ss := range doc.Components.SecuritySchemes { + doc.addSecuritySchemeToSpec(ss, refNameResolver) + } + doc.derefExamples(doc.Components.Examples, refNameResolver) + doc.derefLinks(doc.Components.Links, refNameResolver) + for _, cb := range doc.Components.Callbacks { + doc.addCallbackToSpec(cb, refNameResolver) + if cb != nil && cb.Value != nil { + cb.Ref = "" // always dereference the top level + doc.derefPaths(*cb.Value, refNameResolver) + } + } + + doc.derefPaths(doc.Paths, refNameResolver) +} diff --git a/vendor/github.com/getkin/kin-openapi/openapi3/link.go b/vendor/github.com/getkin/kin-openapi/openapi3/link.go index 7d627b8bc..19a725a86 100644 --- a/vendor/github.com/getkin/kin-openapi/openapi3/link.go +++ b/vendor/github.com/getkin/kin-openapi/openapi3/link.go @@ -25,11 +25,13 @@ func (l Links) JSONLookup(token string) (interface{}, error) { var _ jsonpointer.JSONPointable = (*Links)(nil) -// Link is specified by OpenAPI/Swagger standard version 3.0. +// Link is specified by OpenAPI/Swagger standard version 3. +// See https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#linkObject type Link struct { ExtensionProps - OperationID string `json:"operationId,omitempty" yaml:"operationId,omitempty"` + OperationRef string `json:"operationRef,omitempty" yaml:"operationRef,omitempty"` + OperationID string `json:"operationId,omitempty" yaml:"operationId,omitempty"` Description string `json:"description,omitempty" yaml:"description,omitempty"` Parameters map[string]interface{} `json:"parameters,omitempty" yaml:"parameters,omitempty"` Server *Server `json:"server,omitempty" yaml:"server,omitempty"` diff --git a/vendor/github.com/getkin/kin-openapi/openapi3/loader.go b/vendor/github.com/getkin/kin-openapi/openapi3/loader.go index ff931825f..8af733c3b 100644 --- a/vendor/github.com/getkin/kin-openapi/openapi3/loader.go +++ b/vendor/github.com/getkin/kin-openapi/openapi3/loader.go @@ -5,8 +5,6 @@ import ( "encoding/json" "errors" "fmt" - "io/ioutil" - "net/http" "net/url" "path" "path/filepath" @@ -31,10 +29,12 @@ type Loader struct { IsExternalRefsAllowed bool // ReadFromURIFunc allows overriding the any file/URL reading func - ReadFromURIFunc func(loader *Loader, url *url.URL) ([]byte, error) + ReadFromURIFunc ReadFromURIFunc Context context.Context + rootDir string + visitedPathItemRefs map[string]struct{} visitedDocuments map[string]*T @@ -66,6 +66,7 @@ func (loader *Loader) LoadFromURI(location *url.URL) (*T, error) { // LoadFromFile loads a spec from a local file path func (loader *Loader) LoadFromFile(location string) (*T, error) { + loader.rootDir = path.Dir(location) return loader.LoadFromURI(&url.URL{Path: filepath.ToSlash(location)}) } @@ -118,22 +119,7 @@ func (loader *Loader) readURL(location *url.URL) ([]byte, error) { if f := loader.ReadFromURIFunc; f != nil { return f(loader, location) } - - if location.Scheme != "" && location.Host != "" { - resp, err := http.Get(location.String()) - if err != nil { - return nil, err - } - defer resp.Body.Close() - if resp.StatusCode > 399 { - return nil, fmt.Errorf("error loading %q: request returned status code %d", location.String(), resp.StatusCode) - } - return ioutil.ReadAll(resp.Body) - } - if location.Scheme != "" || location.Host != "" || location.RawQuery != "" { - return nil, fmt.Errorf("unsupported URI: %q", location.String()) - } - return ioutil.ReadFile(location.Path) + return DefaultReadFromURI(loader, location) } // LoadFromData loads a spec from a byte array @@ -305,6 +291,9 @@ func (loader *Loader) resolveComponent( } var cursor interface{} if cursor, err = drill(doc); err != nil { + if path == nil { + return nil, err + } var err2 error data, err2 := loader.readURL(path) if err2 != nil { @@ -346,6 +335,14 @@ func (loader *Loader) resolveComponent( } func drillIntoField(cursor interface{}, fieldName string) (interface{}, error) { + // Special case due to multijson + if s, ok := cursor.(*SchemaRef); ok && fieldName == "additionalProperties" { + if ap := s.Value.AdditionalPropertiesAllowed; ap != nil { + return *ap, nil + } + return s.Value.AdditionalProperties, nil + } + switch val := reflect.Indirect(reflect.ValueOf(cursor)); val.Kind() { case reflect.Map: elementValue := val.MapIndex(reflect.ValueOf(fieldName)) @@ -372,6 +369,10 @@ func drillIntoField(cursor interface{}, fieldName string) (interface{}, error) { field := val.Type().Field(i) tagValue := field.Tag.Get("yaml") yamlKey := strings.Split(tagValue, ",")[0] + if yamlKey == "-" { + tagValue := field.Tag.Get("multijson") + yamlKey = strings.Split(tagValue, ",")[0] + } if yamlKey == fieldName { return val.Field(i).Interface(), nil } @@ -400,6 +401,14 @@ func drillIntoField(cursor interface{}, fieldName string) (interface{}, error) { } } +func (loader *Loader) documentPathForRecursiveRef(current *url.URL, resolvedRef string) *url.URL { + if loader.rootDir == "" { + return current + } + return &url.URL{Path: path.Join(loader.rootDir, resolvedRef)} + +} + func (loader *Loader) resolveRef(doc *T, ref string, path *url.URL) (*T, string, *url.URL, error) { if ref != "" && ref[0] == '#' { return doc, ref, path, nil @@ -459,6 +468,7 @@ func (loader *Loader) resolveHeaderRef(doc *T, component *HeaderRef, documentPat return err } component.Value = resolved.Value + documentPath = loader.documentPathForRecursiveRef(documentPath, resolved.Ref) } } value := component.Value @@ -506,6 +516,7 @@ func (loader *Loader) resolveParameterRef(doc *T, component *ParameterRef, docum return err } component.Value = resolved.Value + documentPath = loader.documentPathForRecursiveRef(documentPath, resolved.Ref) } } value := component.Value @@ -562,6 +573,7 @@ func (loader *Loader) resolveRequestBodyRef(doc *T, component *RequestBodyRef, d return err } component.Value = resolved.Value + documentPath = loader.documentPathForRecursiveRef(documentPath, resolved.Ref) } } value := component.Value @@ -617,6 +629,7 @@ func (loader *Loader) resolveResponseRef(doc *T, component *ResponseRef, documen return err } component.Value = resolved.Value + documentPath = loader.documentPathForRecursiveRef(documentPath, resolved.Ref) } } value := component.Value @@ -686,6 +699,7 @@ func (loader *Loader) resolveSchemaRef(doc *T, component *SchemaRef, documentPat return err } component.Value = resolved.Value + documentPath = loader.documentPathForRecursiveRef(documentPath, resolved.Ref) } } value := component.Value @@ -749,7 +763,7 @@ func (loader *Loader) resolveSecuritySchemeRef(doc *T, component *SecurityScheme if ref := component.Ref; ref != "" { if isSingleRefElement(ref) { var scheme SecurityScheme - if documentPath, err = loader.loadSingleElementFromURI(ref, documentPath, &scheme); err != nil { + if _, err = loader.loadSingleElementFromURI(ref, documentPath, &scheme); err != nil { return err } component.Value = &scheme @@ -763,6 +777,7 @@ func (loader *Loader) resolveSecuritySchemeRef(doc *T, component *SecurityScheme return err } component.Value = resolved.Value + _ = loader.documentPathForRecursiveRef(documentPath, resolved.Ref) } } return nil @@ -785,7 +800,7 @@ func (loader *Loader) resolveExampleRef(doc *T, component *ExampleRef, documentP if ref := component.Ref; ref != "" { if isSingleRefElement(ref) { var example Example - if documentPath, err = loader.loadSingleElementFromURI(ref, documentPath, &example); err != nil { + if _, err = loader.loadSingleElementFromURI(ref, documentPath, &example); err != nil { return err } component.Value = &example @@ -799,6 +814,7 @@ func (loader *Loader) resolveExampleRef(doc *T, component *ExampleRef, documentP return err } component.Value = resolved.Value + _ = loader.documentPathForRecursiveRef(documentPath, resolved.Ref) } } return nil @@ -826,6 +842,7 @@ func (loader *Loader) resolveCallbackRef(doc *T, component *CallbackRef, documen return err } component.Value = resolved.Value + documentPath = loader.documentPathForRecursiveRef(documentPath, resolved.Ref) } } value := component.Value @@ -909,7 +926,7 @@ func (loader *Loader) resolveLinkRef(doc *T, component *LinkRef, documentPath *u if ref := component.Ref; ref != "" { if isSingleRefElement(ref) { var link Link - if documentPath, err = loader.loadSingleElementFromURI(ref, documentPath, &link); err != nil { + if _, err = loader.loadSingleElementFromURI(ref, documentPath, &link); err != nil { return err } component.Value = &link @@ -923,6 +940,7 @@ func (loader *Loader) resolveLinkRef(doc *T, component *LinkRef, documentPath *u return err } component.Value = resolved.Value + _ = loader.documentPathForRecursiveRef(documentPath, resolved.Ref) } } return nil diff --git a/vendor/github.com/getkin/kin-openapi/openapi3/loader_uri_reader.go b/vendor/github.com/getkin/kin-openapi/openapi3/loader_uri_reader.go new file mode 100644 index 000000000..8357a980d --- /dev/null +++ b/vendor/github.com/getkin/kin-openapi/openapi3/loader_uri_reader.go @@ -0,0 +1,104 @@ +package openapi3 + +import ( + "errors" + "fmt" + "io/ioutil" + "net/http" + "net/url" + "path/filepath" +) + +// ReadFromURIFunc defines a function which reads the contents of a resource +// located at a URI. +type ReadFromURIFunc func(loader *Loader, url *url.URL) ([]byte, error) + +// ErrURINotSupported indicates the ReadFromURIFunc does not know how to handle a +// given URI. +var ErrURINotSupported = errors.New("unsupported URI") + +// ReadFromURIs returns a ReadFromURIFunc which tries to read a URI using the +// given reader functions, in the same order. If a reader function does not +// support the URI and returns ErrURINotSupported, the next function is checked +// until a match is found, or the URI is not supported by any. +func ReadFromURIs(readers ...ReadFromURIFunc) ReadFromURIFunc { + return func(loader *Loader, url *url.URL) ([]byte, error) { + for i := range readers { + buf, err := readers[i](loader, url) + if err == ErrURINotSupported { + continue + } else if err != nil { + return nil, err + } + return buf, nil + } + return nil, ErrURINotSupported + } +} + +// DefaultReadFromURI returns a caching ReadFromURIFunc which can read remote +// HTTP URIs and local file URIs. +var DefaultReadFromURI = URIMapCache(ReadFromURIs(ReadFromHTTP(http.DefaultClient), ReadFromFile)) + +// ReadFromHTTP returns a ReadFromURIFunc which uses the given http.Client to +// read the contents from a remote HTTP URI. This client may be customized to +// implement timeouts, RFC 7234 caching, etc. +func ReadFromHTTP(cl *http.Client) ReadFromURIFunc { + return func(loader *Loader, location *url.URL) ([]byte, error) { + if location.Scheme == "" || location.Host == "" { + return nil, ErrURINotSupported + } + req, err := http.NewRequest("GET", location.String(), nil) + if err != nil { + return nil, err + } + resp, err := cl.Do(req) + if err != nil { + return nil, err + } + defer resp.Body.Close() + if resp.StatusCode > 399 { + return nil, fmt.Errorf("error loading %q: request returned status code %d", location.String(), resp.StatusCode) + } + return ioutil.ReadAll(resp.Body) + } +} + +// ReadFromFile is a ReadFromURIFunc which reads local file URIs. +func ReadFromFile(loader *Loader, location *url.URL) ([]byte, error) { + if location.Host != "" { + return nil, ErrURINotSupported + } + if location.Scheme != "" && location.Scheme != "file" { + return nil, ErrURINotSupported + } + return ioutil.ReadFile(location.Path) +} + +// URIMapCache returns a ReadFromURIFunc that caches the contents read from URI +// locations in a simple map. This cache implementation is suitable for +// short-lived processes such as command-line tools which process OpenAPI +// documents. +func URIMapCache(reader ReadFromURIFunc) ReadFromURIFunc { + cache := map[string][]byte{} + return func(loader *Loader, location *url.URL) (buf []byte, err error) { + if location.Scheme == "" || location.Scheme == "file" { + if !filepath.IsAbs(location.Path) { + // Do not cache relative file paths; this can cause trouble if + // the current working directory changes when processing + // multiple top-level documents. + return reader(loader, location) + } + } + uri := location.String() + var ok bool + if buf, ok = cache[uri]; ok { + return + } + if buf, err = reader(loader, location); err != nil { + return + } + cache[uri] = buf + return + } +} diff --git a/vendor/github.com/getkin/kin-openapi/openapi3/media_type.go b/vendor/github.com/getkin/kin-openapi/openapi3/media_type.go index 2dd0842f6..5c001ca64 100644 --- a/vendor/github.com/getkin/kin-openapi/openapi3/media_type.go +++ b/vendor/github.com/getkin/kin-openapi/openapi3/media_type.go @@ -8,6 +8,7 @@ import ( ) // MediaType is specified by OpenAPI/Swagger 3.0 standard. +// See https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#mediaTypeObject type MediaType struct { ExtensionProps diff --git a/vendor/github.com/getkin/kin-openapi/openapi3/openapi3.go b/vendor/github.com/getkin/kin-openapi/openapi3/openapi3.go index ee6887727..d376812b5 100644 --- a/vendor/github.com/getkin/kin-openapi/openapi3/openapi3.go +++ b/vendor/github.com/getkin/kin-openapi/openapi3/openapi3.go @@ -9,8 +9,10 @@ import ( ) // T is the root of an OpenAPI v3 document +// See https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#oasObject type T struct { ExtensionProps + OpenAPI string `json:"openapi" yaml:"openapi"` // Required Components Components `json:"components,omitempty" yaml:"components,omitempty"` Info *Info `json:"info" yaml:"info"` // Required @@ -101,5 +103,23 @@ func (value *T) Validate(ctx context.Context) error { } } + { + wrap := func(e error) error { return fmt.Errorf("invalid tags: %w", e) } + if v := value.Tags; v != nil { + if err := v.Validate(ctx); err != nil { + return wrap(err) + } + } + } + + { + wrap := func(e error) error { return fmt.Errorf("invalid external docs: %w", e) } + if v := value.ExternalDocs; v != nil { + if err := v.Validate(ctx); err != nil { + return wrap(err) + } + } + } + return nil } diff --git a/vendor/github.com/getkin/kin-openapi/openapi3/operation.go b/vendor/github.com/getkin/kin-openapi/openapi3/operation.go index 0de7c421a..29e70c774 100644 --- a/vendor/github.com/getkin/kin-openapi/openapi3/operation.go +++ b/vendor/github.com/getkin/kin-openapi/openapi3/operation.go @@ -3,6 +3,7 @@ package openapi3 import ( "context" "errors" + "fmt" "strconv" "github.com/getkin/kin-openapi/jsoninfo" @@ -10,6 +11,7 @@ import ( ) // Operation represents "operation" specified by" OpenAPI/Swagger 3.0 standard. +// See https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#operation-object type Operation struct { ExtensionProps @@ -138,5 +140,10 @@ func (value *Operation) Validate(ctx context.Context) error { } else { return errors.New("value of responses must be an object") } + if v := value.ExternalDocs; v != nil { + if err := v.Validate(ctx); err != nil { + return fmt.Errorf("invalid external docs: %w", err) + } + } return nil } diff --git a/vendor/github.com/getkin/kin-openapi/openapi3/parameter.go b/vendor/github.com/getkin/kin-openapi/openapi3/parameter.go index f4b91adf0..e283a98fb 100644 --- a/vendor/github.com/getkin/kin-openapi/openapi3/parameter.go +++ b/vendor/github.com/getkin/kin-openapi/openapi3/parameter.go @@ -83,8 +83,10 @@ func (value Parameters) Validate(ctx context.Context) error { } // Parameter is specified by OpenAPI/Swagger 3.0 standard. +// See https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#parameterObject type Parameter struct { ExtensionProps + Name string `json:"name,omitempty" yaml:"name,omitempty"` In string `json:"in,omitempty" yaml:"in,omitempty"` Description string `json:"description,omitempty" yaml:"description,omitempty"` @@ -167,42 +169,42 @@ func (parameter *Parameter) UnmarshalJSON(data []byte) error { return jsoninfo.UnmarshalStrictStruct(data, parameter) } -func (parameter Parameter) JSONLookup(token string) (interface{}, error) { +func (value Parameter) JSONLookup(token string) (interface{}, error) { switch token { case "schema": - if parameter.Schema != nil { - if parameter.Schema.Ref != "" { - return &Ref{Ref: parameter.Schema.Ref}, nil + if value.Schema != nil { + if value.Schema.Ref != "" { + return &Ref{Ref: value.Schema.Ref}, nil } - return parameter.Schema.Value, nil + return value.Schema.Value, nil } case "name": - return parameter.Name, nil + return value.Name, nil case "in": - return parameter.In, nil + return value.In, nil case "description": - return parameter.Description, nil + return value.Description, nil case "style": - return parameter.Style, nil + return value.Style, nil case "explode": - return parameter.Explode, nil + return value.Explode, nil case "allowEmptyValue": - return parameter.AllowEmptyValue, nil + return value.AllowEmptyValue, nil case "allowReserved": - return parameter.AllowReserved, nil + return value.AllowReserved, nil case "deprecated": - return parameter.Deprecated, nil + return value.Deprecated, nil case "required": - return parameter.Required, nil + return value.Required, nil case "example": - return parameter.Example, nil + return value.Example, nil case "examples": - return parameter.Examples, nil + return value.Examples, nil case "content": - return parameter.Content, nil + return value.Content, nil } - v, _, err := jsonpointer.GetForToken(parameter.ExtensionProps, token) + v, _, err := jsonpointer.GetForToken(value.ExtensionProps, token) return v, err } @@ -251,6 +253,10 @@ func (value *Parameter) Validate(ctx context.Context) error { return fmt.Errorf("parameter can't have 'in' value %q", value.In) } + if in == ParameterInPath && !value.Required { + return fmt.Errorf("path parameter %q must be required", value.Name) + } + // Validate a parameter's serialization method. sm, err := value.SerializationMethod() if err != nil { @@ -294,6 +300,7 @@ func (value *Parameter) Validate(ctx context.Context) error { return fmt.Errorf("parameter %q schema is invalid: %v", value.Name, err) } } + if content := value.Content; content != nil { if err := content.Validate(ctx); err != nil { return fmt.Errorf("parameter %q content is invalid: %v", value.Name, err) diff --git a/vendor/github.com/getkin/kin-openapi/openapi3/path_item.go b/vendor/github.com/getkin/kin-openapi/openapi3/path_item.go index a66502046..4473d639d 100644 --- a/vendor/github.com/getkin/kin-openapi/openapi3/path_item.go +++ b/vendor/github.com/getkin/kin-openapi/openapi3/path_item.go @@ -8,8 +8,11 @@ import ( "github.com/getkin/kin-openapi/jsoninfo" ) +// PathItem is specified by OpenAPI/Swagger standard version 3. +// See https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#pathItemObject type PathItem struct { ExtensionProps + Ref string `json:"$ref,omitempty" yaml:"$ref,omitempty"` Summary string `json:"summary,omitempty" yaml:"summary,omitempty"` Description string `json:"description,omitempty" yaml:"description,omitempty"` diff --git a/vendor/github.com/getkin/kin-openapi/openapi3/paths.go b/vendor/github.com/getkin/kin-openapi/openapi3/paths.go index c6ddbf3bf..24ab5f300 100644 --- a/vendor/github.com/getkin/kin-openapi/openapi3/paths.go +++ b/vendor/github.com/getkin/kin-openapi/openapi3/paths.go @@ -6,7 +6,8 @@ import ( "strings" ) -// Paths is specified by OpenAPI/Swagger standard version 3.0. +// Paths is specified by OpenAPI/Swagger standard version 3. +// See https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#paths-object type Paths map[string]*PathItem func (value Paths) Validate(ctx context.Context) error { @@ -21,31 +22,60 @@ func (value Paths) Validate(ctx context.Context) error { pathItem = value[path] } - normalizedPath, pathParamsCount := normalizeTemplatedPath(path) + normalizedPath, _, varsInPath := normalizeTemplatedPath(path) if oldPath, ok := normalizedPaths[normalizedPath]; ok { return fmt.Errorf("conflicting paths %q and %q", path, oldPath) } normalizedPaths[path] = path - var globalCount uint + var commonParams []string for _, parameterRef := range pathItem.Parameters { if parameterRef != nil { if parameter := parameterRef.Value; parameter != nil && parameter.In == ParameterInPath { - globalCount++ + commonParams = append(commonParams, parameter.Name) } } } for method, operation := range pathItem.Operations() { - var count uint + var setParams []string for _, parameterRef := range operation.Parameters { if parameterRef != nil { if parameter := parameterRef.Value; parameter != nil && parameter.In == ParameterInPath { - count++ + setParams = append(setParams, parameter.Name) } } } - if count+globalCount != pathParamsCount { - return fmt.Errorf("operation %s %s must define exactly all path parameters", method, path) + if expected := len(setParams) + len(commonParams); expected != len(varsInPath) { + expected -= len(varsInPath) + if expected < 0 { + expected *= -1 + } + missing := make(map[string]struct{}, expected) + definedParams := append(setParams, commonParams...) + for _, name := range definedParams { + if _, ok := varsInPath[name]; !ok { + missing[name] = struct{}{} + } + } + for name := range varsInPath { + got := false + for _, othername := range definedParams { + if othername == name { + got = true + break + } + } + if !got { + missing[name] = struct{}{} + } + } + if len(missing) != 0 { + missings := make([]string, 0, len(missing)) + for name := range missing { + missings = append(missings, name) + } + return fmt.Errorf("operation %s %s must define exactly all path parameters (missing: %v)", method, path, missings) + } } } @@ -53,6 +83,11 @@ func (value Paths) Validate(ctx context.Context) error { return err } } + + if err := value.validateUniqueOperationIDs(); err != nil { + return err + } + return nil } @@ -75,9 +110,9 @@ func (paths Paths) Find(key string) *PathItem { return pathItem } - normalizedPath, expected := normalizeTemplatedPath(key) + normalizedPath, expected, _ := normalizeTemplatedPath(key) for path, pathItem := range paths { - pathNormalized, got := normalizeTemplatedPath(path) + pathNormalized, got, _ := normalizeTemplatedPath(path) if got == expected && pathNormalized == normalizedPath { return pathItem } @@ -85,43 +120,75 @@ func (paths Paths) Find(key string) *PathItem { return nil } -func normalizeTemplatedPath(path string) (string, uint) { +func (value Paths) validateUniqueOperationIDs() error { + operationIDs := make(map[string]string) + for urlPath, pathItem := range value { + if pathItem == nil { + continue + } + for httpMethod, operation := range pathItem.Operations() { + if operation == nil || operation.OperationID == "" { + continue + } + endpoint := httpMethod + " " + urlPath + if endpointDup, ok := operationIDs[operation.OperationID]; ok { + if endpoint > endpointDup { // For make error message a bit more deterministic. May be useful for tests. + endpoint, endpointDup = endpointDup, endpoint + } + return fmt.Errorf("operations %q and %q have the same operation id %q", + endpoint, endpointDup, operation.OperationID) + } + operationIDs[operation.OperationID] = endpoint + } + } + return nil +} + +func normalizeTemplatedPath(path string) (string, uint, map[string]struct{}) { if strings.IndexByte(path, '{') < 0 { - return path, 0 + return path, 0, nil } - var buf strings.Builder - buf.Grow(len(path)) + var buffTpl strings.Builder + buffTpl.Grow(len(path)) var ( cc rune count uint isVariable bool + vars = make(map[string]struct{}) + buffVar strings.Builder ) for i, c := range path { if isVariable { if c == '}' { - // End path variables + // End path variable + isVariable = false + + vars[buffVar.String()] = struct{}{} + buffVar = strings.Builder{} + // First append possible '*' before this character // The character '}' will be appended if i > 0 && cc == '*' { - buf.WriteRune(cc) + buffTpl.WriteRune(cc) } - isVariable = false } else { - // Skip this character + buffVar.WriteRune(c) continue } + } else if c == '{' { // Begin path variable - // The character '{' will be appended isVariable = true + + // The character '{' will be appended count++ } // Append the character - buf.WriteRune(c) + buffTpl.WriteRune(c) cc = c } - return buf.String(), count + return buffTpl.String(), count, vars } diff --git a/vendor/github.com/getkin/kin-openapi/openapi3/refs.go b/vendor/github.com/getkin/kin-openapi/openapi3/refs.go index 4b64035f8..333cd1740 100644 --- a/vendor/github.com/getkin/kin-openapi/openapi3/refs.go +++ b/vendor/github.com/getkin/kin-openapi/openapi3/refs.go @@ -8,10 +8,13 @@ import ( ) // Ref is specified by OpenAPI/Swagger 3.0 standard. +// See https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#referenceObject type Ref struct { Ref string `json:"$ref" yaml:"$ref"` } +// CallbackRef represents either a Callback or a $ref to a Callback. +// When serializing and both fields are set, Ref is preferred over Value. type CallbackRef struct { Ref string Value *Callback @@ -43,6 +46,8 @@ func (value CallbackRef) JSONLookup(token string) (interface{}, error) { return ptr, err } +// ExampleRef represents either a Example or a $ref to a Example. +// When serializing and both fields are set, Ref is preferred over Value. type ExampleRef struct { Ref string Value *Example @@ -74,6 +79,8 @@ func (value ExampleRef) JSONLookup(token string) (interface{}, error) { return ptr, err } +// HeaderRef represents either a Header or a $ref to a Header. +// When serializing and both fields are set, Ref is preferred over Value. type HeaderRef struct { Ref string Value *Header @@ -105,6 +112,8 @@ func (value HeaderRef) JSONLookup(token string) (interface{}, error) { return ptr, err } +// LinkRef represents either a Link or a $ref to a Link. +// When serializing and both fields are set, Ref is preferred over Value. type LinkRef struct { Ref string Value *Link @@ -125,6 +134,8 @@ func (value *LinkRef) Validate(ctx context.Context) error { return foundUnresolvedRef(value.Ref) } +// ParameterRef represents either a Parameter or a $ref to a Parameter. +// When serializing and both fields are set, Ref is preferred over Value. type ParameterRef struct { Ref string Value *Parameter @@ -156,6 +167,8 @@ func (value ParameterRef) JSONLookup(token string) (interface{}, error) { return ptr, err } +// ResponseRef represents either a Response or a $ref to a Response. +// When serializing and both fields are set, Ref is preferred over Value. type ResponseRef struct { Ref string Value *Response @@ -187,6 +200,8 @@ func (value ResponseRef) JSONLookup(token string) (interface{}, error) { return ptr, err } +// RequestBodyRef represents either a RequestBody or a $ref to a RequestBody. +// When serializing and both fields are set, Ref is preferred over Value. type RequestBodyRef struct { Ref string Value *RequestBody @@ -218,6 +233,8 @@ func (value RequestBodyRef) JSONLookup(token string) (interface{}, error) { return ptr, err } +// SchemaRef represents either a Schema or a $ref to a Schema. +// When serializing and both fields are set, Ref is preferred over Value. type SchemaRef struct { Ref string Value *Schema @@ -256,6 +273,8 @@ func (value SchemaRef) JSONLookup(token string) (interface{}, error) { return ptr, err } +// SecuritySchemeRef represents either a SecurityScheme or a $ref to a SecurityScheme. +// When serializing and both fields are set, Ref is preferred over Value. type SecuritySchemeRef struct { Ref string Value *SecurityScheme diff --git a/vendor/github.com/getkin/kin-openapi/openapi3/request_body.go b/vendor/github.com/getkin/kin-openapi/openapi3/request_body.go index 66b512fa0..0be098c0b 100644 --- a/vendor/github.com/getkin/kin-openapi/openapi3/request_body.go +++ b/vendor/github.com/getkin/kin-openapi/openapi3/request_body.go @@ -2,6 +2,7 @@ package openapi3 import ( "context" + "errors" "fmt" "github.com/getkin/kin-openapi/jsoninfo" @@ -25,11 +26,13 @@ func (r RequestBodies) JSONLookup(token string) (interface{}, error) { } // RequestBody is specified by OpenAPI/Swagger 3.0 standard. +// See https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#requestBodyObject type RequestBody struct { ExtensionProps + Description string `json:"description,omitempty" yaml:"description,omitempty"` Required bool `json:"required,omitempty" yaml:"required,omitempty"` - Content Content `json:"content,omitempty" yaml:"content,omitempty"` + Content Content `json:"content" yaml:"content"` } func NewRequestBody() *RequestBody { @@ -98,10 +101,8 @@ func (requestBody *RequestBody) UnmarshalJSON(data []byte) error { } func (value *RequestBody) Validate(ctx context.Context) error { - if v := value.Content; v != nil { - if err := v.Validate(ctx); err != nil { - return err - } + if value.Content == nil { + return errors.New("content of the request body is required") } - return nil + return value.Content.Validate(ctx) } diff --git a/vendor/github.com/getkin/kin-openapi/openapi3/response.go b/vendor/github.com/getkin/kin-openapi/openapi3/response.go index 2ab33aca2..8e22698f6 100644 --- a/vendor/github.com/getkin/kin-openapi/openapi3/response.go +++ b/vendor/github.com/getkin/kin-openapi/openapi3/response.go @@ -11,6 +11,7 @@ import ( ) // Responses is specified by OpenAPI/Swagger 3.0 standard. +// See https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#responsesObject type Responses map[string]*ResponseRef var _ jsonpointer.JSONPointable = (*Responses)(nil) @@ -54,8 +55,10 @@ func (responses Responses) JSONLookup(token string) (interface{}, error) { } // Response is specified by OpenAPI/Swagger 3.0 standard. +// See https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#responseObject type Response struct { ExtensionProps + Description *string `json:"description,omitempty" yaml:"description,omitempty"` Headers Headers `json:"headers,omitempty" yaml:"headers,omitempty"` Content Content `json:"content,omitempty" yaml:"content,omitempty"` @@ -104,5 +107,16 @@ func (value *Response) Validate(ctx context.Context) error { return err } } + for _, header := range value.Headers { + if err := header.Validate(ctx); err != nil { + return err + } + } + + for _, link := range value.Links { + if err := link.Validate(ctx); err != nil { + return err + } + } return nil } diff --git a/vendor/github.com/getkin/kin-openapi/openapi3/schema.go b/vendor/github.com/getkin/kin-openapi/openapi3/schema.go index 8c59f3c04..ee21bc21b 100644 --- a/vendor/github.com/getkin/kin-openapi/openapi3/schema.go +++ b/vendor/github.com/getkin/kin-openapi/openapi3/schema.go @@ -16,6 +16,15 @@ import ( "github.com/go-openapi/jsonpointer" ) +const ( + TypeArray = "array" + TypeBoolean = "boolean" + TypeInteger = "integer" + TypeNumber = "number" + TypeObject = "object" + TypeString = "string" +) + var ( // SchemaErrorDetailsDisabled disables printing of details about schema errors. SchemaErrorDetailsDisabled = false @@ -93,6 +102,7 @@ func (s SchemaRefs) JSONLookup(token string) (interface{}, error) { } // Schema is specified by OpenAPI/Swagger 3.0 standard. +// See https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#schemaObject type Schema struct { ExtensionProps @@ -109,20 +119,18 @@ type Schema struct { Example interface{} `json:"example,omitempty" yaml:"example,omitempty"` ExternalDocs *ExternalDocs `json:"externalDocs,omitempty" yaml:"externalDocs,omitempty"` - // Object-related, here for struct compactness - AdditionalPropertiesAllowed *bool `json:"-" multijson:"additionalProperties,omitempty" yaml:"-"` // Array-related, here for struct compactness UniqueItems bool `json:"uniqueItems,omitempty" yaml:"uniqueItems,omitempty"` // Number-related, here for struct compactness ExclusiveMin bool `json:"exclusiveMinimum,omitempty" yaml:"exclusiveMinimum,omitempty"` ExclusiveMax bool `json:"exclusiveMaximum,omitempty" yaml:"exclusiveMaximum,omitempty"` // Properties - Nullable bool `json:"nullable,omitempty" yaml:"nullable,omitempty"` - ReadOnly bool `json:"readOnly,omitempty" yaml:"readOnly,omitempty"` - WriteOnly bool `json:"writeOnly,omitempty" yaml:"writeOnly,omitempty"` - AllowEmptyValue bool `json:"allowEmptyValue,omitempty" yaml:"allowEmptyValue,omitempty"` - XML interface{} `json:"xml,omitempty" yaml:"xml,omitempty"` - Deprecated bool `json:"deprecated,omitempty" yaml:"deprecated,omitempty"` + Nullable bool `json:"nullable,omitempty" yaml:"nullable,omitempty"` + ReadOnly bool `json:"readOnly,omitempty" yaml:"readOnly,omitempty"` + WriteOnly bool `json:"writeOnly,omitempty" yaml:"writeOnly,omitempty"` + AllowEmptyValue bool `json:"allowEmptyValue,omitempty" yaml:"allowEmptyValue,omitempty"` + Deprecated bool `json:"deprecated,omitempty" yaml:"deprecated,omitempty"` + XML *XML `json:"xml,omitempty" yaml:"xml,omitempty"` // Number Min *float64 `json:"minimum,omitempty" yaml:"minimum,omitempty"` @@ -141,12 +149,13 @@ type Schema struct { Items *SchemaRef `json:"items,omitempty" yaml:"items,omitempty"` // Object - Required []string `json:"required,omitempty" yaml:"required,omitempty"` - Properties Schemas `json:"properties,omitempty" yaml:"properties,omitempty"` - MinProps uint64 `json:"minProperties,omitempty" yaml:"minProperties,omitempty"` - MaxProps *uint64 `json:"maxProperties,omitempty" yaml:"maxProperties,omitempty"` - AdditionalProperties *SchemaRef `json:"-" multijson:"additionalProperties,omitempty" yaml:"-"` - Discriminator *Discriminator `json:"discriminator,omitempty" yaml:"discriminator,omitempty"` + Required []string `json:"required,omitempty" yaml:"required,omitempty"` + Properties Schemas `json:"properties,omitempty" yaml:"properties,omitempty"` + MinProps uint64 `json:"minProperties,omitempty" yaml:"minProperties,omitempty"` + MaxProps *uint64 `json:"maxProperties,omitempty" yaml:"maxProperties,omitempty"` + AdditionalPropertiesAllowed *bool `multijson:"additionalProperties,omitempty" json:"-" yaml:"-"` // In this order... + AdditionalProperties *SchemaRef `multijson:"additionalProperties,omitempty" json:"-" yaml:"-"` // ...for multijson + Discriminator *Discriminator `json:"discriminator,omitempty" yaml:"discriminator,omitempty"` } var _ jsonpointer.JSONPointable = (*Schema)(nil) @@ -298,73 +307,73 @@ func NewAllOfSchema(schemas ...*Schema) *Schema { func NewBoolSchema() *Schema { return &Schema{ - Type: "boolean", + Type: TypeBoolean, } } func NewFloat64Schema() *Schema { return &Schema{ - Type: "number", + Type: TypeNumber, } } func NewIntegerSchema() *Schema { return &Schema{ - Type: "integer", + Type: TypeInteger, } } func NewInt32Schema() *Schema { return &Schema{ - Type: "integer", + Type: TypeInteger, Format: "int32", } } func NewInt64Schema() *Schema { return &Schema{ - Type: "integer", + Type: TypeInteger, Format: "int64", } } func NewStringSchema() *Schema { return &Schema{ - Type: "string", + Type: TypeString, } } func NewDateTimeSchema() *Schema { return &Schema{ - Type: "string", + Type: TypeString, Format: "date-time", } } func NewUUIDSchema() *Schema { return &Schema{ - Type: "string", + Type: TypeString, Format: "uuid", } } func NewBytesSchema() *Schema { return &Schema{ - Type: "string", + Type: TypeString, Format: "byte", } } func NewArraySchema() *Schema { return &Schema{ - Type: "array", + Type: TypeArray, } } func NewObjectSchema() *Schema { return &Schema{ - Type: "object", - Properties: make(map[string]*SchemaRef), + Type: TypeObject, + Properties: make(Schemas), } } @@ -485,7 +494,7 @@ func (schema *Schema) WithProperty(name string, propertySchema *Schema) *Schema func (schema *Schema) WithPropertyRef(name string, ref *SchemaRef) *Schema { properties := schema.Properties if properties == nil { - properties = make(map[string]*SchemaRef) + properties = make(Schemas) schema.Properties = properties } properties[name] = ref @@ -493,7 +502,7 @@ func (schema *Schema) WithPropertyRef(name string, ref *SchemaRef) *Schema { } func (schema *Schema) WithProperties(properties map[string]*Schema) *Schema { - result := make(map[string]*SchemaRef, len(properties)) + result := make(Schemas, len(properties)) for k, v := range properties { result[k] = &SchemaRef{ Value: v, @@ -638,8 +647,8 @@ func (schema *Schema) validate(ctx context.Context, stack []*Schema) (err error) schemaType := schema.Type switch schemaType { case "": - case "boolean": - case "number": + case TypeBoolean: + case TypeNumber: if format := schema.Format; len(format) > 0 { switch format { case "float", "double": @@ -649,7 +658,7 @@ func (schema *Schema) validate(ctx context.Context, stack []*Schema) (err error) } } } - case "integer": + case TypeInteger: if format := schema.Format; len(format) > 0 { switch format { case "int32", "int64": @@ -659,17 +668,21 @@ func (schema *Schema) validate(ctx context.Context, stack []*Schema) (err error) } } } - case "string": + case TypeString: if format := schema.Format; len(format) > 0 { switch format { - // Supported by OpenAPIv3.0.1: + // Supported by OpenAPIv3.0.3: + // https://spec.openapis.org/oas/v3.0.3 case "byte", "binary", "date", "date-time", "password": - // In JSON Draft-07 (not validated yet though): - case "regex": - case "time", "email", "idn-email": - case "hostname", "idn-hostname", "ipv4", "ipv6": - case "uri", "uri-reference", "iri", "iri-reference", "uri-template": - case "json-pointer", "relative-json-pointer": + // In JSON Draft-07 (not validated yet though): + // https://json-schema.org/draft-07/json-schema-release-notes.html#formats + case "iri", "iri-reference", "uri-template", "idn-email", "idn-hostname": + case "json-pointer", "relative-json-pointer", "regex", "time": + // In JSON Draft 2019-09 (not validated yet though): + // https://json-schema.org/draft/2019-09/release-notes.html#format-vocabulary + case "duration", "uuid": + // Defined in some other specification + case "email", "hostname", "ipv4", "ipv6", "uri", "uri-reference": default: // Try to check for custom defined formats if _, ok := SchemaStringFormats[format]; !ok && !SchemaFormatValidationDisabled { @@ -677,11 +690,16 @@ func (schema *Schema) validate(ctx context.Context, stack []*Schema) (err error) } } } - case "array": + if schema.Pattern != "" { + if err = schema.compilePattern(); err != nil { + return err + } + } + case TypeArray: if schema.Items == nil { return errors.New("when schema type is 'array', schema 'items' must be non-null") } - case "object": + case TypeObject: default: return fmt.Errorf("unsupported 'type' value %q", schemaType) } @@ -716,6 +734,12 @@ func (schema *Schema) validate(ctx context.Context, stack []*Schema) (err error) } } + if v := schema.ExternalDocs; v != nil { + if err = v.Validate(ctx); err != nil { + return fmt.Errorf("invalid external docs: %w", err) + } + } + return } @@ -775,8 +799,6 @@ func (schema *Schema) visitJSON(settings *schemaValidationSettings, value interf } switch value := value.(type) { - case nil: - return schema.visitJSONNull(settings) case bool: return schema.visitJSONBoolean(settings, value) case float64: @@ -787,13 +809,22 @@ func (schema *Schema) visitJSON(settings *schemaValidationSettings, value interf return schema.visitJSONArray(settings, value) case map[string]interface{}: return schema.visitJSONObject(settings, value) - default: - return &SchemaError{ - Value: value, - Schema: schema, - SchemaField: "type", - Reason: fmt.Sprintf("unhandled value of type %T", value), + case map[interface{}]interface{}: // for YAML cf. issue #444 + values := make(map[string]interface{}, len(value)) + for key, v := range value { + if k, ok := key.(string); ok { + values[k] = v + } } + if len(value) == len(values) { + return schema.visitJSONObject(settings, values) + } + } + return &SchemaError{ + Value: value, + Schema: schema, + SchemaField: "type", + Reason: fmt.Sprintf("unhandled value of type %T", value), } } @@ -820,11 +851,7 @@ func (schema *Schema) visitSetOperations(settings *schemaValidationSettings, val if v == nil { return foundUnresolvedRef(ref.Ref) } - var oldfailfast bool - oldfailfast, settings.failfast = settings.failfast, true - err := v.visitJSON(settings, value) - settings.failfast = oldfailfast - if err == nil { + if err := v.visitJSON(settings, value); err == nil { if settings.failfast { return errSchema } @@ -837,33 +864,57 @@ func (schema *Schema) visitSetOperations(settings *schemaValidationSettings, val } if v := schema.OneOf; len(v) > 0 { + var discriminatorRef string + if schema.Discriminator != nil { + pn := schema.Discriminator.PropertyName + if valuemap, okcheck := value.(map[string]interface{}); okcheck { + discriminatorVal, okcheck := valuemap[pn] + if !okcheck { + return errors.New("input does not contain the discriminator property") + } + + discriminatorValString, okcheck := discriminatorVal.(string) + if !okcheck { + return errors.New("descriminator value is not a string") + } + + if discriminatorRef, okcheck = schema.Discriminator.Mapping[discriminatorValString]; len(schema.Discriminator.Mapping) > 0 && !okcheck { + return errors.New("input does not contain a valid discriminator value") + } + } + } + ok := 0 + validationErrors := []error{} for _, item := range v { v := item.Value if v == nil { return foundUnresolvedRef(item.Ref) } - var oldfailfast bool - oldfailfast, settings.failfast = settings.failfast, true - err := v.visitJSON(settings, value) - settings.failfast = oldfailfast - if err == nil { - if schema.Discriminator != nil { - pn := schema.Discriminator.PropertyName - if valuemap, okcheck := value.(map[string]interface{}); okcheck { - if discriminatorVal, okcheck := valuemap[pn]; okcheck == true { - mapref, okcheck := schema.Discriminator.Mapping[discriminatorVal.(string)] - if okcheck && mapref == item.Ref { - ok++ - } - } - } - } else { - ok++ - } + + if discriminatorRef != "" && discriminatorRef != item.Ref { + continue } + + if err := v.visitJSON(settings, value); err != nil { + validationErrors = append(validationErrors, err) + continue + } + + ok++ } + if ok != 1 { + if len(validationErrors) > 1 { + errorMessage := "" + for _, err := range validationErrors { + if errorMessage != "" { + errorMessage += " Or " + } + errorMessage += err.Error() + } + return errors.New("doesn't match schema due to: " + errorMessage) + } if settings.failfast { return errSchema } @@ -874,7 +925,10 @@ func (schema *Schema) visitSetOperations(settings *schemaValidationSettings, val } if ok > 1 { e.Origin = ErrOneOfConflict + } else if len(validationErrors) == 1 { + e.Origin = validationErrors[0] } + return e } } @@ -886,11 +940,7 @@ func (schema *Schema) visitSetOperations(settings *schemaValidationSettings, val if v == nil { return foundUnresolvedRef(item.Ref) } - var oldfailfast bool - oldfailfast, settings.failfast = settings.failfast, true - err := v.visitJSON(settings, value) - settings.failfast = oldfailfast - if err == nil { + if err := v.visitJSON(settings, value); err == nil { ok = true break } @@ -912,11 +962,7 @@ func (schema *Schema) visitSetOperations(settings *schemaValidationSettings, val if v == nil { return foundUnresolvedRef(item.Ref) } - var oldfailfast bool - oldfailfast, settings.failfast = settings.failfast, false - err := v.visitJSON(settings, value) - settings.failfast = oldfailfast - if err != nil { + if err := v.visitJSON(settings, value); err != nil { if settings.failfast { return errSchema } @@ -952,8 +998,8 @@ func (schema *Schema) VisitJSONBoolean(value bool) error { } func (schema *Schema) visitJSONBoolean(settings *schemaValidationSettings, value bool) (err error) { - if schemaType := schema.Type; schemaType != "" && schemaType != "boolean" { - return schema.expectedType(settings, "boolean") + if schemaType := schema.Type; schemaType != "" && schemaType != TypeBoolean { + return schema.expectedType(settings, TypeBoolean) } return } @@ -982,7 +1028,7 @@ func (schema *Schema) visitJSONNumber(settings *schemaValidationSettings, value } me = append(me, err) } - } else if schemaType != "" && schemaType != "number" { + } else if schemaType != "" && schemaType != TypeNumber { return schema.expectedType(settings, "number, integer") } @@ -1046,7 +1092,7 @@ func (schema *Schema) visitJSONNumber(settings *schemaValidationSettings, value Value: value, Schema: schema, SchemaField: "maximum", - Reason: fmt.Sprintf("number must be most %g", *v), + Reason: fmt.Sprintf("number must be at most %g", *v), } if !settings.multiError { return err @@ -1087,8 +1133,8 @@ func (schema *Schema) VisitJSONString(value string) error { } func (schema *Schema) visitJSONString(settings *schemaValidationSettings, value string) error { - if schemaType := schema.Type; schemaType != "" && schemaType != "string" { - return schema.expectedType(settings, "string") + if schemaType := schema.Type; schemaType != "" && schemaType != TypeString { + return schema.expectedType(settings, TypeString) } var me MultiError @@ -1139,15 +1185,9 @@ func (schema *Schema) visitJSONString(settings *schemaValidationSettings, value } // "pattern" - if pattern := schema.Pattern; pattern != "" && schema.compiledPattern == nil { + if schema.Pattern != "" && schema.compiledPattern == nil { var err error - if schema.compiledPattern, err = regexp.Compile(pattern); err != nil { - err = &SchemaError{ - Value: value, - Schema: schema, - SchemaField: "pattern", - Reason: fmt.Sprintf("cannot compile pattern %q: %v", pattern, err), - } + if err = schema.compilePattern(); err != nil { if !settings.multiError { return err } @@ -1159,7 +1199,7 @@ func (schema *Schema) visitJSONString(settings *schemaValidationSettings, value Value: value, Schema: schema, SchemaField: "pattern", - Reason: fmt.Sprintf("string doesn't match the regular expression %q", schema.Pattern), + Reason: fmt.Sprintf(`string doesn't match the regular expression "%s"`, schema.Pattern), } if !settings.multiError { return err @@ -1174,7 +1214,7 @@ func (schema *Schema) visitJSONString(settings *schemaValidationSettings, value switch { case f.regexp != nil && f.callback == nil: if cp := f.regexp; !cp.MatchString(value) { - formatErr = fmt.Sprintf("string doesn't match the format %q (regular expression %q)", format, cp.String()) + formatErr = fmt.Sprintf(`string doesn't match the format %q (regular expression "%s")`, format, cp.String()) } case f.regexp == nil && f.callback != nil: if err := f.callback(value); err != nil { @@ -1212,8 +1252,8 @@ func (schema *Schema) VisitJSONArray(value []interface{}) error { } func (schema *Schema) visitJSONArray(settings *schemaValidationSettings, value []interface{}) error { - if schemaType := schema.Type; schemaType != "" && schemaType != "array" { - return schema.expectedType(settings, "array") + if schemaType := schema.Type; schemaType != "" && schemaType != TypeArray { + return schema.expectedType(settings, TypeArray) } var me MultiError @@ -1308,8 +1348,8 @@ func (schema *Schema) VisitJSONObject(value map[string]interface{}) error { } func (schema *Schema) visitJSONObject(settings *schemaValidationSettings, value map[string]interface{}) error { - if schemaType := schema.Type; schemaType != "" && schemaType != "object" { - return schema.expectedType(settings, "object") + if schemaType := schema.Type; schemaType != "" && schemaType != TypeObject { + return schema.expectedType(settings, TypeObject) } var me MultiError @@ -1383,7 +1423,7 @@ func (schema *Schema) visitJSONObject(settings *schemaValidationSettings, value } } allowed := schema.AdditionalPropertiesAllowed - if additionalProperties != nil || allowed == nil || (allowed != nil && *allowed) { + if additionalProperties != nil || allowed == nil || *allowed { if additionalProperties != nil { if err := additionalProperties.visitJSON(settings, v); err != nil { if settings.failfast { @@ -1461,6 +1501,17 @@ func (schema *Schema) expectedType(settings *schemaValidationSettings, typ strin } } +func (schema *Schema) compilePattern() (err error) { + if schema.compiledPattern, err = regexp.Compile(schema.Pattern); err != nil { + return &SchemaError{ + Schema: schema, + SchemaField: "pattern", + Reason: fmt.Sprintf("cannot compile pattern %q: %v", schema.Pattern, err), + } + } + return nil +} + type SchemaError struct { Value interface{} reversePath []string @@ -1470,6 +1521,8 @@ type SchemaError struct { Origin error } +var _ interface{ Unwrap() error } = SchemaError{} + func markSchemaErrorKey(err error, key string) error { if v, ok := err.(*SchemaError); ok { v.reversePath = append(v.reversePath, key) @@ -1545,6 +1598,10 @@ func (err *SchemaError) Error() string { return buf.String() } +func (err SchemaError) Unwrap() error { + return err.Origin +} + func isSliceOfUniqueItems(xs []interface{}) bool { s := len(xs) m := make(map[string]struct{}, s) diff --git a/vendor/github.com/getkin/kin-openapi/openapi3/schema_formats.go b/vendor/github.com/getkin/kin-openapi/openapi3/schema_formats.go index 1eb41509e..29fbd51fb 100644 --- a/vendor/github.com/getkin/kin-openapi/openapi3/schema_formats.go +++ b/vendor/github.com/getkin/kin-openapi/openapi3/schema_formats.go @@ -4,11 +4,12 @@ import ( "fmt" "net" "regexp" + "strings" ) const ( // FormatOfStringForUUIDOfRFC4122 is an optional predefined format for UUID v1-v5 as specified by RFC4122 - FormatOfStringForUUIDOfRFC4122 = `^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$` + FormatOfStringForUUIDOfRFC4122 = `^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-5][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}$` ) //FormatCallback custom check on exotic formats @@ -37,24 +38,23 @@ func DefineStringFormatCallback(name string, callback FormatCallback) { SchemaStringFormats[name] = Format{callback: callback} } -func validateIP(ip string) (*net.IP, error) { +func validateIP(ip string) error { parsed := net.ParseIP(ip) if parsed == nil { - return nil, &SchemaError{ + return &SchemaError{ Value: ip, Reason: "Not an IP address", } } - return &parsed, nil + return nil } func validateIPv4(ip string) error { - parsed, err := validateIP(ip) - if err != nil { + if err := validateIP(ip); err != nil { return err } - if parsed.To4() == nil { + if !(strings.Count(ip, ":") < 2) { return &SchemaError{ Value: ip, Reason: "Not an IPv4 address (it's IPv6)", @@ -62,13 +62,13 @@ func validateIPv4(ip string) error { } return nil } + func validateIPv6(ip string) error { - parsed, err := validateIP(ip) - if err != nil { + if err := validateIP(ip); err != nil { return err } - if parsed.To4() != nil { + if !(strings.Count(ip, ":") >= 2) { return &SchemaError{ Value: ip, Reason: "Not an IPv6 address (it's IPv4)", @@ -90,7 +90,7 @@ func init() { DefineStringFormat("date", `^[0-9]{4}-(0[0-9]|10|11|12)-([0-2][0-9]|30|31)$`) // date-time - DefineStringFormat("date-time", `^[0-9]{4}-(0[0-9]|10|11|12)-([0-2][0-9]|30|31)T[0-9]{2}:[0-9]{2}:[0-9]{2}(.[0-9]+)?(Z|(\+|-)[0-9]{2}:[0-9]{2})?$`) + DefineStringFormat("date-time", `^[0-9]{4}-(0[0-9]|10|11|12)-([0-2][0-9]|30|31)T[0-9]{2}:[0-9]{2}:[0-9]{2}(\.[0-9]+)?(Z|(\+|-)[0-9]{2}:[0-9]{2})?$`) } diff --git a/vendor/github.com/getkin/kin-openapi/openapi3/security_requirements.go b/vendor/github.com/getkin/kin-openapi/openapi3/security_requirements.go index ce6fcc6f1..df6b6b2d1 100644 --- a/vendor/github.com/getkin/kin-openapi/openapi3/security_requirements.go +++ b/vendor/github.com/getkin/kin-openapi/openapi3/security_requirements.go @@ -24,6 +24,8 @@ func (value SecurityRequirements) Validate(ctx context.Context) error { return nil } +// SecurityRequirement is specified by OpenAPI/Swagger standard version 3. +// See https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#securityRequirementObject type SecurityRequirement map[string][]string func NewSecurityRequirement() SecurityRequirement { diff --git a/vendor/github.com/getkin/kin-openapi/openapi3/security_scheme.go b/vendor/github.com/getkin/kin-openapi/openapi3/security_scheme.go index 990f258d4..9b89fb950 100644 --- a/vendor/github.com/getkin/kin-openapi/openapi3/security_scheme.go +++ b/vendor/github.com/getkin/kin-openapi/openapi3/security_scheme.go @@ -25,6 +25,8 @@ func (s SecuritySchemes) JSONLookup(token string) (interface{}, error) { var _ jsonpointer.JSONPointable = (*SecuritySchemes)(nil) +// SecurityScheme is specified by OpenAPI/Swagger standard version 3. +// See https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#securitySchemeObject type SecurityScheme struct { ExtensionProps @@ -166,8 +168,11 @@ func (value *SecurityScheme) Validate(ctx context.Context) error { return nil } +// OAuthFlows is specified by OpenAPI/Swagger standard version 3. +// See https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#oauthFlowsObject type OAuthFlows struct { ExtensionProps + Implicit *OAuthFlow `json:"implicit,omitempty" yaml:"implicit,omitempty"` Password *OAuthFlow `json:"password,omitempty" yaml:"password,omitempty"` ClientCredentials *OAuthFlow `json:"clientCredentials,omitempty" yaml:"clientCredentials,omitempty"` @@ -207,8 +212,11 @@ func (flows *OAuthFlows) Validate(ctx context.Context) error { return errors.New("no OAuth flow is defined") } +// OAuthFlow is specified by OpenAPI/Swagger standard version 3. +// See https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#oauthFlowObject type OAuthFlow struct { ExtensionProps + AuthorizationURL string `json:"authorizationUrl,omitempty" yaml:"authorizationUrl,omitempty"` TokenURL string `json:"tokenUrl,omitempty" yaml:"tokenUrl,omitempty"` RefreshURL string `json:"refreshUrl,omitempty" yaml:"refreshUrl,omitempty"` diff --git a/vendor/github.com/getkin/kin-openapi/openapi3/server.go b/vendor/github.com/getkin/kin-openapi/openapi3/server.go index 4415bd08f..94092a6e6 100644 --- a/vendor/github.com/getkin/kin-openapi/openapi3/server.go +++ b/vendor/github.com/getkin/kin-openapi/openapi3/server.go @@ -11,7 +11,7 @@ import ( "github.com/getkin/kin-openapi/jsoninfo" ) -// Servers is specified by OpenAPI/Swagger standard version 3.0. +// Servers is specified by OpenAPI/Swagger standard version 3. type Servers []*Server // Validate ensures servers are per the OpenAPIv3 specification. @@ -38,9 +38,11 @@ func (servers Servers) MatchURL(parsedURL *url.URL) (*Server, []string, string) return nil, nil, "" } -// Server is specified by OpenAPI/Swagger standard version 3.0. +// Server is specified by OpenAPI/Swagger standard version 3. +// See https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#serverObject type Server struct { ExtensionProps + URL string `json:"url" yaml:"url"` Description string `json:"description,omitempty" yaml:"description,omitempty"` Variables map[string]*ServerVariable `json:"variables,omitempty" yaml:"variables,omitempty"` @@ -147,9 +149,11 @@ func (value *Server) Validate(ctx context.Context) (err error) { return } -// ServerVariable is specified by OpenAPI/Swagger standard version 3.0. +// ServerVariable is specified by OpenAPI/Swagger standard version 3. +// See https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#server-variable-object type ServerVariable struct { ExtensionProps + Enum []string `json:"enum,omitempty" yaml:"enum,omitempty"` Default string `json:"default,omitempty" yaml:"default,omitempty"` Description string `json:"description,omitempty" yaml:"description,omitempty"` diff --git a/vendor/github.com/getkin/kin-openapi/openapi3/tag.go b/vendor/github.com/getkin/kin-openapi/openapi3/tag.go index 210b69248..6aa9a1ea2 100644 --- a/vendor/github.com/getkin/kin-openapi/openapi3/tag.go +++ b/vendor/github.com/getkin/kin-openapi/openapi3/tag.go @@ -1,6 +1,11 @@ package openapi3 -import "github.com/getkin/kin-openapi/jsoninfo" +import ( + "context" + "fmt" + + "github.com/getkin/kin-openapi/jsoninfo" +) // Tags is specified by OpenAPI/Swagger 3.0 standard. type Tags []*Tag @@ -14,9 +19,20 @@ func (tags Tags) Get(name string) *Tag { return nil } +func (tags Tags) Validate(ctx context.Context) error { + for _, v := range tags { + if err := v.Validate(ctx); err != nil { + return err + } + } + return nil +} + // Tag is specified by OpenAPI/Swagger 3.0 standard. +// See https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#tagObject type Tag struct { ExtensionProps + Name string `json:"name,omitempty" yaml:"name,omitempty"` Description string `json:"description,omitempty" yaml:"description,omitempty"` ExternalDocs *ExternalDocs `json:"externalDocs,omitempty" yaml:"externalDocs,omitempty"` @@ -29,3 +45,12 @@ func (t *Tag) MarshalJSON() ([]byte, error) { func (t *Tag) UnmarshalJSON(data []byte) error { return jsoninfo.UnmarshalStrictStruct(data, t) } + +func (t *Tag) Validate(ctx context.Context) error { + if v := t.ExternalDocs; v != nil { + if err := v.Validate(ctx); err != nil { + return fmt.Errorf("invalid external docs: %w", err) + } + } + return nil +} diff --git a/vendor/github.com/getkin/kin-openapi/openapi3/testdata/recursiveRef/components/Bar.yml b/vendor/github.com/getkin/kin-openapi/openapi3/testdata/recursiveRef/components/Bar.yml new file mode 100644 index 000000000..cc59fc27b --- /dev/null +++ b/vendor/github.com/getkin/kin-openapi/openapi3/testdata/recursiveRef/components/Bar.yml @@ -0,0 +1,2 @@ +type: string +example: bar diff --git a/vendor/github.com/getkin/kin-openapi/openapi3/testdata/recursiveRef/components/Foo.yml b/vendor/github.com/getkin/kin-openapi/openapi3/testdata/recursiveRef/components/Foo.yml new file mode 100644 index 000000000..53a233666 --- /dev/null +++ b/vendor/github.com/getkin/kin-openapi/openapi3/testdata/recursiveRef/components/Foo.yml @@ -0,0 +1,4 @@ +type: object +properties: + bar: + $ref: ../openapi.yml#/components/schemas/Bar diff --git a/vendor/github.com/getkin/kin-openapi/openapi3/testdata/recursiveRef/components/Foo/Foo2.yml b/vendor/github.com/getkin/kin-openapi/openapi3/testdata/recursiveRef/components/Foo/Foo2.yml new file mode 100644 index 000000000..aeac81f48 --- /dev/null +++ b/vendor/github.com/getkin/kin-openapi/openapi3/testdata/recursiveRef/components/Foo/Foo2.yml @@ -0,0 +1,4 @@ +type: object +properties: + foo: + $ref: ../../openapi.yml#/components/schemas/Foo diff --git a/vendor/github.com/getkin/kin-openapi/openapi3/testdata/recursiveRef/openapi.yml b/vendor/github.com/getkin/kin-openapi/openapi3/testdata/recursiveRef/openapi.yml new file mode 100644 index 000000000..3559c8e85 --- /dev/null +++ b/vendor/github.com/getkin/kin-openapi/openapi3/testdata/recursiveRef/openapi.yml @@ -0,0 +1,15 @@ +openapi: "3.0.3" +info: + title: Recursive refs example + version: "1.0" +paths: + /foo: + $ref: ./paths/foo.yml +components: + schemas: + Foo: + $ref: ./components/Foo.yml + Foo2: + $ref: ./components/Foo/Foo2.yml + Bar: + $ref: ./components/Bar.yml diff --git a/vendor/github.com/getkin/kin-openapi/openapi3/testdata/recursiveRef/paths/foo.yml b/vendor/github.com/getkin/kin-openapi/openapi3/testdata/recursiveRef/paths/foo.yml new file mode 100644 index 000000000..1653c7ac7 --- /dev/null +++ b/vendor/github.com/getkin/kin-openapi/openapi3/testdata/recursiveRef/paths/foo.yml @@ -0,0 +1,11 @@ +get: + responses: + "200": + description: OK + content: + application/json: + schema: + type: object + properties: + foo2: + $ref: ../openapi.yml#/components/schemas/Foo2 diff --git a/vendor/github.com/getkin/kin-openapi/openapi3/xml.go b/vendor/github.com/getkin/kin-openapi/openapi3/xml.go new file mode 100644 index 000000000..8fd2abdee --- /dev/null +++ b/vendor/github.com/getkin/kin-openapi/openapi3/xml.go @@ -0,0 +1,31 @@ +package openapi3 + +import ( + "context" + + "github.com/getkin/kin-openapi/jsoninfo" +) + +// XML is specified by OpenAPI/Swagger standard version 3. +// See https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#xmlObject +type XML struct { + ExtensionProps + + Name string `json:"name,omitempty" yaml:"name,omitempty"` + Namespace string `json:"namespace,omitempty" yaml:"namespace,omitempty"` + Prefix string `json:"prefix,omitempty" yaml:"prefix,omitempty"` + Attribute bool `json:"attribute,omitempty" yaml:"attribute,omitempty"` + Wrapped bool `json:"wrapped,omitempty" yaml:"wrapped,omitempty"` +} + +func (value *XML) MarshalJSON() ([]byte, error) { + return jsoninfo.MarshalStrictStruct(value) +} + +func (value *XML) UnmarshalJSON(data []byte) error { + return jsoninfo.UnmarshalStrictStruct(data, value) +} + +func (value *XML) Validate(ctx context.Context) error { + return nil // TODO +} diff --git a/vendor/github.com/getkin/kin-openapi/openapi3filter/authentication_input.go b/vendor/github.com/getkin/kin-openapi/openapi3filter/authentication_input.go new file mode 100644 index 000000000..a53484b99 --- /dev/null +++ b/vendor/github.com/getkin/kin-openapi/openapi3filter/authentication_input.go @@ -0,0 +1,29 @@ +package openapi3filter + +import ( + "fmt" + + "github.com/getkin/kin-openapi/openapi3" +) + +type AuthenticationInput struct { + RequestValidationInput *RequestValidationInput + SecuritySchemeName string + SecurityScheme *openapi3.SecurityScheme + Scopes []string +} + +func (input *AuthenticationInput) NewError(err error) error { + if err == nil { + if len(input.Scopes) == 0 { + err = fmt.Errorf("security requirement %q failed", input.SecuritySchemeName) + } else { + err = fmt.Errorf("security requirement %q (scopes: %+v) failed", input.SecuritySchemeName, input.Scopes) + } + } + return &RequestError{ + Input: input.RequestValidationInput, + Reason: "authorization failed", + Err: err, + } +} diff --git a/vendor/github.com/getkin/kin-openapi/openapi3filter/errors.go b/vendor/github.com/getkin/kin-openapi/openapi3filter/errors.go new file mode 100644 index 000000000..8454c817f --- /dev/null +++ b/vendor/github.com/getkin/kin-openapi/openapi3filter/errors.go @@ -0,0 +1,82 @@ +package openapi3filter + +import ( + "fmt" + + "github.com/getkin/kin-openapi/openapi3" +) + +var _ error = &RequestError{} + +// RequestError is returned by ValidateRequest when request does not match OpenAPI spec +type RequestError struct { + Input *RequestValidationInput + Parameter *openapi3.Parameter + RequestBody *openapi3.RequestBody + Reason string + Err error +} + +var _ interface{ Unwrap() error } = RequestError{} + +func (err *RequestError) Error() string { + reason := err.Reason + if e := err.Err; e != nil { + if len(reason) == 0 { + reason = e.Error() + } else { + reason += ": " + e.Error() + } + } + if v := err.Parameter; v != nil { + return fmt.Sprintf("parameter %q in %s has an error: %s", v.Name, v.In, reason) + } else if v := err.RequestBody; v != nil { + return fmt.Sprintf("request body has an error: %s", reason) + } else { + return reason + } +} + +func (err RequestError) Unwrap() error { + return err.Err +} + +var _ error = &ResponseError{} + +// ResponseError is returned by ValidateResponse when response does not match OpenAPI spec +type ResponseError struct { + Input *ResponseValidationInput + Reason string + Err error +} + +var _ interface{ Unwrap() error } = ResponseError{} + +func (err *ResponseError) Error() string { + reason := err.Reason + if e := err.Err; e != nil { + if len(reason) == 0 { + reason = e.Error() + } else { + reason += ": " + e.Error() + } + } + return reason +} + +func (err ResponseError) Unwrap() error { + return err.Err +} + +var _ error = &SecurityRequirementsError{} + +// SecurityRequirementsError is returned by ValidateSecurityRequirements +// when no requirement is met. +type SecurityRequirementsError struct { + SecurityRequirements openapi3.SecurityRequirements + Errors []error +} + +func (err *SecurityRequirementsError) Error() string { + return "Security requirements failed" +} diff --git a/vendor/github.com/getkin/kin-openapi/openapi3filter/internal.go b/vendor/github.com/getkin/kin-openapi/openapi3filter/internal.go new file mode 100644 index 000000000..5c6a8a6c6 --- /dev/null +++ b/vendor/github.com/getkin/kin-openapi/openapi3filter/internal.go @@ -0,0 +1,25 @@ +package openapi3filter + +import ( + "reflect" + "strings" +) + +func parseMediaType(contentType string) string { + i := strings.IndexByte(contentType, ';') + if i < 0 { + return contentType + } + return contentType[:i] +} + +func isNilValue(value interface{}) bool { + if value == nil { + return true + } + switch reflect.TypeOf(value).Kind() { + case reflect.Ptr, reflect.Map, reflect.Array, reflect.Chan, reflect.Slice: + return reflect.ValueOf(value).IsNil() + } + return false +} diff --git a/vendor/github.com/getkin/kin-openapi/openapi3filter/middleware.go b/vendor/github.com/getkin/kin-openapi/openapi3filter/middleware.go new file mode 100644 index 000000000..3709faf9b --- /dev/null +++ b/vendor/github.com/getkin/kin-openapi/openapi3filter/middleware.go @@ -0,0 +1,273 @@ +package openapi3filter + +import ( + "bytes" + "io" + "io/ioutil" + "log" + "net/http" + + "github.com/getkin/kin-openapi/routers" +) + +// Validator provides HTTP request and response validation middleware. +type Validator struct { + router routers.Router + errFunc ErrFunc + logFunc LogFunc + strict bool +} + +// ErrFunc handles errors that may occur during validation. +type ErrFunc func(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) + +// ErrCode is used for classification of different types of errors that may +// occur during validation. These may be used to write an appropriate response +// in ErrFunc. +type ErrCode int + +const ( + // ErrCodeOK indicates no error. It is also the default value. + ErrCodeOK = 0 + // ErrCodeCannotFindRoute happens when the validator fails to resolve the + // request to a defined OpenAPI route. + ErrCodeCannotFindRoute = iota + // ErrCodeRequestInvalid happens when the inbound request does not conform + // to the OpenAPI 3 specification. + ErrCodeRequestInvalid = iota + // ErrCodeResponseInvalid happens when the wrapped handler response does + // not conform to the OpenAPI 3 specification. + ErrCodeResponseInvalid = iota +) + +func (e ErrCode) responseText() string { + switch e { + case ErrCodeOK: + return "OK" + case ErrCodeCannotFindRoute: + return "not found" + case ErrCodeRequestInvalid: + return "bad request" + default: + return "server error" + } +} + +// NewValidator returns a new response validation middlware, using the given +// routes from an OpenAPI 3 specification. +func NewValidator(router routers.Router, options ...ValidatorOption) *Validator { + v := &Validator{ + router: router, + errFunc: func(w http.ResponseWriter, status int, code ErrCode, _ error) { + http.Error(w, code.responseText(), status) + }, + logFunc: func(message string, err error) { + log.Printf("%s: %v", message, err) + }, + } + for i := range options { + options[i](v) + } + return v +} + +// ValidatorOption defines an option that may be specified when creating a +// Validator. +type ValidatorOption func(*Validator) + +// OnErr provides a callback that handles writing an HTTP response on a +// validation error. This allows customization of error responses without +// prescribing a particular form. This callback is only called on response +// validator errors in Strict mode. +func OnErr(f ErrFunc) ValidatorOption { + return func(v *Validator) { + v.errFunc = f + } +} + +// OnLog provides a callback that handles logging in the Validator. This allows +// the validator to integrate with a services' existing logging system without +// prescribing a particular one. +func OnLog(f LogFunc) ValidatorOption { + return func(v *Validator) { + v.logFunc = f + } +} + +// Strict, if set, causes an internal server error to be sent if the wrapped +// handler response fails response validation. If not set, the response is sent +// and the error is only logged. +func Strict(strict bool) ValidatorOption { + return func(v *Validator) { + v.strict = strict + } +} + +// Middleware returns an http.Handler which wraps the given handler with +// request and response validation. +func (v *Validator) Middleware(h http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + 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) + return + } + requestValidationInput := &RequestValidationInput{ + Request: r, + PathParams: pathParams, + Route: route, + } + if err = ValidateRequest(r.Context(), requestValidationInput); err != nil { + v.logFunc("invalid request", err) + v.errFunc(w, http.StatusBadRequest, ErrCodeRequestInvalid, err) + return + } + + var wr responseWrapper + if v.strict { + wr = &strictResponseWrapper{w: w} + } else { + wr = newWarnResponseWrapper(w) + } + + h.ServeHTTP(wr, r) + + if err = ValidateResponse(r.Context(), &ResponseValidationInput{ + RequestValidationInput: requestValidationInput, + Status: wr.statusCode(), + Header: wr.Header(), + Body: ioutil.NopCloser(bytes.NewBuffer(wr.bodyContents())), + }); err != nil { + v.logFunc("invalid response", err) + if v.strict { + v.errFunc(w, http.StatusInternalServerError, ErrCodeResponseInvalid, err) + } + return + } + + if err = wr.flushBodyContents(); err != nil { + v.logFunc("failed to write response", err) + } + }) +} + +type responseWrapper interface { + http.ResponseWriter + + // flushBodyContents writes the buffered response to the client, if it has + // not yet been written. + flushBodyContents() error + + // statusCode returns the response status code, 0 if not set yet. + statusCode() int + + // bodyContents returns the buffered + bodyContents() []byte +} + +type warnResponseWrapper struct { + w http.ResponseWriter + headerWritten bool + status int + body bytes.Buffer + tee io.Writer +} + +func newWarnResponseWrapper(w http.ResponseWriter) *warnResponseWrapper { + wr := &warnResponseWrapper{ + w: w, + } + wr.tee = io.MultiWriter(w, &wr.body) + return wr +} + +// Write implements http.ResponseWriter. +func (wr *warnResponseWrapper) Write(b []byte) (int, error) { + if !wr.headerWritten { + wr.WriteHeader(http.StatusOK) + } + return wr.tee.Write(b) +} + +// WriteHeader implements http.ResponseWriter. +func (wr *warnResponseWrapper) WriteHeader(status int) { + if !wr.headerWritten { + // If the header hasn't been written, record the status for response + // validation. + wr.status = status + wr.headerWritten = true + } + wr.w.WriteHeader(wr.status) +} + +// Header implements http.ResponseWriter. +func (wr *warnResponseWrapper) Header() http.Header { + return wr.w.Header() +} + +// Flush implements the optional http.Flusher interface. +func (wr *warnResponseWrapper) Flush() { + // If the wrapped http.ResponseWriter implements optional http.Flusher, + // pass through. + if fl, ok := wr.w.(http.Flusher); ok { + fl.Flush() + } +} + +func (wr *warnResponseWrapper) flushBodyContents() error { + return nil +} + +func (wr *warnResponseWrapper) statusCode() int { + return wr.status +} + +func (wr *warnResponseWrapper) bodyContents() []byte { + return wr.body.Bytes() +} + +type strictResponseWrapper struct { + w http.ResponseWriter + headerWritten bool + status int + body bytes.Buffer +} + +// Write implements http.ResponseWriter. +func (wr *strictResponseWrapper) Write(b []byte) (int, error) { + if !wr.headerWritten { + wr.WriteHeader(http.StatusOK) + } + return wr.body.Write(b) +} + +// WriteHeader implements http.ResponseWriter. +func (wr *strictResponseWrapper) WriteHeader(status int) { + if !wr.headerWritten { + wr.status = status + wr.headerWritten = true + } +} + +// Header implements http.ResponseWriter. +func (wr *strictResponseWrapper) Header() http.Header { + return wr.w.Header() +} + +func (wr *strictResponseWrapper) flushBodyContents() error { + wr.w.WriteHeader(wr.status) + _, err := wr.w.Write(wr.body.Bytes()) + return err +} + +func (wr *strictResponseWrapper) statusCode() int { + return wr.status +} + +func (wr *strictResponseWrapper) bodyContents() []byte { + return wr.body.Bytes() +} diff --git a/vendor/github.com/getkin/kin-openapi/openapi3filter/options.go b/vendor/github.com/getkin/kin-openapi/openapi3filter/options.go new file mode 100644 index 000000000..1622339e2 --- /dev/null +++ b/vendor/github.com/getkin/kin-openapi/openapi3filter/options.go @@ -0,0 +1,24 @@ +package openapi3filter + +// DefaultOptions do not set an AuthenticationFunc. +// A spec with security schemes defined will not pass validation +// unless an AuthenticationFunc is defined. +var DefaultOptions = &Options{} + +// Options used by ValidateRequest and ValidateResponse +type Options struct { + // Set ExcludeRequestBody so ValidateRequest skips request body validation + ExcludeRequestBody bool + + // Set ExcludeResponseBody so ValidateResponse skips response body validation + ExcludeResponseBody bool + + // Set IncludeResponseStatus so ValidateResponse fails on response + // status not defined in OpenAPI spec + IncludeResponseStatus bool + + MultiError bool + + // See NoopAuthenticationFunc + AuthenticationFunc AuthenticationFunc +} diff --git a/vendor/github.com/getkin/kin-openapi/openapi3filter/req_resp_decoder.go b/vendor/github.com/getkin/kin-openapi/openapi3filter/req_resp_decoder.go new file mode 100644 index 000000000..0408d8da3 --- /dev/null +++ b/vendor/github.com/getkin/kin-openapi/openapi3filter/req_resp_decoder.go @@ -0,0 +1,1081 @@ +package openapi3filter + +import ( + "encoding/json" + "errors" + "fmt" + "io" + "io/ioutil" + "mime" + "mime/multipart" + "net/http" + "net/url" + "regexp" + "strconv" + "strings" + + "gopkg.in/yaml.v2" + + "github.com/getkin/kin-openapi/openapi3" +) + +// ParseErrorKind describes a kind of ParseError. +// The type simplifies comparison of errors. +type ParseErrorKind int + +const ( + // KindOther describes an untyped parsing error. + KindOther ParseErrorKind = iota + // KindUnsupportedFormat describes an error that happens when a value has an unsupported format. + KindUnsupportedFormat + // KindInvalidFormat describes an error that happens when a value does not conform a format + // that is required by a serialization method. + KindInvalidFormat +) + +// ParseError describes errors which happens while parse operation's parameters, requestBody, or response. +type ParseError struct { + Kind ParseErrorKind + Value interface{} + Reason string + Cause error + + path []interface{} +} + +var _ interface{ Unwrap() error } = ParseError{} + +func (e *ParseError) Error() string { + var msg []string + if p := e.Path(); len(p) > 0 { + var arr []string + for _, v := range p { + arr = append(arr, fmt.Sprintf("%v", v)) + } + msg = append(msg, fmt.Sprintf("path %v", strings.Join(arr, "."))) + } + msg = append(msg, e.innerError()) + return strings.Join(msg, ": ") +} + +func (e *ParseError) innerError() string { + var msg []string + if e.Value != nil { + msg = append(msg, fmt.Sprintf("value %v", e.Value)) + } + if e.Reason != "" { + msg = append(msg, e.Reason) + } + if e.Cause != nil { + if v, ok := e.Cause.(*ParseError); ok { + msg = append(msg, v.innerError()) + } else { + msg = append(msg, e.Cause.Error()) + } + } + return strings.Join(msg, ": ") +} + +// RootCause returns a root cause of ParseError. +func (e *ParseError) RootCause() error { + if v, ok := e.Cause.(*ParseError); ok { + return v.RootCause() + } + return e.Cause +} + +func (e ParseError) Unwrap() error { + return e.Cause +} + +// Path returns a path to the root cause. +func (e *ParseError) Path() []interface{} { + var path []interface{} + if v, ok := e.Cause.(*ParseError); ok { + p := v.Path() + if len(p) > 0 { + path = append(path, p...) + } + } + if len(e.path) > 0 { + path = append(path, e.path...) + } + return path +} + +func invalidSerializationMethodErr(sm *openapi3.SerializationMethod) error { + return fmt.Errorf("invalid serialization method: style=%q, explode=%v", sm.Style, sm.Explode) +} + +// Decodes a parameter defined via the content property as an object. It uses +// the user specified decoder, or our build-in decoder for application/json +func decodeContentParameter(param *openapi3.Parameter, input *RequestValidationInput) ( + value interface{}, schema *openapi3.Schema, found bool, err error) { + + var paramValues []string + switch param.In { + case openapi3.ParameterInPath: + var paramValue string + if paramValue, found = input.PathParams[param.Name]; found { + paramValues = []string{paramValue} + } + case openapi3.ParameterInQuery: + paramValues, found = input.GetQueryParams()[param.Name] + case openapi3.ParameterInHeader: + var headerValues []string + if headerValues, found = input.Request.Header[http.CanonicalHeaderKey(param.Name)]; found { + paramValues = headerValues + } + case openapi3.ParameterInCookie: + var cookie *http.Cookie + if cookie, err = input.Request.Cookie(param.Name); err == http.ErrNoCookie { + found = false + } else if err != nil { + return + } else { + paramValues = []string{cookie.Value} + found = true + } + default: + err = fmt.Errorf("unsupported parameter.in: %q", param.In) + return + } + + if !found { + if param.Required { + err = fmt.Errorf("parameter %q is required, but missing", param.Name) + } + return + } + + decoder := input.ParamDecoder + if decoder == nil { + decoder = defaultContentParameterDecoder + } + + value, schema, err = decoder(param, paramValues) + return +} + +func defaultContentParameterDecoder(param *openapi3.Parameter, values []string) ( + outValue interface{}, outSchema *openapi3.Schema, err error) { + // Only query parameters can have multiple values. + if len(values) > 1 && param.In != openapi3.ParameterInQuery { + err = fmt.Errorf("%s parameter %q cannot have multiple values", param.In, param.Name) + return + } + + content := param.Content + if content == nil { + err = fmt.Errorf("parameter %q expected to have content", param.Name) + return + } + + // We only know how to decode a parameter if it has one content, application/json + if len(content) != 1 { + err = fmt.Errorf("multiple content types for parameter %q", param.Name) + return + } + + mt := content.Get("application/json") + if mt == nil { + err = fmt.Errorf("parameter %q has no content schema", param.Name) + return + } + outSchema = mt.Schema.Value + + if len(values) == 1 { + if err = json.Unmarshal([]byte(values[0]), &outValue); err != nil { + err = fmt.Errorf("error unmarshaling parameter %q", param.Name) + return + } + } else { + outArray := make([]interface{}, 0, len(values)) + for _, v := range values { + var item interface{} + if err = json.Unmarshal([]byte(v), &item); err != nil { + err = fmt.Errorf("error unmarshaling parameter %q", param.Name) + return + } + outArray = append(outArray, item) + } + outValue = outArray + } + return +} + +type valueDecoder interface { + DecodePrimitive(param string, sm *openapi3.SerializationMethod, schema *openapi3.SchemaRef) (interface{}, bool, error) + DecodeArray(param string, sm *openapi3.SerializationMethod, schema *openapi3.SchemaRef) ([]interface{}, bool, error) + DecodeObject(param string, sm *openapi3.SerializationMethod, schema *openapi3.SchemaRef) (map[string]interface{}, bool, error) +} + +// decodeStyledParameter returns a value of an operation's parameter from HTTP request for +// parameters defined using the style format, and whether the parameter is supplied in the input. +// The function returns ParseError when HTTP request contains an invalid value of a parameter. +func decodeStyledParameter(param *openapi3.Parameter, input *RequestValidationInput) (interface{}, bool, error) { + sm, err := param.SerializationMethod() + if err != nil { + return nil, false, err + } + + var dec valueDecoder + switch param.In { + case openapi3.ParameterInPath: + if len(input.PathParams) == 0 { + return nil, false, nil + } + dec = &pathParamDecoder{pathParams: input.PathParams} + case openapi3.ParameterInQuery: + if len(input.GetQueryParams()) == 0 { + return nil, false, nil + } + dec = &urlValuesDecoder{values: input.GetQueryParams()} + case openapi3.ParameterInHeader: + dec = &headerParamDecoder{header: input.Request.Header} + case openapi3.ParameterInCookie: + dec = &cookieParamDecoder{req: input.Request} + default: + return nil, false, fmt.Errorf("unsupported parameter's 'in': %s", param.In) + } + + return decodeValue(dec, param.Name, sm, param.Schema, param.Required) +} + +func decodeValue(dec valueDecoder, param string, sm *openapi3.SerializationMethod, schema *openapi3.SchemaRef, required bool) (interface{}, bool, error) { + var found bool + + if len(schema.Value.AllOf) > 0 { + var value interface{} + var err error + for _, sr := range schema.Value.AllOf { + var f bool + value, f, err = decodeValue(dec, param, sm, sr, required) + found = found || f + if value == nil || err != nil { + break + } + } + return value, found, err + } + + if len(schema.Value.AnyOf) > 0 { + for _, sr := range schema.Value.AnyOf { + value, f, _ := decodeValue(dec, param, sm, sr, required) + found = found || f + if value != nil { + return value, found, nil + } + } + if required { + return nil, found, fmt.Errorf("decoding anyOf for parameter %q failed", param) + } + return nil, found, nil + } + + if len(schema.Value.OneOf) > 0 { + isMatched := 0 + var value interface{} + for _, sr := range schema.Value.OneOf { + v, f, _ := decodeValue(dec, param, sm, sr, required) + found = found || f + if v != nil { + value = v + isMatched++ + } + } + if isMatched == 1 { + return value, found, nil + } else if isMatched > 1 { + return nil, found, fmt.Errorf("decoding oneOf failed: %d schemas matched", isMatched) + } + if required { + return nil, found, fmt.Errorf("decoding oneOf failed: %q is required", param) + } + return nil, found, nil + } + + if schema.Value.Not != nil { + // TODO(decode not): handle decoding "not" JSON Schema + return nil, found, errors.New("not implemented: decoding 'not'") + } + + if schema.Value.Type != "" { + var decodeFn func(param string, sm *openapi3.SerializationMethod, schema *openapi3.SchemaRef) (interface{}, bool, error) + switch schema.Value.Type { + case "array": + decodeFn = func(param string, sm *openapi3.SerializationMethod, schema *openapi3.SchemaRef) (interface{}, bool, error) { + return dec.DecodeArray(param, sm, schema) + } + case "object": + decodeFn = func(param string, sm *openapi3.SerializationMethod, schema *openapi3.SchemaRef) (interface{}, bool, error) { + return dec.DecodeObject(param, sm, schema) + } + default: + decodeFn = dec.DecodePrimitive + } + return decodeFn(param, sm, schema) + } + switch vDecoder := dec.(type) { + case *pathParamDecoder: + _, found = vDecoder.pathParams[param] + case *urlValuesDecoder: + _, found = vDecoder.values[param] + case *headerParamDecoder: + _, found = vDecoder.header[param] + case *cookieParamDecoder: + _, err := vDecoder.req.Cookie(param) + found = err != http.ErrNoCookie + default: + return nil, found, errors.New("unsupported decoder") + } + return nil, found, nil +} + +// pathParamDecoder decodes values of path parameters. +type pathParamDecoder struct { + pathParams map[string]string +} + +func (d *pathParamDecoder) DecodePrimitive(param string, sm *openapi3.SerializationMethod, schema *openapi3.SchemaRef) (interface{}, bool, error) { + var prefix string + switch sm.Style { + case "simple": + // A prefix is empty for style "simple". + case "label": + prefix = "." + case "matrix": + prefix = ";" + param + "=" + default: + return nil, false, invalidSerializationMethodErr(sm) + } + + if d.pathParams == nil { + // HTTP request does not contains a value of the target path parameter. + return nil, false, nil + } + raw, ok := d.pathParams[param] + if !ok || raw == "" { + // HTTP request does not contains a value of the target path parameter. + return nil, false, nil + } + src, err := cutPrefix(raw, prefix) + if err != nil { + return nil, ok, err + } + val, err := parsePrimitive(src, schema) + return val, ok, err +} + +func (d *pathParamDecoder) DecodeArray(param string, sm *openapi3.SerializationMethod, schema *openapi3.SchemaRef) ([]interface{}, bool, error) { + var prefix, delim string + switch { + case sm.Style == "simple": + delim = "," + case sm.Style == "label" && !sm.Explode: + prefix = "." + delim = "," + case sm.Style == "label" && sm.Explode: + prefix = "." + delim = "." + case sm.Style == "matrix" && !sm.Explode: + prefix = ";" + param + "=" + delim = "," + case sm.Style == "matrix" && sm.Explode: + prefix = ";" + param + "=" + delim = ";" + param + "=" + default: + return nil, false, invalidSerializationMethodErr(sm) + } + + if d.pathParams == nil { + // HTTP request does not contains a value of the target path parameter. + return nil, false, nil + } + raw, ok := d.pathParams[param] + if !ok || raw == "" { + // HTTP request does not contains a value of the target path parameter. + return nil, false, nil + } + src, err := cutPrefix(raw, prefix) + if err != nil { + return nil, ok, err + } + val, err := parseArray(strings.Split(src, delim), schema) + return val, ok, err +} + +func (d *pathParamDecoder) DecodeObject(param string, sm *openapi3.SerializationMethod, schema *openapi3.SchemaRef) (map[string]interface{}, bool, error) { + var prefix, propsDelim, valueDelim string + switch { + case sm.Style == "simple" && !sm.Explode: + propsDelim = "," + valueDelim = "," + case sm.Style == "simple" && sm.Explode: + propsDelim = "," + valueDelim = "=" + case sm.Style == "label" && !sm.Explode: + prefix = "." + propsDelim = "," + valueDelim = "," + case sm.Style == "label" && sm.Explode: + prefix = "." + propsDelim = "." + valueDelim = "=" + case sm.Style == "matrix" && !sm.Explode: + prefix = ";" + param + "=" + propsDelim = "," + valueDelim = "," + case sm.Style == "matrix" && sm.Explode: + prefix = ";" + propsDelim = ";" + valueDelim = "=" + default: + return nil, false, invalidSerializationMethodErr(sm) + } + + if d.pathParams == nil { + // HTTP request does not contains a value of the target path parameter. + return nil, false, nil + } + raw, ok := d.pathParams[param] + if !ok || raw == "" { + // HTTP request does not contains a value of the target path parameter. + return nil, false, nil + } + src, err := cutPrefix(raw, prefix) + if err != nil { + return nil, ok, err + } + props, err := propsFromString(src, propsDelim, valueDelim) + if err != nil { + return nil, ok, err + } + val, err := makeObject(props, schema) + return val, ok, err +} + +// cutPrefix validates that a raw value of a path parameter has the specified prefix, +// and returns a raw value without the prefix. +func cutPrefix(raw, prefix string) (string, error) { + if prefix == "" { + return raw, nil + } + if len(raw) < len(prefix) || raw[:len(prefix)] != prefix { + return "", &ParseError{ + Kind: KindInvalidFormat, + Value: raw, + Reason: fmt.Sprintf("a value must be prefixed with %q", prefix), + } + } + return raw[len(prefix):], nil +} + +// urlValuesDecoder decodes values of query parameters. +type urlValuesDecoder struct { + values url.Values +} + +func (d *urlValuesDecoder) DecodePrimitive(param string, sm *openapi3.SerializationMethod, schema *openapi3.SchemaRef) (interface{}, bool, error) { + if sm.Style != "form" { + return nil, false, invalidSerializationMethodErr(sm) + } + + values, ok := d.values[param] + if len(values) == 0 { + // HTTP request does not contain a value of the target query parameter. + return nil, ok, nil + } + val, err := parsePrimitive(values[0], schema) + return val, ok, err +} + +func (d *urlValuesDecoder) DecodeArray(param string, sm *openapi3.SerializationMethod, schema *openapi3.SchemaRef) ([]interface{}, bool, error) { + if sm.Style == "deepObject" { + return nil, false, invalidSerializationMethodErr(sm) + } + + values, ok := d.values[param] + if len(values) == 0 { + // HTTP request does not contain a value of the target query parameter. + return nil, ok, nil + } + if !sm.Explode { + var delim string + switch sm.Style { + case "form": + delim = "," + case "spaceDelimited": + delim = " " + case "pipeDelimited": + delim = "|" + } + values = strings.Split(values[0], delim) + } + val, err := parseArray(values, schema) + return val, ok, err +} + +func (d *urlValuesDecoder) DecodeObject(param string, sm *openapi3.SerializationMethod, schema *openapi3.SchemaRef) (map[string]interface{}, bool, error) { + var propsFn func(url.Values) (map[string]string, error) + switch sm.Style { + case "form": + propsFn = func(params url.Values) (map[string]string, error) { + if len(params) == 0 { + // HTTP request does not contain query parameters. + return nil, nil + } + if sm.Explode { + props := make(map[string]string) + for key, values := range params { + props[key] = values[0] + } + return props, nil + } + values := params[param] + if len(values) == 0 { + // HTTP request does not contain a value of the target query parameter. + return nil, nil + } + return propsFromString(values[0], ",", ",") + } + case "deepObject": + propsFn = func(params url.Values) (map[string]string, error) { + props := make(map[string]string) + for key, values := range params { + groups := regexp.MustCompile(fmt.Sprintf("%s\\[(.+?)\\]", param)).FindAllStringSubmatch(key, -1) + if len(groups) == 0 { + // A query parameter's name does not match the required format, so skip it. + continue + } + props[groups[0][1]] = values[0] + } + if len(props) == 0 { + // HTTP request does not contain query parameters encoded by rules of style "deepObject". + return nil, nil + } + return props, nil + } + default: + return nil, false, invalidSerializationMethodErr(sm) + } + + props, err := propsFn(d.values) + if err != nil { + return nil, false, err + } + if props == nil { + return nil, false, nil + } + + // check the props + found := false + for propName := range schema.Value.Properties { + if _, ok := props[propName]; ok { + found = true + break + } + } + val, err := makeObject(props, schema) + return val, found, err +} + +// headerParamDecoder decodes values of header parameters. +type headerParamDecoder struct { + header http.Header +} + +func (d *headerParamDecoder) DecodePrimitive(param string, sm *openapi3.SerializationMethod, schema *openapi3.SchemaRef) (interface{}, bool, error) { + if sm.Style != "simple" { + return nil, false, invalidSerializationMethodErr(sm) + } + + raw, ok := d.header[http.CanonicalHeaderKey(param)] + if !ok || len(raw) == 0 { + // HTTP request does not contains a corresponding header or has the empty value + return nil, ok, nil + } + + val, err := parsePrimitive(raw[0], schema) + return val, ok, err +} + +func (d *headerParamDecoder) DecodeArray(param string, sm *openapi3.SerializationMethod, schema *openapi3.SchemaRef) ([]interface{}, bool, error) { + if sm.Style != "simple" { + return nil, false, invalidSerializationMethodErr(sm) + } + + raw, ok := d.header[http.CanonicalHeaderKey(param)] + if !ok || len(raw) == 0 { + // HTTP request does not contains a corresponding header + return nil, ok, nil + } + + val, err := parseArray(strings.Split(raw[0], ","), schema) + return val, ok, err +} + +func (d *headerParamDecoder) DecodeObject(param string, sm *openapi3.SerializationMethod, schema *openapi3.SchemaRef) (map[string]interface{}, bool, error) { + if sm.Style != "simple" { + return nil, false, invalidSerializationMethodErr(sm) + } + valueDelim := "," + if sm.Explode { + valueDelim = "=" + } + + raw, ok := d.header[http.CanonicalHeaderKey(param)] + if !ok || len(raw) == 0 { + // HTTP request does not contain a corresponding header. + return nil, ok, nil + } + props, err := propsFromString(raw[0], ",", valueDelim) + if err != nil { + return nil, ok, err + } + val, err := makeObject(props, schema) + return val, ok, err +} + +// cookieParamDecoder decodes values of cookie parameters. +type cookieParamDecoder struct { + req *http.Request +} + +func (d *cookieParamDecoder) DecodePrimitive(param string, sm *openapi3.SerializationMethod, schema *openapi3.SchemaRef) (interface{}, bool, error) { + if sm.Style != "form" { + return nil, false, invalidSerializationMethodErr(sm) + } + + cookie, err := d.req.Cookie(param) + found := err != http.ErrNoCookie + if !found { + // HTTP request does not contain a corresponding cookie. + return nil, found, nil + } + if err != nil { + return nil, found, fmt.Errorf("decoding param %q: %s", param, err) + } + + val, err := parsePrimitive(cookie.Value, schema) + return val, found, err +} + +func (d *cookieParamDecoder) DecodeArray(param string, sm *openapi3.SerializationMethod, schema *openapi3.SchemaRef) ([]interface{}, bool, error) { + if sm.Style != "form" || sm.Explode { + return nil, false, invalidSerializationMethodErr(sm) + } + + cookie, err := d.req.Cookie(param) + found := err != http.ErrNoCookie + if !found { + // HTTP request does not contain a corresponding cookie. + return nil, found, nil + } + if err != nil { + return nil, found, fmt.Errorf("decoding param %q: %s", param, err) + } + val, err := parseArray(strings.Split(cookie.Value, ","), schema) + return val, found, err +} + +func (d *cookieParamDecoder) DecodeObject(param string, sm *openapi3.SerializationMethod, schema *openapi3.SchemaRef) (map[string]interface{}, bool, error) { + if sm.Style != "form" || sm.Explode { + return nil, false, invalidSerializationMethodErr(sm) + } + + cookie, err := d.req.Cookie(param) + found := err != http.ErrNoCookie + if !found { + // HTTP request does not contain a corresponding cookie. + return nil, found, nil + } + if err != nil { + return nil, found, fmt.Errorf("decoding param %q: %s", param, err) + } + props, err := propsFromString(cookie.Value, ",", ",") + if err != nil { + return nil, found, err + } + val, err := makeObject(props, schema) + return val, found, err +} + +// propsFromString returns a properties map that is created by splitting a source string by propDelim and valueDelim. +// The source string must have a valid format: pairs separated by . +// The function returns an error when the source string has an invalid format. +func propsFromString(src, propDelim, valueDelim string) (map[string]string, error) { + props := make(map[string]string) + pairs := strings.Split(src, propDelim) + + // When propDelim and valueDelim is equal the source string follow the next rule: + // every even item of pairs is a properies's name, and the subsequent odd item is a property's value. + if propDelim == valueDelim { + // Taking into account the rule above, a valid source string must be splitted by propDelim + // to an array with an even number of items. + if len(pairs)%2 != 0 { + return nil, &ParseError{ + Kind: KindInvalidFormat, + Value: src, + Reason: fmt.Sprintf("a value must be a list of object's properties in format \"name%svalue\" separated by %s", valueDelim, propDelim), + } + } + for i := 0; i < len(pairs)/2; i++ { + props[pairs[i*2]] = pairs[i*2+1] + } + return props, nil + } + + // When propDelim and valueDelim is not equal the source string follow the next rule: + // every item of pairs is a string that follows format . + for _, pair := range pairs { + prop := strings.Split(pair, valueDelim) + if len(prop) != 2 { + return nil, &ParseError{ + Kind: KindInvalidFormat, + Value: src, + Reason: fmt.Sprintf("a value must be a list of object's properties in format \"name%svalue\" separated by %s", valueDelim, propDelim), + } + } + props[prop[0]] = prop[1] + } + return props, nil +} + +// makeObject returns an object that contains properties from props. +// A value of every property is parsed as a primitive value. +// The function returns an error when an error happened while parse object's properties. +func makeObject(props map[string]string, schema *openapi3.SchemaRef) (map[string]interface{}, error) { + obj := make(map[string]interface{}) + for propName, propSchema := range schema.Value.Properties { + value, err := parsePrimitive(props[propName], propSchema) + if err != nil { + if v, ok := err.(*ParseError); ok { + return nil, &ParseError{path: []interface{}{propName}, Cause: v} + } + return nil, fmt.Errorf("property %q: %s", propName, err) + } + obj[propName] = value + } + return obj, nil +} + +// parseArray returns an array that contains items from a raw array. +// Every item is parsed as a primitive value. +// The function returns an error when an error happened while parse array's items. +func parseArray(raw []string, schemaRef *openapi3.SchemaRef) ([]interface{}, error) { + var value []interface{} + for i, v := range raw { + item, err := parsePrimitive(v, schemaRef.Value.Items) + if err != nil { + if v, ok := err.(*ParseError); ok { + return nil, &ParseError{path: []interface{}{i}, Cause: v} + } + return nil, fmt.Errorf("item %d: %s", i, err) + } + + // If the items are nil, then the array is nil. There shouldn't be case where some values are actual primitive + // values and some are nil values. + if item == nil { + return nil, nil + } + value = append(value, item) + } + return value, nil +} + +// parsePrimitive returns a value that is created by parsing a source string to a primitive type +// that is specified by a schema. The function returns nil when the source string is empty. +// The function panics when a schema has a non primitive type. +func parsePrimitive(raw string, schema *openapi3.SchemaRef) (interface{}, error) { + if raw == "" { + return nil, nil + } + switch schema.Value.Type { + case "integer": + v, err := strconv.ParseFloat(raw, 64) + if err != nil { + return nil, &ParseError{Kind: KindInvalidFormat, Value: raw, Reason: "an invalid integer", Cause: err} + } + return v, nil + case "number": + v, err := strconv.ParseFloat(raw, 64) + if err != nil { + return nil, &ParseError{Kind: KindInvalidFormat, Value: raw, Reason: "an invalid number", Cause: err} + } + return v, nil + case "boolean": + v, err := strconv.ParseBool(raw) + if err != nil { + return nil, &ParseError{Kind: KindInvalidFormat, Value: raw, Reason: "an invalid number", Cause: err} + } + return v, nil + case "string": + return raw, nil + default: + panic(fmt.Sprintf("schema has non primitive type %q", schema.Value.Type)) + } +} + +// EncodingFn is a function that returns an encoding of a request body's part. +type EncodingFn func(partName string) *openapi3.Encoding + +// BodyDecoder is an interface to decode a body of a request or response. +// An implementation must return a value that is a primitive, []interface{}, or map[string]interface{}. +type BodyDecoder func(io.Reader, http.Header, *openapi3.SchemaRef, EncodingFn) (interface{}, error) + +// bodyDecoders contains decoders for supported content types of a body. +// By default, there is content type "application/json" is supported only. +var bodyDecoders = make(map[string]BodyDecoder) + +// RegisteredBodyDecoder returns the registered body decoder for the given content type. +// +// If no decoder was registered for the given content type, nil is returned. +// This call is not thread-safe: body decoders should not be created/destroyed by multiple goroutines. +func RegisteredBodyDecoder(contentType string) BodyDecoder { + return bodyDecoders[contentType] +} + +// RegisterBodyDecoder registers a request body's decoder for a content type. +// +// If a decoder for the specified content type already exists, the function replaces +// it with the specified decoder. +// This call is not thread-safe: body decoders should not be created/destroyed by multiple goroutines. +func RegisterBodyDecoder(contentType string, decoder BodyDecoder) { + if contentType == "" { + panic("contentType is empty") + } + if decoder == nil { + panic("decoder is not defined") + } + bodyDecoders[contentType] = decoder +} + +// UnregisterBodyDecoder dissociates a body decoder from a content type. +// +// Decoding this content type will result in an error. +// This call is not thread-safe: body decoders should not be created/destroyed by multiple goroutines. +func UnregisterBodyDecoder(contentType string) { + if contentType == "" { + panic("contentType is empty") + } + delete(bodyDecoders, contentType) +} + +var headerCT = http.CanonicalHeaderKey("Content-Type") + +const prefixUnsupportedCT = "unsupported content type" + +// decodeBody returns a decoded body. +// The function returns ParseError when a body is invalid. +func decodeBody(body io.Reader, header http.Header, schema *openapi3.SchemaRef, encFn EncodingFn) (interface{}, error) { + contentType := header.Get(headerCT) + if contentType == "" { + if _, ok := body.(*multipart.Part); ok { + contentType = "text/plain" + } + } + mediaType := parseMediaType(contentType) + decoder, ok := bodyDecoders[mediaType] + if !ok { + return nil, &ParseError{ + Kind: KindUnsupportedFormat, + Reason: fmt.Sprintf("%s %q", prefixUnsupportedCT, mediaType), + } + } + value, err := decoder(body, header, schema, encFn) + if err != nil { + return nil, err + } + return value, nil +} + +func init() { + RegisterBodyDecoder("text/plain", plainBodyDecoder) + RegisterBodyDecoder("application/json", jsonBodyDecoder) + RegisterBodyDecoder("application/x-yaml", yamlBodyDecoder) + RegisterBodyDecoder("application/yaml", yamlBodyDecoder) + RegisterBodyDecoder("application/problem+json", jsonBodyDecoder) + RegisterBodyDecoder("application/x-www-form-urlencoded", urlencodedBodyDecoder) + RegisterBodyDecoder("multipart/form-data", multipartBodyDecoder) + RegisterBodyDecoder("application/octet-stream", FileBodyDecoder) +} + +func plainBodyDecoder(body io.Reader, header http.Header, schema *openapi3.SchemaRef, encFn EncodingFn) (interface{}, error) { + data, err := ioutil.ReadAll(body) + if err != nil { + return nil, &ParseError{Kind: KindInvalidFormat, Cause: err} + } + return string(data), nil +} + +func jsonBodyDecoder(body io.Reader, header http.Header, schema *openapi3.SchemaRef, encFn EncodingFn) (interface{}, error) { + var value interface{} + if err := json.NewDecoder(body).Decode(&value); err != nil { + return nil, &ParseError{Kind: KindInvalidFormat, Cause: err} + } + return value, nil +} + +func yamlBodyDecoder(body io.Reader, header http.Header, schema *openapi3.SchemaRef, encFn EncodingFn) (interface{}, error) { + var value interface{} + if err := yaml.NewDecoder(body).Decode(&value); err != nil { + return nil, &ParseError{Kind: KindInvalidFormat, Cause: err} + } + return value, nil +} + +func urlencodedBodyDecoder(body io.Reader, header http.Header, schema *openapi3.SchemaRef, encFn EncodingFn) (interface{}, error) { + // Validate schema of request body. + // By the OpenAPI 3 specification request body's schema must have type "object". + // Properties of the schema describes individual parts of request body. + if schema.Value.Type != "object" { + return nil, errors.New("unsupported schema of request body") + } + for propName, propSchema := range schema.Value.Properties { + switch propSchema.Value.Type { + case "object": + return nil, fmt.Errorf("unsupported schema of request body's property %q", propName) + case "array": + items := propSchema.Value.Items.Value + if items.Type != "string" && items.Type != "integer" && items.Type != "number" && items.Type != "boolean" { + return nil, fmt.Errorf("unsupported schema of request body's property %q", propName) + } + } + } + + // Parse form. + b, err := ioutil.ReadAll(body) + if err != nil { + return nil, err + } + values, err := url.ParseQuery(string(b)) + if err != nil { + return nil, err + } + + // Make an object value from form values. + obj := make(map[string]interface{}) + dec := &urlValuesDecoder{values: values} + for name, prop := range schema.Value.Properties { + var ( + value interface{} + enc *openapi3.Encoding + ) + if encFn != nil { + enc = encFn(name) + } + sm := enc.SerializationMethod() + + if value, _, err = decodeValue(dec, name, sm, prop, false); err != nil { + return nil, err + } + obj[name] = value + } + + return obj, nil +} + +func multipartBodyDecoder(body io.Reader, header http.Header, schema *openapi3.SchemaRef, encFn EncodingFn) (interface{}, error) { + if schema.Value.Type != "object" { + return nil, errors.New("unsupported schema of request body") + } + + // Parse form. + values := make(map[string][]interface{}) + contentType := header.Get(headerCT) + _, params, err := mime.ParseMediaType(contentType) + if err != nil { + return nil, err + } + mr := multipart.NewReader(body, params["boundary"]) + for { + var part *multipart.Part + if part, err = mr.NextPart(); err == io.EOF { + break + } + if err != nil { + return nil, err + } + + var ( + name = part.FormName() + enc *openapi3.Encoding + ) + if encFn != nil { + enc = encFn(name) + } + subEncFn := func(string) *openapi3.Encoding { return enc } + // If the property's schema has type "array" it is means that the form contains a few parts with the same name. + // Every such part has a type that is defined by an items schema in the property's schema. + var valueSchema *openapi3.SchemaRef + var exists bool + valueSchema, exists = schema.Value.Properties[name] + if !exists { + anyProperties := schema.Value.AdditionalPropertiesAllowed + if anyProperties != nil { + switch *anyProperties { + case true: + //additionalProperties: true + continue + default: + //additionalProperties: false + return nil, &ParseError{Kind: KindOther, Cause: fmt.Errorf("part %s: undefined", name)} + } + } + if schema.Value.AdditionalProperties == nil { + return nil, &ParseError{Kind: KindOther, Cause: fmt.Errorf("part %s: undefined", name)} + } + valueSchema, exists = schema.Value.AdditionalProperties.Value.Properties[name] + if !exists { + return nil, &ParseError{Kind: KindOther, Cause: fmt.Errorf("part %s: undefined", name)} + } + } + if valueSchema.Value.Type == "array" { + valueSchema = valueSchema.Value.Items + } + + var value interface{} + if value, err = decodeBody(part, http.Header(part.Header), valueSchema, subEncFn); err != nil { + if v, ok := err.(*ParseError); ok { + return nil, &ParseError{path: []interface{}{name}, Cause: v} + } + return nil, fmt.Errorf("part %s: %s", name, err) + } + values[name] = append(values[name], value) + } + + allTheProperties := make(map[string]*openapi3.SchemaRef) + for k, v := range schema.Value.Properties { + allTheProperties[k] = v + } + if schema.Value.AdditionalProperties != nil { + for k, v := range schema.Value.AdditionalProperties.Value.Properties { + allTheProperties[k] = v + } + } + // Make an object value from form values. + obj := make(map[string]interface{}) + for name, prop := range allTheProperties { + vv := values[name] + if len(vv) == 0 { + continue + } + if prop.Value.Type == "array" { + obj[name] = vv + } else { + obj[name] = vv[0] + } + } + + return obj, nil +} + +// FileBodyDecoder is a body decoder that decodes a file body to a string. +func FileBodyDecoder(body io.Reader, header http.Header, schema *openapi3.SchemaRef, encFn EncodingFn) (interface{}, error) { + data, err := ioutil.ReadAll(body) + if err != nil { + return nil, err + } + return string(data), nil +} diff --git a/vendor/github.com/getkin/kin-openapi/openapi3filter/validate_request.go b/vendor/github.com/getkin/kin-openapi/openapi3filter/validate_request.go new file mode 100644 index 000000000..fae6b09f9 --- /dev/null +++ b/vendor/github.com/getkin/kin-openapi/openapi3filter/validate_request.go @@ -0,0 +1,332 @@ +package openapi3filter + +import ( + "bytes" + "context" + "errors" + "fmt" + "io/ioutil" + "net/http" + "sort" + + "github.com/getkin/kin-openapi/openapi3" +) + +// ErrAuthenticationServiceMissing is returned when no authentication service +// is defined for the request validator +var ErrAuthenticationServiceMissing = errors.New("missing AuthenticationFunc") + +// ErrInvalidRequired is returned when a required value of a parameter or request body is not defined. +var ErrInvalidRequired = errors.New("value is required but missing") + +// ErrInvalidEmptyValue is returned when a value of a parameter or request body is empty while it's not allowed. +var ErrInvalidEmptyValue = errors.New("empty value is not allowed") + +// ValidateRequest is used to validate the given input according to previous +// loaded OpenAPIv3 spec. If the input does not match the OpenAPIv3 spec, a +// non-nil error will be returned. +// +// Note: One can tune the behavior of uniqueItems: true verification +// by registering a custom function with openapi3.RegisterArrayUniqueItemsChecker +func ValidateRequest(ctx context.Context, input *RequestValidationInput) error { + var ( + err error + me openapi3.MultiError + ) + + options := input.Options + if options == nil { + options = DefaultOptions + } + route := input.Route + operation := route.Operation + operationParameters := operation.Parameters + pathItemParameters := route.PathItem.Parameters + + // Security + security := operation.Security + // If there aren't any security requirements for the operation + if security == nil { + // Use the global security requirements. + security = &route.Spec.Security + } + if security != nil { + if err = ValidateSecurityRequirements(ctx, input, *security); err != nil && !options.MultiError { + return err + } + + if err != nil { + me = append(me, err) + } + } + + // For each parameter of the PathItem + for _, parameterRef := range pathItemParameters { + parameter := parameterRef.Value + if operationParameters != nil { + if override := operationParameters.GetByInAndName(parameter.In, parameter.Name); override != nil { + continue + } + } + + if err = ValidateParameter(ctx, input, parameter); err != nil && !options.MultiError { + return err + } + + if err != nil { + me = append(me, err) + } + } + + // For each parameter of the Operation + for _, parameter := range operationParameters { + if err = ValidateParameter(ctx, input, parameter.Value); err != nil && !options.MultiError { + return err + } + + if err != nil { + me = append(me, err) + } + } + + // RequestBody + requestBody := operation.RequestBody + if requestBody != nil && !options.ExcludeRequestBody { + if err = ValidateRequestBody(ctx, input, requestBody.Value); err != nil && !options.MultiError { + return err + } + + if err != nil { + me = append(me, err) + } + } + + if len(me) > 0 { + return me + } + + return nil +} + +// ValidateParameter validates a parameter's value by JSON schema. +// The function returns RequestError with a ParseError cause when unable to parse a value. +// The function returns RequestError with ErrInvalidRequired cause when a value of a required parameter is not defined. +// The function returns RequestError with ErrInvalidEmptyValue cause when a value of a required parameter is not defined. +// The function returns RequestError with a openapi3.SchemaError cause when a value is invalid by JSON schema. +func ValidateParameter(ctx context.Context, input *RequestValidationInput, parameter *openapi3.Parameter) error { + if parameter.Schema == nil && parameter.Content == nil { + // We have no schema for the parameter. Assume that everything passes + // a schema-less check, but this could also be an error. The OpenAPI + // validation allows this to happen. + return nil + } + + options := input.Options + if options == nil { + options = DefaultOptions + } + + var value interface{} + var err error + var found bool + var schema *openapi3.Schema + + // Validation will ensure that we either have content or schema. + if parameter.Content != nil { + if value, schema, found, err = decodeContentParameter(parameter, input); err != nil { + return &RequestError{Input: input, Parameter: parameter, Err: err} + } + } else { + if value, found, err = decodeStyledParameter(parameter, input); err != nil { + return &RequestError{Input: input, Parameter: parameter, Err: err} + } + schema = parameter.Schema.Value + } + // Validate a parameter's value and presence. + if parameter.Required && !found { + return &RequestError{Input: input, Parameter: parameter, Reason: ErrInvalidRequired.Error(), Err: ErrInvalidRequired} + } + + if isNilValue(value) { + if !parameter.AllowEmptyValue && found { + return &RequestError{Input: input, Parameter: parameter, Reason: ErrInvalidEmptyValue.Error(), Err: ErrInvalidEmptyValue} + } + return nil + } + if schema == nil { + // A parameter's schema is not defined so skip validation of a parameter's value. + return nil + } + + var opts []openapi3.SchemaValidationOption + if options.MultiError { + opts = make([]openapi3.SchemaValidationOption, 0, 1) + opts = append(opts, openapi3.MultiErrors()) + } + if err = schema.VisitJSON(value, opts...); err != nil { + return &RequestError{Input: input, Parameter: parameter, Err: err} + } + return nil +} + +const prefixInvalidCT = "header Content-Type has unexpected value" + +// ValidateRequestBody validates data of a request's body. +// +// The function returns RequestError with ErrInvalidRequired cause when a value is required but not defined. +// The function returns RequestError with a openapi3.SchemaError cause when a value is invalid by JSON schema. +func ValidateRequestBody(ctx context.Context, input *RequestValidationInput, requestBody *openapi3.RequestBody) error { + var ( + req = input.Request + data []byte + ) + + options := input.Options + if options == nil { + options = DefaultOptions + } + + if req.Body != http.NoBody && req.Body != nil { + defer req.Body.Close() + var err error + if data, err = ioutil.ReadAll(req.Body); err != nil { + return &RequestError{ + Input: input, + RequestBody: requestBody, + Reason: "reading failed", + Err: err, + } + } + // Put the data back into the input + req.Body = ioutil.NopCloser(bytes.NewReader(data)) + } + + if len(data) == 0 { + if requestBody.Required { + return &RequestError{Input: input, RequestBody: requestBody, Err: ErrInvalidRequired} + } + return nil + } + + content := requestBody.Content + if len(content) == 0 { + // A request's body does not have declared content, so skip validation. + return nil + } + + inputMIME := req.Header.Get(headerCT) + contentType := requestBody.Content.Get(inputMIME) + if contentType == nil { + return &RequestError{ + Input: input, + RequestBody: requestBody, + Reason: fmt.Sprintf("%s %q", prefixInvalidCT, inputMIME), + } + } + + if contentType.Schema == nil { + // A JSON schema that describes the received data is not declared, so skip validation. + return nil + } + + encFn := func(name string) *openapi3.Encoding { return contentType.Encoding[name] } + value, err := decodeBody(bytes.NewReader(data), req.Header, contentType.Schema, encFn) + if err != nil { + return &RequestError{ + Input: input, + RequestBody: requestBody, + Reason: "failed to decode request body", + Err: err, + } + } + + opts := make([]openapi3.SchemaValidationOption, 0, 2) // 2 potential opts here + opts = append(opts, openapi3.VisitAsRequest()) + if options.MultiError { + opts = append(opts, openapi3.MultiErrors()) + } + + // Validate JSON with the schema + if err := contentType.Schema.Value.VisitJSON(value, opts...); err != nil { + return &RequestError{ + Input: input, + RequestBody: requestBody, + Reason: "doesn't match the schema", + Err: err, + } + } + return nil +} + +// ValidateSecurityRequirements goes through multiple OpenAPI 3 security +// requirements in order and returns nil on the first valid requirement. +// If no requirement is met, errors are returned in order. +func ValidateSecurityRequirements(ctx context.Context, input *RequestValidationInput, srs openapi3.SecurityRequirements) error { + if len(srs) == 0 { + return nil + } + var errs []error + for _, sr := range srs { + if err := validateSecurityRequirement(ctx, input, sr); err != nil { + if len(errs) == 0 { + errs = make([]error, 0, len(srs)) + } + errs = append(errs, err) + continue + } + return nil + } + return &SecurityRequirementsError{ + SecurityRequirements: srs, + Errors: errs, + } +} + +// validateSecurityRequirement validates a single OpenAPI 3 security requirement +func validateSecurityRequirement(ctx context.Context, input *RequestValidationInput, securityRequirement openapi3.SecurityRequirement) error { + doc := input.Route.Spec + securitySchemes := doc.Components.SecuritySchemes + + // Ensure deterministic order + names := make([]string, 0, len(securityRequirement)) + for name := range securityRequirement { + names = append(names, name) + } + sort.Strings(names) + + // Get authentication function + options := input.Options + if options == nil { + options = DefaultOptions + } + f := options.AuthenticationFunc + if f == nil { + return ErrAuthenticationServiceMissing + } + + // For each scheme for the requirement + for _, name := range names { + var securityScheme *openapi3.SecurityScheme + if securitySchemes != nil { + if ref := securitySchemes[name]; ref != nil { + securityScheme = ref.Value + } + } + if securityScheme == nil { + return &RequestError{ + Input: input, + Err: fmt.Errorf("security scheme %q is not declared", name), + } + } + scopes := securityRequirement[name] + if err := f(ctx, &AuthenticationInput{ + RequestValidationInput: input, + SecuritySchemeName: name, + SecurityScheme: securityScheme, + Scopes: scopes, + }); err != nil { + return err + } + } + return nil +} diff --git a/vendor/github.com/getkin/kin-openapi/openapi3filter/validate_request_input.go b/vendor/github.com/getkin/kin-openapi/openapi3filter/validate_request_input.go new file mode 100644 index 000000000..91dd102b6 --- /dev/null +++ b/vendor/github.com/getkin/kin-openapi/openapi3filter/validate_request_input.go @@ -0,0 +1,38 @@ +package openapi3filter + +import ( + "net/http" + "net/url" + + "github.com/getkin/kin-openapi/openapi3" + "github.com/getkin/kin-openapi/routers" +) + +// A ContentParameterDecoder takes a parameter definition from the OpenAPI spec, +// and the value which we received for it. It is expected to return the +// value unmarshaled into an interface which can be traversed for +// validation, it should also return the schema to be used for validating the +// object, since there can be more than one in the content spec. +// +// If a query parameter appears multiple times, values[] will have more +// than one value, but for all other parameter types it should have just +// one. +type ContentParameterDecoder func(param *openapi3.Parameter, values []string) (interface{}, *openapi3.Schema, error) + +type RequestValidationInput struct { + Request *http.Request + PathParams map[string]string + QueryParams url.Values + Route *routers.Route + Options *Options + ParamDecoder ContentParameterDecoder +} + +func (input *RequestValidationInput) GetQueryParams() url.Values { + q := input.QueryParams + if q == nil { + q = input.Request.URL.Query() + input.QueryParams = q + } + return q +} diff --git a/vendor/github.com/getkin/kin-openapi/openapi3filter/validate_response.go b/vendor/github.com/getkin/kin-openapi/openapi3filter/validate_response.go new file mode 100644 index 000000000..7cb713ace --- /dev/null +++ b/vendor/github.com/getkin/kin-openapi/openapi3filter/validate_response.go @@ -0,0 +1,138 @@ +// Package openapi3filter validates that requests and inputs request an OpenAPI 3 specification file. +package openapi3filter + +import ( + "bytes" + "context" + "fmt" + "io/ioutil" + "net/http" + + "github.com/getkin/kin-openapi/openapi3" +) + +// ValidateResponse is used to validate the given input according to previous +// loaded OpenAPIv3 spec. If the input does not match the OpenAPIv3 spec, a +// non-nil error will be returned. +// +// Note: One can tune the behavior of uniqueItems: true verification +// by registering a custom function with openapi3.RegisterArrayUniqueItemsChecker +func ValidateResponse(ctx context.Context, input *ResponseValidationInput) error { + req := input.RequestValidationInput.Request + switch req.Method { + case "HEAD": + return nil + } + status := input.Status + + // These status codes will never be validated. + // TODO: The list is probably missing some. + switch status { + case http.StatusNotModified, + http.StatusPermanentRedirect, + http.StatusTemporaryRedirect, + http.StatusMovedPermanently: + return nil + } + route := input.RequestValidationInput.Route + options := input.Options + if options == nil { + options = DefaultOptions + } + + // Find input for the current status + responses := route.Operation.Responses + if len(responses) == 0 { + return nil + } + responseRef := responses.Get(status) // Response + if responseRef == nil { + responseRef = responses.Default() // Default input + } + if responseRef == nil { + // By default, status that is not documented is allowed. + if !options.IncludeResponseStatus { + return nil + } + return &ResponseError{Input: input, Reason: "status is not supported"} + } + response := responseRef.Value + if response == nil { + return &ResponseError{Input: input, Reason: "response has not been resolved"} + } + + if options.ExcludeResponseBody { + // A user turned off validation of a response's body. + return nil + } + + content := response.Content + if len(content) == 0 || options.ExcludeResponseBody { + // An operation does not contains a validation schema for responses with this status code. + return nil + } + + inputMIME := input.Header.Get(headerCT) + contentType := content.Get(inputMIME) + if contentType == nil { + return &ResponseError{ + Input: input, + Reason: fmt.Sprintf("response header Content-Type has unexpected value: %q", inputMIME), + } + } + + if contentType.Schema == nil { + // An operation does not contains a validation schema for responses with this status code. + return nil + } + + // Read response's body. + body := input.Body + + // Response would contain partial or empty input body + // after we begin reading. + // Ensure that this doesn't happen. + input.Body = nil + + // Ensure we close the reader + defer body.Close() + + // Read all + data, err := ioutil.ReadAll(body) + if err != nil { + return &ResponseError{ + Input: input, + Reason: "failed to read response body", + Err: err, + } + } + + // Put the data back into the response. + input.SetBodyBytes(data) + + encFn := func(name string) *openapi3.Encoding { return contentType.Encoding[name] } + value, err := decodeBody(bytes.NewBuffer(data), input.Header, contentType.Schema, encFn) + if err != nil { + return &ResponseError{ + Input: input, + Reason: "failed to decode response body", + Err: err, + } + } + + opts := make([]openapi3.SchemaValidationOption, 0, 2) // 2 potential opts here + opts = append(opts, openapi3.VisitAsRequest()) + if options.MultiError { + opts = append(opts, openapi3.MultiErrors()) + } + + // Validate data with the schema. + if err := contentType.Schema.Value.VisitJSON(value, opts...); err != nil { + return &ResponseError{ + Input: input, + Reason: "response body doesn't match the schema", + Err: err, + } + } + return nil +} diff --git a/vendor/github.com/getkin/kin-openapi/openapi3filter/validate_response_input.go b/vendor/github.com/getkin/kin-openapi/openapi3filter/validate_response_input.go new file mode 100644 index 000000000..edf38730a --- /dev/null +++ b/vendor/github.com/getkin/kin-openapi/openapi3filter/validate_response_input.go @@ -0,0 +1,42 @@ +package openapi3filter + +import ( + "bytes" + "io" + "io/ioutil" + "net/http" +) + +type ResponseValidationInput struct { + RequestValidationInput *RequestValidationInput + Status int + Header http.Header + Body io.ReadCloser + Options *Options +} + +func (input *ResponseValidationInput) SetBodyBytes(value []byte) *ResponseValidationInput { + input.Body = ioutil.NopCloser(bytes.NewReader(value)) + return input +} + +var JSONPrefixes = []string{ + ")]}',\n", +} + +// TrimJSONPrefix trims one of the possible prefixes +func TrimJSONPrefix(data []byte) []byte { +search: + for _, prefix := range JSONPrefixes { + if len(data) < len(prefix) { + continue + } + for i, b := range data[:len(prefix)] { + if b != prefix[i] { + continue search + } + } + return data[len(prefix):] + } + return data +} diff --git a/vendor/github.com/getkin/kin-openapi/openapi3filter/validation_error.go b/vendor/github.com/getkin/kin-openapi/openapi3filter/validation_error.go new file mode 100644 index 000000000..7e685cdef --- /dev/null +++ b/vendor/github.com/getkin/kin-openapi/openapi3filter/validation_error.go @@ -0,0 +1,85 @@ +package openapi3filter + +import ( + "bytes" + "strconv" +) + +// ValidationError struct provides granular error information +// useful for communicating issues back to end user and developer. +// Based on https://jsonapi.org/format/#error-objects +type ValidationError struct { + // A unique identifier for this particular occurrence of the problem. + Id string `json:"id,omitempty" yaml:"id,omitempty"` + // The HTTP status code applicable to this problem. + Status int `json:"status,omitempty" yaml:"status,omitempty"` + // An application-specific error code, expressed as a string value. + Code string `json:"code,omitempty" yaml:"code,omitempty"` + // A short, human-readable summary of the problem. It **SHOULD NOT** change from occurrence to occurrence of the problem, except for purposes of localization. + Title string `json:"title,omitempty" yaml:"title,omitempty"` + // A human-readable explanation specific to this occurrence of the problem. + Detail string `json:"detail,omitempty" yaml:"detail,omitempty"` + // An object containing references to the source of the error + Source *ValidationErrorSource `json:"source,omitempty" yaml:"source,omitempty"` +} + +// ValidationErrorSource struct +type ValidationErrorSource struct { + // A JSON Pointer [RFC6901] to the associated entity in the request document [e.g. \"/data\" for a primary data object, or \"/data/attributes/title\" for a specific attribute]. + Pointer string `json:"pointer,omitempty" yaml:"pointer,omitempty"` + // A string indicating which query parameter caused the error. + Parameter string `json:"parameter,omitempty" yaml:"parameter,omitempty"` +} + +var _ error = &ValidationError{} + +// Error implements the error interface. +func (e *ValidationError) Error() string { + b := new(bytes.Buffer) + b.WriteString("[") + if e.Status != 0 { + b.WriteString(strconv.Itoa(e.Status)) + } + b.WriteString("]") + b.WriteString("[") + if e.Code != "" { + b.WriteString(e.Code) + } + b.WriteString("]") + b.WriteString("[") + if e.Id != "" { + b.WriteString(e.Id) + } + b.WriteString("]") + b.WriteString(" ") + if e.Title != "" { + b.WriteString(e.Title) + b.WriteString(" ") + } + if e.Detail != "" { + b.WriteString("| ") + b.WriteString(e.Detail) + b.WriteString(" ") + } + if e.Source != nil { + b.WriteString("[source ") + if e.Source.Parameter != "" { + b.WriteString("parameter=") + b.WriteString(e.Source.Parameter) + } else if e.Source.Pointer != "" { + b.WriteString("pointer=") + b.WriteString(e.Source.Pointer) + } + b.WriteString("]") + } + + if b.Len() == 0 { + return "no error" + } + return b.String() +} + +// StatusCode implements the StatusCoder interface for DefaultErrorEncoder +func (e *ValidationError) StatusCode() int { + return e.Status +} diff --git a/vendor/github.com/getkin/kin-openapi/openapi3filter/validation_error_encoder.go b/vendor/github.com/getkin/kin-openapi/openapi3filter/validation_error_encoder.go new file mode 100644 index 000000000..779887db0 --- /dev/null +++ b/vendor/github.com/getkin/kin-openapi/openapi3filter/validation_error_encoder.go @@ -0,0 +1,185 @@ +package openapi3filter + +import ( + "context" + "fmt" + "net/http" + "strings" + + "github.com/getkin/kin-openapi/openapi3" + "github.com/getkin/kin-openapi/routers" +) + +// ValidationErrorEncoder wraps a base ErrorEncoder to handle ValidationErrors +type ValidationErrorEncoder struct { + Encoder ErrorEncoder +} + +// Encode implements the ErrorEncoder interface for encoding ValidationErrors +func (enc *ValidationErrorEncoder) Encode(ctx context.Context, err error, w http.ResponseWriter) { + if e, ok := err.(*routers.RouteError); ok { + cErr := convertRouteError(e) + enc.Encoder(ctx, cErr, w) + return + } + + e, ok := err.(*RequestError) + if !ok { + enc.Encoder(ctx, err, w) + return + } + + var cErr *ValidationError + if e.Err == nil { + cErr = convertBasicRequestError(e) + } else if e.Err == ErrInvalidRequired { + cErr = convertErrInvalidRequired(e) + } else if e.Err == ErrInvalidEmptyValue { + cErr = convertErrInvalidEmptyValue(e) + } else if innerErr, ok := e.Err.(*ParseError); ok { + cErr = convertParseError(e, innerErr) + } else if innerErr, ok := e.Err.(*openapi3.SchemaError); ok { + cErr = convertSchemaError(e, innerErr) + } + + if cErr != nil { + enc.Encoder(ctx, cErr, w) + return + } + enc.Encoder(ctx, err, w) +} + +func convertRouteError(e *routers.RouteError) *ValidationError { + status := http.StatusNotFound + if e.Error() == routers.ErrMethodNotAllowed.Error() { + status = http.StatusMethodNotAllowed + } + return &ValidationError{Status: status, Title: e.Error()} +} + +func convertBasicRequestError(e *RequestError) *ValidationError { + if strings.HasPrefix(e.Reason, prefixInvalidCT) { + if strings.HasSuffix(e.Reason, `""`) { + return &ValidationError{ + Status: http.StatusUnsupportedMediaType, + Title: "header Content-Type is required", + } + } + return &ValidationError{ + Status: http.StatusUnsupportedMediaType, + Title: prefixUnsupportedCT + strings.TrimPrefix(e.Reason, prefixInvalidCT), + } + } + return &ValidationError{ + Status: http.StatusBadRequest, + Title: e.Error(), + } +} + +func convertErrInvalidRequired(e *RequestError) *ValidationError { + if e.Err == ErrInvalidRequired && e.Parameter != nil { + return &ValidationError{ + Status: http.StatusBadRequest, + Title: fmt.Sprintf("parameter %q in %s is required", e.Parameter.Name, e.Parameter.In), + } + } + return &ValidationError{ + Status: http.StatusBadRequest, + Title: e.Error(), + } +} + +func convertErrInvalidEmptyValue(e *RequestError) *ValidationError { + if e.Err == ErrInvalidEmptyValue && e.Parameter != nil { + return &ValidationError{ + Status: http.StatusBadRequest, + Title: fmt.Sprintf("parameter %q in %s is not allowed to be empty", e.Parameter.Name, e.Parameter.In), + } + } + return &ValidationError{ + Status: http.StatusBadRequest, + Title: e.Error(), + } +} + +func convertParseError(e *RequestError, innerErr *ParseError) *ValidationError { + // We treat path params of the wrong type like a 404 instead of a 400 + if innerErr.Kind == KindInvalidFormat && e.Parameter != nil && e.Parameter.In == "path" { + return &ValidationError{ + Status: http.StatusNotFound, + Title: fmt.Sprintf("resource not found with %q value: %v", e.Parameter.Name, innerErr.Value), + } + } else if strings.HasPrefix(innerErr.Reason, prefixUnsupportedCT) { + return &ValidationError{ + Status: http.StatusUnsupportedMediaType, + Title: innerErr.Reason, + } + } else if innerErr.RootCause() != nil { + if rootErr, ok := innerErr.Cause.(*ParseError); ok && + rootErr.Kind == KindInvalidFormat && e.Parameter.In == "query" { + return &ValidationError{ + Status: http.StatusBadRequest, + Title: fmt.Sprintf("parameter %q in %s is invalid: %v is %s", + e.Parameter.Name, e.Parameter.In, rootErr.Value, rootErr.Reason), + } + } + return &ValidationError{ + Status: http.StatusBadRequest, + Title: innerErr.Reason, + } + } + return nil +} + +func convertSchemaError(e *RequestError, innerErr *openapi3.SchemaError) *ValidationError { + cErr := &ValidationError{Title: innerErr.Reason} + + // Handle "Origin" error + if originErr, ok := innerErr.Origin.(*openapi3.SchemaError); ok { + cErr = convertSchemaError(e, originErr) + } + + // Add http status code + if e.Parameter != nil { + cErr.Status = http.StatusBadRequest + } else if e.RequestBody != nil { + cErr.Status = http.StatusUnprocessableEntity + } + + // Add error source + if e.Parameter != nil { + // We have a JSONPointer in the query param too so need to + // make sure 'Parameter' check takes priority over 'Pointer' + cErr.Source = &ValidationErrorSource{Parameter: e.Parameter.Name} + } else if ptr := innerErr.JSONPointer(); ptr != nil { + cErr.Source = &ValidationErrorSource{Pointer: toJSONPointer(ptr)} + } + + // Add details on allowed values for enums + if innerErr.SchemaField == "enum" { + enums := make([]string, 0, len(innerErr.Schema.Enum)) + for _, enum := range innerErr.Schema.Enum { + enums = append(enums, fmt.Sprintf("%v", enum)) + } + cErr.Detail = fmt.Sprintf("value %v at %s must be one of: %s", + innerErr.Value, + toJSONPointer(innerErr.JSONPointer()), + strings.Join(enums, ", ")) + value := fmt.Sprintf("%v", innerErr.Value) + if e.Parameter != nil && + (e.Parameter.Explode == nil || *e.Parameter.Explode) && + (e.Parameter.Style == "" || e.Parameter.Style == "form") && + strings.Contains(value, ",") { + parts := strings.Split(value, ",") + cErr.Detail = fmt.Sprintf("%s; perhaps you intended '?%s=%s'", + cErr.Detail, + e.Parameter.Name, + strings.Join(parts, "&"+e.Parameter.Name+"=")) + } + } + return cErr +} + +func toJSONPointer(reversePath []string) string { + return "/" + strings.Join(reversePath, "/") +} diff --git a/vendor/github.com/getkin/kin-openapi/openapi3filter/validation_handler.go b/vendor/github.com/getkin/kin-openapi/openapi3filter/validation_handler.go new file mode 100644 index 000000000..eeb1ca1ea --- /dev/null +++ b/vendor/github.com/getkin/kin-openapi/openapi3filter/validation_handler.go @@ -0,0 +1,103 @@ +package openapi3filter + +import ( + "context" + "net/http" + + "github.com/getkin/kin-openapi/openapi3" + "github.com/getkin/kin-openapi/routers" + legacyrouter "github.com/getkin/kin-openapi/routers/legacy" +) + +type AuthenticationFunc func(context.Context, *AuthenticationInput) error + +func NoopAuthenticationFunc(context.Context, *AuthenticationInput) error { return nil } + +var _ AuthenticationFunc = NoopAuthenticationFunc + +type ValidationHandler struct { + Handler http.Handler + AuthenticationFunc AuthenticationFunc + File string + ErrorEncoder ErrorEncoder + router routers.Router +} + +func (h *ValidationHandler) Load() error { + loader := openapi3.NewLoader() + doc, err := loader.LoadFromFile(h.File) + if err != nil { + return err + } + if err := doc.Validate(loader.Context); err != nil { + return err + } + if h.router, err = legacyrouter.NewRouter(doc); err != nil { + return err + } + + // set defaults + if h.Handler == nil { + h.Handler = http.DefaultServeMux + } + if h.AuthenticationFunc == nil { + h.AuthenticationFunc = NoopAuthenticationFunc + } + if h.ErrorEncoder == nil { + h.ErrorEncoder = DefaultErrorEncoder + } + + return nil +} + +func (h *ValidationHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { + if handled := h.before(w, r); handled { + return + } + // TODO: validateResponse + h.Handler.ServeHTTP(w, r) +} + +// Middleware implements gorilla/mux MiddlewareFunc +func (h *ValidationHandler) Middleware(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if handled := h.before(w, r); handled { + return + } + // TODO: validateResponse + next.ServeHTTP(w, r) + }) +} + +func (h *ValidationHandler) before(w http.ResponseWriter, r *http.Request) (handled bool) { + if err := h.validateRequest(r); err != nil { + h.ErrorEncoder(r.Context(), err, w) + return true + } + return false +} + +func (h *ValidationHandler) validateRequest(r *http.Request) error { + // Find route + route, pathParams, err := h.router.FindRoute(r) + if err != nil { + return err + } + + options := &Options{ + AuthenticationFunc: h.AuthenticationFunc, + } + + // Validate request + requestValidationInput := &RequestValidationInput{ + Request: r, + PathParams: pathParams, + Route: route, + Options: options, + } + if err = ValidateRequest(r.Context(), requestValidationInput); err != nil { + return err + } + + return nil +} diff --git a/vendor/github.com/getkin/kin-openapi/openapi3filter/validation_kit.go b/vendor/github.com/getkin/kin-openapi/openapi3filter/validation_kit.go new file mode 100644 index 000000000..9e11e4fc8 --- /dev/null +++ b/vendor/github.com/getkin/kin-openapi/openapi3filter/validation_kit.go @@ -0,0 +1,85 @@ +package openapi3filter + +import ( + "context" + "encoding/json" + "net/http" +) + +/////////////////////////////////////////////////////////////////////////////////// +// We didn't want to tie kin-openapi too tightly with go-kit. +// This file contains the ErrorEncoder and DefaultErrorEncoder function +// borrowed from this project. +// +// The MIT License (MIT) +// +// Copyright (c) 2015 Peter Bourgon +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +/////////////////////////////////////////////////////////////////////////////////// + +// ErrorEncoder is responsible for encoding an error to the ResponseWriter. +// Users are encouraged to use custom ErrorEncoders to encode HTTP errors to +// their clients, and will likely want to pass and check for their own error +// types. See the example shipping/handling service. +type ErrorEncoder func(ctx context.Context, err error, w http.ResponseWriter) + +// StatusCoder is checked by DefaultErrorEncoder. If an error value implements +// StatusCoder, the StatusCode will be used when encoding the error. By default, +// StatusInternalServerError (500) is used. +type StatusCoder interface { + StatusCode() int +} + +// Headerer is checked by DefaultErrorEncoder. If an error value implements +// Headerer, the provided headers will be applied to the response writer, after +// the Content-Type is set. +type Headerer interface { + Headers() http.Header +} + +// DefaultErrorEncoder writes the error to the ResponseWriter, by default a +// content type of text/plain, a body of the plain text of the error, and a +// status code of 500. If the error implements Headerer, the provided headers +// will be applied to the response. If the error implements json.Marshaler, and +// the marshaling succeeds, a content type of application/json and the JSON +// encoded form of the error will be used. If the error implements StatusCoder, +// the provided StatusCode will be used instead of 500. +func DefaultErrorEncoder(_ context.Context, err error, w http.ResponseWriter) { + contentType, body := "text/plain; charset=utf-8", []byte(err.Error()) + if marshaler, ok := err.(json.Marshaler); ok { + if jsonBody, marshalErr := marshaler.MarshalJSON(); marshalErr == nil { + contentType, body = "application/json; charset=utf-8", jsonBody + } + } + w.Header().Set("Content-Type", contentType) + if headerer, ok := err.(Headerer); ok { + for k, values := range headerer.Headers() { + for _, v := range values { + w.Header().Add(k, v) + } + } + } + code := http.StatusInternalServerError + if sc, ok := err.(StatusCoder); ok { + code = sc.StatusCode() + } + w.WriteHeader(code) + w.Write(body) +} diff --git a/vendor/github.com/getkin/kin-openapi/routers/legacy/pathpattern/node.go b/vendor/github.com/getkin/kin-openapi/routers/legacy/pathpattern/node.go new file mode 100644 index 000000000..862199864 --- /dev/null +++ b/vendor/github.com/getkin/kin-openapi/routers/legacy/pathpattern/node.go @@ -0,0 +1,328 @@ +// Package pathpattern implements path matching. +// +// Examples of supported patterns: +// * "/" +// * "/abc"" +// * "/abc/{variable}" (matches until next '/' or end-of-string) +// * "/abc/{variable*}" (matches everything, including "/abc" if "/abc" has noot) +// * "/abc/{ variable | prefix_(.*}_suffix }" (matches regular expressions) +package pathpattern + +import ( + "bytes" + "fmt" + "regexp" + "sort" + "strings" +) + +var DefaultOptions = &Options{ + SupportWildcard: true, +} + +type Options struct { + SupportWildcard bool + SupportRegExp bool +} + +// PathFromHost converts a host pattern to a path pattern. +// +// Examples: +// * PathFromHost("some-subdomain.domain.com", false) -> "com/./domain/./some-subdomain" +// * PathFromHost("some-subdomain.domain.com", true) -> "com/./domain/./subdomain/-/some" +func PathFromHost(host string, specialDashes bool) string { + buf := make([]byte, 0, len(host)) + end := len(host) + + // Go from end to start + for start := end - 1; start >= 0; start-- { + switch host[start] { + case '.': + buf = append(buf, host[start+1:end]...) + buf = append(buf, '/', '.', '/') + end = start + case '-': + if specialDashes { + buf = append(buf, host[start+1:end]...) + buf = append(buf, '/', '-', '/') + end = start + } + } + } + buf = append(buf, host[:end]...) + return string(buf) +} + +type Node struct { + VariableNames []string + Value interface{} + Suffixes SuffixList +} + +func (currentNode *Node) String() string { + buf := bytes.NewBuffer(make([]byte, 0, 255)) + currentNode.toBuffer(buf, "") + return buf.String() +} + +func (currentNode *Node) toBuffer(buf *bytes.Buffer, linePrefix string) { + if value := currentNode.Value; value != nil { + buf.WriteString(linePrefix) + buf.WriteString("VALUE: ") + fmt.Fprint(buf, value) + buf.WriteString("\n") + } + suffixes := currentNode.Suffixes + if len(suffixes) > 0 { + newLinePrefix := linePrefix + " " + for _, suffix := range suffixes { + buf.WriteString(linePrefix) + buf.WriteString("PATTERN: ") + buf.WriteString(suffix.String()) + buf.WriteString("\n") + suffix.Node.toBuffer(buf, newLinePrefix) + } + } +} + +type SuffixKind int + +// Note that order is important! +const ( + // SuffixKindConstant matches a constant string + SuffixKindConstant = SuffixKind(iota) + + // SuffixKindRegExp matches a regular expression + SuffixKindRegExp + + // SuffixKindVariable matches everything until '/' + SuffixKindVariable + + // SuffixKindEverything matches everything (until end-of-string) + SuffixKindEverything +) + +// Suffix describes condition that +type Suffix struct { + Kind SuffixKind + Pattern string + + // compiled regular expression + regExp *regexp.Regexp + + // Next node + Node *Node +} + +func EqualSuffix(a, b Suffix) bool { + return a.Kind == b.Kind && a.Pattern == b.Pattern +} + +func (suffix Suffix) String() string { + switch suffix.Kind { + case SuffixKindConstant: + return suffix.Pattern + case SuffixKindVariable: + return "{_}" + case SuffixKindEverything: + return "{_*}" + default: + return "{_|" + suffix.Pattern + "}" + } +} + +type SuffixList []Suffix + +func (list SuffixList) Less(i, j int) bool { + a, b := list[i], list[j] + ak, bk := a.Kind, b.Kind + if ak < bk { + return true + } else if bk < ak { + return false + } + return a.Pattern > b.Pattern +} + +func (list SuffixList) Len() int { + return len(list) +} + +func (list SuffixList) Swap(i, j int) { + a, b := list[i], list[j] + list[i], list[j] = b, a +} + +func (currentNode *Node) MustAdd(path string, value interface{}, options *Options) { + node, err := currentNode.CreateNode(path, options) + if err != nil { + panic(err) + } + node.Value = value +} + +func (currentNode *Node) Add(path string, value interface{}, options *Options) error { + node, err := currentNode.CreateNode(path, options) + if err != nil { + return err + } + node.Value = value + return nil +} + +func (currentNode *Node) CreateNode(path string, options *Options) (*Node, error) { + if options == nil { + options = DefaultOptions + } + for strings.HasSuffix(path, "/") { + path = path[:len(path)-1] + } + remaining := path + var variableNames []string +loop: + for { + //remaining = strings.TrimPrefix(remaining, "/") + if len(remaining) == 0 { + // This node is the right one + // Check whether another route already leads to this node + currentNode.VariableNames = variableNames + return currentNode, nil + } + + suffix := Suffix{} + var i int + if strings.HasPrefix(remaining, "/") { + remaining = remaining[1:] + suffix.Kind = SuffixKindConstant + suffix.Pattern = "/" + } else { + i = strings.IndexAny(remaining, "/{") + if i < 0 { + i = len(remaining) + } + if i > 0 { + // Constant string pattern + suffix.Kind = SuffixKindConstant + suffix.Pattern = remaining[:i] + remaining = remaining[i:] + } else if remaining[0] == '{' { + // This is probably a variable + suffix.Kind = SuffixKindVariable + + // Find variable name + i := strings.IndexByte(remaining, '}') + if i < 0 { + return nil, fmt.Errorf("missing '}' in: %s", path) + } + variableName := strings.TrimSpace(remaining[1:i]) + remaining = remaining[i+1:] + + if options.SupportRegExp { + // See if it has regular expression + i = strings.IndexByte(variableName, '|') + if i >= 0 { + suffix.Kind = SuffixKindRegExp + suffix.Pattern = strings.TrimSpace(variableName[i+1:]) + variableName = strings.TrimSpace(variableName[:i]) + } + } + if suffix.Kind == SuffixKindVariable && options.SupportWildcard { + if strings.HasSuffix(variableName, "*") { + suffix.Kind = SuffixKindEverything + } + } + variableNames = append(variableNames, variableName) + } + } + + // Find existing matcher + for _, existing := range currentNode.Suffixes { + if EqualSuffix(existing, suffix) { + currentNode = existing.Node + continue loop + } + } + + // Compile regular expression + if suffix.Kind == SuffixKindRegExp { + regExp, err := regexp.Compile(suffix.Pattern) + if err != nil { + return nil, fmt.Errorf("invalid regular expression in: %s", path) + } + suffix.regExp = regExp + } + + // Create new node + newNode := &Node{} + suffix.Node = newNode + currentNode.Suffixes = append(currentNode.Suffixes, suffix) + sort.Sort(currentNode.Suffixes) + currentNode = newNode + continue loop + } +} + +func (currentNode *Node) Match(path string) (*Node, []string) { + for strings.HasSuffix(path, "/") { + path = path[:len(path)-1] + } + variableValues := make([]string, 0, 8) + return currentNode.matchRemaining(path, variableValues) +} + +func (currentNode *Node) matchRemaining(remaining string, paramValues []string) (*Node, []string) { + // Check if this node matches + if len(remaining) == 0 && currentNode.Value != nil { + return currentNode, paramValues + } + + // See if any suffix matches + for _, suffix := range currentNode.Suffixes { + var resultNode *Node + var resultValues []string + switch suffix.Kind { + case SuffixKindConstant: + pattern := suffix.Pattern + if strings.HasPrefix(remaining, pattern) { + newRemaining := remaining[len(pattern):] + resultNode, resultValues = suffix.Node.matchRemaining(newRemaining, paramValues) + } else if len(remaining) == 0 && pattern == "/" { + resultNode, resultValues = suffix.Node.matchRemaining(remaining, paramValues) + } + case SuffixKindVariable: + i := strings.IndexByte(remaining, '/') + if i < 0 { + i = len(remaining) + } + newParamValues := append(paramValues, remaining[:i]) + newRemaining := remaining[i:] + resultNode, resultValues = suffix.Node.matchRemaining(newRemaining, newParamValues) + case SuffixKindEverything: + newParamValues := append(paramValues, remaining) + resultNode, resultValues = suffix.Node, newParamValues + case SuffixKindRegExp: + i := strings.IndexByte(remaining, '/') + if i < 0 { + i = len(remaining) + } + paramValue := remaining[:i] + regExp := suffix.regExp + if regExp.MatchString(paramValue) { + matches := regExp.FindStringSubmatch(paramValue) + if len(matches) > 1 { + paramValue = matches[1] + } + newParamValues := append(paramValues, paramValue) + newRemaining := remaining[i:] + resultNode, resultValues = suffix.Node.matchRemaining(newRemaining, newParamValues) + } + } + if resultNode != nil && resultNode.Value != nil { + // This suffix matched + return resultNode, resultValues + } + } + + // No suffix matched + return nil, nil +} diff --git a/vendor/github.com/getkin/kin-openapi/routers/legacy/router.go b/vendor/github.com/getkin/kin-openapi/routers/legacy/router.go new file mode 100644 index 000000000..f1f47d9ed --- /dev/null +++ b/vendor/github.com/getkin/kin-openapi/routers/legacy/router.go @@ -0,0 +1,167 @@ +// Package legacy implements a router. +// +// It differs from the gorilla/mux router: +// * it provides granular errors: "path not found", "method not allowed", "variable missing from path" +// * it does not handle matching routes with extensions (e.g. /books/{id}.json) +// * it handles path patterns with a different syntax (e.g. /params/{x}/{y}/{z.*}) +package legacy + +import ( + "context" + "errors" + "fmt" + "net/http" + "strings" + + "github.com/getkin/kin-openapi/openapi3" + "github.com/getkin/kin-openapi/routers" + "github.com/getkin/kin-openapi/routers/legacy/pathpattern" +) + +// Routers maps a HTTP request to a Router. +type Routers []*Router + +// FindRoute extracts the route and parameters of an http.Request +func (rs Routers) FindRoute(req *http.Request) (routers.Router, *routers.Route, map[string]string, error) { + for _, router := range rs { + // Skip routers that have DO NOT have servers + if len(router.doc.Servers) == 0 { + continue + } + route, pathParams, err := router.FindRoute(req) + if err == nil { + return router, route, pathParams, nil + } + } + for _, router := range rs { + // Skip routers that DO have servers + if len(router.doc.Servers) > 0 { + continue + } + route, pathParams, err := router.FindRoute(req) + if err == nil { + return router, route, pathParams, nil + } + } + return nil, nil, nil, &routers.RouteError{ + Reason: "none of the routers match", + } +} + +// Router maps a HTTP request to an OpenAPI operation. +type Router struct { + doc *openapi3.T + pathNode *pathpattern.Node +} + +// NewRouter creates a new router. +// +// If the given OpenAPIv3 document has servers, router will use them. +// All operations of the document will be added to the router. +func NewRouter(doc *openapi3.T) (routers.Router, error) { + if err := doc.Validate(context.Background()); err != nil { + return nil, fmt.Errorf("validating OpenAPI failed: %v", err) + } + router := &Router{doc: doc} + root := router.node() + for path, pathItem := range doc.Paths { + for method, operation := range pathItem.Operations() { + method = strings.ToUpper(method) + if err := root.Add(method+" "+path, &routers.Route{ + Spec: doc, + Path: path, + PathItem: pathItem, + Method: method, + Operation: operation, + }, nil); err != nil { + return nil, err + } + } + } + return router, nil +} + +// AddRoute adds a route in the router. +func (router *Router) AddRoute(route *routers.Route) error { + method := route.Method + if method == "" { + return errors.New("route is missing method") + } + method = strings.ToUpper(method) + path := route.Path + if path == "" { + return errors.New("route is missing path") + } + return router.node().Add(method+" "+path, router, nil) +} + +func (router *Router) node() *pathpattern.Node { + root := router.pathNode + if root == nil { + root = &pathpattern.Node{} + router.pathNode = root + } + return root +} + +// FindRoute extracts the route and parameters of an http.Request +func (router *Router) FindRoute(req *http.Request) (*routers.Route, map[string]string, error) { + method, url := req.Method, req.URL + doc := router.doc + + // Get server + servers := doc.Servers + var server *openapi3.Server + var remainingPath string + var pathParams map[string]string + if len(servers) == 0 { + remainingPath = url.Path + } else { + var paramValues []string + server, paramValues, remainingPath = servers.MatchURL(url) + if server == nil { + return nil, nil, &routers.RouteError{ + Reason: routers.ErrPathNotFound.Error(), + } + } + pathParams = make(map[string]string, 8) + paramNames, err := server.ParameterNames() + if err != nil { + return nil, nil, err + } + for i, value := range paramValues { + name := paramNames[i] + pathParams[name] = value + } + } + + // Get PathItem + root := router.node() + var route *routers.Route + node, paramValues := root.Match(method + " " + remainingPath) + if node != nil { + route, _ = node.Value.(*routers.Route) + } + if route == nil { + pathItem := doc.Paths[remainingPath] + if pathItem == nil { + return nil, nil, &routers.RouteError{Reason: routers.ErrPathNotFound.Error()} + } + if pathItem.GetOperation(method) == nil { + return nil, nil, &routers.RouteError{Reason: routers.ErrMethodNotAllowed.Error()} + } + } + + if pathParams == nil { + pathParams = make(map[string]string, len(paramValues)) + } + paramKeys := node.VariableNames + for i, value := range paramValues { + key := paramKeys[i] + if strings.HasSuffix(key, "*") { + key = key[:len(key)-1] + } + pathParams[key] = value + } + return route, pathParams, nil +} diff --git a/vendor/github.com/getkin/kin-openapi/routers/types.go b/vendor/github.com/getkin/kin-openapi/routers/types.go new file mode 100644 index 000000000..93746cfe9 --- /dev/null +++ b/vendor/github.com/getkin/kin-openapi/routers/types.go @@ -0,0 +1,42 @@ +package routers + +import ( + "net/http" + + "github.com/getkin/kin-openapi/openapi3" +) + +// Router helps link http.Request.s and an OpenAPIv3 spec +type Router interface { + // FindRoute matches an HTTP request with the operation it resolves to. + // Hosts are matched from the OpenAPIv3 servers key. + // + // If you experience ErrPathNotFound and have localhost hosts specified as your servers, + // turning these server URLs as relative (leaving only the path) should resolve this. + // + // See openapi3filter for example uses with request and response validation. + FindRoute(req *http.Request) (route *Route, pathParams map[string]string, err error) +} + +// Route describes the operation an http.Request can match +type Route struct { + Spec *openapi3.T + Server *openapi3.Server + Path string + PathItem *openapi3.PathItem + Method string + Operation *openapi3.Operation +} + +// ErrPathNotFound is returned when no route match is found +var ErrPathNotFound error = &RouteError{"no matching operation was found"} + +// ErrMethodNotAllowed is returned when no method of the matched route matches +var ErrMethodNotAllowed error = &RouteError{"method not allowed"} + +// RouteError describes Router errors +type RouteError struct { + Reason string +} + +func (e *RouteError) Error() string { return e.Reason } diff --git a/vendor/github.com/go-openapi/swag/.gitattributes b/vendor/github.com/go-openapi/swag/.gitattributes new file mode 100644 index 000000000..49ad52766 --- /dev/null +++ b/vendor/github.com/go-openapi/swag/.gitattributes @@ -0,0 +1,2 @@ +# gofmt always uses LF, whereas Git uses CRLF on Windows. +*.go text eol=lf diff --git a/vendor/github.com/go-openapi/swag/.golangci.yml b/vendor/github.com/go-openapi/swag/.golangci.yml index 625c3d6af..2a4a71f3a 100644 --- a/vendor/github.com/go-openapi/swag/.golangci.yml +++ b/vendor/github.com/go-openapi/swag/.golangci.yml @@ -20,3 +20,31 @@ linters: - lll - gochecknoinits - gochecknoglobals + - nlreturn + - testpackage + - wrapcheck + - gomnd + - exhaustive + - exhaustivestruct + - goerr113 + - wsl + - whitespace + - gofumpt + - godot + - nestif + - godox + - funlen + - gci + - gocognit + - paralleltest + - thelper + - ifshort + - gomoddirectives + - cyclop + - forcetypeassert + - ireturn + - tagliatelle + - varnamelen + - goimports + - tenv + - golint diff --git a/vendor/github.com/go-openapi/swag/.travis.yml b/vendor/github.com/go-openapi/swag/.travis.yml deleted file mode 100644 index aa26d8763..000000000 --- a/vendor/github.com/go-openapi/swag/.travis.yml +++ /dev/null @@ -1,15 +0,0 @@ -after_success: -- bash <(curl -s https://codecov.io/bash) -go: -- 1.11.x -- 1.12.x -install: -- GO111MODULE=off go get -u gotest.tools/gotestsum -env: -- GO111MODULE=on -language: go -notifications: - slack: - secure: QUWvCkBBK09GF7YtEvHHVt70JOkdlNBG0nIKu/5qc4/nW5HP8I2w0SEf/XR2je0eED1Qe3L/AfMCWwrEj+IUZc3l4v+ju8X8R3Lomhme0Eb0jd1MTMCuPcBT47YCj0M7RON7vXtbFfm1hFJ/jLe5+9FXz0hpXsR24PJc5ZIi/ogNwkaPqG4BmndzecpSh0vc2FJPZUD9LT0I09REY/vXR0oQAalLkW0asGD5taHZTUZq/kBpsNxaAFrLM23i4mUcf33M5fjLpvx5LRICrX/57XpBrDh2TooBU6Qj3CgoY0uPRYUmSNxbVx1czNzl2JtEpb5yjoxfVPQeg0BvQM00G8LJINISR+ohrjhkZmAqchDupAX+yFrxTtORa78CtnIL6z/aTNlgwwVD8kvL/1pFA/JWYmKDmz93mV/+6wubGzNSQCstzjkFA4/iZEKewKUoRIAi/fxyscP6L/rCpmY/4llZZvrnyTqVbt6URWpopUpH4rwYqreXAtJxJsfBJIeSmUIiDIOMGkCTvyTEW3fWGmGoqWtSHLoaWDyAIGb7azb+KvfpWtEcoPFWfSWU+LGee0A/YsUhBl7ADB9A0CJEuR8q4BPpKpfLwPKSiKSAXL7zDkyjExyhtgqbSl2jS+rKIHOZNL8JkCcTP2MKMVd563C5rC5FMKqu3S9m2b6380E= -script: -- gotestsum -f short-verbose -- -race -coverprofile=coverage.txt -covermode=atomic ./... diff --git a/vendor/github.com/go-openapi/swag/README.md b/vendor/github.com/go-openapi/swag/README.md index eb60ae80a..217f6fa50 100644 --- a/vendor/github.com/go-openapi/swag/README.md +++ b/vendor/github.com/go-openapi/swag/README.md @@ -2,7 +2,6 @@ [![license](http://img.shields.io/badge/license-Apache%20v2-orange.svg)](https://raw.githubusercontent.com/go-openapi/swag/master/LICENSE) [![GoDoc](https://godoc.org/github.com/go-openapi/swag?status.svg)](http://godoc.org/github.com/go-openapi/swag) -[![GolangCI](https://golangci.com/badges/github.com/go-openapi/swag.svg)](https://golangci.com) [![Go Report Card](https://goreportcard.com/badge/github.com/go-openapi/swag)](https://goreportcard.com/report/github.com/go-openapi/swag) Contains a bunch of helper functions for go-openapi and go-swagger projects. diff --git a/vendor/github.com/go-openapi/swag/convert.go b/vendor/github.com/go-openapi/swag/convert.go index 7da35c316..fc085aeb8 100644 --- a/vendor/github.com/go-openapi/swag/convert.go +++ b/vendor/github.com/go-openapi/swag/convert.go @@ -88,7 +88,7 @@ func ConvertFloat64(str string) (float64, error) { return strconv.ParseFloat(str, 64) } -// ConvertInt8 turn a string into int8 boolean +// ConvertInt8 turn a string into an int8 func ConvertInt8(str string) (int8, error) { i, err := strconv.ParseInt(str, 10, 8) if err != nil { @@ -97,7 +97,7 @@ func ConvertInt8(str string) (int8, error) { return int8(i), nil } -// ConvertInt16 turn a string into a int16 +// ConvertInt16 turn a string into an int16 func ConvertInt16(str string) (int16, error) { i, err := strconv.ParseInt(str, 10, 16) if err != nil { @@ -106,7 +106,7 @@ func ConvertInt16(str string) (int16, error) { return int16(i), nil } -// ConvertInt32 turn a string into a int32 +// ConvertInt32 turn a string into an int32 func ConvertInt32(str string) (int32, error) { i, err := strconv.ParseInt(str, 10, 32) if err != nil { @@ -115,12 +115,12 @@ func ConvertInt32(str string) (int32, error) { return int32(i), nil } -// ConvertInt64 turn a string into a int64 +// ConvertInt64 turn a string into an int64 func ConvertInt64(str string) (int64, error) { return strconv.ParseInt(str, 10, 64) } -// ConvertUint8 turn a string into a uint8 +// ConvertUint8 turn a string into an uint8 func ConvertUint8(str string) (uint8, error) { i, err := strconv.ParseUint(str, 10, 8) if err != nil { @@ -129,7 +129,7 @@ func ConvertUint8(str string) (uint8, error) { return uint8(i), nil } -// ConvertUint16 turn a string into a uint16 +// ConvertUint16 turn a string into an uint16 func ConvertUint16(str string) (uint16, error) { i, err := strconv.ParseUint(str, 10, 16) if err != nil { @@ -138,7 +138,7 @@ func ConvertUint16(str string) (uint16, error) { return uint16(i), nil } -// ConvertUint32 turn a string into a uint32 +// ConvertUint32 turn a string into an uint32 func ConvertUint32(str string) (uint32, error) { i, err := strconv.ParseUint(str, 10, 32) if err != nil { @@ -147,7 +147,7 @@ func ConvertUint32(str string) (uint32, error) { return uint32(i), nil } -// ConvertUint64 turn a string into a uint64 +// ConvertUint64 turn a string into an uint64 func ConvertUint64(str string) (uint64, error) { return strconv.ParseUint(str, 10, 64) } diff --git a/vendor/github.com/go-openapi/swag/convert_types.go b/vendor/github.com/go-openapi/swag/convert_types.go index c95e4e78b..c49cc473a 100644 --- a/vendor/github.com/go-openapi/swag/convert_types.go +++ b/vendor/github.com/go-openapi/swag/convert_types.go @@ -181,12 +181,12 @@ func IntValueMap(src map[string]*int) map[string]int { return dst } -// Int32 returns a pointer to of the int64 value passed in. +// Int32 returns a pointer to of the int32 value passed in. func Int32(v int32) *int32 { return &v } -// Int32Value returns the value of the int64 pointer passed in or +// Int32Value returns the value of the int32 pointer passed in or // 0 if the pointer is nil. func Int32Value(v *int32) int32 { if v != nil { @@ -195,7 +195,7 @@ func Int32Value(v *int32) int32 { return 0 } -// Int32Slice converts a slice of int64 values into a slice of +// Int32Slice converts a slice of int32 values into a slice of // int32 pointers func Int32Slice(src []int32) []*int32 { dst := make([]*int32, len(src)) @@ -299,13 +299,80 @@ func Int64ValueMap(src map[string]*int64) map[string]int64 { return dst } -// Uint returns a pouinter to of the uint value passed in. +// Uint16 returns a pointer to of the uint16 value passed in. +func Uint16(v uint16) *uint16 { + return &v +} + +// Uint16Value returns the value of the uint16 pointer passed in or +// 0 if the pointer is nil. +func Uint16Value(v *uint16) uint16 { + if v != nil { + return *v + } + + return 0 +} + +// Uint16Slice converts a slice of uint16 values into a slice of +// uint16 pointers +func Uint16Slice(src []uint16) []*uint16 { + dst := make([]*uint16, len(src)) + for i := 0; i < len(src); i++ { + dst[i] = &(src[i]) + } + + return dst +} + +// Uint16ValueSlice converts a slice of uint16 pointers into a slice of +// uint16 values +func Uint16ValueSlice(src []*uint16) []uint16 { + dst := make([]uint16, len(src)) + + for i := 0; i < len(src); i++ { + if src[i] != nil { + dst[i] = *(src[i]) + } + } + + return dst +} + +// Uint16Map converts a string map of uint16 values into a string +// map of uint16 pointers +func Uint16Map(src map[string]uint16) map[string]*uint16 { + dst := make(map[string]*uint16) + + for k, val := range src { + v := val + dst[k] = &v + } + + return dst +} + +// Uint16ValueMap converts a string map of uint16 pointers into a string +// map of uint16 values +func Uint16ValueMap(src map[string]*uint16) map[string]uint16 { + dst := make(map[string]uint16) + + for k, val := range src { + if val != nil { + dst[k] = *val + } + } + + return dst +} + +// Uint returns a pointer to of the uint value passed in. func Uint(v uint) *uint { return &v } -// UintValue returns the value of the uint pouinter passed in or -// 0 if the pouinter is nil. +// UintValue returns the value of the uint pointer passed in or +// 0 if the pointer is nil. func UintValue(v *uint) uint { if v != nil { return *v @@ -313,8 +380,8 @@ func UintValue(v *uint) uint { return 0 } -// UintSlice converts a slice of uint values uinto a slice of -// uint pouinters +// UintSlice converts a slice of uint values into a slice of +// uint pointers func UintSlice(src []uint) []*uint { dst := make([]*uint, len(src)) for i := 0; i < len(src); i++ { @@ -323,7 +390,7 @@ func UintSlice(src []uint) []*uint { return dst } -// UintValueSlice converts a slice of uint pouinters uinto a slice of +// UintValueSlice converts a slice of uint pointers into a slice of // uint values func UintValueSlice(src []*uint) []uint { dst := make([]uint, len(src)) @@ -335,8 +402,8 @@ func UintValueSlice(src []*uint) []uint { return dst } -// UintMap converts a string map of uint values uinto a string -// map of uint pouinters +// UintMap converts a string map of uint values into a string +// map of uint pointers func UintMap(src map[string]uint) map[string]*uint { dst := make(map[string]*uint) for k, val := range src { @@ -346,7 +413,7 @@ func UintMap(src map[string]uint) map[string]*uint { return dst } -// UintValueMap converts a string map of uint pouinters uinto a string +// UintValueMap converts a string map of uint pointers into a string // map of uint values func UintValueMap(src map[string]*uint) map[string]uint { dst := make(map[string]uint) @@ -358,13 +425,13 @@ func UintValueMap(src map[string]*uint) map[string]uint { return dst } -// Uint32 returns a pouinter to of the uint64 value passed in. +// Uint32 returns a pointer to of the uint32 value passed in. func Uint32(v uint32) *uint32 { return &v } -// Uint32Value returns the value of the uint64 pouinter passed in or -// 0 if the pouinter is nil. +// Uint32Value returns the value of the uint32 pointer passed in or +// 0 if the pointer is nil. func Uint32Value(v *uint32) uint32 { if v != nil { return *v @@ -372,8 +439,8 @@ func Uint32Value(v *uint32) uint32 { return 0 } -// Uint32Slice converts a slice of uint64 values uinto a slice of -// uint32 pouinters +// Uint32Slice converts a slice of uint32 values into a slice of +// uint32 pointers func Uint32Slice(src []uint32) []*uint32 { dst := make([]*uint32, len(src)) for i := 0; i < len(src); i++ { @@ -382,7 +449,7 @@ func Uint32Slice(src []uint32) []*uint32 { return dst } -// Uint32ValueSlice converts a slice of uint32 pouinters uinto a slice of +// Uint32ValueSlice converts a slice of uint32 pointers into a slice of // uint32 values func Uint32ValueSlice(src []*uint32) []uint32 { dst := make([]uint32, len(src)) @@ -394,8 +461,8 @@ func Uint32ValueSlice(src []*uint32) []uint32 { return dst } -// Uint32Map converts a string map of uint32 values uinto a string -// map of uint32 pouinters +// Uint32Map converts a string map of uint32 values into a string +// map of uint32 pointers func Uint32Map(src map[string]uint32) map[string]*uint32 { dst := make(map[string]*uint32) for k, val := range src { @@ -405,7 +472,7 @@ func Uint32Map(src map[string]uint32) map[string]*uint32 { return dst } -// Uint32ValueMap converts a string map of uint32 pouinters uinto a string +// Uint32ValueMap converts a string map of uint32 pointers into a string // map of uint32 values func Uint32ValueMap(src map[string]*uint32) map[string]uint32 { dst := make(map[string]uint32) @@ -417,13 +484,13 @@ func Uint32ValueMap(src map[string]*uint32) map[string]uint32 { return dst } -// Uint64 returns a pouinter to of the uint64 value passed in. +// Uint64 returns a pointer to of the uint64 value passed in. func Uint64(v uint64) *uint64 { return &v } -// Uint64Value returns the value of the uint64 pouinter passed in or -// 0 if the pouinter is nil. +// Uint64Value returns the value of the uint64 pointer passed in or +// 0 if the pointer is nil. func Uint64Value(v *uint64) uint64 { if v != nil { return *v @@ -431,8 +498,8 @@ func Uint64Value(v *uint64) uint64 { return 0 } -// Uint64Slice converts a slice of uint64 values uinto a slice of -// uint64 pouinters +// Uint64Slice converts a slice of uint64 values into a slice of +// uint64 pointers func Uint64Slice(src []uint64) []*uint64 { dst := make([]*uint64, len(src)) for i := 0; i < len(src); i++ { @@ -441,7 +508,7 @@ func Uint64Slice(src []uint64) []*uint64 { return dst } -// Uint64ValueSlice converts a slice of uint64 pouinters uinto a slice of +// Uint64ValueSlice converts a slice of uint64 pointers into a slice of // uint64 values func Uint64ValueSlice(src []*uint64) []uint64 { dst := make([]uint64, len(src)) @@ -453,8 +520,8 @@ func Uint64ValueSlice(src []*uint64) []uint64 { return dst } -// Uint64Map converts a string map of uint64 values uinto a string -// map of uint64 pouinters +// Uint64Map converts a string map of uint64 values into a string +// map of uint64 pointers func Uint64Map(src map[string]uint64) map[string]*uint64 { dst := make(map[string]*uint64) for k, val := range src { @@ -464,7 +531,7 @@ func Uint64Map(src map[string]uint64) map[string]*uint64 { return dst } -// Uint64ValueMap converts a string map of uint64 pouinters uinto a string +// Uint64ValueMap converts a string map of uint64 pointers into a string // map of uint64 values func Uint64ValueMap(src map[string]*uint64) map[string]uint64 { dst := make(map[string]uint64) @@ -476,6 +543,74 @@ func Uint64ValueMap(src map[string]*uint64) map[string]uint64 { return dst } +// Float32 returns a pointer to of the float32 value passed in. +func Float32(v float32) *float32 { + return &v +} + +// Float32Value returns the value of the float32 pointer passed in or +// 0 if the pointer is nil. +func Float32Value(v *float32) float32 { + if v != nil { + return *v + } + + return 0 +} + +// Float32Slice converts a slice of float32 values into a slice of +// float32 pointers +func Float32Slice(src []float32) []*float32 { + dst := make([]*float32, len(src)) + + for i := 0; i < len(src); i++ { + dst[i] = &(src[i]) + } + + return dst +} + +// Float32ValueSlice converts a slice of float32 pointers into a slice of +// float32 values +func Float32ValueSlice(src []*float32) []float32 { + dst := make([]float32, len(src)) + + for i := 0; i < len(src); i++ { + if src[i] != nil { + dst[i] = *(src[i]) + } + } + + return dst +} + +// Float32Map converts a string map of float32 values into a string +// map of float32 pointers +func Float32Map(src map[string]float32) map[string]*float32 { + dst := make(map[string]*float32) + + for k, val := range src { + v := val + dst[k] = &v + } + + return dst +} + +// Float32ValueMap converts a string map of float32 pointers into a string +// map of float32 values +func Float32ValueMap(src map[string]*float32) map[string]float32 { + dst := make(map[string]float32) + + for k, val := range src { + if val != nil { + dst[k] = *val + } + } + + return dst +} + // Float64 returns a pointer to of the float64 value passed in. func Float64(v float64) *float64 { return &v diff --git a/vendor/github.com/go-openapi/swag/file.go b/vendor/github.com/go-openapi/swag/file.go new file mode 100644 index 000000000..16accc55f --- /dev/null +++ b/vendor/github.com/go-openapi/swag/file.go @@ -0,0 +1,33 @@ +// Copyright 2015 go-swagger maintainers +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package swag + +import "mime/multipart" + +// File represents an uploaded file. +type File struct { + Data multipart.File + Header *multipart.FileHeader +} + +// Read bytes from the file +func (f *File) Read(p []byte) (n int, err error) { + return f.Data.Read(p) +} + +// Close the file +func (f *File) Close() error { + return f.Data.Close() +} diff --git a/vendor/github.com/go-openapi/swag/go.mod b/vendor/github.com/go-openapi/swag/go.mod index 15bbb0822..fb29b65b2 100644 --- a/vendor/github.com/go-openapi/swag/go.mod +++ b/vendor/github.com/go-openapi/swag/go.mod @@ -2,13 +2,17 @@ module github.com/go-openapi/swag require ( github.com/davecgh/go-spew v1.1.1 // indirect - github.com/kr/pretty v0.1.0 // indirect - github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63 - github.com/stretchr/testify v1.3.0 - gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 // indirect - gopkg.in/yaml.v2 v2.2.2 + github.com/kr/text v0.2.0 // indirect + github.com/mailru/easyjson v0.7.6 + github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e // indirect + github.com/stretchr/testify v1.6.1 + gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f // indirect + gopkg.in/yaml.v2 v2.4.0 + gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776 // indirect ) replace github.com/golang/lint => golang.org/x/lint v0.0.0-20190409202823-959b441ac422 replace sourcegraph.com/sourcegraph/go-diff => github.com/sourcegraph/go-diff v0.5.1 + +go 1.11 diff --git a/vendor/github.com/go-openapi/swag/go.sum b/vendor/github.com/go-openapi/swag/go.sum index 33469f54a..a45da809a 100644 --- a/vendor/github.com/go-openapi/swag/go.sum +++ b/vendor/github.com/go-openapi/swag/go.sum @@ -1,20 +1,29 @@ +github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= -github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= +github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= -github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63 h1:nTT4s92Dgz2HlrB2NaMgvlfqHH39OgMhA7z3PK7PGD4= -github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/mailru/easyjson v0.7.6 h1:8yTIVnZgCoiM1TgqoeTl+LfU5Jg6/xL3QhGQnimLYnA= +github.com/mailru/easyjson v0.7.6/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= +github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs= +github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/stretchr/objx v0.1.0 h1:4G4v2dO3VZwixGIRoQ5Lfboy6nUhCyYzaqnIAPPhYs4= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q= -github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0= +github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= -gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= -gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU= +gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776 h1:tQIYjPdBoyREyB9XMu+nnTclpTYkz2zFM+lzLJFO4gQ= +gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/vendor/github.com/go-openapi/swag/json.go b/vendor/github.com/go-openapi/swag/json.go index edf93d84c..7e9902ca3 100644 --- a/vendor/github.com/go-openapi/swag/json.go +++ b/vendor/github.com/go-openapi/swag/json.go @@ -51,7 +51,7 @@ type ejUnmarshaler interface { UnmarshalEasyJSON(w *jlexer.Lexer) } -// WriteJSON writes json data, prefers finding an appropriate interface to short-circuit the marshaller +// WriteJSON writes json data, prefers finding an appropriate interface to short-circuit the marshaler // so it takes the fastest option available. func WriteJSON(data interface{}) ([]byte, error) { if d, ok := data.(ejMarshaler); ok { @@ -65,8 +65,8 @@ func WriteJSON(data interface{}) ([]byte, error) { return json.Marshal(data) } -// ReadJSON reads json data, prefers finding an appropriate interface to short-circuit the unmarshaller -// so it takes the fastes option available +// ReadJSON reads json data, prefers finding an appropriate interface to short-circuit the unmarshaler +// so it takes the fastest option available func ReadJSON(data []byte, value interface{}) error { trimmedData := bytes.Trim(data, "\x00") if d, ok := value.(ejUnmarshaler); ok { @@ -189,7 +189,7 @@ func FromDynamicJSON(data, target interface{}) error { return json.Unmarshal(b, target) } -// NameProvider represents an object capabale of translating from go property names +// NameProvider represents an object capable of translating from go property names // to json property names // This type is thread-safe. type NameProvider struct { diff --git a/vendor/github.com/go-openapi/swag/loading.go b/vendor/github.com/go-openapi/swag/loading.go index 70f4fb361..9a6040972 100644 --- a/vendor/github.com/go-openapi/swag/loading.go +++ b/vendor/github.com/go-openapi/swag/loading.go @@ -19,7 +19,9 @@ import ( "io/ioutil" "log" "net/http" + "net/url" "path/filepath" + "runtime" "strings" "time" ) @@ -27,6 +29,15 @@ import ( // LoadHTTPTimeout the default timeout for load requests var LoadHTTPTimeout = 30 * time.Second +// LoadHTTPBasicAuthUsername the username to use when load requests require basic auth +var LoadHTTPBasicAuthUsername = "" + +// LoadHTTPBasicAuthPassword the password to use when load requests require basic auth +var LoadHTTPBasicAuthPassword = "" + +// LoadHTTPCustomHeaders an optional collection of custom HTTP headers for load requests +var LoadHTTPCustomHeaders = map[string]string{} + // LoadFromFileOrHTTP loads the bytes from a file or a remote http server based on the path passed in func LoadFromFileOrHTTP(path string) ([]byte, error) { return LoadStrategy(path, ioutil.ReadFile, loadHTTPBytes(LoadHTTPTimeout))(path) @@ -48,6 +59,26 @@ func LoadStrategy(path string, local, remote func(string) ([]byte, error)) func( if err != nil { return nil, err } + + if strings.HasPrefix(pth, `file://`) { + if runtime.GOOS == "windows" { + // support for canonical file URIs on windows. + // Zero tolerance here for dodgy URIs. + u, _ := url.Parse(upth) + if u.Host != "" { + // assume UNC name (volume share) + // file://host/share/folder\... ==> \\host\share\path\folder + // NOTE: UNC port not yet supported + upth = strings.Join([]string{`\`, u.Host, u.Path}, `\`) + } else { + // file:///c:/folder/... ==> just remove the leading slash + upth = strings.TrimPrefix(upth, `file:///`) + } + } else { + upth = strings.TrimPrefix(upth, `file://`) + } + } + return local(filepath.FromSlash(upth)) } } @@ -55,10 +86,19 @@ func LoadStrategy(path string, local, remote func(string) ([]byte, error)) func( func loadHTTPBytes(timeout time.Duration) func(path string) ([]byte, error) { return func(path string) ([]byte, error) { client := &http.Client{Timeout: timeout} - req, err := http.NewRequest("GET", path, nil) + req, err := http.NewRequest("GET", path, nil) // nolint: noctx if err != nil { return nil, err } + + if LoadHTTPBasicAuthUsername != "" && LoadHTTPBasicAuthPassword != "" { + req.SetBasicAuth(LoadHTTPBasicAuthUsername, LoadHTTPBasicAuthPassword) + } + + for key, val := range LoadHTTPCustomHeaders { + req.Header.Set(key, val) + } + resp, err := client.Do(req) defer func() { if resp != nil { diff --git a/vendor/github.com/go-openapi/swag/post_go18.go b/vendor/github.com/go-openapi/swag/post_go18.go index c2e686d31..f5228b82c 100644 --- a/vendor/github.com/go-openapi/swag/post_go18.go +++ b/vendor/github.com/go-openapi/swag/post_go18.go @@ -12,6 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. +//go:build go1.8 // +build go1.8 package swag diff --git a/vendor/github.com/go-openapi/swag/post_go19.go b/vendor/github.com/go-openapi/swag/post_go19.go index eb2f2d8bc..7c7da9c08 100644 --- a/vendor/github.com/go-openapi/swag/post_go19.go +++ b/vendor/github.com/go-openapi/swag/post_go19.go @@ -12,6 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. +//go:build go1.9 // +build go1.9 package swag diff --git a/vendor/github.com/go-openapi/swag/pre_go18.go b/vendor/github.com/go-openapi/swag/pre_go18.go index 6607f3393..2757d9b95 100644 --- a/vendor/github.com/go-openapi/swag/pre_go18.go +++ b/vendor/github.com/go-openapi/swag/pre_go18.go @@ -12,6 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. +//go:build !go1.8 // +build !go1.8 package swag diff --git a/vendor/github.com/go-openapi/swag/pre_go19.go b/vendor/github.com/go-openapi/swag/pre_go19.go index 4bae187d1..0565db377 100644 --- a/vendor/github.com/go-openapi/swag/pre_go19.go +++ b/vendor/github.com/go-openapi/swag/pre_go19.go @@ -12,6 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. +//go:build !go1.9 // +build !go1.9 package swag diff --git a/vendor/github.com/go-openapi/swag/util.go b/vendor/github.com/go-openapi/swag/util.go index 9eac16afb..193702f2c 100644 --- a/vendor/github.com/go-openapi/swag/util.go +++ b/vendor/github.com/go-openapi/swag/util.go @@ -31,7 +31,7 @@ var isInitialism func(string) bool // GoNamePrefixFunc sets an optional rule to prefix go names // which do not start with a letter. // -// e.g. to help converting "123" into "{prefix}123" +// e.g. to help convert "123" into "{prefix}123" // // The default is to prefix with "X" var GoNamePrefixFunc func(string) string @@ -91,7 +91,7 @@ func init() { } const ( - //collectionFormatComma = "csv" + // collectionFormatComma = "csv" collectionFormatSpace = "ssv" collectionFormatTab = "tsv" collectionFormatPipe = "pipes" @@ -370,7 +370,7 @@ func IsZero(data interface{}) bool { // AddInitialisms add additional initialisms func AddInitialisms(words ...string) { for _, word := range words { - //commonInitialisms[upper(word)] = true + // commonInitialisms[upper(word)] = true commonInitialisms.add(upper(word)) } // sort again diff --git a/vendor/github.com/josharian/intern/README.md b/vendor/github.com/josharian/intern/README.md new file mode 100644 index 000000000..ffc44b219 --- /dev/null +++ b/vendor/github.com/josharian/intern/README.md @@ -0,0 +1,5 @@ +Docs: https://godoc.org/github.com/josharian/intern + +See also [Go issue 5160](https://golang.org/issue/5160). + +License: MIT diff --git a/vendor/github.com/josharian/intern/go.mod b/vendor/github.com/josharian/intern/go.mod new file mode 100644 index 000000000..f2262ff0d --- /dev/null +++ b/vendor/github.com/josharian/intern/go.mod @@ -0,0 +1,3 @@ +module github.com/josharian/intern + +go 1.5 diff --git a/vendor/github.com/josharian/intern/intern.go b/vendor/github.com/josharian/intern/intern.go new file mode 100644 index 000000000..7acb1fe90 --- /dev/null +++ b/vendor/github.com/josharian/intern/intern.go @@ -0,0 +1,44 @@ +// Package intern interns strings. +// Interning is best effort only. +// Interned strings may be removed automatically +// at any time without notification. +// All functions may be called concurrently +// with themselves and each other. +package intern + +import "sync" + +var ( + pool sync.Pool = sync.Pool{ + New: func() interface{} { + return make(map[string]string) + }, + } +) + +// String returns s, interned. +func String(s string) string { + m := pool.Get().(map[string]string) + c, ok := m[s] + if ok { + pool.Put(m) + return c + } + m[s] = s + pool.Put(m) + return s +} + +// Bytes returns b converted to a string, interned. +func Bytes(b []byte) string { + m := pool.Get().(map[string]string) + c, ok := m[string(b)] + if ok { + pool.Put(m) + return c + } + s := string(b) + m[s] = s + pool.Put(m) + return s +} diff --git a/vendor/github.com/josharian/intern/license.md b/vendor/github.com/josharian/intern/license.md new file mode 100644 index 000000000..353d3055f --- /dev/null +++ b/vendor/github.com/josharian/intern/license.md @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2019 Josh Bleecher Snyder + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/vendor/github.com/mailru/easyjson/buffer/pool.go b/vendor/github.com/mailru/easyjson/buffer/pool.go index 07fb4bc1f..598a54af9 100644 --- a/vendor/github.com/mailru/easyjson/buffer/pool.go +++ b/vendor/github.com/mailru/easyjson/buffer/pool.go @@ -4,6 +4,7 @@ package buffer import ( "io" + "net" "sync" ) @@ -52,14 +53,12 @@ func putBuf(buf []byte) { // getBuf gets a chunk from reuse pool or creates a new one if reuse failed. func getBuf(size int) []byte { - if size < config.PooledSize { - return make([]byte, 0, size) - } - - if c := buffers[size]; c != nil { - v := c.Get() - if v != nil { - return v.([]byte) + if size >= config.PooledSize { + if c := buffers[size]; c != nil { + v := c.Get() + if v != nil { + return v.([]byte) + } } } return make([]byte, 0, size) @@ -78,9 +77,12 @@ type Buffer struct { // EnsureSpace makes sure that the current chunk contains at least s free bytes, // possibly creating a new chunk. func (b *Buffer) EnsureSpace(s int) { - if cap(b.Buf)-len(b.Buf) >= s { - return + if cap(b.Buf)-len(b.Buf) < s { + b.ensureSpaceSlow(s) } +} + +func (b *Buffer) ensureSpaceSlow(s int) { l := len(b.Buf) if l > 0 { if cap(b.toPool) != cap(b.Buf) { @@ -105,18 +107,22 @@ func (b *Buffer) EnsureSpace(s int) { // AppendByte appends a single byte to buffer. func (b *Buffer) AppendByte(data byte) { - if cap(b.Buf) == len(b.Buf) { // EnsureSpace won't be inlined. - b.EnsureSpace(1) - } + b.EnsureSpace(1) b.Buf = append(b.Buf, data) } // AppendBytes appends a byte slice to buffer. func (b *Buffer) AppendBytes(data []byte) { + if len(data) <= cap(b.Buf)-len(b.Buf) { + b.Buf = append(b.Buf, data...) // fast path + } else { + b.appendBytesSlow(data) + } +} + +func (b *Buffer) appendBytesSlow(data []byte) { for len(data) > 0 { - if cap(b.Buf) == len(b.Buf) { // EnsureSpace won't be inlined. - b.EnsureSpace(1) - } + b.EnsureSpace(1) sz := cap(b.Buf) - len(b.Buf) if sz > len(data) { @@ -128,12 +134,18 @@ func (b *Buffer) AppendBytes(data []byte) { } } -// AppendBytes appends a string to buffer. +// AppendString appends a string to buffer. func (b *Buffer) AppendString(data string) { + if len(data) <= cap(b.Buf)-len(b.Buf) { + b.Buf = append(b.Buf, data...) // fast path + } else { + b.appendStringSlow(data) + } +} + +func (b *Buffer) appendStringSlow(data string) { for len(data) > 0 { - if cap(b.Buf) == len(b.Buf) { // EnsureSpace won't be inlined. - b.EnsureSpace(1) - } + b.EnsureSpace(1) sz := cap(b.Buf) - len(b.Buf) if sz > len(data) { @@ -156,18 +168,14 @@ func (b *Buffer) Size() int { // DumpTo outputs the contents of a buffer to a writer and resets the buffer. func (b *Buffer) DumpTo(w io.Writer) (written int, err error) { - var n int - for _, buf := range b.bufs { - if err == nil { - n, err = w.Write(buf) - written += n - } - putBuf(buf) + bufs := net.Buffers(b.bufs) + if len(b.Buf) > 0 { + bufs = append(bufs, b.Buf) } + n, err := bufs.WriteTo(w) - if err == nil { - n, err = w.Write(b.Buf) - written += n + for _, buf := range b.bufs { + putBuf(buf) } putBuf(b.toPool) @@ -175,7 +183,7 @@ func (b *Buffer) DumpTo(w io.Writer) (written int, err error) { b.Buf = nil b.toPool = nil - return + return int(n), err } // BuildBytes creates a single byte slice with all the contents of the buffer. Data is @@ -192,7 +200,7 @@ func (b *Buffer) BuildBytes(reuse ...[]byte) []byte { var ret []byte size := b.Size() - // If we got a buffer as argument and it is big enought, reuse it. + // If we got a buffer as argument and it is big enough, reuse it. if len(reuse) == 1 && cap(reuse[0]) >= size { ret = reuse[0][:0] } else { diff --git a/vendor/github.com/mailru/easyjson/jlexer/lexer.go b/vendor/github.com/mailru/easyjson/jlexer/lexer.go index ddd376b84..a42e9d65a 100644 --- a/vendor/github.com/mailru/easyjson/jlexer/lexer.go +++ b/vendor/github.com/mailru/easyjson/jlexer/lexer.go @@ -5,6 +5,7 @@ package jlexer import ( + "bytes" "encoding/base64" "encoding/json" "errors" @@ -14,6 +15,8 @@ import ( "unicode" "unicode/utf16" "unicode/utf8" + + "github.com/josharian/intern" ) // tokenKind determines type of a token. @@ -32,9 +35,10 @@ const ( type token struct { kind tokenKind // Type of a token. - boolValue bool // Value if a boolean literal token. - byteValue []byte // Raw value of a token. - delimValue byte + boolValue bool // Value if a boolean literal token. + byteValueCloned bool // true if byteValue was allocated and does not refer to original json body + byteValue []byte // Raw value of a token. + delimValue byte } // Lexer is a JSON lexer: it iterates over JSON tokens in a byte slice. @@ -240,23 +244,65 @@ func (r *Lexer) fetchNumber() { // findStringLen tries to scan into the string literal for ending quote char to determine required size. // The size will be exact if no escapes are present and may be inexact if there are escaped chars. -func findStringLen(data []byte) (isValid, hasEscapes bool, length int) { - delta := 0 - - for i := 0; i < len(data); i++ { - switch data[i] { - case '\\': - i++ - delta++ - if i < len(data) && data[i] == 'u' { - delta++ - } - case '"': - return true, (delta > 0), (i - delta) +func findStringLen(data []byte) (isValid bool, length int) { + for { + idx := bytes.IndexByte(data, '"') + if idx == -1 { + return false, len(data) } + if idx == 0 || (idx > 0 && data[idx-1] != '\\') { + return true, length + idx + } + + // count \\\\\\\ sequences. even number of slashes means quote is not really escaped + cnt := 1 + for idx-cnt-1 >= 0 && data[idx-cnt-1] == '\\' { + cnt++ + } + if cnt%2 == 0 { + return true, length + idx + } + + length += idx + 1 + data = data[idx+1:] + } +} + +// unescapeStringToken performs unescaping of string token. +// if no escaping is needed, original string is returned, otherwise - a new one allocated +func (r *Lexer) unescapeStringToken() (err error) { + data := r.token.byteValue + var unescapedData []byte + + for { + i := bytes.IndexByte(data, '\\') + if i == -1 { + break + } + + escapedRune, escapedBytes, err := decodeEscape(data[i:]) + if err != nil { + r.errParse(err.Error()) + return err + } + + if unescapedData == nil { + unescapedData = make([]byte, 0, len(r.token.byteValue)) + } + + var d [4]byte + s := utf8.EncodeRune(d[:], escapedRune) + unescapedData = append(unescapedData, data[:i]...) + unescapedData = append(unescapedData, d[:s]...) + + data = data[i+escapedBytes:] } - return false, false, len(data) + if unescapedData != nil { + r.token.byteValue = append(unescapedData, data...) + r.token.byteValueCloned = true + } + return } // getu4 decodes \uXXXX from the beginning of s, returning the hex value, @@ -286,36 +332,30 @@ func getu4(s []byte) rune { return val } -// processEscape processes a single escape sequence and returns number of bytes processed. -func (r *Lexer) processEscape(data []byte) (int, error) { +// decodeEscape processes a single escape sequence and returns number of bytes processed. +func decodeEscape(data []byte) (decoded rune, bytesProcessed int, err error) { if len(data) < 2 { - return 0, fmt.Errorf("syntax error at %v", string(data)) + return 0, 0, errors.New("incorrect escape symbol \\ at the end of token") } c := data[1] switch c { case '"', '/', '\\': - r.token.byteValue = append(r.token.byteValue, c) - return 2, nil + return rune(c), 2, nil case 'b': - r.token.byteValue = append(r.token.byteValue, '\b') - return 2, nil + return '\b', 2, nil case 'f': - r.token.byteValue = append(r.token.byteValue, '\f') - return 2, nil + return '\f', 2, nil case 'n': - r.token.byteValue = append(r.token.byteValue, '\n') - return 2, nil + return '\n', 2, nil case 'r': - r.token.byteValue = append(r.token.byteValue, '\r') - return 2, nil + return '\r', 2, nil case 't': - r.token.byteValue = append(r.token.byteValue, '\t') - return 2, nil + return '\t', 2, nil case 'u': rr := getu4(data) if rr < 0 { - return 0, errors.New("syntax error") + return 0, 0, errors.New("incorrectly escaped \\uXXXX sequence") } read := 6 @@ -328,13 +368,10 @@ func (r *Lexer) processEscape(data []byte) (int, error) { rr = unicode.ReplacementChar } } - var d [4]byte - s := utf8.EncodeRune(d[:], rr) - r.token.byteValue = append(r.token.byteValue, d[:s]...) - return read, nil + return rr, read, nil } - return 0, errors.New("syntax error") + return 0, 0, errors.New("incorrectly escaped bytes") } // fetchString scans a string literal token. @@ -342,43 +379,14 @@ func (r *Lexer) fetchString() { r.pos++ data := r.Data[r.pos:] - isValid, hasEscapes, length := findStringLen(data) + isValid, length := findStringLen(data) if !isValid { r.pos += length r.errParse("unterminated string literal") return } - if !hasEscapes { - r.token.byteValue = data[:length] - r.pos += length + 1 - return - } - - r.token.byteValue = make([]byte, 0, length) - p := 0 - for i := 0; i < len(data); { - switch data[i] { - case '"': - r.pos += i + 1 - r.token.byteValue = append(r.token.byteValue, data[p:i]...) - i++ - return - - case '\\': - r.token.byteValue = append(r.token.byteValue, data[p:i]...) - off, err := r.processEscape(data[i:]) - if err != nil { - r.errParse(err.Error()) - return - } - i += off - p = i - - default: - i++ - } - } - r.errParse("unterminated string literal") + r.token.byteValue = data[:length] + r.pos += length + 1 // skip closing '"' as well } // scanToken scans the next token if no token is currently available in the lexer. @@ -602,7 +610,7 @@ func (r *Lexer) Consumed() { } } -func (r *Lexer) unsafeString() (string, []byte) { +func (r *Lexer) unsafeString(skipUnescape bool) (string, []byte) { if r.token.kind == tokenUndef && r.Ok() { r.FetchToken() } @@ -610,6 +618,13 @@ func (r *Lexer) unsafeString() (string, []byte) { r.errInvalidToken("string") return "", nil } + if !skipUnescape { + if err := r.unescapeStringToken(); err != nil { + r.errInvalidToken("string") + return "", nil + } + } + bytes := r.token.byteValue ret := bytesToStr(r.token.byteValue) r.consume() @@ -621,13 +636,19 @@ func (r *Lexer) unsafeString() (string, []byte) { // Warning: returned string may point to the input buffer, so the string should not outlive // the input buffer. Intended pattern of usage is as an argument to a switch statement. func (r *Lexer) UnsafeString() string { - ret, _ := r.unsafeString() + ret, _ := r.unsafeString(false) return ret } // UnsafeBytes returns the byte slice if the token is a string literal. func (r *Lexer) UnsafeBytes() []byte { - _, ret := r.unsafeString() + _, ret := r.unsafeString(false) + return ret +} + +// UnsafeFieldName returns current member name string token +func (r *Lexer) UnsafeFieldName(skipUnescape bool) string { + ret, _ := r.unsafeString(skipUnescape) return ret } @@ -640,7 +661,34 @@ func (r *Lexer) String() string { r.errInvalidToken("string") return "" } - ret := string(r.token.byteValue) + if err := r.unescapeStringToken(); err != nil { + r.errInvalidToken("string") + return "" + } + var ret string + if r.token.byteValueCloned { + ret = bytesToStr(r.token.byteValue) + } else { + ret = string(r.token.byteValue) + } + r.consume() + return ret +} + +// StringIntern reads a string literal, and performs string interning on it. +func (r *Lexer) StringIntern() string { + if r.token.kind == tokenUndef && r.Ok() { + r.FetchToken() + } + if !r.Ok() || r.token.kind != tokenString { + r.errInvalidToken("string") + return "" + } + if err := r.unescapeStringToken(); err != nil { + r.errInvalidToken("string") + return "" + } + ret := intern.Bytes(r.token.byteValue) r.consume() return ret } @@ -839,7 +887,7 @@ func (r *Lexer) Int() int { } func (r *Lexer) Uint8Str() uint8 { - s, b := r.unsafeString() + s, b := r.unsafeString(false) if !r.Ok() { return 0 } @@ -856,7 +904,7 @@ func (r *Lexer) Uint8Str() uint8 { } func (r *Lexer) Uint16Str() uint16 { - s, b := r.unsafeString() + s, b := r.unsafeString(false) if !r.Ok() { return 0 } @@ -873,7 +921,7 @@ func (r *Lexer) Uint16Str() uint16 { } func (r *Lexer) Uint32Str() uint32 { - s, b := r.unsafeString() + s, b := r.unsafeString(false) if !r.Ok() { return 0 } @@ -890,7 +938,7 @@ func (r *Lexer) Uint32Str() uint32 { } func (r *Lexer) Uint64Str() uint64 { - s, b := r.unsafeString() + s, b := r.unsafeString(false) if !r.Ok() { return 0 } @@ -915,7 +963,7 @@ func (r *Lexer) UintptrStr() uintptr { } func (r *Lexer) Int8Str() int8 { - s, b := r.unsafeString() + s, b := r.unsafeString(false) if !r.Ok() { return 0 } @@ -932,7 +980,7 @@ func (r *Lexer) Int8Str() int8 { } func (r *Lexer) Int16Str() int16 { - s, b := r.unsafeString() + s, b := r.unsafeString(false) if !r.Ok() { return 0 } @@ -949,7 +997,7 @@ func (r *Lexer) Int16Str() int16 { } func (r *Lexer) Int32Str() int32 { - s, b := r.unsafeString() + s, b := r.unsafeString(false) if !r.Ok() { return 0 } @@ -966,7 +1014,7 @@ func (r *Lexer) Int32Str() int32 { } func (r *Lexer) Int64Str() int64 { - s, b := r.unsafeString() + s, b := r.unsafeString(false) if !r.Ok() { return 0 } @@ -1004,7 +1052,7 @@ func (r *Lexer) Float32() float32 { } func (r *Lexer) Float32Str() float32 { - s, b := r.unsafeString() + s, b := r.unsafeString(false) if !r.Ok() { return 0 } @@ -1037,7 +1085,7 @@ func (r *Lexer) Float64() float64 { } func (r *Lexer) Float64Str() float64 { - s, b := r.unsafeString() + s, b := r.unsafeString(false) if !r.Ok() { return 0 } diff --git a/vendor/github.com/mailru/easyjson/jwriter/writer.go b/vendor/github.com/mailru/easyjson/jwriter/writer.go index b9ed7ccaa..2c5b20105 100644 --- a/vendor/github.com/mailru/easyjson/jwriter/writer.go +++ b/vendor/github.com/mailru/easyjson/jwriter/writer.go @@ -270,16 +270,25 @@ func (w *Writer) Bool(v bool) { const chars = "0123456789abcdef" -func isNotEscapedSingleChar(c byte, escapeHTML bool) bool { - // Note: might make sense to use a table if there are more chars to escape. With 4 chars - // it benchmarks the same. - if escapeHTML { - return c != '<' && c != '>' && c != '&' && c != '\\' && c != '"' && c >= 0x20 && c < utf8.RuneSelf - } else { - return c != '\\' && c != '"' && c >= 0x20 && c < utf8.RuneSelf +func getTable(falseValues ...int) [128]bool { + table := [128]bool{} + + for i := 0; i < 128; i++ { + table[i] = true } + + for _, v := range falseValues { + table[v] = false + } + + return table } +var ( + htmlEscapeTable = getTable(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, '"', '&', '<', '>', '\\') + htmlNoEscapeTable = getTable(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, '"', '\\') +) + func (w *Writer) String(s string) { w.Buffer.AppendByte('"') @@ -288,15 +297,21 @@ func (w *Writer) String(s string) { p := 0 // last non-escape symbol + escapeTable := &htmlEscapeTable + if w.NoEscapeHTML { + escapeTable = &htmlNoEscapeTable + } + for i := 0; i < len(s); { c := s[i] - if isNotEscapedSingleChar(c, !w.NoEscapeHTML) { - // single-width character, no escaping is required - i++ - continue - } else if c < utf8.RuneSelf { - // single-with character, need to escape + if c < utf8.RuneSelf { + if escapeTable[c] { + // single-width character, no escaping is required + i++ + continue + } + w.Buffer.AppendString(s[p:i]) switch c { case '\t': diff --git a/vendor/modules.txt b/vendor/modules.txt index 5707ec4e2..fb3db731a 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -153,15 +153,20 @@ github.com/deepmap/oapi-codegen/pkg/util github.com/dimchansky/utfbom # github.com/dougm/pretty v0.0.0-20171025230240-2ee9d7453c02 github.com/dougm/pretty -# github.com/getkin/kin-openapi v0.61.0 +# github.com/getkin/kin-openapi v0.93.0 ## explicit github.com/getkin/kin-openapi/jsoninfo github.com/getkin/kin-openapi/openapi3 +github.com/getkin/kin-openapi/openapi3filter +github.com/getkin/kin-openapi/routers +github.com/getkin/kin-openapi/routers/legacy +github.com/getkin/kin-openapi/routers/legacy/pathpattern # github.com/ghodss/yaml v1.0.0 github.com/ghodss/yaml # github.com/go-openapi/jsonpointer v0.19.5 github.com/go-openapi/jsonpointer -# github.com/go-openapi/swag v0.19.5 +# github.com/go-openapi/swag v0.21.1 +## explicit github.com/go-openapi/swag # github.com/gobwas/glob v0.2.3 ## explicit @@ -255,6 +260,8 @@ github.com/jackc/pgx/v4/pgxpool github.com/jackc/puddle # github.com/jmespath/go-jmespath v0.4.0 github.com/jmespath/go-jmespath +# github.com/josharian/intern v1.0.0 +github.com/josharian/intern # github.com/json-iterator/go v1.1.12 github.com/json-iterator/go # github.com/julienschmidt/httprouter v1.3.0 @@ -275,7 +282,7 @@ github.com/labstack/gommon/bytes github.com/labstack/gommon/color github.com/labstack/gommon/log github.com/labstack/gommon/random -# github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e +# github.com/mailru/easyjson v0.7.6 github.com/mailru/easyjson/buffer github.com/mailru/easyjson/jlexer github.com/mailru/easyjson/jwriter