From 6a99db5770ed893b614ae481fbbf0c44ffc4ae3c Mon Sep 17 00:00:00 2001 From: Alex Schneider Date: Mon, 29 Nov 2021 11:05:27 +0100 Subject: [PATCH 1/6] Update module --- go.mod | 6 +- go.sum | 10 + .../getkin/kin-openapi/jsoninfo/field_info.go | 3 + .../getkin/kin-openapi/jsoninfo/marshal.go | 6 +- .../getkin/kin-openapi/jsoninfo/unmarshal.go | 12 +- .../jsoninfo/unsupported_properties_error.go | 11 +- .../getkin/kin-openapi/openapi3/callback.go | 6 +- .../getkin/kin-openapi/openapi3/components.go | 14 +- .../getkin/kin-openapi/openapi3/content.go | 6 +- .../kin-openapi/openapi3/discriminator.go | 2 +- .../getkin/kin-openapi/openapi3/doc.go | 5 +- .../getkin/kin-openapi/openapi3/encoding.go | 12 +- .../getkin/kin-openapi/openapi3/examples.go | 7 +- .../getkin/kin-openapi/openapi3/header.go | 78 +- .../getkin/kin-openapi/openapi3/info.go | 16 +- .../kin-openapi/openapi3/internalize_refs.go | 369 ++++++ .../getkin/kin-openapi/openapi3/link.go | 4 +- .../getkin/kin-openapi/openapi3/loader.go | 1047 +++++++++++++++++ .../getkin/kin-openapi/openapi3/media_type.go | 8 +- .../openapi3/{swagger.go => openapi3.go} | 49 +- .../getkin/kin-openapi/openapi3/operation.go | 16 +- .../getkin/kin-openapi/openapi3/parameter.go | 114 +- .../getkin/kin-openapi/openapi3/path_item.go | 10 +- .../getkin/kin-openapi/openapi3/paths.go | 87 +- .../getkin/kin-openapi/openapi3/refs.go | 80 +- .../kin-openapi/openapi3/request_body.go | 6 +- .../getkin/kin-openapi/openapi3/response.go | 16 +- .../getkin/kin-openapi/openapi3/schema.go | 272 +++-- .../kin-openapi/openapi3/schema_formats.go | 20 +- .../openapi3/security_requirements.go | 8 +- .../kin-openapi/openapi3/security_scheme.go | 66 +- .../getkin/kin-openapi/openapi3/server.go | 52 +- .../kin-openapi/openapi3/swagger_loader.go | 955 --------------- .../openapi3filter/authentication_input.go | 13 +- .../kin-openapi/openapi3filter/errors.go | 48 +- .../kin-openapi/openapi3filter/options.go | 10 +- .../openapi3filter/req_resp_decoder.go | 116 +- .../kin-openapi/openapi3filter/router.go | 217 ---- .../openapi3filter/validate_request.go | 60 +- .../openapi3filter/validate_request_input.go | 5 +- .../openapi3filter/validate_response.go | 14 +- .../validation_error_encoder.go | 123 +- .../openapi3filter/validation_handler.go | 22 +- .../{ => routers/legacy}/pathpattern/node.go | 4 +- .../kin-openapi/routers/legacy/router.go | 164 +++ .../getkin/kin-openapi/routers/types.go | 35 + .../mattn/go-colorable/noncolorable.go | 5 +- vendor/github.com/mattn/go-isatty/.travis.yml | 14 - .../github.com/mattn/go-isatty/isatty_bsd.go | 1 + .../mattn/go-isatty/isatty_others.go | 3 +- .../mattn/go-isatty/isatty_plan9.go | 1 + .../mattn/go-isatty/isatty_solaris.go | 9 +- .../mattn/go-isatty/isatty_tcgets.go | 3 +- .../mattn/go-isatty/isatty_windows.go | 6 +- .../github.com/mattn/go-isatty/renovate.json | 8 - vendor/modules.txt | 10 +- 56 files changed, 2420 insertions(+), 1844 deletions(-) create mode 100644 vendor/github.com/getkin/kin-openapi/openapi3/internalize_refs.go create mode 100644 vendor/github.com/getkin/kin-openapi/openapi3/loader.go rename vendor/github.com/getkin/kin-openapi/openapi3/{swagger.go => openapi3.go} (60%) delete mode 100644 vendor/github.com/getkin/kin-openapi/openapi3/swagger_loader.go delete mode 100644 vendor/github.com/getkin/kin-openapi/openapi3filter/router.go rename vendor/github.com/getkin/kin-openapi/{ => routers/legacy}/pathpattern/node.go (98%) 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 delete mode 100644 vendor/github.com/mattn/go-isatty/.travis.yml delete mode 100644 vendor/github.com/mattn/go-isatty/renovate.json diff --git a/go.mod b/go.mod index 899d42143..b970cff9a 100644 --- a/go.mod +++ b/go.mod @@ -8,7 +8,7 @@ require ( github.com/dgrijalva/jwt-go/v4 v4.0.0-preview1 github.com/docker/go-units v0.4.0 github.com/fatih/color v1.10.0 - github.com/getkin/kin-openapi v0.49.0 + github.com/getkin/kin-openapi v0.83.0 github.com/hashicorp/hcl/v2 v2.10.1 github.com/jimlambrt/go-oauth-pkce-code-verifier v0.0.0-20201220003123-6363600dffda github.com/mitchellh/go-wordwrap v1.0.1 // indirect @@ -57,8 +57,8 @@ require ( github.com/jonboulle/clockwork v0.2.0 // indirect github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e // indirect github.com/mattermost/xml-roundtrip-validator v0.0.0-20201208211235-fe770d50d911 // indirect - github.com/mattn/go-colorable v0.1.8 // indirect - github.com/mattn/go-isatty v0.0.12 // indirect + github.com/mattn/go-colorable v0.1.9 // indirect + github.com/mattn/go-isatty v0.0.14 // indirect github.com/matttproud/golang_protobuf_extensions v1.0.1 // indirect github.com/sergi/go-diff v1.2.0 // indirect go.opentelemetry.io/otel/internal/metric v0.23.0 // indirect diff --git a/go.sum b/go.sum index 988f9e97c..c7656dc90 100644 --- a/go.sum +++ b/go.sum @@ -87,8 +87,12 @@ github.com/envoyproxy/go-control-plane v0.9.9-0.20210512163311-63b5d3c536b0/go.m github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/fatih/color v1.10.0 h1:s36xzo75JdqLaaWoiEHk767eHiwo0598uUxyfiPkDsg= github.com/fatih/color v1.10.0/go.mod h1:ELkj/draVOlAH/xkhN6mQ50Qd0MPOk5AAr3maGEBuJM= +github.com/fatih/color v1.13.0 h1:8LOYc1KYPPmyKMuN8QV2DNRWNbLo6LZ0iLs8+mlH53w= +github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk= github.com/getkin/kin-openapi v0.49.0 h1:nKSq662fS0kZ11+Wu3FLg3GQGL0UuH1VxF8wV1QuDEU= github.com/getkin/kin-openapi v0.49.0/go.mod h1:ZJSfy1PxJv2QQvH9EdBj3nupRTVvV42mkW6zKUlRBwk= +github.com/getkin/kin-openapi v0.83.0 h1:qQbfSsapSPuRS73xhElJ85bWFo2REHNXBXAQ1kqqlCE= +github.com/getkin/kin-openapi v0.83.0/go.mod h1:660oXbgy5JFMKreazJaQTw7o+X00qeSyhcnluiMv+Xg= github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= @@ -164,6 +168,7 @@ github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm4 github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= +github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= github.com/grpc-ecosystem/grpc-gateway v1.16.0 h1:gmcG1KaJ57LophUzW0Hy8NmPhnMZb4M0+kPpLofRdBo= github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= @@ -201,8 +206,12 @@ github.com/mattermost/xml-roundtrip-validator v0.0.0-20201208211235-fe770d50d911 github.com/mattermost/xml-roundtrip-validator v0.0.0-20201208211235-fe770d50d911/go.mod h1:qccnGMcpgwcNaBnxqpJpWWUiPNr5H3O8eDgGV9gT5To= github.com/mattn/go-colorable v0.1.8 h1:c1ghPdyEDarC70ftn0y+A/Ee++9zz8ljHG1b13eJ0s8= github.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= +github.com/mattn/go-colorable v0.1.9 h1:sqDoxXbdeALODt0DAeJCVp38ps9ZogZEAXjus69YV3U= +github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY= github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= +github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y= +github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= github.com/mitchellh/go-wordwrap v0.0.0-20150314170334-ad45545899c7/go.mod h1:ZXFpozHsX6DPmq2I0TCekCxypsnAUbP2oI0UX1GXzOo= @@ -443,6 +452,7 @@ golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210915083310-ed5796bab164 h1:7ZDGnxgHAMw7thfC5bEos0RDAccZKxioiWBhfIe+tvw= golang.org/x/sys v0.0.0-20210915083310-ed5796bab164/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= 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.go b/vendor/github.com/getkin/kin-openapi/jsoninfo/marshal.go index 93de99a56..2a98d68fb 100644 --- a/vendor/github.com/getkin/kin-openapi/jsoninfo/marshal.go +++ b/vendor/github.com/getkin/kin-openapi/jsoninfo/marshal.go @@ -59,11 +59,11 @@ func (encoder *ObjectEncoder) EncodeStructFieldsAndExtensions(value interface{}) // Follow "encoding/json" semantics if reflection.Kind() != reflect.Ptr { // Panic because this is a clear programming error - panic(fmt.Errorf("Value %s is not a pointer", reflection.Type().String())) + panic(fmt.Errorf("value %s is not a pointer", reflection.Type().String())) } if reflection.IsNil() { // Panic because this is a clear programming error - panic(fmt.Errorf("Value %s is nil", reflection.Type().String())) + panic(fmt.Errorf("value %s is nil", reflection.Type().String())) } // Take the element @@ -146,7 +146,7 @@ iteration: continue iteration } default: - panic(fmt.Errorf("Field '%s' has unsupported type %s", field.JSONName, field.Type.String())) + panic(fmt.Errorf("field %q has unsupported type %s", field.JSONName, field.Type.String())) } // No special treament is needed diff --git a/vendor/github.com/getkin/kin-openapi/jsoninfo/unmarshal.go b/vendor/github.com/getkin/kin-openapi/jsoninfo/unmarshal.go index 329718758..ce3c337a3 100644 --- a/vendor/github.com/getkin/kin-openapi/jsoninfo/unmarshal.go +++ b/vendor/github.com/getkin/kin-openapi/jsoninfo/unmarshal.go @@ -25,7 +25,7 @@ type ObjectDecoder struct { func NewObjectDecoder(data []byte) (*ObjectDecoder, error) { var remainingFields map[string]json.RawMessage if err := json.Unmarshal(data, &remainingFields); err != nil { - return nil, fmt.Errorf("Failed to unmarshal extension properties: %v\nInput: %s", err, data) + return nil, fmt.Errorf("failed to unmarshal extension properties: %v (%s)", err, data) } return &ObjectDecoder{ Data: data, @@ -41,10 +41,10 @@ func (decoder *ObjectDecoder) DecodeExtensionMap() map[string]json.RawMessage { func (decoder *ObjectDecoder) DecodeStructFieldsAndExtensions(value interface{}) error { reflection := reflect.ValueOf(value) if reflection.Kind() != reflect.Ptr { - panic(fmt.Errorf("Value %T is not a pointer", value)) + panic(fmt.Errorf("value %T is not a pointer", value)) } if reflection.IsNil() { - panic(fmt.Errorf("Value %T is nil", value)) + panic(fmt.Errorf("value %T is nil", value)) } reflection = reflection.Elem() for (reflection.Kind() == reflect.Interface || reflection.Kind() == reflect.Ptr) && !reflection.IsNil() { @@ -52,7 +52,7 @@ func (decoder *ObjectDecoder) DecodeStructFieldsAndExtensions(value interface{}) } reflectionType := reflection.Type() if reflectionType.Kind() != reflect.Struct { - panic(fmt.Errorf("Value %T is not a struct", value)) + panic(fmt.Errorf("value %T is not a struct", value)) } typeInfo := GetTypeInfo(reflectionType) @@ -87,7 +87,7 @@ func (decoder *ObjectDecoder) DecodeStructFieldsAndExtensions(value interface{}) continue } } - return fmt.Errorf("Error while unmarshalling property '%s' (%s): %v", + return fmt.Errorf("failed to unmarshal property %q (%s): %v", field.JSONName, fieldValue.Type().String(), err) } if !isPtr { @@ -109,7 +109,7 @@ func (decoder *ObjectDecoder) DecodeStructFieldsAndExtensions(value interface{}) continue } } - return fmt.Errorf("Error while unmarshalling property '%s' (%s): %v", + return fmt.Errorf("failed to unmarshal property %q (%s): %v", field.JSONName, fieldPtr.Type().String(), err) } diff --git a/vendor/github.com/getkin/kin-openapi/jsoninfo/unsupported_properties_error.go b/vendor/github.com/getkin/kin-openapi/jsoninfo/unsupported_properties_error.go index 258efef28..f69aafdc3 100644 --- a/vendor/github.com/getkin/kin-openapi/jsoninfo/unsupported_properties_error.go +++ b/vendor/github.com/getkin/kin-openapi/jsoninfo/unsupported_properties_error.go @@ -4,7 +4,6 @@ import ( "encoding/json" "fmt" "sort" - "strings" ) // UnsupportedPropertiesError is a helper for extensions that want to refuse @@ -27,7 +26,7 @@ func (err *UnsupportedPropertiesError) Error() string { m := err.UnsupportedProperties typeInfo := GetTypeInfoForValue(err.Value) if m == nil || typeInfo == nil { - return "Invalid UnsupportedPropertiesError" + return fmt.Sprintf("invalid %T", *err) } keys := make([]string, 0, len(m)) for k := range m { @@ -36,10 +35,8 @@ func (err *UnsupportedPropertiesError) Error() string { sort.Strings(keys) supported := typeInfo.FieldNames() if len(supported) == 0 { - return fmt.Sprintf("Type '%T' doesn't take any properties. Unsupported properties: '%s'\n", - err.Value, strings.Join(keys, "', '")) + return fmt.Sprintf("type \"%T\" doesn't take any properties. Unsupported properties: %+v", + err.Value, keys) } - return fmt.Sprintf("Unsupported properties: '%s'\nSupported properties are: '%s'", - strings.Join(keys, "', '"), - strings.Join(supported, "', '")) + return fmt.Sprintf("unsupported properties: %+v (supported properties are: %+v)", keys, supported) } diff --git a/vendor/github.com/getkin/kin-openapi/openapi3/callback.go b/vendor/github.com/getkin/kin-openapi/openapi3/callback.go index 334233104..8995e4792 100644 --- a/vendor/github.com/getkin/kin-openapi/openapi3/callback.go +++ b/vendor/github.com/getkin/kin-openapi/openapi3/callback.go @@ -13,7 +13,7 @@ var _ jsonpointer.JSONPointable = (*Callbacks)(nil) func (c Callbacks) JSONLookup(token string) (interface{}, error) { ref, ok := c[token] - if ref == nil || ok == false { + if ref == nil || !ok { return nil, fmt.Errorf("object has no field %q", token) } @@ -26,9 +26,9 @@ func (c Callbacks) JSONLookup(token string) (interface{}, error) { // Callback is specified by OpenAPI/Swagger standard version 3.0. type Callback map[string]*PathItem -func (value Callback) Validate(c context.Context) error { +func (value Callback) Validate(ctx context.Context) error { for _, v := range value { - if err := v.Validate(c); err != nil { + if err := v.Validate(ctx); err != nil { return err } } diff --git a/vendor/github.com/getkin/kin-openapi/openapi3/components.go b/vendor/github.com/getkin/kin-openapi/openapi3/components.go index e01f961d2..7acafabf9 100644 --- a/vendor/github.com/getkin/kin-openapi/openapi3/components.go +++ b/vendor/github.com/getkin/kin-openapi/openapi3/components.go @@ -34,12 +34,12 @@ func (components *Components) UnmarshalJSON(data []byte) error { return jsoninfo.UnmarshalStrictStruct(data, components) } -func (components *Components) Validate(c context.Context) (err error) { +func (components *Components) Validate(ctx context.Context) (err error) { for k, v := range components.Schemas { if err = ValidateIdentifier(k); err != nil { return } - if err = v.Validate(c); err != nil { + if err = v.Validate(ctx); err != nil { return } } @@ -48,7 +48,7 @@ func (components *Components) Validate(c context.Context) (err error) { if err = ValidateIdentifier(k); err != nil { return } - if err = v.Validate(c); err != nil { + if err = v.Validate(ctx); err != nil { return } } @@ -57,7 +57,7 @@ func (components *Components) Validate(c context.Context) (err error) { if err = ValidateIdentifier(k); err != nil { return } - if err = v.Validate(c); err != nil { + if err = v.Validate(ctx); err != nil { return } } @@ -66,7 +66,7 @@ func (components *Components) Validate(c context.Context) (err error) { if err = ValidateIdentifier(k); err != nil { return } - if err = v.Validate(c); err != nil { + if err = v.Validate(ctx); err != nil { return } } @@ -75,7 +75,7 @@ func (components *Components) Validate(c context.Context) (err error) { if err = ValidateIdentifier(k); err != nil { return } - if err = v.Validate(c); err != nil { + if err = v.Validate(ctx); err != nil { return } } @@ -84,7 +84,7 @@ func (components *Components) Validate(c context.Context) (err error) { if err = ValidateIdentifier(k); err != nil { return } - if err = v.Validate(c); err != nil { + if err = v.Validate(ctx); err != nil { return } } diff --git a/vendor/github.com/getkin/kin-openapi/openapi3/content.go b/vendor/github.com/getkin/kin-openapi/openapi3/content.go index abe376e3e..5edb7d3fa 100644 --- a/vendor/github.com/getkin/kin-openapi/openapi3/content.go +++ b/vendor/github.com/getkin/kin-openapi/openapi3/content.go @@ -104,10 +104,10 @@ func (content Content) Get(mime string) *MediaType { return content["*/*"] } -func (content Content) Validate(c context.Context) error { - for _, v := range content { +func (value Content) Validate(ctx context.Context) error { + for _, v := range value { // Validate MediaType - if err := v.Validate(c); err != nil { + if err := v.Validate(ctx); err != nil { return err } } diff --git a/vendor/github.com/getkin/kin-openapi/openapi3/discriminator.go b/vendor/github.com/getkin/kin-openapi/openapi3/discriminator.go index de518d578..82ad7040b 100644 --- a/vendor/github.com/getkin/kin-openapi/openapi3/discriminator.go +++ b/vendor/github.com/getkin/kin-openapi/openapi3/discriminator.go @@ -21,6 +21,6 @@ func (value *Discriminator) UnmarshalJSON(data []byte) error { return jsoninfo.UnmarshalStrictStruct(data, value) } -func (value *Discriminator) Validate(c context.Context) error { +func (value *Discriminator) Validate(ctx context.Context) error { return nil } diff --git a/vendor/github.com/getkin/kin-openapi/openapi3/doc.go b/vendor/github.com/getkin/kin-openapi/openapi3/doc.go index efe8b4a0c..fc2735cb7 100644 --- a/vendor/github.com/getkin/kin-openapi/openapi3/doc.go +++ b/vendor/github.com/getkin/kin-openapi/openapi3/doc.go @@ -1,5 +1,4 @@ -// Package openapi3 parses and writes OpenAPI 3 specifications. +// Package openapi3 parses and writes OpenAPI 3 specification documents. // -// The OpenAPI 3.0 specification can be found at: -// https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.0.md +// See https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.0.md package openapi3 diff --git a/vendor/github.com/getkin/kin-openapi/openapi3/encoding.go b/vendor/github.com/getkin/kin-openapi/openapi3/encoding.go index 16b7a2694..ad48b9160 100644 --- a/vendor/github.com/getkin/kin-openapi/openapi3/encoding.go +++ b/vendor/github.com/getkin/kin-openapi/openapi3/encoding.go @@ -61,21 +61,21 @@ func (encoding *Encoding) SerializationMethod() *SerializationMethod { return sm } -func (encoding *Encoding) Validate(c context.Context) error { - if encoding == nil { +func (value *Encoding) Validate(ctx context.Context) error { + if value == nil { return nil } - for k, v := range encoding.Headers { + for k, v := range value.Headers { if err := ValidateIdentifier(k); err != nil { return nil } - if err := v.Validate(c); err != nil { + if err := v.Validate(ctx); err != nil { return nil } } // Validate a media types's serialization method. - sm := encoding.SerializationMethod() + sm := value.SerializationMethod() switch { case sm.Style == SerializationForm && sm.Explode, sm.Style == SerializationForm && !sm.Explode, @@ -86,7 +86,7 @@ func (encoding *Encoding) Validate(c context.Context) error { sm.Style == SerializationDeepObject && sm.Explode: // it is a valid default: - return fmt.Errorf("Serialization method with style=%q and explode=%v is not supported by media type", sm.Style, sm.Explode) + return fmt.Errorf("serialization method with style=%q and explode=%v is not supported by media type", sm.Style, sm.Explode) } return nil diff --git a/vendor/github.com/getkin/kin-openapi/openapi3/examples.go b/vendor/github.com/getkin/kin-openapi/openapi3/examples.go index 5f6255bf3..f7f90ce54 100644 --- a/vendor/github.com/getkin/kin-openapi/openapi3/examples.go +++ b/vendor/github.com/getkin/kin-openapi/openapi3/examples.go @@ -1,6 +1,7 @@ package openapi3 import ( + "context" "fmt" "github.com/getkin/kin-openapi/jsoninfo" @@ -13,7 +14,7 @@ var _ jsonpointer.JSONPointable = (*Examples)(nil) func (e Examples) JSONLookup(token string) (interface{}, error) { ref, ok := e[token] - if ref == nil || ok == false { + if ref == nil || !ok { return nil, fmt.Errorf("object has no field %q", token) } @@ -46,3 +47,7 @@ func (example *Example) MarshalJSON() ([]byte, error) { func (example *Example) UnmarshalJSON(data []byte) error { return jsoninfo.UnmarshalStrictStruct(data, example) } + +func (value *Example) Validate(ctx context.Context) error { + return nil // TODO +} diff --git a/vendor/github.com/getkin/kin-openapi/openapi3/header.go b/vendor/github.com/getkin/kin-openapi/openapi3/header.go index 3adb2ea5a..5fdc31771 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" @@ -14,7 +15,7 @@ var _ jsonpointer.JSONPointable = (*Headers)(nil) func (h Headers) JSONLookup(token string) (interface{}, error) { ref, ok := h[token] - if ref == nil || ok == false { + if ref == nil || !ok { return nil, fmt.Errorf("object has no field %q", token) } @@ -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.0.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) } -func (value *Header) Validate(c context.Context) error { - if v := value.Schema; v != nil { - if err := v.Validate(c); err != nil { - return err +// 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 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 25e675e66..2adffff1a 100644 --- a/vendor/github.com/getkin/kin-openapi/openapi3/info.go +++ b/vendor/github.com/getkin/kin-openapi/openapi3/info.go @@ -26,25 +26,25 @@ func (value *Info) UnmarshalJSON(data []byte) error { return jsoninfo.UnmarshalStrictStruct(data, value) } -func (value *Info) Validate(c context.Context) error { +func (value *Info) Validate(ctx context.Context) error { if contact := value.Contact; contact != nil { - if err := contact.Validate(c); err != nil { + if err := contact.Validate(ctx); err != nil { return err } } if license := value.License; license != nil { - if err := license.Validate(c); err != nil { + if err := license.Validate(ctx); err != nil { return err } } if value.Version == "" { - return errors.New("value of version must be a non-empty JSON string") + return errors.New("value of version must be a non-empty string") } if value.Title == "" { - return errors.New("value of title must be a non-empty JSON string") + return errors.New("value of title must be a non-empty string") } return nil @@ -66,7 +66,7 @@ func (value *Contact) UnmarshalJSON(data []byte) error { return jsoninfo.UnmarshalStrictStruct(data, value) } -func (value *Contact) Validate(c context.Context) error { +func (value *Contact) Validate(ctx context.Context) error { return nil } @@ -85,9 +85,9 @@ func (value *License) UnmarshalJSON(data []byte) error { return jsoninfo.UnmarshalStrictStruct(data, value) } -func (value *License) Validate(c context.Context) error { +func (value *License) Validate(ctx context.Context) error { if value.Name == "" { - return errors.New("value of license name must be a non-empty JSON string") + return errors.New("value of license name must be a non-empty string") } return nil } 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 0fe1a1c74..7d627b8bc 100644 --- a/vendor/github.com/getkin/kin-openapi/openapi3/link.go +++ b/vendor/github.com/getkin/kin-openapi/openapi3/link.go @@ -44,12 +44,12 @@ func (value *Link) UnmarshalJSON(data []byte) error { return jsoninfo.UnmarshalStrictStruct(data, value) } -func (value *Link) Validate(c context.Context) error { +func (value *Link) Validate(ctx context.Context) error { if value.OperationID == "" && value.OperationRef == "" { return errors.New("missing operationId or operationRef on link") } if value.OperationID != "" && value.OperationRef != "" { - return fmt.Errorf("operationId '%s' and operationRef '%s' are mutually exclusive", value.OperationID, value.OperationRef) + return fmt.Errorf("operationId %q and operationRef %q are mutually exclusive", value.OperationID, value.OperationRef) } return nil } diff --git a/vendor/github.com/getkin/kin-openapi/openapi3/loader.go b/vendor/github.com/getkin/kin-openapi/openapi3/loader.go new file mode 100644 index 000000000..0b8d0e1cc --- /dev/null +++ b/vendor/github.com/getkin/kin-openapi/openapi3/loader.go @@ -0,0 +1,1047 @@ +package openapi3 + +import ( + "context" + "encoding/json" + "errors" + "fmt" + "io/ioutil" + "net/http" + "net/url" + "path" + "path/filepath" + "reflect" + "strconv" + "strings" + + "github.com/ghodss/yaml" +) + +func foundUnresolvedRef(ref string) error { + return fmt.Errorf("found unresolved ref: %q", ref) +} + +func failedToResolveRefFragmentPart(value, what string) error { + return fmt.Errorf("failed to resolve %q in fragment in URI: %q", what, value) +} + +// Loader helps deserialize an OpenAPIv3 document +type Loader struct { + // IsExternalRefsAllowed enables visiting other files + IsExternalRefsAllowed bool + + // ReadFromURIFunc allows overriding the any file/URL reading func + ReadFromURIFunc func(loader *Loader, url *url.URL) ([]byte, error) + + Context context.Context + + rootDir string + + visitedPathItemRefs map[string]struct{} + + visitedDocuments map[string]*T + + visitedExample map[*Example]struct{} + visitedHeader map[*Header]struct{} + visitedLink map[*Link]struct{} + visitedParameter map[*Parameter]struct{} + visitedRequestBody map[*RequestBody]struct{} + visitedResponse map[*Response]struct{} + visitedSchema map[*Schema]struct{} + visitedSecurityScheme map[*SecurityScheme]struct{} +} + +// NewLoader returns an empty Loader +func NewLoader() *Loader { + return &Loader{} +} + +func (loader *Loader) resetVisitedPathItemRefs() { + loader.visitedPathItemRefs = make(map[string]struct{}) +} + +// LoadFromURI loads a spec from a remote URL +func (loader *Loader) LoadFromURI(location *url.URL) (*T, error) { + loader.resetVisitedPathItemRefs() + return loader.loadFromURIInternal(location) +} + +// 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)}) +} + +func (loader *Loader) loadFromURIInternal(location *url.URL) (*T, error) { + data, err := loader.readURL(location) + if err != nil { + return nil, err + } + return loader.loadFromDataWithPathInternal(data, location) +} + +func (loader *Loader) allowsExternalRefs(ref string) (err error) { + if !loader.IsExternalRefsAllowed { + err = fmt.Errorf("encountered disallowed external reference: %q", ref) + } + return +} + +// loadSingleElementFromURI reads the data from ref and unmarshals to the passed element. +func (loader *Loader) loadSingleElementFromURI(ref string, rootPath *url.URL, element interface{}) (*url.URL, error) { + if err := loader.allowsExternalRefs(ref); err != nil { + return nil, err + } + + parsedURL, err := url.Parse(ref) + if err != nil { + return nil, err + } + if fragment := parsedURL.Fragment; fragment != "" { + return nil, fmt.Errorf("unexpected ref fragment %q", fragment) + } + + resolvedPath, err := resolvePath(rootPath, parsedURL) + if err != nil { + return nil, fmt.Errorf("could not resolve path: %v", err) + } + + data, err := loader.readURL(resolvedPath) + if err != nil { + return nil, err + } + if err := yaml.Unmarshal(data, element); err != nil { + return nil, err + } + + return resolvedPath, nil +} + +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) +} + +// LoadFromData loads a spec from a byte array +func (loader *Loader) LoadFromData(data []byte) (*T, error) { + loader.resetVisitedPathItemRefs() + doc := &T{} + if err := yaml.Unmarshal(data, doc); err != nil { + return nil, err + } + if err := loader.ResolveRefsIn(doc, nil); err != nil { + return nil, err + } + return doc, nil +} + +// LoadFromDataWithPath takes the OpenAPI document data in bytes and a path where the resolver can find referred +// elements and returns a *T with all resolved data or an error if unable to load data or resolve refs. +func (loader *Loader) LoadFromDataWithPath(data []byte, location *url.URL) (*T, error) { + loader.resetVisitedPathItemRefs() + return loader.loadFromDataWithPathInternal(data, location) +} + +func (loader *Loader) loadFromDataWithPathInternal(data []byte, location *url.URL) (*T, error) { + if loader.visitedDocuments == nil { + loader.visitedDocuments = make(map[string]*T) + } + uri := location.String() + if doc, ok := loader.visitedDocuments[uri]; ok { + return doc, nil + } + + doc := &T{} + loader.visitedDocuments[uri] = doc + + if err := yaml.Unmarshal(data, doc); err != nil { + return nil, err + } + if err := loader.ResolveRefsIn(doc, location); err != nil { + return nil, err + } + + return doc, nil +} + +// ResolveRefsIn expands references if for instance spec was just unmarshalled +func (loader *Loader) ResolveRefsIn(doc *T, location *url.URL) (err error) { + if loader.visitedPathItemRefs == nil { + loader.resetVisitedPathItemRefs() + } + + // Visit all components + components := doc.Components + for _, component := range components.Headers { + if err = loader.resolveHeaderRef(doc, component, location); err != nil { + return + } + } + for _, component := range components.Parameters { + if err = loader.resolveParameterRef(doc, component, location); err != nil { + return + } + } + for _, component := range components.RequestBodies { + if err = loader.resolveRequestBodyRef(doc, component, location); err != nil { + return + } + } + for _, component := range components.Responses { + if err = loader.resolveResponseRef(doc, component, location); err != nil { + return + } + } + for _, component := range components.Schemas { + if err = loader.resolveSchemaRef(doc, component, location); err != nil { + return + } + } + for _, component := range components.SecuritySchemes { + if err = loader.resolveSecuritySchemeRef(doc, component, location); err != nil { + return + } + } + for _, component := range components.Examples { + if err = loader.resolveExampleRef(doc, component, location); err != nil { + return + } + } + for _, component := range components.Callbacks { + if err = loader.resolveCallbackRef(doc, component, location); err != nil { + return + } + } + + // Visit all operations + for entrypoint, pathItem := range doc.Paths { + if pathItem == nil { + continue + } + if err = loader.resolvePathItemRef(doc, entrypoint, pathItem, location); err != nil { + return + } + } + + return +} + +func join(basePath *url.URL, relativePath *url.URL) (*url.URL, error) { + if basePath == nil { + return relativePath, nil + } + newPath, err := url.Parse(basePath.String()) + if err != nil { + return nil, fmt.Errorf("cannot copy path: %q", basePath.String()) + } + newPath.Path = path.Join(path.Dir(newPath.Path), relativePath.Path) + return newPath, nil +} + +func resolvePath(basePath *url.URL, componentPath *url.URL) (*url.URL, error) { + if componentPath.Scheme == "" && componentPath.Host == "" { + // support absolute paths + if componentPath.Path[0] == '/' { + return componentPath, nil + } + return join(basePath, componentPath) + } + return componentPath, nil +} + +func isSingleRefElement(ref string) bool { + return !strings.Contains(ref, "#") +} + +func (loader *Loader) resolveComponent( + doc *T, + ref string, + path *url.URL, + resolved interface{}, +) ( + componentPath *url.URL, + err error, +) { + if doc, ref, componentPath, err = loader.resolveRef(doc, ref, path); err != nil { + return nil, err + } + + parsedURL, err := url.Parse(ref) + if err != nil { + return nil, fmt.Errorf("cannot parse reference: %q: %v", ref, parsedURL) + } + fragment := parsedURL.Fragment + if !strings.HasPrefix(fragment, "/") { + return nil, fmt.Errorf("expected fragment prefix '#/' in URI %q", ref) + } + + drill := func(cursor interface{}) (interface{}, error) { + for _, pathPart := range strings.Split(fragment[1:], "/") { + pathPart = unescapeRefString(pathPart) + + if cursor, err = drillIntoField(cursor, pathPart); err != nil { + e := failedToResolveRefFragmentPart(ref, pathPart) + return nil, fmt.Errorf("%s: %s", e.Error(), err.Error()) + } + if cursor == nil { + return nil, failedToResolveRefFragmentPart(ref, pathPart) + } + } + return cursor, nil + } + 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 { + return nil, err + } + if err2 = yaml.Unmarshal(data, &cursor); err2 != nil { + return nil, err + } + if cursor, err2 = drill(cursor); err2 != nil || cursor == nil { + return nil, err + } + err = nil + } + + switch { + case reflect.TypeOf(cursor) == reflect.TypeOf(resolved): + reflect.ValueOf(resolved).Elem().Set(reflect.ValueOf(cursor).Elem()) + return componentPath, nil + + case reflect.TypeOf(cursor) == reflect.TypeOf(map[string]interface{}{}): + codec := func(got, expect interface{}) error { + enc, err := json.Marshal(got) + if err != nil { + return err + } + if err = json.Unmarshal(enc, expect); err != nil { + return err + } + return nil + } + if err := codec(cursor, resolved); err != nil { + return nil, fmt.Errorf("bad data in %q", ref) + } + return componentPath, nil + + default: + return nil, fmt.Errorf("bad data in %q", ref) + } +} + +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)) + if !elementValue.IsValid() { + return nil, fmt.Errorf("map key %q not found", fieldName) + } + return elementValue.Interface(), nil + + case reflect.Slice: + i, err := strconv.ParseUint(fieldName, 10, 32) + if err != nil { + return nil, err + } + index := int(i) + if 0 > index || index >= val.Len() { + return nil, errors.New("slice index out of bounds") + } + return val.Index(index).Interface(), nil + + case reflect.Struct: + hasFields := false + for i := 0; i < val.NumField(); i++ { + hasFields = true + 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 + } + } + // if cursor is a "ref wrapper" struct (e.g. RequestBodyRef), + if _, ok := val.Type().FieldByName("Value"); ok { + // try digging into its Value field + return drillIntoField(val.FieldByName("Value").Interface(), fieldName) + } + if hasFields { + if ff := val.Type().Field(0); ff.PkgPath == "" && ff.Name == "ExtensionProps" { + extensions := val.Field(0).Interface().(ExtensionProps).Extensions + if enc, ok := extensions[fieldName]; ok { + var dec interface{} + if err := json.Unmarshal(enc.(json.RawMessage), &dec); err != nil { + return nil, err + } + return dec, nil + } + } + } + return nil, fmt.Errorf("struct field %q not found", fieldName) + + default: + return nil, errors.New("not a map, slice nor struct") + } +} + +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 + } + + if err := loader.allowsExternalRefs(ref); err != nil { + return nil, "", nil, err + } + + parsedURL, err := url.Parse(ref) + if err != nil { + return nil, "", nil, fmt.Errorf("cannot parse reference: %q: %v", ref, parsedURL) + } + fragment := parsedURL.Fragment + parsedURL.Fragment = "" + + var resolvedPath *url.URL + if resolvedPath, err = resolvePath(path, parsedURL); err != nil { + return nil, "", nil, fmt.Errorf("error resolving path: %v", err) + } + + if doc, err = loader.loadFromURIInternal(resolvedPath); err != nil { + return nil, "", nil, fmt.Errorf("error resolving reference %q: %v", ref, err) + } + + return doc, "#" + fragment, resolvedPath, nil +} + +func (loader *Loader) resolveHeaderRef(doc *T, component *HeaderRef, documentPath *url.URL) (err error) { + if component != nil && component.Value != nil { + if loader.visitedHeader == nil { + loader.visitedHeader = make(map[*Header]struct{}) + } + if _, ok := loader.visitedHeader[component.Value]; ok { + return nil + } + loader.visitedHeader[component.Value] = struct{}{} + } + + if component == nil { + return errors.New("invalid header: value MUST be an object") + } + if ref := component.Ref; ref != "" { + if isSingleRefElement(ref) { + var header Header + if documentPath, err = loader.loadSingleElementFromURI(ref, documentPath, &header); err != nil { + return err + } + component.Value = &header + } else { + var resolved HeaderRef + componentPath, err := loader.resolveComponent(doc, ref, documentPath, &resolved) + if err != nil { + return err + } + if err := loader.resolveHeaderRef(doc, &resolved, componentPath); err != nil { + return err + } + component.Value = resolved.Value + documentPath = loader.documentPathForRecursiveRef(documentPath, resolved.Ref) + } + } + value := component.Value + if value == nil { + return nil + } + + if schema := value.Schema; schema != nil { + if err := loader.resolveSchemaRef(doc, schema, documentPath); err != nil { + return err + } + } + return nil +} + +func (loader *Loader) resolveParameterRef(doc *T, component *ParameterRef, documentPath *url.URL) (err error) { + if component != nil && component.Value != nil { + if loader.visitedParameter == nil { + loader.visitedParameter = make(map[*Parameter]struct{}) + } + if _, ok := loader.visitedParameter[component.Value]; ok { + return nil + } + loader.visitedParameter[component.Value] = struct{}{} + } + + if component == nil { + return errors.New("invalid parameter: value MUST be an object") + } + ref := component.Ref + if ref != "" { + if isSingleRefElement(ref) { + var param Parameter + if documentPath, err = loader.loadSingleElementFromURI(ref, documentPath, ¶m); err != nil { + return err + } + component.Value = ¶m + } else { + var resolved ParameterRef + componentPath, err := loader.resolveComponent(doc, ref, documentPath, &resolved) + if err != nil { + return err + } + if err := loader.resolveParameterRef(doc, &resolved, componentPath); err != nil { + return err + } + component.Value = resolved.Value + documentPath = loader.documentPathForRecursiveRef(documentPath, resolved.Ref) + } + } + value := component.Value + if value == nil { + return nil + } + + if value.Content != nil && value.Schema != nil { + return errors.New("cannot contain both schema and content in a parameter") + } + for _, contentType := range value.Content { + if schema := contentType.Schema; schema != nil { + if err := loader.resolveSchemaRef(doc, schema, documentPath); err != nil { + return err + } + } + } + if schema := value.Schema; schema != nil { + if err := loader.resolveSchemaRef(doc, schema, documentPath); err != nil { + return err + } + } + return nil +} + +func (loader *Loader) resolveRequestBodyRef(doc *T, component *RequestBodyRef, documentPath *url.URL) (err error) { + if component != nil && component.Value != nil { + if loader.visitedRequestBody == nil { + loader.visitedRequestBody = make(map[*RequestBody]struct{}) + } + if _, ok := loader.visitedRequestBody[component.Value]; ok { + return nil + } + loader.visitedRequestBody[component.Value] = struct{}{} + } + + if component == nil { + return errors.New("invalid requestBody: value MUST be an object") + } + if ref := component.Ref; ref != "" { + if isSingleRefElement(ref) { + var requestBody RequestBody + if documentPath, err = loader.loadSingleElementFromURI(ref, documentPath, &requestBody); err != nil { + return err + } + component.Value = &requestBody + } else { + var resolved RequestBodyRef + componentPath, err := loader.resolveComponent(doc, ref, documentPath, &resolved) + if err != nil { + return err + } + if err = loader.resolveRequestBodyRef(doc, &resolved, componentPath); err != nil { + return err + } + component.Value = resolved.Value + documentPath = loader.documentPathForRecursiveRef(documentPath, resolved.Ref) + } + } + value := component.Value + if value == nil { + return nil + } + + for _, contentType := range value.Content { + for name, example := range contentType.Examples { + if err := loader.resolveExampleRef(doc, example, documentPath); err != nil { + return err + } + contentType.Examples[name] = example + } + if schema := contentType.Schema; schema != nil { + if err := loader.resolveSchemaRef(doc, schema, documentPath); err != nil { + return err + } + } + } + return nil +} + +func (loader *Loader) resolveResponseRef(doc *T, component *ResponseRef, documentPath *url.URL) (err error) { + if component != nil && component.Value != nil { + if loader.visitedResponse == nil { + loader.visitedResponse = make(map[*Response]struct{}) + } + if _, ok := loader.visitedResponse[component.Value]; ok { + return nil + } + loader.visitedResponse[component.Value] = struct{}{} + } + + if component == nil { + return errors.New("invalid response: value MUST be an object") + } + ref := component.Ref + if ref != "" { + if isSingleRefElement(ref) { + var resp Response + if documentPath, err = loader.loadSingleElementFromURI(ref, documentPath, &resp); err != nil { + return err + } + component.Value = &resp + } else { + var resolved ResponseRef + componentPath, err := loader.resolveComponent(doc, ref, documentPath, &resolved) + if err != nil { + return err + } + if err := loader.resolveResponseRef(doc, &resolved, componentPath); err != nil { + return err + } + component.Value = resolved.Value + documentPath = loader.documentPathForRecursiveRef(documentPath, resolved.Ref) + } + } + value := component.Value + if value == nil { + return nil + } + + for _, header := range value.Headers { + if err := loader.resolveHeaderRef(doc, header, documentPath); err != nil { + return err + } + } + for _, contentType := range value.Content { + if contentType == nil { + continue + } + for name, example := range contentType.Examples { + if err := loader.resolveExampleRef(doc, example, documentPath); err != nil { + return err + } + contentType.Examples[name] = example + } + if schema := contentType.Schema; schema != nil { + if err := loader.resolveSchemaRef(doc, schema, documentPath); err != nil { + return err + } + contentType.Schema = schema + } + } + for _, link := range value.Links { + if err := loader.resolveLinkRef(doc, link, documentPath); err != nil { + return err + } + } + return nil +} + +func (loader *Loader) resolveSchemaRef(doc *T, component *SchemaRef, documentPath *url.URL) (err error) { + if component != nil && component.Value != nil { + if loader.visitedSchema == nil { + loader.visitedSchema = make(map[*Schema]struct{}) + } + if _, ok := loader.visitedSchema[component.Value]; ok { + return nil + } + loader.visitedSchema[component.Value] = struct{}{} + } + + if component == nil { + return errors.New("invalid schema: value MUST be an object") + } + ref := component.Ref + if ref != "" { + if isSingleRefElement(ref) { + var schema Schema + if documentPath, err = loader.loadSingleElementFromURI(ref, documentPath, &schema); err != nil { + return err + } + component.Value = &schema + } else { + var resolved SchemaRef + componentPath, err := loader.resolveComponent(doc, ref, documentPath, &resolved) + if err != nil { + return err + } + if err := loader.resolveSchemaRef(doc, &resolved, componentPath); err != nil { + return err + } + component.Value = resolved.Value + documentPath = loader.documentPathForRecursiveRef(documentPath, resolved.Ref) + } + } + value := component.Value + if value == nil { + return nil + } + + // ResolveRefs referred schemas + if v := value.Items; v != nil { + if err := loader.resolveSchemaRef(doc, v, documentPath); err != nil { + return err + } + } + for _, v := range value.Properties { + if err := loader.resolveSchemaRef(doc, v, documentPath); err != nil { + return err + } + } + if v := value.AdditionalProperties; v != nil { + if err := loader.resolveSchemaRef(doc, v, documentPath); err != nil { + return err + } + } + if v := value.Not; v != nil { + if err := loader.resolveSchemaRef(doc, v, documentPath); err != nil { + return err + } + } + for _, v := range value.AllOf { + if err := loader.resolveSchemaRef(doc, v, documentPath); err != nil { + return err + } + } + for _, v := range value.AnyOf { + if err := loader.resolveSchemaRef(doc, v, documentPath); err != nil { + return err + } + } + for _, v := range value.OneOf { + if err := loader.resolveSchemaRef(doc, v, documentPath); err != nil { + return err + } + } + return nil +} + +func (loader *Loader) resolveSecuritySchemeRef(doc *T, component *SecuritySchemeRef, documentPath *url.URL) (err error) { + if component != nil && component.Value != nil { + if loader.visitedSecurityScheme == nil { + loader.visitedSecurityScheme = make(map[*SecurityScheme]struct{}) + } + if _, ok := loader.visitedSecurityScheme[component.Value]; ok { + return nil + } + loader.visitedSecurityScheme[component.Value] = struct{}{} + } + + if component == nil { + return errors.New("invalid securityScheme: value MUST be an object") + } + if ref := component.Ref; ref != "" { + if isSingleRefElement(ref) { + var scheme SecurityScheme + if _, err = loader.loadSingleElementFromURI(ref, documentPath, &scheme); err != nil { + return err + } + component.Value = &scheme + } else { + var resolved SecuritySchemeRef + componentPath, err := loader.resolveComponent(doc, ref, documentPath, &resolved) + if err != nil { + return err + } + if err := loader.resolveSecuritySchemeRef(doc, &resolved, componentPath); err != nil { + return err + } + component.Value = resolved.Value + _ = loader.documentPathForRecursiveRef(documentPath, resolved.Ref) + } + } + return nil +} + +func (loader *Loader) resolveExampleRef(doc *T, component *ExampleRef, documentPath *url.URL) (err error) { + if component != nil && component.Value != nil { + if loader.visitedExample == nil { + loader.visitedExample = make(map[*Example]struct{}) + } + if _, ok := loader.visitedExample[component.Value]; ok { + return nil + } + loader.visitedExample[component.Value] = struct{}{} + } + + if component == nil { + return errors.New("invalid example: value MUST be an object") + } + if ref := component.Ref; ref != "" { + if isSingleRefElement(ref) { + var example Example + if _, err = loader.loadSingleElementFromURI(ref, documentPath, &example); err != nil { + return err + } + component.Value = &example + } else { + var resolved ExampleRef + componentPath, err := loader.resolveComponent(doc, ref, documentPath, &resolved) + if err != nil { + return err + } + if err := loader.resolveExampleRef(doc, &resolved, componentPath); err != nil { + return err + } + component.Value = resolved.Value + _ = loader.documentPathForRecursiveRef(documentPath, resolved.Ref) + } + } + return nil +} + +func (loader *Loader) resolveCallbackRef(doc *T, component *CallbackRef, documentPath *url.URL) (err error) { + + if component == nil { + return errors.New("invalid callback: value MUST be an object") + } + if ref := component.Ref; ref != "" { + if isSingleRefElement(ref) { + var resolved Callback + if documentPath, err = loader.loadSingleElementFromURI(ref, documentPath, &resolved); err != nil { + return err + } + component.Value = &resolved + } else { + var resolved CallbackRef + componentPath, err := loader.resolveComponent(doc, ref, documentPath, &resolved) + if err != nil { + return err + } + if err := loader.resolveCallbackRef(doc, &resolved, componentPath); err != nil { + return err + } + component.Value = resolved.Value + documentPath = loader.documentPathForRecursiveRef(documentPath, resolved.Ref) + } + } + value := component.Value + if value == nil { + return nil + } + + for entrypoint, pathItem := range *value { + entrypoint, pathItem := entrypoint, pathItem + err = func() (err error) { + key := "-" + if documentPath != nil { + key = documentPath.EscapedPath() + } + key += entrypoint + if _, ok := loader.visitedPathItemRefs[key]; ok { + return nil + } + loader.visitedPathItemRefs[key] = struct{}{} + + if pathItem == nil { + return errors.New("invalid path item: value MUST be an object") + } + ref := pathItem.Ref + if ref != "" { + if isSingleRefElement(ref) { + var p PathItem + if documentPath, err = loader.loadSingleElementFromURI(ref, documentPath, &p); err != nil { + return err + } + *pathItem = p + } else { + if doc, ref, documentPath, err = loader.resolveRef(doc, ref, documentPath); err != nil { + return + } + + rest := strings.TrimPrefix(ref, "#/components/callbacks/") + if rest == ref { + return fmt.Errorf(`expected prefix "#/components/callbacks/" in URI %q`, ref) + } + id := unescapeRefString(rest) + + definitions := doc.Components.Callbacks + if definitions == nil { + return failedToResolveRefFragmentPart(ref, "callbacks") + } + resolved := definitions[id] + if resolved == nil { + return failedToResolveRefFragmentPart(ref, id) + } + + for _, p := range *resolved.Value { + *pathItem = *p + break + } + } + } + return loader.resolvePathItemRefContinued(doc, pathItem, documentPath) + }() + if err != nil { + return err + } + } + return nil +} + +func (loader *Loader) resolveLinkRef(doc *T, component *LinkRef, documentPath *url.URL) (err error) { + if component != nil && component.Value != nil { + if loader.visitedLink == nil { + loader.visitedLink = make(map[*Link]struct{}) + } + if _, ok := loader.visitedLink[component.Value]; ok { + return nil + } + loader.visitedLink[component.Value] = struct{}{} + } + + if component == nil { + return errors.New("invalid link: value MUST be an object") + } + if ref := component.Ref; ref != "" { + if isSingleRefElement(ref) { + var link Link + if _, err = loader.loadSingleElementFromURI(ref, documentPath, &link); err != nil { + return err + } + component.Value = &link + } else { + var resolved LinkRef + componentPath, err := loader.resolveComponent(doc, ref, documentPath, &resolved) + if err != nil { + return err + } + if err := loader.resolveLinkRef(doc, &resolved, componentPath); err != nil { + return err + } + component.Value = resolved.Value + _ = loader.documentPathForRecursiveRef(documentPath, resolved.Ref) + } + } + return nil +} + +func (loader *Loader) resolvePathItemRef(doc *T, entrypoint string, pathItem *PathItem, documentPath *url.URL) (err error) { + key := "_" + if documentPath != nil { + key = documentPath.EscapedPath() + } + key += entrypoint + if _, ok := loader.visitedPathItemRefs[key]; ok { + return nil + } + loader.visitedPathItemRefs[key] = struct{}{} + + if pathItem == nil { + return errors.New("invalid path item: value MUST be an object") + } + ref := pathItem.Ref + if ref != "" { + if isSingleRefElement(ref) { + var p PathItem + if documentPath, err = loader.loadSingleElementFromURI(ref, documentPath, &p); err != nil { + return err + } + *pathItem = p + } else { + if doc, ref, documentPath, err = loader.resolveRef(doc, ref, documentPath); err != nil { + return + } + + rest := strings.TrimPrefix(ref, "#/paths/") + if rest == ref { + return fmt.Errorf(`expected prefix "#/paths/" in URI %q`, ref) + } + id := unescapeRefString(rest) + + definitions := doc.Paths + if definitions == nil { + return failedToResolveRefFragmentPart(ref, "paths") + } + resolved := definitions[id] + if resolved == nil { + return failedToResolveRefFragmentPart(ref, id) + } + + *pathItem = *resolved + } + } + return loader.resolvePathItemRefContinued(doc, pathItem, documentPath) +} + +func (loader *Loader) resolvePathItemRefContinued(doc *T, pathItem *PathItem, documentPath *url.URL) (err error) { + for _, parameter := range pathItem.Parameters { + if err = loader.resolveParameterRef(doc, parameter, documentPath); err != nil { + return + } + } + for _, operation := range pathItem.Operations() { + for _, parameter := range operation.Parameters { + if err = loader.resolveParameterRef(doc, parameter, documentPath); err != nil { + return + } + } + if requestBody := operation.RequestBody; requestBody != nil { + if err = loader.resolveRequestBodyRef(doc, requestBody, documentPath); err != nil { + return + } + } + for _, response := range operation.Responses { + if err = loader.resolveResponseRef(doc, response, documentPath); err != nil { + return + } + } + for _, callback := range operation.Callbacks { + if err = loader.resolveCallbackRef(doc, callback, documentPath); err != nil { + return + } + } + } + return +} + +func unescapeRefString(ref string) string { + return strings.Replace(strings.Replace(ref, "~1", "/", -1), "~0", "~", -1) +} 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 6d2f2cb7a..2dd0842f6 100644 --- a/vendor/github.com/getkin/kin-openapi/openapi3/media_type.go +++ b/vendor/github.com/getkin/kin-openapi/openapi3/media_type.go @@ -67,12 +67,12 @@ func (mediaType *MediaType) UnmarshalJSON(data []byte) error { return jsoninfo.UnmarshalStrictStruct(data, mediaType) } -func (mediaType *MediaType) Validate(c context.Context) error { - if mediaType == nil { +func (value *MediaType) Validate(ctx context.Context) error { + if value == nil { return nil } - if schema := mediaType.Schema; schema != nil { - if err := schema.Validate(c); err != nil { + if schema := value.Schema; schema != nil { + if err := schema.Validate(ctx); err != nil { return err } } diff --git a/vendor/github.com/getkin/kin-openapi/openapi3/swagger.go b/vendor/github.com/getkin/kin-openapi/openapi3/openapi3.go similarity index 60% rename from vendor/github.com/getkin/kin-openapi/openapi3/swagger.go rename to vendor/github.com/getkin/kin-openapi/openapi3/openapi3.go index 06be8a343..ee6887727 100644 --- a/vendor/github.com/getkin/kin-openapi/openapi3/swagger.go +++ b/vendor/github.com/getkin/kin-openapi/openapi3/openapi3.go @@ -8,7 +8,8 @@ import ( "github.com/getkin/kin-openapi/jsoninfo" ) -type Swagger struct { +// T is the root of an OpenAPI v3 document +type T struct { ExtensionProps OpenAPI string `json:"openapi" yaml:"openapi"` // Required Components Components `json:"components,omitempty" yaml:"components,omitempty"` @@ -20,19 +21,19 @@ type Swagger struct { ExternalDocs *ExternalDocs `json:"externalDocs,omitempty" yaml:"externalDocs,omitempty"` } -func (swagger *Swagger) MarshalJSON() ([]byte, error) { - return jsoninfo.MarshalStrictStruct(swagger) +func (doc *T) MarshalJSON() ([]byte, error) { + return jsoninfo.MarshalStrictStruct(doc) } -func (swagger *Swagger) UnmarshalJSON(data []byte) error { - return jsoninfo.UnmarshalStrictStruct(data, swagger) +func (doc *T) UnmarshalJSON(data []byte) error { + return jsoninfo.UnmarshalStrictStruct(data, doc) } -func (swagger *Swagger) AddOperation(path string, method string, operation *Operation) { - paths := swagger.Paths +func (doc *T) AddOperation(path string, method string, operation *Operation) { + paths := doc.Paths if paths == nil { paths = make(Paths) - swagger.Paths = paths + doc.Paths = paths } pathItem := paths[path] if pathItem == nil { @@ -42,50 +43,50 @@ func (swagger *Swagger) AddOperation(path string, method string, operation *Oper pathItem.SetOperation(method, operation) } -func (swagger *Swagger) AddServer(server *Server) { - swagger.Servers = append(swagger.Servers, server) +func (doc *T) AddServer(server *Server) { + doc.Servers = append(doc.Servers, server) } -func (swagger *Swagger) Validate(c context.Context) error { - if swagger.OpenAPI == "" { - return errors.New("value of openapi must be a non-empty JSON string") +func (value *T) Validate(ctx context.Context) error { + if value.OpenAPI == "" { + return errors.New("value of openapi must be a non-empty string") } // NOTE: only mention info/components/paths/... key in this func's errors. { wrap := func(e error) error { return fmt.Errorf("invalid components: %v", e) } - if err := swagger.Components.Validate(c); err != nil { + if err := value.Components.Validate(ctx); err != nil { return wrap(err) } } { wrap := func(e error) error { return fmt.Errorf("invalid info: %v", e) } - if v := swagger.Info; v != nil { - if err := v.Validate(c); err != nil { + if v := value.Info; v != nil { + if err := v.Validate(ctx); err != nil { return wrap(err) } } else { - return wrap(errors.New("must be a JSON object")) + return wrap(errors.New("must be an object")) } } { wrap := func(e error) error { return fmt.Errorf("invalid paths: %v", e) } - if v := swagger.Paths; v != nil { - if err := v.Validate(c); err != nil { + if v := value.Paths; v != nil { + if err := v.Validate(ctx); err != nil { return wrap(err) } } else { - return wrap(errors.New("must be a JSON object")) + return wrap(errors.New("must be an object")) } } { wrap := func(e error) error { return fmt.Errorf("invalid security: %v", e) } - if v := swagger.Security; v != nil { - if err := v.Validate(c); err != nil { + if v := value.Security; v != nil { + if err := v.Validate(ctx); err != nil { return wrap(err) } } @@ -93,8 +94,8 @@ func (swagger *Swagger) Validate(c context.Context) error { { wrap := func(e error) error { return fmt.Errorf("invalid servers: %v", e) } - if v := swagger.Servers; v != nil { - if err := v.Validate(c); err != nil { + if v := value.Servers; v != nil { + if err := v.Validate(ctx); err != nil { return wrap(err) } } diff --git a/vendor/github.com/getkin/kin-openapi/openapi3/operation.go b/vendor/github.com/getkin/kin-openapi/openapi3/operation.go index 08b43127b..0de7c421a 100644 --- a/vendor/github.com/getkin/kin-openapi/openapi3/operation.go +++ b/vendor/github.com/getkin/kin-openapi/openapi3/operation.go @@ -120,23 +120,23 @@ func (operation *Operation) AddResponse(status int, response *Response) { } } -func (operation *Operation) Validate(c context.Context) error { - if v := operation.Parameters; v != nil { - if err := v.Validate(c); err != nil { +func (value *Operation) Validate(ctx context.Context) error { + if v := value.Parameters; v != nil { + if err := v.Validate(ctx); err != nil { return err } } - if v := operation.RequestBody; v != nil { - if err := v.Validate(c); err != nil { + if v := value.RequestBody; v != nil { + if err := v.Validate(ctx); err != nil { return err } } - if v := operation.Responses; v != nil { - if err := v.Validate(c); err != nil { + if v := value.Responses; v != nil { + if err := v.Validate(ctx); err != nil { return err } } else { - return errors.New("value of responses must be a JSON object") + return errors.New("value of responses must be an object") } 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 8603bd7bc..2081e4e1d 100644 --- a/vendor/github.com/getkin/kin-openapi/openapi3/parameter.go +++ b/vendor/github.com/getkin/kin-openapi/openapi3/parameter.go @@ -38,7 +38,7 @@ func (p Parameters) JSONLookup(token string) (interface{}, error) { } if index < 0 || index >= len(p) { - return nil, fmt.Errorf("index out of bounds array[0,%d] index '%d'", len(p), index) + return nil, fmt.Errorf("index %d out of bounds of array of length %d", index, len(p)) } ref := p[index] @@ -64,9 +64,9 @@ func (parameters Parameters) GetByInAndName(in string, name string) *Parameter { return nil } -func (parameters Parameters) Validate(c context.Context) error { +func (value Parameters) Validate(ctx context.Context) error { dupes := make(map[string]struct{}) - for _, item := range parameters { + for _, item := range value { if v := item.Value; v != nil { key := v.In + ":" + v.Name if _, ok := dupes[key]; ok { @@ -75,7 +75,7 @@ func (parameters Parameters) Validate(c context.Context) error { dupes[key] = struct{}{} } - if err := item.Validate(c); err != nil { + if err := item.Validate(ctx); err != nil { return err } } @@ -83,6 +83,7 @@ func (parameters Parameters) Validate(c context.Context) error { } // Parameter is specified by OpenAPI/Swagger 3.0 standard. +// See https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.0.md#parameterObject type Parameter struct { ExtensionProps Name string `json:"name,omitempty" yaml:"name,omitempty"` @@ -167,42 +168,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 } @@ -236,11 +237,11 @@ func (parameter *Parameter) SerializationMethod() (*SerializationMethod, error) } } -func (parameter *Parameter) Validate(c context.Context) error { - if parameter.Name == "" { +func (value *Parameter) Validate(ctx context.Context) error { + if value.Name == "" { return errors.New("parameter name can't be blank") } - in := parameter.In + in := value.In switch in { case ParameterInPath, @@ -248,55 +249,56 @@ func (parameter *Parameter) Validate(c context.Context) error { ParameterInHeader, ParameterInCookie: default: - return fmt.Errorf("parameter can't have 'in' value %q", parameter.In) + return fmt.Errorf("parameter can't have 'in' value %q", value.In) } // Validate a parameter's serialization method. - sm, err := parameter.SerializationMethod() + sm, err := value.SerializationMethod() if err != nil { return err } var smSupported bool switch { - case parameter.In == ParameterInPath && sm.Style == SerializationSimple && !sm.Explode, - parameter.In == ParameterInPath && sm.Style == SerializationSimple && sm.Explode, - parameter.In == ParameterInPath && sm.Style == SerializationLabel && !sm.Explode, - parameter.In == ParameterInPath && sm.Style == SerializationLabel && sm.Explode, - parameter.In == ParameterInPath && sm.Style == SerializationMatrix && !sm.Explode, - parameter.In == ParameterInPath && sm.Style == SerializationMatrix && sm.Explode, - - parameter.In == ParameterInQuery && sm.Style == SerializationForm && sm.Explode, - parameter.In == ParameterInQuery && sm.Style == SerializationForm && !sm.Explode, - parameter.In == ParameterInQuery && sm.Style == SerializationSpaceDelimited && sm.Explode, - parameter.In == ParameterInQuery && sm.Style == SerializationSpaceDelimited && !sm.Explode, - parameter.In == ParameterInQuery && sm.Style == SerializationPipeDelimited && sm.Explode, - parameter.In == ParameterInQuery && sm.Style == SerializationPipeDelimited && !sm.Explode, - parameter.In == ParameterInQuery && sm.Style == SerializationDeepObject && sm.Explode, - - parameter.In == ParameterInHeader && sm.Style == SerializationSimple && !sm.Explode, - parameter.In == ParameterInHeader && sm.Style == SerializationSimple && sm.Explode, - - parameter.In == ParameterInCookie && sm.Style == SerializationForm && !sm.Explode, - parameter.In == ParameterInCookie && sm.Style == SerializationForm && sm.Explode: + case value.In == ParameterInPath && sm.Style == SerializationSimple && !sm.Explode, + value.In == ParameterInPath && sm.Style == SerializationSimple && sm.Explode, + value.In == ParameterInPath && sm.Style == SerializationLabel && !sm.Explode, + value.In == ParameterInPath && sm.Style == SerializationLabel && sm.Explode, + value.In == ParameterInPath && sm.Style == SerializationMatrix && !sm.Explode, + value.In == ParameterInPath && sm.Style == SerializationMatrix && sm.Explode, + + value.In == ParameterInQuery && sm.Style == SerializationForm && sm.Explode, + value.In == ParameterInQuery && sm.Style == SerializationForm && !sm.Explode, + value.In == ParameterInQuery && sm.Style == SerializationSpaceDelimited && sm.Explode, + value.In == ParameterInQuery && sm.Style == SerializationSpaceDelimited && !sm.Explode, + value.In == ParameterInQuery && sm.Style == SerializationPipeDelimited && sm.Explode, + value.In == ParameterInQuery && sm.Style == SerializationPipeDelimited && !sm.Explode, + value.In == ParameterInQuery && sm.Style == SerializationDeepObject && sm.Explode, + + value.In == ParameterInHeader && sm.Style == SerializationSimple && !sm.Explode, + value.In == ParameterInHeader && sm.Style == SerializationSimple && sm.Explode, + + value.In == ParameterInCookie && sm.Style == SerializationForm && !sm.Explode, + value.In == ParameterInCookie && sm.Style == SerializationForm && sm.Explode: smSupported = true } if !smSupported { e := fmt.Errorf("serialization method with style=%q and explode=%v is not supported by a %s parameter", sm.Style, sm.Explode, in) - return fmt.Errorf("parameter %q schema is invalid: %v", parameter.Name, e) + return fmt.Errorf("parameter %q schema is invalid: %v", value.Name, e) } - if (parameter.Schema == nil) == (parameter.Content == nil) { + if (value.Schema == nil) == (value.Content == nil) { e := errors.New("parameter must contain exactly one of content and schema") - return fmt.Errorf("parameter %q schema is invalid: %v", parameter.Name, e) + return fmt.Errorf("parameter %q schema is invalid: %v", value.Name, e) } - if schema := parameter.Schema; schema != nil { - if err := schema.Validate(c); err != nil { - return fmt.Errorf("parameter %q schema is invalid: %v", parameter.Name, err) + if schema := value.Schema; schema != nil { + if err := schema.Validate(ctx); err != nil { + return fmt.Errorf("parameter %q schema is invalid: %v", value.Name, err) } } - if content := parameter.Content; content != nil { - if err := content.Validate(c); err != nil { - return fmt.Errorf("parameter %q content is invalid: %v", parameter.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) } } return nil 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 2ed578aa0..a66502046 100644 --- a/vendor/github.com/getkin/kin-openapi/openapi3/path_item.go +++ b/vendor/github.com/getkin/kin-openapi/openapi3/path_item.go @@ -87,7 +87,7 @@ func (pathItem *PathItem) GetOperation(method string) *Operation { case http.MethodTrace: return pathItem.Trace default: - panic(fmt.Errorf("Unsupported HTTP method '%s'", method)) + panic(fmt.Errorf("unsupported HTTP method %q", method)) } } @@ -112,13 +112,13 @@ func (pathItem *PathItem) SetOperation(method string, operation *Operation) { case http.MethodTrace: pathItem.Trace = operation default: - panic(fmt.Errorf("Unsupported HTTP method '%s'", method)) + panic(fmt.Errorf("unsupported HTTP method %q", method)) } } -func (pathItem *PathItem) Validate(c context.Context) error { - for _, operation := range pathItem.Operations() { - if err := operation.Validate(c); err != nil { +func (value *PathItem) Validate(ctx context.Context) error { + for _, operation := range value.Operations() { + if err := operation.Validate(ctx); err != nil { return err } } diff --git a/vendor/github.com/getkin/kin-openapi/openapi3/paths.go b/vendor/github.com/getkin/kin-openapi/openapi3/paths.go index baafaaabc..bdb87ae7d 100644 --- a/vendor/github.com/getkin/kin-openapi/openapi3/paths.go +++ b/vendor/github.com/getkin/kin-openapi/openapi3/paths.go @@ -9,47 +9,76 @@ import ( // Paths is specified by OpenAPI/Swagger standard version 3.0. type Paths map[string]*PathItem -func (paths Paths) Validate(c context.Context) error { +func (value Paths) Validate(ctx context.Context) error { normalizedPaths := make(map[string]string) - for path, pathItem := range paths { + for path, pathItem := range value { if path == "" || path[0] != '/' { return fmt.Errorf("path %q does not start with a forward slash (/)", path) } if pathItem == nil { - paths[path] = &PathItem{} - pathItem = paths[path] + value[path] = &PathItem{} + 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) + } } } - if err := pathItem.Validate(c); err != nil { + if err := pathItem.Validate(ctx); err != nil { return err } } @@ -75,9 +104,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 +114,51 @@ func (paths Paths) Find(key string) *PathItem { return nil } -func normalizeTemplatedPath(path string) (string, uint) { +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 a086e367e..4b64035f8 100644 --- a/vendor/github.com/getkin/kin-openapi/openapi3/refs.go +++ b/vendor/github.com/getkin/kin-openapi/openapi3/refs.go @@ -27,12 +27,11 @@ func (value *CallbackRef) UnmarshalJSON(data []byte) error { return jsoninfo.UnmarshalRef(data, &value.Ref, &value.Value) } -func (value *CallbackRef) Validate(c context.Context) error { - v := value.Value - if v == nil { - return foundUnresolvedRef(value.Ref) +func (value *CallbackRef) Validate(ctx context.Context) error { + if v := value.Value; v != nil { + return v.Validate(ctx) } - return v.Validate(c) + return foundUnresolvedRef(value.Ref) } func (value CallbackRef) JSONLookup(token string) (interface{}, error) { @@ -59,8 +58,11 @@ func (value *ExampleRef) UnmarshalJSON(data []byte) error { return jsoninfo.UnmarshalRef(data, &value.Ref, &value.Value) } -func (value *ExampleRef) Validate(c context.Context) error { - return nil +func (value *ExampleRef) Validate(ctx context.Context) error { + if v := value.Value; v != nil { + return v.Validate(ctx) + } + return foundUnresolvedRef(value.Ref) } func (value ExampleRef) JSONLookup(token string) (interface{}, error) { @@ -87,13 +89,13 @@ func (value *HeaderRef) UnmarshalJSON(data []byte) error { return jsoninfo.UnmarshalRef(data, &value.Ref, &value.Value) } -func (value *HeaderRef) Validate(c context.Context) error { - v := value.Value - if v == nil { - return foundUnresolvedRef(value.Ref) +func (value *HeaderRef) Validate(ctx context.Context) error { + if v := value.Value; v != nil { + return v.Validate(ctx) } - return v.Validate(c) + return foundUnresolvedRef(value.Ref) } + func (value HeaderRef) JSONLookup(token string) (interface{}, error) { if token == "$ref" { return value.Ref, nil @@ -116,12 +118,11 @@ func (value *LinkRef) UnmarshalJSON(data []byte) error { return jsoninfo.UnmarshalRef(data, &value.Ref, &value.Value) } -func (value *LinkRef) Validate(c context.Context) error { - v := value.Value - if v == nil { - return foundUnresolvedRef(value.Ref) +func (value *LinkRef) Validate(ctx context.Context) error { + if v := value.Value; v != nil { + return v.Validate(ctx) } - return v.Validate(c) + return foundUnresolvedRef(value.Ref) } type ParameterRef struct { @@ -139,12 +140,11 @@ func (value *ParameterRef) UnmarshalJSON(data []byte) error { return jsoninfo.UnmarshalRef(data, &value.Ref, &value.Value) } -func (value *ParameterRef) Validate(c context.Context) error { - v := value.Value - if v == nil { - return foundUnresolvedRef(value.Ref) +func (value *ParameterRef) Validate(ctx context.Context) error { + if v := value.Value; v != nil { + return v.Validate(ctx) } - return v.Validate(c) + return foundUnresolvedRef(value.Ref) } func (value ParameterRef) JSONLookup(token string) (interface{}, error) { @@ -171,12 +171,11 @@ func (value *ResponseRef) UnmarshalJSON(data []byte) error { return jsoninfo.UnmarshalRef(data, &value.Ref, &value.Value) } -func (value *ResponseRef) Validate(c context.Context) error { - v := value.Value - if v == nil { - return foundUnresolvedRef(value.Ref) +func (value *ResponseRef) Validate(ctx context.Context) error { + if v := value.Value; v != nil { + return v.Validate(ctx) } - return v.Validate(c) + return foundUnresolvedRef(value.Ref) } func (value ResponseRef) JSONLookup(token string) (interface{}, error) { @@ -203,12 +202,11 @@ func (value *RequestBodyRef) UnmarshalJSON(data []byte) error { return jsoninfo.UnmarshalRef(data, &value.Ref, &value.Value) } -func (value *RequestBodyRef) Validate(c context.Context) error { - v := value.Value - if v == nil { - return foundUnresolvedRef(value.Ref) +func (value *RequestBodyRef) Validate(ctx context.Context) error { + if v := value.Value; v != nil { + return v.Validate(ctx) } - return v.Validate(c) + return foundUnresolvedRef(value.Ref) } func (value RequestBodyRef) JSONLookup(token string) (interface{}, error) { @@ -242,12 +240,11 @@ func (value *SchemaRef) UnmarshalJSON(data []byte) error { return jsoninfo.UnmarshalRef(data, &value.Ref, &value.Value) } -func (value *SchemaRef) Validate(c context.Context) error { - v := value.Value - if v == nil { - return foundUnresolvedRef(value.Ref) +func (value *SchemaRef) Validate(ctx context.Context) error { + if v := value.Value; v != nil { + return v.Validate(ctx) } - return v.Validate(c) + return foundUnresolvedRef(value.Ref) } func (value SchemaRef) JSONLookup(token string) (interface{}, error) { @@ -274,12 +271,11 @@ func (value *SecuritySchemeRef) UnmarshalJSON(data []byte) error { return jsoninfo.UnmarshalRef(data, &value.Ref, &value.Value) } -func (value *SecuritySchemeRef) Validate(c context.Context) error { - v := value.Value - if v == nil { - return foundUnresolvedRef(value.Ref) +func (value *SecuritySchemeRef) Validate(ctx context.Context) error { + if v := value.Value; v != nil { + return v.Validate(ctx) } - return v.Validate(c) + return foundUnresolvedRef(value.Ref) } func (value SecuritySchemeRef) JSONLookup(token string) (interface{}, error) { 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 ad871e8fd..66b512fa0 100644 --- a/vendor/github.com/getkin/kin-openapi/openapi3/request_body.go +++ b/vendor/github.com/getkin/kin-openapi/openapi3/request_body.go @@ -97,9 +97,9 @@ func (requestBody *RequestBody) UnmarshalJSON(data []byte) error { return jsoninfo.UnmarshalStrictStruct(data, requestBody) } -func (requestBody *RequestBody) Validate(c context.Context) error { - if v := requestBody.Content; v != nil { - if err := v.Validate(c); err != nil { +func (value *RequestBody) Validate(ctx context.Context) error { + if v := value.Content; v != nil { + if err := v.Validate(ctx); err != nil { return err } } diff --git a/vendor/github.com/getkin/kin-openapi/openapi3/response.go b/vendor/github.com/getkin/kin-openapi/openapi3/response.go index 7c4da1dc2..2ab33aca2 100644 --- a/vendor/github.com/getkin/kin-openapi/openapi3/response.go +++ b/vendor/github.com/getkin/kin-openapi/openapi3/response.go @@ -29,12 +29,12 @@ func (responses Responses) Get(status int) *ResponseRef { return responses[strconv.FormatInt(int64(status), 10)] } -func (responses Responses) Validate(c context.Context) error { - if len(responses) == 0 { +func (value Responses) Validate(ctx context.Context) error { + if len(value) == 0 { return errors.New("the responses object MUST contain at least one response code") } - for _, v := range responses { - if err := v.Validate(c); err != nil { + for _, v := range value { + if err := v.Validate(ctx); err != nil { return err } } @@ -94,13 +94,13 @@ func (response *Response) UnmarshalJSON(data []byte) error { return jsoninfo.UnmarshalStrictStruct(data, response) } -func (response *Response) Validate(c context.Context) error { - if response.Description == nil { +func (value *Response) Validate(ctx context.Context) error { + if value.Description == nil { return errors.New("a short description of the response is required") } - if content := response.Content; content != nil { - if err := content.Validate(c); err != nil { + if content := value.Content; content != nil { + if err := content.Validate(ctx); err != nil { return err } } diff --git a/vendor/github.com/getkin/kin-openapi/openapi3/schema.go b/vendor/github.com/getkin/kin-openapi/openapi3/schema.go index bed51c159..a0796989a 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 @@ -23,15 +32,15 @@ var ( //SchemaFormatValidationDisabled disables validation of schema type formats. SchemaFormatValidationDisabled = false - errSchema = errors.New("Input does not match the schema") + errSchema = errors.New("input does not match the schema") // ErrOneOfConflict is the SchemaError Origin when data matches more than one oneOf schema ErrOneOfConflict = errors.New("input matches more than one oneOf schemas") // ErrSchemaInputNaN may be returned when validating a number - ErrSchemaInputNaN = errors.New("NaN is not allowed") + ErrSchemaInputNaN = errors.New("floating point NaN is not allowed") // ErrSchemaInputInf may be returned when validating a number - ErrSchemaInputInf = errors.New("Inf is not allowed") + ErrSchemaInputInf = errors.New("floating point Inf is not allowed") ) // Float64Ptr is a helper for defining OpenAPI schemas. @@ -109,8 +118,6 @@ 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 @@ -141,12 +148,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,72 +306,72 @@ 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", + Type: TypeObject, Properties: make(map[string]*SchemaRef), } } @@ -448,6 +456,7 @@ func (schema *Schema) WithMaxLengthDecodedBase64(i int64) *Schema { func (schema *Schema) WithPattern(pattern string) *Schema { schema.Pattern = pattern + schema.compiledPattern = nil return schema } @@ -578,11 +587,11 @@ func (schema *Schema) IsEmpty() bool { return true } -func (schema *Schema) Validate(c context.Context) error { - return schema.validate(c, []*Schema{}) +func (value *Schema) Validate(ctx context.Context) error { + return value.validate(ctx, []*Schema{}) } -func (schema *Schema) validate(c context.Context, stack []*Schema) (err error) { +func (schema *Schema) validate(ctx context.Context, stack []*Schema) (err error) { for _, existing := range stack { if existing == schema { return @@ -591,7 +600,7 @@ func (schema *Schema) validate(c context.Context, stack []*Schema) (err error) { stack = append(stack, schema) if schema.ReadOnly && schema.WriteOnly { - return errors.New("A property MUST NOT be marked as both readOnly and writeOnly being true") + return errors.New("a property MUST NOT be marked as both readOnly and writeOnly being true") } for _, item := range schema.OneOf { @@ -599,7 +608,7 @@ func (schema *Schema) validate(c context.Context, stack []*Schema) (err error) { if v == nil { return foundUnresolvedRef(item.Ref) } - if err = v.validate(c, stack); err == nil { + if err = v.validate(ctx, stack); err == nil { return } } @@ -609,7 +618,7 @@ func (schema *Schema) validate(c context.Context, stack []*Schema) (err error) { if v == nil { return foundUnresolvedRef(item.Ref) } - if err = v.validate(c, stack); err != nil { + if err = v.validate(ctx, stack); err != nil { return } } @@ -619,7 +628,7 @@ func (schema *Schema) validate(c context.Context, stack []*Schema) (err error) { if v == nil { return foundUnresolvedRef(item.Ref) } - if err = v.validate(c, stack); err != nil { + if err = v.validate(ctx, stack); err != nil { return } } @@ -629,7 +638,7 @@ func (schema *Schema) validate(c context.Context, stack []*Schema) (err error) { if v == nil { return foundUnresolvedRef(ref.Ref) } - if err = v.validate(c, stack); err != nil { + if err = v.validate(ctx, stack); err != nil { return } } @@ -637,8 +646,8 @@ func (schema *Schema) validate(c 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": @@ -648,7 +657,7 @@ func (schema *Schema) validate(c context.Context, stack []*Schema) (err error) { } } } - case "integer": + case TypeInteger: if format := schema.Format; len(format) > 0 { switch format { case "int32", "int64": @@ -658,7 +667,7 @@ func (schema *Schema) validate(c context.Context, stack []*Schema) (err error) { } } } - case "string": + case TypeString: if format := schema.Format; len(format) > 0 { switch format { // Supported by OpenAPIv3.0.1: @@ -676,13 +685,18 @@ func (schema *Schema) validate(c 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") + 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 '%s'", schemaType) + return fmt.Errorf("unsupported 'type' value %q", schemaType) } if ref := schema.Items; ref != nil { @@ -690,7 +704,7 @@ func (schema *Schema) validate(c context.Context, stack []*Schema) (err error) { if v == nil { return foundUnresolvedRef(ref.Ref) } - if err = v.validate(c, stack); err != nil { + if err = v.validate(ctx, stack); err != nil { return } } @@ -700,7 +714,7 @@ func (schema *Schema) validate(c context.Context, stack []*Schema) (err error) { if v == nil { return foundUnresolvedRef(ref.Ref) } - if err = v.validate(c, stack); err != nil { + if err = v.validate(ctx, stack); err != nil { return } } @@ -710,7 +724,7 @@ func (schema *Schema) validate(c context.Context, stack []*Schema) (err error) { if v == nil { return foundUnresolvedRef(ref.Ref) } - if err = v.validate(c, stack); err != nil { + if err = v.validate(ctx, stack); err != nil { return } } @@ -786,14 +800,23 @@ 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("Not a JSON value: %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), + } } func (schema *Schema) visitSetOperations(settings *schemaValidationSettings, value interface{}) (err error) { @@ -810,7 +833,7 @@ func (schema *Schema) visitSetOperations(settings *schemaValidationSettings, val Value: value, Schema: schema, SchemaField: "enum", - Reason: "JSON value is not one of the allowed values", + Reason: "value is not one of the allowed values", } } @@ -819,11 +842,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 } @@ -836,33 +855,52 @@ 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") + } + + if discriminatorRef, okcheck = schema.Discriminator.Mapping[discriminatorVal.(string)]; 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 } @@ -873,7 +911,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 } } @@ -885,11 +926,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 } @@ -911,11 +948,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 } @@ -951,8 +984,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 } @@ -981,7 +1014,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") } @@ -994,7 +1027,7 @@ func (schema *Schema) visitJSONNumber(settings *schemaValidationSettings, value Value: value, Schema: schema, SchemaField: "exclusiveMinimum", - Reason: fmt.Sprintf("Number must be more than %g", *schema.Min), + Reason: fmt.Sprintf("number must be more than %g", *schema.Min), } if !settings.multiError { return err @@ -1011,7 +1044,7 @@ func (schema *Schema) visitJSONNumber(settings *schemaValidationSettings, value Value: value, Schema: schema, SchemaField: "exclusiveMaximum", - Reason: fmt.Sprintf("Number must be less than %g", *schema.Max), + Reason: fmt.Sprintf("number must be less than %g", *schema.Max), } if !settings.multiError { return err @@ -1028,7 +1061,7 @@ func (schema *Schema) visitJSONNumber(settings *schemaValidationSettings, value Value: value, Schema: schema, SchemaField: "minimum", - Reason: fmt.Sprintf("Number must be at least %g", *v), + Reason: fmt.Sprintf("number must be at least %g", *v), } if !settings.multiError { return err @@ -1045,7 +1078,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 most %g", *v), } if !settings.multiError { return err @@ -1086,8 +1119,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 @@ -1113,7 +1146,7 @@ func (schema *Schema) visitJSONString(settings *schemaValidationSettings, value Value: value, Schema: schema, SchemaField: "minLength", - Reason: fmt.Sprintf("Minimum string length is %d", minLength), + Reason: fmt.Sprintf("minimum string length is %d", minLength), } if !settings.multiError { return err @@ -1128,7 +1161,7 @@ func (schema *Schema) visitJSONString(settings *schemaValidationSettings, value Value: value, Schema: schema, SchemaField: "maxLength", - Reason: fmt.Sprintf("Maximum string length is %d", *maxLength), + Reason: fmt.Sprintf("maximum string length is %d", *maxLength), } if !settings.multiError { return err @@ -1138,15 +1171,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 } @@ -1158,7 +1185,7 @@ func (schema *Schema) visitJSONString(settings *schemaValidationSettings, value Value: value, Schema: schema, SchemaField: "pattern", - Reason: fmt.Sprintf("JSON 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 @@ -1173,7 +1200,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("JSON 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 { @@ -1211,8 +1238,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 @@ -1228,7 +1255,7 @@ func (schema *Schema) visitJSONArray(settings *schemaValidationSettings, value [ Value: value, Schema: schema, SchemaField: "minItems", - Reason: fmt.Sprintf("Minimum number of items is %d", v), + Reason: fmt.Sprintf("minimum number of items is %d", v), } if !settings.multiError { return err @@ -1245,7 +1272,7 @@ func (schema *Schema) visitJSONArray(settings *schemaValidationSettings, value [ Value: value, Schema: schema, SchemaField: "maxItems", - Reason: fmt.Sprintf("Maximum number of items is %d", *v), + Reason: fmt.Sprintf("maximum number of items is %d", *v), } if !settings.multiError { return err @@ -1265,7 +1292,7 @@ func (schema *Schema) visitJSONArray(settings *schemaValidationSettings, value [ Value: value, Schema: schema, SchemaField: "uniqueItems", - Reason: fmt.Sprintf("Duplicate items found"), + Reason: "duplicate items found", } if !settings.multiError { return err @@ -1307,8 +1334,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 @@ -1326,7 +1353,7 @@ func (schema *Schema) visitJSONObject(settings *schemaValidationSettings, value Value: value, Schema: schema, SchemaField: "minProperties", - Reason: fmt.Sprintf("There must be at least %d properties", v), + Reason: fmt.Sprintf("there must be at least %d properties", v), } if !settings.multiError { return err @@ -1343,7 +1370,7 @@ func (schema *Schema) visitJSONObject(settings *schemaValidationSettings, value Value: value, Schema: schema, SchemaField: "maxProperties", - Reason: fmt.Sprintf("There must be at most %d properties", *v), + Reason: fmt.Sprintf("there must be at most %d properties", *v), } if !settings.multiError { return err @@ -1408,7 +1435,7 @@ func (schema *Schema) visitJSONObject(settings *schemaValidationSettings, value Value: value, Schema: schema, SchemaField: "properties", - Reason: fmt.Sprintf("Property '%s' is unsupported", k), + Reason: fmt.Sprintf("property %q is unsupported", k), } if !settings.multiError { return err @@ -1432,7 +1459,7 @@ func (schema *Schema) visitJSONObject(settings *schemaValidationSettings, value Value: value, Schema: schema, SchemaField: "required", - Reason: fmt.Sprintf("Property '%s' is missing", k), + Reason: fmt.Sprintf("property %q is missing", k), }, k) if !settings.multiError { return err @@ -1460,6 +1487,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 @@ -1469,6 +1507,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) @@ -1544,6 +1584,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) @@ -1572,5 +1616,5 @@ func RegisterArrayUniqueItemsChecker(fn SliceUniqueItemsChecker) { } func unsupportedFormat(format string) error { - return fmt.Errorf("Unsupported 'format' value '%s'", format) + return fmt.Errorf("unsupported 'format' value %q", format) } 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 ab2992bc9..095bb2228 100644 --- a/vendor/github.com/getkin/kin-openapi/openapi3/schema_formats.go +++ b/vendor/github.com/getkin/kin-openapi/openapi3/schema_formats.go @@ -4,6 +4,7 @@ import ( "fmt" "net" "regexp" + "strings" ) const ( @@ -26,7 +27,7 @@ var SchemaStringFormats = make(map[string]Format, 8) func DefineStringFormat(name string, pattern string) { re, err := regexp.Compile(pattern) if err != nil { - err := fmt.Errorf("Format '%v' has invalid pattern '%v': %v", name, pattern, err) + err := fmt.Errorf("format %q has invalid pattern %q: %v", name, pattern, err) panic(err) } SchemaStringFormats[name] = Format{regexp: re} @@ -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)", 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 1d2c745f7..ce6fcc6f1 100644 --- a/vendor/github.com/getkin/kin-openapi/openapi3/security_requirements.go +++ b/vendor/github.com/getkin/kin-openapi/openapi3/security_requirements.go @@ -15,9 +15,9 @@ func (srs *SecurityRequirements) With(securityRequirement SecurityRequirement) * return srs } -func (srs SecurityRequirements) Validate(c context.Context) error { - for _, item := range srs { - if err := item.Validate(c); err != nil { +func (value SecurityRequirements) Validate(ctx context.Context) error { + for _, item := range value { + if err := item.Validate(ctx); err != nil { return err } } @@ -38,6 +38,6 @@ func (security SecurityRequirement) Authenticate(provider string, scopes ...stri return security } -func (security SecurityRequirement) Validate(c context.Context) error { +func (value SecurityRequirement) Validate(ctx context.Context) error { return nil } 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 8d6c7c1fb..990f258d4 100644 --- a/vendor/github.com/getkin/kin-openapi/openapi3/security_scheme.go +++ b/vendor/github.com/getkin/kin-openapi/openapi3/security_scheme.go @@ -103,65 +103,65 @@ func (ss *SecurityScheme) WithBearerFormat(value string) *SecurityScheme { return ss } -func (ss *SecurityScheme) Validate(c context.Context) error { +func (value *SecurityScheme) Validate(ctx context.Context) error { hasIn := false hasBearerFormat := false hasFlow := false - switch ss.Type { + switch value.Type { case "apiKey": hasIn = true case "http": - scheme := ss.Scheme + scheme := value.Scheme switch scheme { case "bearer": hasBearerFormat = true case "basic", "negotiate", "digest": default: - return fmt.Errorf("Security scheme of type 'http' has invalid 'scheme' value '%s'", scheme) + return fmt.Errorf("security scheme of type 'http' has invalid 'scheme' value %q", scheme) } case "oauth2": hasFlow = true case "openIdConnect": - if ss.OpenIdConnectUrl == "" { - return fmt.Errorf("No OIDC URL found for openIdConnect security scheme %q", ss.Name) + if value.OpenIdConnectUrl == "" { + return fmt.Errorf("no OIDC URL found for openIdConnect security scheme %q", value.Name) } default: - return fmt.Errorf("Security scheme 'type' can't be '%v'", ss.Type) + return fmt.Errorf("security scheme 'type' can't be %q", value.Type) } // Validate "in" and "name" if hasIn { - switch ss.In { + switch value.In { case "query", "header", "cookie": default: - return fmt.Errorf("Security scheme of type 'apiKey' should have 'in'. It can be 'query', 'header' or 'cookie', not '%s'", ss.In) + return fmt.Errorf("security scheme of type 'apiKey' should have 'in'. It can be 'query', 'header' or 'cookie', not %q", value.In) } - if ss.Name == "" { - return errors.New("Security scheme of type 'apiKey' should have 'name'") + if value.Name == "" { + return errors.New("security scheme of type 'apiKey' should have 'name'") } - } else if len(ss.In) > 0 { - return fmt.Errorf("Security scheme of type '%s' can't have 'in'", ss.Type) - } else if len(ss.Name) > 0 { - return errors.New("Security scheme of type 'apiKey' can't have 'name'") + } else if len(value.In) > 0 { + return fmt.Errorf("security scheme of type %q can't have 'in'", value.Type) + } else if len(value.Name) > 0 { + return errors.New("security scheme of type 'apiKey' can't have 'name'") } // Validate "format" // "bearerFormat" is an arbitrary string so we only check if the scheme supports it - if !hasBearerFormat && len(ss.BearerFormat) > 0 { - return fmt.Errorf("Security scheme of type '%v' can't have 'bearerFormat'", ss.Type) + if !hasBearerFormat && len(value.BearerFormat) > 0 { + return fmt.Errorf("security scheme of type %q can't have 'bearerFormat'", value.Type) } // Validate "flow" if hasFlow { - flow := ss.Flows + flow := value.Flows if flow == nil { - return fmt.Errorf("Security scheme of type '%v' should have 'flows'", ss.Type) + return fmt.Errorf("security scheme of type %q should have 'flows'", value.Type) } - if err := flow.Validate(c); err != nil { - return fmt.Errorf("Security scheme 'flow' is invalid: %v", err) + if err := flow.Validate(ctx); err != nil { + return fmt.Errorf("security scheme 'flow' is invalid: %v", err) } - } else if ss.Flows != nil { - return fmt.Errorf("Security scheme of type '%s' can't have 'flows'", ss.Type) + } else if value.Flows != nil { + return fmt.Errorf("security scheme of type %q can't have 'flows'", value.Type) } return nil } @@ -191,20 +191,20 @@ func (flows *OAuthFlows) UnmarshalJSON(data []byte) error { return jsoninfo.UnmarshalStrictStruct(data, flows) } -func (flows *OAuthFlows) Validate(c context.Context) error { +func (flows *OAuthFlows) Validate(ctx context.Context) error { if v := flows.Implicit; v != nil { - return v.Validate(c, oAuthFlowTypeImplicit) + return v.Validate(ctx, oAuthFlowTypeImplicit) } if v := flows.Password; v != nil { - return v.Validate(c, oAuthFlowTypePassword) + return v.Validate(ctx, oAuthFlowTypePassword) } if v := flows.ClientCredentials; v != nil { - return v.Validate(c, oAuthFlowTypeClientCredentials) + return v.Validate(ctx, oAuthFlowTypeClientCredentials) } if v := flows.AuthorizationCode; v != nil { - return v.Validate(c, oAuthFlowAuthorizationCode) + return v.Validate(ctx, oAuthFlowAuthorizationCode) } - return errors.New("No OAuth flow is defined") + return errors.New("no OAuth flow is defined") } type OAuthFlow struct { @@ -223,19 +223,19 @@ func (flow *OAuthFlow) UnmarshalJSON(data []byte) error { return jsoninfo.UnmarshalStrictStruct(data, flow) } -func (flow *OAuthFlow) Validate(c context.Context, typ oAuthFlowType) error { +func (flow *OAuthFlow) Validate(ctx context.Context, typ oAuthFlowType) error { if typ == oAuthFlowAuthorizationCode || typ == oAuthFlowTypeImplicit { if v := flow.AuthorizationURL; v == "" { - return errors.New("An OAuth flow is missing 'authorizationUrl in authorizationCode or implicit '") + return errors.New("an OAuth flow is missing 'authorizationUrl in authorizationCode or implicit '") } } if typ != oAuthFlowTypeImplicit { if v := flow.TokenURL; v == "" { - return errors.New("An OAuth flow is missing 'tokenUrl in not implicit'") + return errors.New("an OAuth flow is missing 'tokenUrl in not implicit'") } } if v := flow.Scopes; v == nil { - return errors.New("An OAuth flow is missing 'scopes'") + return errors.New("an OAuth flow is missing 'scopes'") } return nil } diff --git a/vendor/github.com/getkin/kin-openapi/openapi3/server.go b/vendor/github.com/getkin/kin-openapi/openapi3/server.go index eeeae0668..4415bd08f 100644 --- a/vendor/github.com/getkin/kin-openapi/openapi3/server.go +++ b/vendor/github.com/getkin/kin-openapi/openapi3/server.go @@ -3,6 +3,7 @@ package openapi3 import ( "context" "errors" + "fmt" "math" "net/url" "strings" @@ -14,9 +15,9 @@ import ( type Servers []*Server // Validate ensures servers are per the OpenAPIv3 specification. -func (servers Servers) Validate(c context.Context) error { - for _, v := range servers { - if err := v.Validate(c); err != nil { +func (value Servers) Validate(ctx context.Context) error { + for _, v := range value { + if err := v.Validate(ctx); err != nil { return err } } @@ -64,7 +65,7 @@ func (server Server) ParameterNames() ([]string, error) { pattern = pattern[i+1:] i = strings.IndexByte(pattern, '}') if i < 0 { - return nil, errors.New("Missing '}'") + return nil, errors.New("missing '}'") } params = append(params, strings.TrimSpace(pattern[:i])) pattern = pattern[i+1:] @@ -124,12 +125,22 @@ func (server Server) MatchRawURL(input string) ([]string, string, bool) { return params, input, true } -func (server *Server) Validate(c context.Context) (err error) { - if server.URL == "" { - return errors.New("value of url must be a non-empty JSON string") +func (value *Server) Validate(ctx context.Context) (err error) { + if value.URL == "" { + return errors.New("value of url must be a non-empty string") } - for _, v := range server.Variables { - if err = v.Validate(c); err != nil { + opening, closing := strings.Count(value.URL, "{"), strings.Count(value.URL, "}") + if opening != closing { + return errors.New("server URL has mismatched { and }") + } + if opening != len(value.Variables) { + return errors.New("server has undeclared variables") + } + for name, v := range value.Variables { + if !strings.Contains(value.URL, fmt.Sprintf("{%s}", name)) { + return errors.New("server has undeclared variables") + } + if err = v.Validate(ctx); err != nil { return } } @@ -139,9 +150,9 @@ func (server *Server) Validate(c context.Context) (err error) { // ServerVariable is specified by OpenAPI/Swagger standard version 3.0. type ServerVariable struct { ExtensionProps - Enum []interface{} `json:"enum,omitempty" yaml:"enum,omitempty"` - Default interface{} `json:"default,omitempty" yaml:"default,omitempty"` - Description string `json:"description,omitempty" yaml:"description,omitempty"` + Enum []string `json:"enum,omitempty" yaml:"enum,omitempty"` + Default string `json:"default,omitempty" yaml:"default,omitempty"` + Description string `json:"description,omitempty" yaml:"description,omitempty"` } func (serverVariable *ServerVariable) MarshalJSON() ([]byte, error) { @@ -152,18 +163,13 @@ func (serverVariable *ServerVariable) UnmarshalJSON(data []byte) error { return jsoninfo.UnmarshalStrictStruct(data, serverVariable) } -func (serverVariable *ServerVariable) Validate(c context.Context) error { - switch serverVariable.Default.(type) { - case float64, string: - default: - return errors.New("value of default must be either JSON number or JSON string") - } - for _, item := range serverVariable.Enum { - switch item.(type) { - case float64, string: - default: - return errors.New("All 'enum' items must be either a number or a string") +func (value *ServerVariable) Validate(ctx context.Context) error { + if value.Default == "" { + data, err := value.MarshalJSON() + if err != nil { + return err } + return fmt.Errorf("field default is required in %s", data) } return nil } diff --git a/vendor/github.com/getkin/kin-openapi/openapi3/swagger_loader.go b/vendor/github.com/getkin/kin-openapi/openapi3/swagger_loader.go deleted file mode 100644 index 3bb902dff..000000000 --- a/vendor/github.com/getkin/kin-openapi/openapi3/swagger_loader.go +++ /dev/null @@ -1,955 +0,0 @@ -package openapi3 - -import ( - "context" - "encoding/json" - "errors" - "fmt" - "io/ioutil" - "net/http" - "net/url" - "path" - "reflect" - "strconv" - "strings" - - "github.com/ghodss/yaml" -) - -func foundUnresolvedRef(ref string) error { - return fmt.Errorf("found unresolved ref: %q", ref) -} - -func failedToResolveRefFragmentPart(value, what string) error { - return fmt.Errorf("failed to resolve %q in fragment in URI: %q", what, value) -} - -// SwaggerLoader helps deserialize a Swagger object -type SwaggerLoader struct { - // IsExternalRefsAllowed enables visiting other files - IsExternalRefsAllowed bool - - // ReadFromURIFunc allows overriding the any file/URL reading func - ReadFromURIFunc func(loader *SwaggerLoader, url *url.URL) ([]byte, error) - - Context context.Context - - visitedPathItemRefs map[string]struct{} - - visitedDocuments map[string]*Swagger - - visitedExample map[*Example]struct{} - visitedHeader map[*Header]struct{} - visitedLink map[*Link]struct{} - visitedParameter map[*Parameter]struct{} - visitedRequestBody map[*RequestBody]struct{} - visitedResponse map[*Response]struct{} - visitedSchema map[*Schema]struct{} - visitedSecurityScheme map[*SecurityScheme]struct{} -} - -// NewSwaggerLoader returns an empty SwaggerLoader -func NewSwaggerLoader() *SwaggerLoader { - return &SwaggerLoader{} -} - -func (swaggerLoader *SwaggerLoader) resetVisitedPathItemRefs() { - swaggerLoader.visitedPathItemRefs = make(map[string]struct{}) -} - -// LoadSwaggerFromURI loads a spec from a remote URL -func (swaggerLoader *SwaggerLoader) LoadSwaggerFromURI(location *url.URL) (*Swagger, error) { - swaggerLoader.resetVisitedPathItemRefs() - return swaggerLoader.loadSwaggerFromURIInternal(location) -} - -func (swaggerLoader *SwaggerLoader) loadSwaggerFromURIInternal(location *url.URL) (*Swagger, error) { - data, err := swaggerLoader.readURL(location) - if err != nil { - return nil, err - } - return swaggerLoader.loadSwaggerFromDataWithPathInternal(data, location) -} - -// loadSingleElementFromURI read the data from ref and unmarshal to JSON to the -// passed element. -func (swaggerLoader *SwaggerLoader) loadSingleElementFromURI(ref string, rootPath *url.URL, element json.Unmarshaler) error { - if !swaggerLoader.IsExternalRefsAllowed { - return fmt.Errorf("encountered non-allowed external reference: %q", ref) - } - - parsedURL, err := url.Parse(ref) - if err != nil { - return err - } - - if parsedURL.Fragment != "" { - return errors.New("references to files which contain more than one element definition are not supported") - } - - resolvedPath, err := resolvePath(rootPath, parsedURL) - if err != nil { - return fmt.Errorf("could not resolve path: %v", err) - } - - data, err := swaggerLoader.readURL(resolvedPath) - if err != nil { - return err - } - if err := yaml.Unmarshal(data, element); err != nil { - return err - } - - return nil -} - -func (swaggerLoader *SwaggerLoader) readURL(location *url.URL) ([]byte, error) { - if f := swaggerLoader.ReadFromURIFunc; f != nil { - return f(swaggerLoader, location) - } - - if location.Scheme != "" && location.Host != "" { - resp, err := http.Get(location.String()) - if err != nil { - return nil, err - } - defer resp.Body.Close() - 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) -} - -// LoadSwaggerFromFile loads a spec from a local file path -func (swaggerLoader *SwaggerLoader) LoadSwaggerFromFile(path string) (*Swagger, error) { - swaggerLoader.resetVisitedPathItemRefs() - return swaggerLoader.loadSwaggerFromFileInternal(path) -} - -func (swaggerLoader *SwaggerLoader) loadSwaggerFromFileInternal(path string) (*Swagger, error) { - pathAsURL := &url.URL{Path: path} - data, err := swaggerLoader.readURL(pathAsURL) - if err != nil { - return nil, err - } - return swaggerLoader.loadSwaggerFromDataWithPathInternal(data, pathAsURL) -} - -// LoadSwaggerFromData loads a spec from a byte array -func (swaggerLoader *SwaggerLoader) LoadSwaggerFromData(data []byte) (*Swagger, error) { - swaggerLoader.resetVisitedPathItemRefs() - return swaggerLoader.loadSwaggerFromDataInternal(data) -} - -func (swaggerLoader *SwaggerLoader) loadSwaggerFromDataInternal(data []byte) (*Swagger, error) { - doc := &Swagger{} - if err := yaml.Unmarshal(data, doc); err != nil { - return nil, err - } - if err := swaggerLoader.ResolveRefsIn(doc, nil); err != nil { - return nil, err - } - return doc, nil -} - -// LoadSwaggerFromDataWithPath takes the OpenApi spec data in bytes and a path where the resolver can find referred -// elements and returns a *Swagger with all resolved data or an error if unable to load data or resolve refs. -func (swaggerLoader *SwaggerLoader) LoadSwaggerFromDataWithPath(data []byte, path *url.URL) (*Swagger, error) { - swaggerLoader.resetVisitedPathItemRefs() - return swaggerLoader.loadSwaggerFromDataWithPathInternal(data, path) -} - -func (swaggerLoader *SwaggerLoader) loadSwaggerFromDataWithPathInternal(data []byte, path *url.URL) (*Swagger, error) { - if swaggerLoader.visitedDocuments == nil { - swaggerLoader.visitedDocuments = make(map[string]*Swagger) - } - uri := path.String() - if doc, ok := swaggerLoader.visitedDocuments[uri]; ok { - return doc, nil - } - - swagger := &Swagger{} - swaggerLoader.visitedDocuments[uri] = swagger - - if err := yaml.Unmarshal(data, swagger); err != nil { - return nil, err - } - if err := swaggerLoader.ResolveRefsIn(swagger, path); err != nil { - return nil, err - } - - return swagger, nil -} - -// ResolveRefsIn expands references if for instance spec was just unmarshalled -func (swaggerLoader *SwaggerLoader) ResolveRefsIn(swagger *Swagger, path *url.URL) (err error) { - if swaggerLoader.visitedPathItemRefs == nil { - swaggerLoader.resetVisitedPathItemRefs() - } - - // Visit all components - components := swagger.Components - for _, component := range components.Headers { - if err = swaggerLoader.resolveHeaderRef(swagger, component, path); err != nil { - return - } - } - for _, component := range components.Parameters { - if err = swaggerLoader.resolveParameterRef(swagger, component, path); err != nil { - return - } - } - for _, component := range components.RequestBodies { - if err = swaggerLoader.resolveRequestBodyRef(swagger, component, path); err != nil { - return - } - } - for _, component := range components.Responses { - if err = swaggerLoader.resolveResponseRef(swagger, component, path); err != nil { - return - } - } - for _, component := range components.Schemas { - if err = swaggerLoader.resolveSchemaRef(swagger, component, path); err != nil { - return - } - } - for _, component := range components.SecuritySchemes { - if err = swaggerLoader.resolveSecuritySchemeRef(swagger, component, path); err != nil { - return - } - } - for _, component := range components.Examples { - if err = swaggerLoader.resolveExampleRef(swagger, component, path); err != nil { - return - } - } - - // Visit all operations - for entrypoint, pathItem := range swagger.Paths { - if pathItem == nil { - continue - } - if err = swaggerLoader.resolvePathItemRef(swagger, entrypoint, pathItem, path); err != nil { - return - } - } - - return -} - -func copyURL(basePath *url.URL) (*url.URL, error) { - return url.Parse(basePath.String()) -} - -func join(basePath *url.URL, relativePath *url.URL) (*url.URL, error) { - if basePath == nil { - return relativePath, nil - } - newPath, err := copyURL(basePath) - if err != nil { - return nil, fmt.Errorf("cannot copy path: %q", basePath.String()) - } - newPath.Path = path.Join(path.Dir(newPath.Path), relativePath.Path) - return newPath, nil -} - -func resolvePath(basePath *url.URL, componentPath *url.URL) (*url.URL, error) { - if componentPath.Scheme == "" && componentPath.Host == "" { - // support absolute paths - if componentPath.Path[0] == '/' { - return componentPath, nil - } - return join(basePath, componentPath) - } - return componentPath, nil -} - -func isSingleRefElement(ref string) bool { - return !strings.Contains(ref, "#") -} - -func (swaggerLoader *SwaggerLoader) resolveComponent( - swagger *Swagger, - ref string, - path *url.URL, - resolved interface{}, -) ( - componentPath *url.URL, - err error, -) { - if swagger, ref, componentPath, err = swaggerLoader.resolveRefSwagger(swagger, ref, path); err != nil { - return nil, err - } - - parsedURL, err := url.Parse(ref) - if err != nil { - return nil, fmt.Errorf("cannot parse reference: %q: %v", ref, parsedURL) - } - fragment := parsedURL.Fragment - if !strings.HasPrefix(fragment, "/") { - return nil, fmt.Errorf("expected fragment prefix '#/' in URI %q", ref) - } - - var cursor interface{} - cursor = swagger - for _, pathPart := range strings.Split(fragment[1:], "/") { - pathPart = unescapeRefString(pathPart) - - if cursor, err = drillIntoSwaggerField(cursor, pathPart); err != nil { - e := failedToResolveRefFragmentPart(ref, pathPart) - return nil, fmt.Errorf("%s: %s", e.Error(), err.Error()) - } - if cursor == nil { - return nil, failedToResolveRefFragmentPart(ref, pathPart) - } - } - - switch { - case reflect.TypeOf(cursor) == reflect.TypeOf(resolved): - reflect.ValueOf(resolved).Elem().Set(reflect.ValueOf(cursor).Elem()) - return componentPath, nil - - case reflect.TypeOf(cursor) == reflect.TypeOf(map[string]interface{}{}): - codec := func(got, expect interface{}) error { - enc, err := json.Marshal(got) - if err != nil { - return err - } - if err = json.Unmarshal(enc, expect); err != nil { - return err - } - return nil - } - if err := codec(cursor, resolved); err != nil { - return nil, fmt.Errorf("bad data in %q", ref) - } - return componentPath, nil - - default: - return nil, fmt.Errorf("bad data in %q", ref) - } -} - -func drillIntoSwaggerField(cursor interface{}, fieldName string) (interface{}, error) { - switch val := reflect.Indirect(reflect.ValueOf(cursor)); val.Kind() { - case reflect.Map: - elementValue := val.MapIndex(reflect.ValueOf(fieldName)) - if !elementValue.IsValid() { - return nil, fmt.Errorf("map key %q not found", fieldName) - } - return elementValue.Interface(), nil - - case reflect.Slice: - i, err := strconv.ParseUint(fieldName, 10, 32) - if err != nil { - return nil, err - } - index := int(i) - if 0 > index || index >= val.Len() { - return nil, errors.New("slice index out of bounds") - } - return val.Index(index).Interface(), nil - - case reflect.Struct: - hasFields := false - for i := 0; i < val.NumField(); i++ { - hasFields = true - field := val.Type().Field(i) - tagValue := field.Tag.Get("yaml") - yamlKey := strings.Split(tagValue, ",")[0] - if yamlKey == fieldName { - return val.Field(i).Interface(), nil - } - } - // if cursor is a "ref wrapper" struct (e.g. RequestBodyRef), - if _, ok := val.Type().FieldByName("Value"); ok { - // try digging into its Value field - return drillIntoSwaggerField(val.FieldByName("Value").Interface(), fieldName) - } - if hasFields { - if ff := val.Type().Field(0); ff.PkgPath == "" && ff.Name == "ExtensionProps" { - extensions := val.Field(0).Interface().(ExtensionProps).Extensions - if enc, ok := extensions[fieldName]; ok { - var dec interface{} - if err := json.Unmarshal(enc.(json.RawMessage), &dec); err != nil { - return nil, err - } - return dec, nil - } - } - } - return nil, fmt.Errorf("struct field %q not found", fieldName) - - default: - return nil, errors.New("not a map, slice nor struct") - } -} - -func (swaggerLoader *SwaggerLoader) resolveRefSwagger(swagger *Swagger, ref string, path *url.URL) (*Swagger, string, *url.URL, error) { - componentPath := path - if !strings.HasPrefix(ref, "#") { - if !swaggerLoader.IsExternalRefsAllowed { - return nil, "", nil, fmt.Errorf("encountered non-allowed external reference: %q", ref) - } - parsedURL, err := url.Parse(ref) - if err != nil { - return nil, "", nil, fmt.Errorf("cannot parse reference: %q: %v", ref, parsedURL) - } - fragment := parsedURL.Fragment - parsedURL.Fragment = "" - - resolvedPath, err := resolvePath(path, parsedURL) - if err != nil { - return nil, "", nil, fmt.Errorf("error resolving path: %v", err) - } - - if swagger, err = swaggerLoader.loadSwaggerFromURIInternal(resolvedPath); err != nil { - return nil, "", nil, fmt.Errorf("error resolving reference %q: %v", ref, err) - } - ref = "#" + fragment - componentPath = resolvedPath - } - return swagger, ref, componentPath, nil -} - -func (swaggerLoader *SwaggerLoader) resolveHeaderRef(swagger *Swagger, component *HeaderRef, documentPath *url.URL) error { - if component != nil && component.Value != nil { - if swaggerLoader.visitedHeader == nil { - swaggerLoader.visitedHeader = make(map[*Header]struct{}) - } - if _, ok := swaggerLoader.visitedHeader[component.Value]; ok { - return nil - } - swaggerLoader.visitedHeader[component.Value] = struct{}{} - } - - const prefix = "#/components/headers/" - if component == nil { - return errors.New("invalid header: value MUST be a JSON object") - } - if ref := component.Ref; ref != "" { - if isSingleRefElement(ref) { - var header Header - if err := swaggerLoader.loadSingleElementFromURI(ref, documentPath, &header); err != nil { - return err - } - - component.Value = &header - } else { - var resolved HeaderRef - componentPath, err := swaggerLoader.resolveComponent(swagger, ref, documentPath, &resolved) - if err != nil { - return err - } - if err := swaggerLoader.resolveHeaderRef(swagger, &resolved, componentPath); err != nil { - return err - } - component.Value = resolved.Value - } - } - value := component.Value - if value == nil { - return nil - } - if schema := value.Schema; schema != nil { - if err := swaggerLoader.resolveSchemaRef(swagger, schema, documentPath); err != nil { - return err - } - } - return nil -} - -func (swaggerLoader *SwaggerLoader) resolveParameterRef(swagger *Swagger, component *ParameterRef, documentPath *url.URL) error { - if component != nil && component.Value != nil { - if swaggerLoader.visitedParameter == nil { - swaggerLoader.visitedParameter = make(map[*Parameter]struct{}) - } - if _, ok := swaggerLoader.visitedParameter[component.Value]; ok { - return nil - } - swaggerLoader.visitedParameter[component.Value] = struct{}{} - } - - const prefix = "#/components/parameters/" - if component == nil { - return errors.New("invalid parameter: value MUST be a JSON object") - } - ref := component.Ref - if ref != "" { - if isSingleRefElement(ref) { - var param Parameter - if err := swaggerLoader.loadSingleElementFromURI(ref, documentPath, ¶m); err != nil { - return err - } - component.Value = ¶m - } else { - var resolved ParameterRef - componentPath, err := swaggerLoader.resolveComponent(swagger, ref, documentPath, &resolved) - if err != nil { - return err - } - if err := swaggerLoader.resolveParameterRef(swagger, &resolved, componentPath); err != nil { - return err - } - component.Value = resolved.Value - } - } - value := component.Value - if value == nil { - return nil - } - - refDocumentPath, err := referencedDocumentPath(documentPath, ref) - if err != nil { - return err - } - - if value.Content != nil && value.Schema != nil { - return errors.New("cannot contain both schema and content in a parameter") - } - for _, contentType := range value.Content { - if schema := contentType.Schema; schema != nil { - if err := swaggerLoader.resolveSchemaRef(swagger, schema, refDocumentPath); err != nil { - return err - } - } - } - if schema := value.Schema; schema != nil { - if err := swaggerLoader.resolveSchemaRef(swagger, schema, refDocumentPath); err != nil { - return err - } - } - return nil -} - -func (swaggerLoader *SwaggerLoader) resolveRequestBodyRef(swagger *Swagger, component *RequestBodyRef, documentPath *url.URL) error { - if component != nil && component.Value != nil { - if swaggerLoader.visitedRequestBody == nil { - swaggerLoader.visitedRequestBody = make(map[*RequestBody]struct{}) - } - if _, ok := swaggerLoader.visitedRequestBody[component.Value]; ok { - return nil - } - swaggerLoader.visitedRequestBody[component.Value] = struct{}{} - } - - const prefix = "#/components/requestBodies/" - if component == nil { - return errors.New("invalid requestBody: value MUST be a JSON object") - } - if ref := component.Ref; ref != "" { - if isSingleRefElement(ref) { - var requestBody RequestBody - if err := swaggerLoader.loadSingleElementFromURI(ref, documentPath, &requestBody); err != nil { - return err - } - - component.Value = &requestBody - } else { - var resolved RequestBodyRef - componentPath, err := swaggerLoader.resolveComponent(swagger, ref, documentPath, &resolved) - if err != nil { - return err - } - if err = swaggerLoader.resolveRequestBodyRef(swagger, &resolved, componentPath); err != nil { - return err - } - component.Value = resolved.Value - } - } - value := component.Value - if value == nil { - return nil - } - for _, contentType := range value.Content { - for name, example := range contentType.Examples { - if err := swaggerLoader.resolveExampleRef(swagger, example, documentPath); err != nil { - return err - } - contentType.Examples[name] = example - } - if schema := contentType.Schema; schema != nil { - if err := swaggerLoader.resolveSchemaRef(swagger, schema, documentPath); err != nil { - return err - } - } - } - return nil -} - -func (swaggerLoader *SwaggerLoader) resolveResponseRef(swagger *Swagger, component *ResponseRef, documentPath *url.URL) error { - if component != nil && component.Value != nil { - if swaggerLoader.visitedResponse == nil { - swaggerLoader.visitedResponse = make(map[*Response]struct{}) - } - if _, ok := swaggerLoader.visitedResponse[component.Value]; ok { - return nil - } - swaggerLoader.visitedResponse[component.Value] = struct{}{} - } - - const prefix = "#/components/responses/" - if component == nil { - return errors.New("invalid response: value MUST be a JSON object") - } - ref := component.Ref - if ref != "" { - if isSingleRefElement(ref) { - var resp Response - if err := swaggerLoader.loadSingleElementFromURI(ref, documentPath, &resp); err != nil { - return err - } - component.Value = &resp - } else { - var resolved ResponseRef - componentPath, err := swaggerLoader.resolveComponent(swagger, ref, documentPath, &resolved) - if err != nil { - return err - } - if err := swaggerLoader.resolveResponseRef(swagger, &resolved, componentPath); err != nil { - return err - } - component.Value = resolved.Value - } - } - refDocumentPath, err := referencedDocumentPath(documentPath, ref) - if err != nil { - return err - } - - value := component.Value - if value == nil { - return nil - } - for _, header := range value.Headers { - if err := swaggerLoader.resolveHeaderRef(swagger, header, refDocumentPath); err != nil { - return err - } - } - for _, contentType := range value.Content { - if contentType == nil { - continue - } - for name, example := range contentType.Examples { - if err := swaggerLoader.resolveExampleRef(swagger, example, refDocumentPath); err != nil { - return err - } - contentType.Examples[name] = example - } - if schema := contentType.Schema; schema != nil { - if err := swaggerLoader.resolveSchemaRef(swagger, schema, refDocumentPath); err != nil { - return err - } - contentType.Schema = schema - } - } - for _, link := range value.Links { - if err := swaggerLoader.resolveLinkRef(swagger, link, refDocumentPath); err != nil { - return err - } - } - return nil -} - -func (swaggerLoader *SwaggerLoader) resolveSchemaRef(swagger *Swagger, component *SchemaRef, documentPath *url.URL) error { - if component != nil && component.Value != nil { - if swaggerLoader.visitedSchema == nil { - swaggerLoader.visitedSchema = make(map[*Schema]struct{}) - } - if _, ok := swaggerLoader.visitedSchema[component.Value]; ok { - return nil - } - swaggerLoader.visitedSchema[component.Value] = struct{}{} - } - - const prefix = "#/components/schemas/" - if component == nil { - return errors.New("invalid schema: value MUST be a JSON object") - } - ref := component.Ref - if ref != "" { - if isSingleRefElement(ref) { - var schema Schema - if err := swaggerLoader.loadSingleElementFromURI(ref, documentPath, &schema); err != nil { - return err - } - component.Value = &schema - } else { - var resolved SchemaRef - componentPath, err := swaggerLoader.resolveComponent(swagger, ref, documentPath, &resolved) - if err != nil { - return err - } - if err := swaggerLoader.resolveSchemaRef(swagger, &resolved, componentPath); err != nil { - return err - } - component.Value = resolved.Value - } - } - - refDocumentPath, err := referencedDocumentPath(documentPath, ref) - if err != nil { - return err - } - - value := component.Value - if value == nil { - return nil - } - - // ResolveRefs referred schemas - if v := value.Items; v != nil { - if err := swaggerLoader.resolveSchemaRef(swagger, v, refDocumentPath); err != nil { - return err - } - } - for _, v := range value.Properties { - if err := swaggerLoader.resolveSchemaRef(swagger, v, refDocumentPath); err != nil { - return err - } - } - if v := value.AdditionalProperties; v != nil { - if err := swaggerLoader.resolveSchemaRef(swagger, v, refDocumentPath); err != nil { - return err - } - } - if v := value.Not; v != nil { - if err := swaggerLoader.resolveSchemaRef(swagger, v, refDocumentPath); err != nil { - return err - } - } - for _, v := range value.AllOf { - if err := swaggerLoader.resolveSchemaRef(swagger, v, refDocumentPath); err != nil { - return err - } - } - for _, v := range value.AnyOf { - if err := swaggerLoader.resolveSchemaRef(swagger, v, refDocumentPath); err != nil { - return err - } - } - for _, v := range value.OneOf { - if err := swaggerLoader.resolveSchemaRef(swagger, v, refDocumentPath); err != nil { - return err - } - } - - return nil -} - -func (swaggerLoader *SwaggerLoader) resolveSecuritySchemeRef(swagger *Swagger, component *SecuritySchemeRef, documentPath *url.URL) error { - if component != nil && component.Value != nil { - if swaggerLoader.visitedSecurityScheme == nil { - swaggerLoader.visitedSecurityScheme = make(map[*SecurityScheme]struct{}) - } - if _, ok := swaggerLoader.visitedSecurityScheme[component.Value]; ok { - return nil - } - swaggerLoader.visitedSecurityScheme[component.Value] = struct{}{} - } - - const prefix = "#/components/securitySchemes/" - if component == nil { - return errors.New("invalid securityScheme: value MUST be a JSON object") - } - if ref := component.Ref; ref != "" { - if isSingleRefElement(ref) { - var scheme SecurityScheme - if err := swaggerLoader.loadSingleElementFromURI(ref, documentPath, &scheme); err != nil { - return err - } - - component.Value = &scheme - } else { - var resolved SecuritySchemeRef - componentPath, err := swaggerLoader.resolveComponent(swagger, ref, documentPath, &resolved) - if err != nil { - return err - } - if err := swaggerLoader.resolveSecuritySchemeRef(swagger, &resolved, componentPath); err != nil { - return err - } - component.Value = resolved.Value - } - } - return nil -} - -func (swaggerLoader *SwaggerLoader) resolveExampleRef(swagger *Swagger, component *ExampleRef, documentPath *url.URL) error { - if component != nil && component.Value != nil { - if swaggerLoader.visitedExample == nil { - swaggerLoader.visitedExample = make(map[*Example]struct{}) - } - if _, ok := swaggerLoader.visitedExample[component.Value]; ok { - return nil - } - swaggerLoader.visitedExample[component.Value] = struct{}{} - } - - const prefix = "#/components/examples/" - if component == nil { - return errors.New("invalid example: value MUST be a JSON object") - } - if ref := component.Ref; ref != "" { - if isSingleRefElement(ref) { - var example Example - if err := swaggerLoader.loadSingleElementFromURI(ref, documentPath, &example); err != nil { - return err - } - - component.Value = &example - } else { - var resolved ExampleRef - componentPath, err := swaggerLoader.resolveComponent(swagger, ref, documentPath, &resolved) - if err != nil { - return err - } - if err := swaggerLoader.resolveExampleRef(swagger, &resolved, componentPath); err != nil { - return err - } - component.Value = resolved.Value - } - } - return nil -} - -func (swaggerLoader *SwaggerLoader) resolveLinkRef(swagger *Swagger, component *LinkRef, documentPath *url.URL) error { - if component != nil && component.Value != nil { - if swaggerLoader.visitedLink == nil { - swaggerLoader.visitedLink = make(map[*Link]struct{}) - } - if _, ok := swaggerLoader.visitedLink[component.Value]; ok { - return nil - } - swaggerLoader.visitedLink[component.Value] = struct{}{} - } - - const prefix = "#/components/links/" - if component == nil { - return errors.New("invalid link: value MUST be a JSON object") - } - if ref := component.Ref; ref != "" { - if isSingleRefElement(ref) { - var link Link - if err := swaggerLoader.loadSingleElementFromURI(ref, documentPath, &link); err != nil { - return err - } - - component.Value = &link - } else { - var resolved LinkRef - componentPath, err := swaggerLoader.resolveComponent(swagger, ref, documentPath, &resolved) - if err != nil { - return err - } - if err := swaggerLoader.resolveLinkRef(swagger, &resolved, componentPath); err != nil { - return err - } - component.Value = resolved.Value - } - } - return nil -} - -func (swaggerLoader *SwaggerLoader) resolvePathItemRef(swagger *Swagger, entrypoint string, pathItem *PathItem, documentPath *url.URL) (err error) { - key := "_" - if documentPath != nil { - key = documentPath.EscapedPath() - } - key += entrypoint - if _, ok := swaggerLoader.visitedPathItemRefs[key]; ok { - return nil - } - swaggerLoader.visitedPathItemRefs[key] = struct{}{} - - const prefix = "#/paths/" - if pathItem == nil { - return errors.New("invalid path item: value MUST be a JSON object") - } - ref := pathItem.Ref - if ref != "" { - if isSingleRefElement(ref) { - var p PathItem - if err := swaggerLoader.loadSingleElementFromURI(ref, documentPath, &p); err != nil { - return err - } - *pathItem = p - } else { - if swagger, ref, documentPath, err = swaggerLoader.resolveRefSwagger(swagger, ref, documentPath); err != nil { - return - } - - if !strings.HasPrefix(ref, prefix) { - err = fmt.Errorf("expected prefix %q in URI %q", prefix, ref) - return - } - id := unescapeRefString(ref[len(prefix):]) - - definitions := swagger.Paths - if definitions == nil { - return failedToResolveRefFragmentPart(ref, "paths") - } - resolved := definitions[id] - if resolved == nil { - return failedToResolveRefFragmentPart(ref, id) - } - - *pathItem = *resolved - } - } - - refDocumentPath, err := referencedDocumentPath(documentPath, ref) - if err != nil { - return err - } - - for _, parameter := range pathItem.Parameters { - if err = swaggerLoader.resolveParameterRef(swagger, parameter, refDocumentPath); err != nil { - return - } - } - for _, operation := range pathItem.Operations() { - for _, parameter := range operation.Parameters { - if err = swaggerLoader.resolveParameterRef(swagger, parameter, refDocumentPath); err != nil { - return - } - } - if requestBody := operation.RequestBody; requestBody != nil { - if err = swaggerLoader.resolveRequestBodyRef(swagger, requestBody, refDocumentPath); err != nil { - return - } - } - for _, response := range operation.Responses { - if err = swaggerLoader.resolveResponseRef(swagger, response, refDocumentPath); err != nil { - return - } - } - } - - return nil -} - -func unescapeRefString(ref string) string { - return strings.Replace(strings.Replace(ref, "~1", "/", -1), "~0", "~", -1) -} - -func referencedDocumentPath(documentPath *url.URL, ref string) (*url.URL, error) { - if documentPath == nil { - return nil, nil - } - - newDocumentPath, err := copyURL(documentPath) - if err != nil { - return nil, err - } - refPath, err := url.Parse(ref) - if err != nil { - return nil, err - } - newDocumentPath.Path = path.Join(path.Dir(newDocumentPath.Path), path.Dir(refPath.Path)) + "/" - - return newDocumentPath, nil -} diff --git a/vendor/github.com/getkin/kin-openapi/openapi3filter/authentication_input.go b/vendor/github.com/getkin/kin-openapi/openapi3filter/authentication_input.go index bae7c43d3..a53484b99 100644 --- a/vendor/github.com/getkin/kin-openapi/openapi3filter/authentication_input.go +++ b/vendor/github.com/getkin/kin-openapi/openapi3filter/authentication_input.go @@ -2,7 +2,6 @@ package openapi3filter import ( "fmt" - "strings" "github.com/getkin/kin-openapi/openapi3" ) @@ -16,19 +15,15 @@ type AuthenticationInput struct { func (input *AuthenticationInput) NewError(err error) error { if err == nil { - scopes := input.Scopes - if len(scopes) == 0 { - err = fmt.Errorf("Security requirement '%s' failed", - input.SecuritySchemeName) + if len(input.Scopes) == 0 { + err = fmt.Errorf("security requirement %q failed", input.SecuritySchemeName) } else { - err = fmt.Errorf("Security requirement '%s' (scopes: '%s') failed", - input.SecuritySchemeName, - strings.Join(input.Scopes, "', '")) + err = fmt.Errorf("security requirement %q (scopes: %+v) failed", input.SecuritySchemeName, input.Scopes) } } return &RequestError{ Input: input.RequestValidationInput, - Reason: "Authorization failed", + 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 index 9b46ebda6..8454c817f 100644 --- a/vendor/github.com/getkin/kin-openapi/openapi3filter/errors.go +++ b/vendor/github.com/getkin/kin-openapi/openapi3filter/errors.go @@ -1,44 +1,23 @@ package openapi3filter import ( - "errors" "fmt" - "net/http" "github.com/getkin/kin-openapi/openapi3" ) -var ( - errRouteMissingSwagger = errors.New("Route is missing OpenAPI specification") - errRouteMissingOperation = errors.New("Route is missing OpenAPI operation") - ErrAuthenticationServiceMissing = errors.New("Request validator doesn't have an authentication service defined") -) - -type RouteError struct { - Route Route - Reason string -} - -func (err *RouteError) Error() string { - return err.Reason -} +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 - Status int Reason string Err error } -func (err *RequestError) HTTPStatus() int { - status := err.Status - if status == 0 { - status = http.StatusBadRequest - } - return status -} +var _ interface{ Unwrap() error } = RequestError{} func (err *RequestError) Error() string { reason := err.Reason @@ -50,20 +29,29 @@ func (err *RequestError) Error() string { } } if v := err.Parameter; v != nil { - return fmt.Sprintf("Parameter '%s' in %s has an error: %s", v.Name, v.In, reason) + 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) + 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 { @@ -76,6 +64,14 @@ func (err *ResponseError) Error() string { 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 diff --git a/vendor/github.com/getkin/kin-openapi/openapi3filter/options.go b/vendor/github.com/getkin/kin-openapi/openapi3filter/options.go index b0fb39df3..1622339e2 100644 --- a/vendor/github.com/getkin/kin-openapi/openapi3filter/options.go +++ b/vendor/github.com/getkin/kin-openapi/openapi3filter/options.go @@ -7,8 +7,14 @@ var DefaultOptions = &Options{} // Options used by ValidateRequest and ValidateResponse type Options struct { - ExcludeRequestBody bool - ExcludeResponseBody bool + // 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 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 index 163bc69b2..cb58b62b6 100644 --- a/vendor/github.com/getkin/kin-openapi/openapi3filter/req_resp_decoder.go +++ b/vendor/github.com/getkin/kin-openapi/openapi3filter/req_resp_decoder.go @@ -14,6 +14,8 @@ import ( "strconv" "strings" + "gopkg.in/yaml.v2" + "github.com/getkin/kin-openapi/openapi3" ) @@ -41,6 +43,8 @@ type ParseError struct { path []interface{} } +var _ interface{ Unwrap() error } = ParseError{} + func (e *ParseError) Error() string { var msg []string if p := e.Path(); len(p) > 0 { @@ -80,6 +84,10 @@ func (e *ParseError) RootCause() error { 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{} @@ -130,13 +138,13 @@ func decodeContentParameter(param *openapi3.Parameter, input *RequestValidationI found = true } default: - err = fmt.Errorf("unsupported parameter's 'in': %s", param.In) + err = fmt.Errorf("unsupported parameter.in: %q", param.In) return } if !found { if param.Required { - err = fmt.Errorf("parameter '%s' is required, but missing", param.Name) + err = fmt.Errorf("parameter %q is required, but missing", param.Name) } return } @@ -154,32 +162,32 @@ 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 '%s' can't have multiple values", param.In, param.Name) + 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 '%s' expected to have content", param.Name) + 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 '%s'", param.Name) + err = fmt.Errorf("multiple content types for parameter %q", param.Name) return } mt := content.Get("application/json") if mt == nil { - err = fmt.Errorf("parameter '%s' has no json content schema", param.Name) + 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 '%s' as json", param.Name) + err = fmt.Errorf("error unmarshaling parameter %q", param.Name) return } } else { @@ -187,7 +195,7 @@ func defaultContentParameterDecoder(param *openapi3.Parameter, values []string) for _, v := range values { var item interface{} if err = json.Unmarshal([]byte(v), &item); err != nil { - err = fmt.Errorf("error unmarshaling parameter '%s' as json", param.Name) + err = fmt.Errorf("error unmarshaling parameter %q", param.Name) return } outArray = append(outArray, item) @@ -257,12 +265,10 @@ func decodeValue(dec valueDecoder, param string, sm *openapi3.SerializationMetho return value, nil } } - if required == true { + if required { return nil, fmt.Errorf("decoding anyOf for parameter %q failed", param) - } else { - return nil, nil } - + return nil, nil } if len(schema.Value.OneOf) > 0 { @@ -280,12 +286,12 @@ func decodeValue(dec valueDecoder, param string, sm *openapi3.SerializationMetho } else if isMatched > 1 { return nil, fmt.Errorf("decoding oneOf failed: %d schemas matched", isMatched) } - if required == true { + if required { return nil, fmt.Errorf("decoding oneOf failed: %q is required", param) - } else { - return nil, nil } + return nil, nil } + if schema.Value.Not != nil { // TODO(decode not): handle decoding "not" JSON Schema return nil, errors.New("not implemented: decoding 'not'") @@ -306,6 +312,7 @@ func decodeValue(dec valueDecoder, param string, sm *openapi3.SerializationMetho } return decodeFn(param, sm, schema) } + return nil, nil } @@ -331,7 +338,7 @@ func (d *pathParamDecoder) DecodePrimitive(param string, sm *openapi3.Serializat // HTTP request does not contains a value of the target path parameter. return nil, nil } - raw, ok := d.pathParams[paramKey(param, sm)] + raw, ok := d.pathParams[param] if !ok || raw == "" { // HTTP request does not contains a value of the target path parameter. return nil, nil @@ -368,7 +375,7 @@ func (d *pathParamDecoder) DecodeArray(param string, sm *openapi3.SerializationM // HTTP request does not contains a value of the target path parameter. return nil, nil } - raw, ok := d.pathParams[paramKey(param, sm)] + raw, ok := d.pathParams[param] if !ok || raw == "" { // HTTP request does not contains a value of the target path parameter. return nil, nil @@ -383,25 +390,25 @@ func (d *pathParamDecoder) DecodeArray(param string, sm *openapi3.SerializationM func (d *pathParamDecoder) DecodeObject(param string, sm *openapi3.SerializationMethod, schema *openapi3.SchemaRef) (map[string]interface{}, error) { var prefix, propsDelim, valueDelim string switch { - case sm.Style == "simple" && sm.Explode == false: + case sm.Style == "simple" && !sm.Explode: propsDelim = "," valueDelim = "," - case sm.Style == "simple" && sm.Explode == true: + case sm.Style == "simple" && sm.Explode: propsDelim = "," valueDelim = "=" - case sm.Style == "label" && sm.Explode == false: + case sm.Style == "label" && !sm.Explode: prefix = "." propsDelim = "," valueDelim = "," - case sm.Style == "label" && sm.Explode == true: + case sm.Style == "label" && sm.Explode: prefix = "." propsDelim = "." valueDelim = "=" - case sm.Style == "matrix" && sm.Explode == false: + case sm.Style == "matrix" && !sm.Explode: prefix = ";" + param + "=" propsDelim = "," valueDelim = "," - case sm.Style == "matrix" && sm.Explode == true: + case sm.Style == "matrix" && sm.Explode: prefix = ";" propsDelim = ";" valueDelim = "=" @@ -413,7 +420,7 @@ func (d *pathParamDecoder) DecodeObject(param string, sm *openapi3.Serialization // HTTP request does not contains a value of the target path parameter. return nil, nil } - raw, ok := d.pathParams[paramKey(param, sm)] + raw, ok := d.pathParams[param] if !ok || raw == "" { // HTTP request does not contains a value of the target path parameter. return nil, nil @@ -429,18 +436,6 @@ func (d *pathParamDecoder) DecodeObject(param string, sm *openapi3.Serialization return makeObject(props, schema) } -// paramKey returns a key to get a raw value of a path parameter. -func paramKey(param string, sm *openapi3.SerializationMethod) string { - switch sm.Style { - case "label": - return "." + param - case "matrix": - return ";" + param - default: - return param - } -} - // 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) { @@ -737,8 +732,8 @@ func parseArray(raw []string, schemaRef *openapi3.SchemaRef) ([]interface{}, err } // parsePrimitive returns a value that is created by parsing a source string to a primitive type -// that is specified by a JSON schema. The function returns nil when the source string is empty. -// The function panics when a JSON schema has a non 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 @@ -780,10 +775,19 @@ type BodyDecoder func(io.Reader, http.Header, *openapi3.SchemaRef, EncodingFn) ( // 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") @@ -797,6 +801,7 @@ func RegisterBodyDecoder(contentType string, decoder BodyDecoder) { // 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") @@ -804,16 +809,25 @@ func UnregisterBodyDecoder(contentType string) { 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(http.CanonicalHeaderKey("Content-Type")) + 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("unsupported content type %q", mediaType), + Reason: fmt.Sprintf("%s %q", prefixUnsupportedCT, mediaType), } } value, err := decoder(body, header, schema, encFn) @@ -826,6 +840,8 @@ func decodeBody(body io.Reader, header http.Header, schema *openapi3.SchemaRef, 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) @@ -848,21 +864,29 @@ func jsonBodyDecoder(body io.Reader, header http.Header, schema *openapi3.Schema 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 JSON schema of request body. + // 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 JSON schema of request body") + 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 JSON schema of request body's property %q", propName) + 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 JSON schema of request body's property %q", propName) + return nil, fmt.Errorf("unsupported schema of request body's property %q", propName) } } } @@ -901,12 +925,12 @@ func urlencodedBodyDecoder(body io.Reader, header http.Header, schema *openapi3. 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 JSON schema of request body") + return nil, errors.New("unsupported schema of request body") } // Parse form. values := make(map[string][]interface{}) - contentType := header.Get(http.CanonicalHeaderKey("Content-Type")) + contentType := header.Get(headerCT) _, params, err := mime.ParseMediaType(contentType) if err != nil { return nil, err diff --git a/vendor/github.com/getkin/kin-openapi/openapi3filter/router.go b/vendor/github.com/getkin/kin-openapi/openapi3filter/router.go deleted file mode 100644 index 3904750eb..000000000 --- a/vendor/github.com/getkin/kin-openapi/openapi3filter/router.go +++ /dev/null @@ -1,217 +0,0 @@ -package openapi3filter - -import ( - "context" - "errors" - "fmt" - "net/http" - "net/url" - "strings" - - "github.com/getkin/kin-openapi/openapi3" - "github.com/getkin/kin-openapi/pathpattern" -) - -type Route struct { - Swagger *openapi3.Swagger - Server *openapi3.Server - Path string - PathItem *openapi3.PathItem - Method string - Operation *openapi3.Operation - - // For developers who want use the router for handling too - Handler http.Handler -} - -// Routers maps a HTTP request to a Router. -type Routers []*Router - -func (routers Routers) FindRoute(method string, url *url.URL) (*Router, *Route, map[string]string, error) { - for _, router := range routers { - // Skip routers that have DO NOT have servers - if len(router.swagger.Servers) == 0 { - continue - } - route, pathParams, err := router.FindRoute(method, url) - if err == nil { - return router, route, pathParams, nil - } - } - for _, router := range routers { - // Skip routers that DO have servers - if len(router.swagger.Servers) > 0 { - continue - } - route, pathParams, err := router.FindRoute(method, url) - if err == nil { - return router, route, pathParams, nil - } - } - return nil, nil, nil, &RouteError{ - Reason: "None of the routers matches", - } -} - -// Router maps a HTTP request to an OpenAPI operation. -type Router struct { - swagger *openapi3.Swagger - pathNode *pathpattern.Node -} - -// NewRouter creates a new router. -// -// If the given Swagger has servers, router will use them. -// All operations of the Swagger will be added to the router. -func NewRouter() *Router { - return &Router{} -} - -// WithSwaggerFromFile loads the Swagger file and adds it using WithSwagger. -// Panics on any error. -func (router *Router) WithSwaggerFromFile(path string) *Router { - if err := router.AddSwaggerFromFile(path); err != nil { - panic(err) - } - return router -} - -// WithSwagger adds all operations in the OpenAPI specification. -// Panics on any error. -func (router *Router) WithSwagger(swagger *openapi3.Swagger) *Router { - if err := router.AddSwagger(swagger); err != nil { - panic(err) - } - return router -} - -// AddSwaggerFromFile loads the Swagger file and adds it using AddSwagger. -func (router *Router) AddSwaggerFromFile(path string) error { - swagger, err := openapi3.NewSwaggerLoader().LoadSwaggerFromFile(path) - if err != nil { - return err - } - return router.AddSwagger(swagger) -} - -// AddSwagger adds all operations in the OpenAPI specification. -func (router *Router) AddSwagger(swagger *openapi3.Swagger) error { - if err := swagger.Validate(context.TODO()); err != nil { - return fmt.Errorf("Validating Swagger failed: %v", err) - } - router.swagger = swagger - root := router.node() - for path, pathItem := range swagger.Paths { - for method, operation := range pathItem.Operations() { - method = strings.ToUpper(method) - if err := root.Add(method+" "+path, &Route{ - Swagger: swagger, - Path: path, - PathItem: pathItem, - Method: method, - Operation: operation, - }, nil); err != nil { - return err - } - } - } - return nil -} - -// AddRoute adds a route in the router. -func (router *Router) AddRoute(route *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 -} - -func (router *Router) FindRoute(method string, url *url.URL) (*Route, map[string]string, error) { - swagger := router.swagger - - // Get server - servers := swagger.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, &RouteError{ - Route: Route{ - Swagger: swagger, - }, - Reason: "Does not match any server", - } - } - pathParams = make(map[string]string, 8) - paramNames, _ := server.ParameterNames() - for i, value := range paramValues { - name := paramNames[i] - pathParams[name] = value - } - } - - // Get PathItem - root := router.node() - var route *Route - node, paramValues := root.Match(method + " " + remainingPath) - if node != nil { - route, _ = node.Value.(*Route) - } - if route == nil { - pathItem := swagger.Paths[remainingPath] - if pathItem == nil { - return nil, nil, &RouteError{ - Route: Route{ - Swagger: swagger, - Server: server, - }, - Reason: "Path was not found", - } - } - - // Get operation - if pathItem.GetOperation(method) == nil { - return nil, nil, &RouteError{ - Route: Route{ - Swagger: swagger, - Server: server, - }, - Reason: "Path doesn't support the HTTP method", - } - } - - } - - 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/openapi3filter/validate_request.go b/vendor/github.com/getkin/kin-openapi/openapi3filter/validate_request.go index 0af54c299..2f9a5f14c 100644 --- a/vendor/github.com/getkin/kin-openapi/openapi3filter/validate_request.go +++ b/vendor/github.com/getkin/kin-openapi/openapi3filter/validate_request.go @@ -12,8 +12,12 @@ import ( "github.com/getkin/kin-openapi/openapi3" ) -// ErrInvalidRequired is an error that happens when a required value of a parameter or request's body is not defined. -var ErrInvalidRequired = errors.New("must have a value") +// 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") // ValidateRequest is used to validate the given input according to previous // loaded OpenAPIv3 spec. If the input does not match the OpenAPIv3 spec, a @@ -21,7 +25,7 @@ var ErrInvalidRequired = errors.New("must have a value") // // Note: One can tune the behavior of uniqueItems: true verification // by registering a custom function with openapi3.RegisterArrayUniqueItemsChecker -func ValidateRequest(c context.Context, input *RequestValidationInput) error { +func ValidateRequest(ctx context.Context, input *RequestValidationInput) error { var ( err error me openapi3.MultiError @@ -32,13 +36,7 @@ func ValidateRequest(c context.Context, input *RequestValidationInput) error { options = DefaultOptions } route := input.Route - if route == nil { - return errors.New("invalid route") - } operation := route.Operation - if operation == nil { - return errRouteMissingOperation - } operationParameters := operation.Parameters pathItemParameters := route.PathItem.Parameters @@ -51,7 +49,7 @@ func ValidateRequest(c context.Context, input *RequestValidationInput) error { } } - if err = ValidateParameter(c, input, parameter); err != nil && !options.MultiError { + if err = ValidateParameter(ctx, input, parameter); err != nil && !options.MultiError { return err } @@ -62,7 +60,7 @@ func ValidateRequest(c context.Context, input *RequestValidationInput) error { // For each parameter of the Operation for _, parameter := range operationParameters { - if err = ValidateParameter(c, input, parameter.Value); err != nil && !options.MultiError { + if err = ValidateParameter(ctx, input, parameter.Value); err != nil && !options.MultiError { return err } @@ -74,7 +72,7 @@ func ValidateRequest(c context.Context, input *RequestValidationInput) error { // RequestBody requestBody := operation.RequestBody if requestBody != nil && !options.ExcludeRequestBody { - if err = ValidateRequestBody(c, input, requestBody.Value); err != nil && !options.MultiError { + if err = ValidateRequestBody(ctx, input, requestBody.Value); err != nil && !options.MultiError { return err } @@ -87,14 +85,11 @@ func ValidateRequest(c context.Context, input *RequestValidationInput) error { security := operation.Security // If there aren't any security requirements for the operation if security == nil { - if route.Swagger == nil { - return errRouteMissingSwagger - } // Use the global security requirements. - security = &route.Swagger.Security + security = &route.Spec.Security } if security != nil { - if err = ValidateSecurityRequirements(c, input, *security); err != nil && !options.MultiError { + if err = ValidateSecurityRequirements(ctx, input, *security); err != nil && !options.MultiError { return err } @@ -114,10 +109,10 @@ func ValidateRequest(c context.Context, input *RequestValidationInput) error { // 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 a openapi3.SchemaError cause when a value is invalid by JSON schema. -func ValidateParameter(c context.Context, input *RequestValidationInput, parameter *openapi3.Parameter) error { +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 Swagger + // a schema-less check, but this could also be an error. The OpenAPI // validation allows this to happen. return nil } @@ -145,7 +140,7 @@ func ValidateParameter(c context.Context, input *RequestValidationInput, paramet // Validate a parameter's value. if value == nil { if parameter.Required { - return &RequestError{Input: input, Parameter: parameter, Reason: "must have a value", Err: ErrInvalidRequired} + return &RequestError{Input: input, Parameter: parameter, Reason: ErrInvalidRequired.Error(), Err: ErrInvalidRequired} } return nil } @@ -165,11 +160,13 @@ func ValidateParameter(c context.Context, input *RequestValidationInput, paramet 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(c context.Context, input *RequestValidationInput, requestBody *openapi3.RequestBody) error { +func ValidateRequestBody(ctx context.Context, input *RequestValidationInput, requestBody *openapi3.RequestBody) error { var ( req = input.Request data []byte @@ -208,13 +205,13 @@ func ValidateRequestBody(c context.Context, input *RequestValidationInput, reque return nil } - inputMIME := req.Header.Get("Content-Type") + inputMIME := req.Header.Get(headerCT) contentType := requestBody.Content.Get(inputMIME) if contentType == nil { return &RequestError{ Input: input, RequestBody: requestBody, - Reason: fmt.Sprintf("header 'Content-Type' has unexpected value: %q", inputMIME), + Reason: fmt.Sprintf("%s %q", prefixInvalidCT, inputMIME), } } @@ -255,13 +252,13 @@ func ValidateRequestBody(c context.Context, input *RequestValidationInput, reque // 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(c context.Context, input *RequestValidationInput, srs openapi3.SecurityRequirements) error { +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(c, input, sr); err != nil { + if err := validateSecurityRequirement(ctx, input, sr); err != nil { if len(errs) == 0 { errs = make([]error, 0, len(srs)) } @@ -277,12 +274,9 @@ func ValidateSecurityRequirements(c context.Context, input *RequestValidationInp } // validateSecurityRequirement validates a single OpenAPI 3 security requirement -func validateSecurityRequirement(c context.Context, input *RequestValidationInput, securityRequirement openapi3.SecurityRequirement) error { - swagger := input.Route.Swagger - if swagger == nil { - return errRouteMissingSwagger - } - securitySchemes := swagger.Components.SecuritySchemes +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)) @@ -312,11 +306,11 @@ func validateSecurityRequirement(c context.Context, input *RequestValidationInpu if securityScheme == nil { return &RequestError{ Input: input, - Err: fmt.Errorf("Security scheme '%s' is not declared", name), + Err: fmt.Errorf("security scheme %q is not declared", name), } } scopes := securityRequirement[name] - if err := f(c, &AuthenticationInput{ + if err := f(ctx, &AuthenticationInput{ RequestValidationInput: input, SecuritySchemeName: name, SecurityScheme: securityScheme, 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 index 44bc8579a..91dd102b6 100644 --- a/vendor/github.com/getkin/kin-openapi/openapi3filter/validate_request_input.go +++ b/vendor/github.com/getkin/kin-openapi/openapi3filter/validate_request_input.go @@ -5,9 +5,10 @@ import ( "net/url" "github.com/getkin/kin-openapi/openapi3" + "github.com/getkin/kin-openapi/routers" ) -// A ContentParameterDecoder takes a parameter definition from the swagger spec, +// 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 @@ -22,7 +23,7 @@ type RequestValidationInput struct { Request *http.Request PathParams map[string]string QueryParams url.Values - Route *Route + Route *routers.Route Options *Options ParamDecoder ContentParameterDecoder } diff --git a/vendor/github.com/getkin/kin-openapi/openapi3filter/validate_response.go b/vendor/github.com/getkin/kin-openapi/openapi3filter/validate_response.go index f203802a4..7cb713ace 100644 --- a/vendor/github.com/getkin/kin-openapi/openapi3filter/validate_response.go +++ b/vendor/github.com/getkin/kin-openapi/openapi3filter/validate_response.go @@ -17,20 +17,13 @@ import ( // // Note: One can tune the behavior of uniqueItems: true verification // by registering a custom function with openapi3.RegisterArrayUniqueItemsChecker -func ValidateResponse(c context.Context, input *ResponseValidationInput) error { +func ValidateResponse(ctx context.Context, input *ResponseValidationInput) error { req := input.RequestValidationInput.Request switch req.Method { case "HEAD": return nil } status := input.Status - if status < 100 { - return &ResponseError{ - Input: input, - Reason: "illegal status code", - Err: fmt.Errorf("Status %d", status), - } - } // These status codes will never be validated. // TODO: The list is probably missing some. @@ -61,7 +54,6 @@ func ValidateResponse(c context.Context, input *ResponseValidationInput) error { if !options.IncludeResponseStatus { return nil } - return &ResponseError{Input: input, Reason: "status is not supported"} } response := responseRef.Value @@ -80,12 +72,12 @@ func ValidateResponse(c context.Context, input *ResponseValidationInput) error { return nil } - inputMIME := input.Header.Get("Content-Type") + inputMIME := input.Header.Get(headerCT) contentType := content.Get(inputMIME) if contentType == nil { return &ResponseError{ Input: input, - Reason: fmt.Sprintf("input header 'Content-Type' has unexpected value: %q", inputMIME), + Reason: fmt.Sprintf("response header Content-Type has unexpected value: %q", inputMIME), } } 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 index 34e4af94d..707b22d4a 100644 --- a/vendor/github.com/getkin/kin-openapi/openapi3filter/validation_error_encoder.go +++ b/vendor/github.com/getkin/kin-openapi/openapi3filter/validation_error_encoder.go @@ -4,10 +4,10 @@ import ( "context" "fmt" "net/http" - "regexp" "strings" "github.com/getkin/kin-openapi/openapi3" + "github.com/getkin/kin-openapi/routers" ) // ValidationErrorEncoder wraps a base ErrorEncoder to handle ValidationErrors @@ -17,10 +17,8 @@ type ValidationErrorEncoder struct { // Encode implements the ErrorEncoder interface for encoding ValidationErrors func (enc *ValidationErrorEncoder) Encode(ctx context.Context, err error, w http.ResponseWriter) { - var cErr *ValidationError - - if e, ok := err.(*RouteError); ok { - cErr = convertRouteError(e) + if e, ok := err.(*routers.RouteError); ok { + cErr := convertRouteError(e) enc.Encoder(ctx, cErr, w) return } @@ -31,6 +29,7 @@ func (enc *ValidationErrorEncoder) Encode(ctx context.Context, err error, w http return } + var cErr *ValidationError if e.Err == nil { cErr = convertBasicRequestError(e) } else if e.Err == ErrInvalidRequired { @@ -43,95 +42,80 @@ func (enc *ValidationErrorEncoder) Encode(ctx context.Context, err error, w http if cErr != nil { enc.Encoder(ctx, cErr, w) - } else { - enc.Encoder(ctx, err, w) + return } + enc.Encoder(ctx, err, w) } -func convertRouteError(e *RouteError) *ValidationError { - var cErr *ValidationError - switch e.Reason { - case "Path doesn't support the HTTP method": - cErr = &ValidationError{Status: http.StatusMethodNotAllowed, Title: e.Reason} - default: - cErr = &ValidationError{Status: http.StatusNotFound, Title: e.Reason} +func convertRouteError(e *routers.RouteError) *ValidationError { + status := http.StatusNotFound + if e.Error() == routers.ErrMethodNotAllowed.Error() { + status = http.StatusMethodNotAllowed } - return cErr + return &ValidationError{Status: status, Title: e.Error()} } func convertBasicRequestError(e *RequestError) *ValidationError { - var cErr *ValidationError - unsupportedContentType := "header 'Content-Type' has unexpected value: " - if strings.HasPrefix(e.Reason, unsupportedContentType) { - if strings.HasSuffix(e.Reason, `: ""`) { - cErr = &ValidationError{ + if strings.HasPrefix(e.Reason, prefixInvalidCT) { + if strings.HasSuffix(e.Reason, `""`) { + return &ValidationError{ Status: http.StatusUnsupportedMediaType, - Title: "header 'Content-Type' is required", - } - } else { - cErr = &ValidationError{ - Status: http.StatusUnsupportedMediaType, - Title: "unsupported content type " + strings.TrimPrefix(e.Reason, unsupportedContentType), + Title: "header Content-Type is required", } } - } else { - cErr = &ValidationError{ - Status: http.StatusBadRequest, - Title: e.Error(), + return &ValidationError{ + Status: http.StatusUnsupportedMediaType, + Title: prefixUnsupportedCT + strings.TrimPrefix(e.Reason, prefixInvalidCT), } } - return cErr + return &ValidationError{ + Status: http.StatusBadRequest, + Title: e.Error(), + } } func convertErrInvalidRequired(e *RequestError) *ValidationError { - var cErr *ValidationError if e.Reason == ErrInvalidRequired.Error() && e.Parameter != nil { - cErr = &ValidationError{ + return &ValidationError{ Status: http.StatusBadRequest, - Title: fmt.Sprintf("Parameter '%s' in %s is required", e.Parameter.Name, e.Parameter.In), - } - } else { - cErr = &ValidationError{ - Status: http.StatusBadRequest, - Title: e.Error(), + Title: fmt.Sprintf("parameter %q in %s is required", e.Parameter.Name, e.Parameter.In), } } - return cErr + return &ValidationError{ + Status: http.StatusBadRequest, + Title: e.Error(), + } } func convertParseError(e *RequestError, innerErr *ParseError) *ValidationError { - var cErr *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" { - cErr = &ValidationError{ + return &ValidationError{ Status: http.StatusNotFound, - Title: fmt.Sprintf("Resource not found with '%s' value: %v", e.Parameter.Name, innerErr.Value), + Title: fmt.Sprintf("resource not found with %q value: %v", e.Parameter.Name, innerErr.Value), } - } else if strings.HasPrefix(innerErr.Reason, "unsupported content type") { - cErr = &ValidationError{ + } 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" { - cErr = &ValidationError{ + return &ValidationError{ Status: http.StatusBadRequest, - Title: fmt.Sprintf("Parameter '%s' in %s is invalid: %v is %s", + Title: fmt.Sprintf("parameter %q in %s is invalid: %v is %s", e.Parameter.Name, e.Parameter.In, rootErr.Value, rootErr.Reason), } - } else { - cErr = &ValidationError{ - Status: http.StatusBadRequest, - Title: innerErr.Reason, - } + } + return &ValidationError{ + Status: http.StatusBadRequest, + Title: innerErr.Reason, } } - return cErr + return nil } -var propertyMissingNameRE = regexp.MustCompile(`Property '(?P[^']*)' is missing`) - func convertSchemaError(e *RequestError, innerErr *openapi3.SchemaError) *ValidationError { cErr := &ValidationError{Title: innerErr.Reason} @@ -151,34 +135,31 @@ func convertSchemaError(e *RequestError, innerErr *openapi3.SchemaError) *Valida 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 innerErr.JSONPointer() != nil { - pointer := innerErr.JSONPointer() - - cErr.Source = &ValidationErrorSource{ - Pointer: toJSONPointer(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" && - innerErr.Reason == "JSON value is not one of the allowed values" { + 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, ", ")) + 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 == true) && + (e.Parameter.Explode == nil || *e.Parameter.Explode) && (e.Parameter.Style == "" || e.Parameter.Style == "form") && strings.Contains(value, ",") { parts := strings.Split(value, ",") - cErr.Detail = cErr.Detail + "; " + fmt.Sprintf("perhaps you intended '?%s=%s'", - e.Parameter.Name, strings.Join(parts, "&"+e.Parameter.Name+"=")) + cErr.Detail = fmt.Sprintf("%s; perhaps you intended '?%s=%s'", + cErr.Detail, + e.Parameter.Name, + strings.Join(parts, "&"+e.Parameter.Name+"=")) } } return cErr diff --git a/vendor/github.com/getkin/kin-openapi/openapi3filter/validation_handler.go b/vendor/github.com/getkin/kin-openapi/openapi3filter/validation_handler.go index 336187f49..eeb1ca1ea 100644 --- a/vendor/github.com/getkin/kin-openapi/openapi3filter/validation_handler.go +++ b/vendor/github.com/getkin/kin-openapi/openapi3filter/validation_handler.go @@ -3,6 +3,10 @@ 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 @@ -14,15 +18,21 @@ var _ AuthenticationFunc = NoopAuthenticationFunc type ValidationHandler struct { Handler http.Handler AuthenticationFunc AuthenticationFunc - SwaggerFile string + File string ErrorEncoder ErrorEncoder - router *Router + router routers.Router } func (h *ValidationHandler) Load() error { - h.router = NewRouter() - - if err := h.router.AddSwaggerFromFile(h.SwaggerFile); err != nil { + 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 } @@ -69,7 +79,7 @@ func (h *ValidationHandler) before(w http.ResponseWriter, r *http.Request) (hand func (h *ValidationHandler) validateRequest(r *http.Request) error { // Find route - route, pathParams, err := h.router.FindRoute(r.Method, r.URL) + route, pathParams, err := h.router.FindRoute(r) if err != nil { return err } diff --git a/vendor/github.com/getkin/kin-openapi/pathpattern/node.go b/vendor/github.com/getkin/kin-openapi/routers/legacy/pathpattern/node.go similarity index 98% rename from vendor/github.com/getkin/kin-openapi/pathpattern/node.go rename to vendor/github.com/getkin/kin-openapi/routers/legacy/pathpattern/node.go index 43e2959d4..862199864 100644 --- a/vendor/github.com/getkin/kin-openapi/pathpattern/node.go +++ b/vendor/github.com/getkin/kin-openapi/routers/legacy/pathpattern/node.go @@ -212,7 +212,7 @@ loop: // Find variable name i := strings.IndexByte(remaining, '}') if i < 0 { - return nil, fmt.Errorf("Missing '}' in: %s", path) + return nil, fmt.Errorf("missing '}' in: %s", path) } variableName := strings.TrimSpace(remaining[1:i]) remaining = remaining[i+1:] @@ -247,7 +247,7 @@ loop: if suffix.Kind == SuffixKindRegExp { regExp, err := regexp.Compile(suffix.Pattern) if err != nil { - return nil, fmt.Errorf("Invalid regular expression in: %s", path) + return nil, fmt.Errorf("invalid regular expression in: %s", path) } suffix.regExp = regExp } 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..ecaae1348 --- /dev/null +++ b/vendor/github.com/getkin/kin-openapi/routers/legacy/router.go @@ -0,0 +1,164 @@ +// 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, _ := server.ParameterNames() + 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..3cdbee32c --- /dev/null +++ b/vendor/github.com/getkin/kin-openapi/routers/types.go @@ -0,0 +1,35 @@ +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(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/mattn/go-colorable/noncolorable.go b/vendor/github.com/mattn/go-colorable/noncolorable.go index 95f2c6be2..2dcb09aab 100644 --- a/vendor/github.com/mattn/go-colorable/noncolorable.go +++ b/vendor/github.com/mattn/go-colorable/noncolorable.go @@ -27,7 +27,10 @@ loop: } if c1 != 0x1b { bw[0] = c1 - w.out.Write(bw[:]) + _, err = w.out.Write(bw[:]) + if err != nil { + break loop + } continue } c2, err := er.ReadByte() diff --git a/vendor/github.com/mattn/go-isatty/.travis.yml b/vendor/github.com/mattn/go-isatty/.travis.yml deleted file mode 100644 index 604314dd4..000000000 --- a/vendor/github.com/mattn/go-isatty/.travis.yml +++ /dev/null @@ -1,14 +0,0 @@ -language: go -sudo: false -go: - - 1.13.x - - tip - -before_install: - - go get -t -v ./... - -script: - - ./go.test.sh - -after_success: - - bash <(curl -s https://codecov.io/bash) diff --git a/vendor/github.com/mattn/go-isatty/isatty_bsd.go b/vendor/github.com/mattn/go-isatty/isatty_bsd.go index 711f28808..39bbcf00f 100644 --- a/vendor/github.com/mattn/go-isatty/isatty_bsd.go +++ b/vendor/github.com/mattn/go-isatty/isatty_bsd.go @@ -1,3 +1,4 @@ +//go:build (darwin || freebsd || openbsd || netbsd || dragonfly) && !appengine // +build darwin freebsd openbsd netbsd dragonfly // +build !appengine diff --git a/vendor/github.com/mattn/go-isatty/isatty_others.go b/vendor/github.com/mattn/go-isatty/isatty_others.go index ff714a376..31503226f 100644 --- a/vendor/github.com/mattn/go-isatty/isatty_others.go +++ b/vendor/github.com/mattn/go-isatty/isatty_others.go @@ -1,4 +1,5 @@ -// +build appengine js nacl +//go:build appengine || js || nacl || wasm +// +build appengine js nacl wasm package isatty diff --git a/vendor/github.com/mattn/go-isatty/isatty_plan9.go b/vendor/github.com/mattn/go-isatty/isatty_plan9.go index c5b6e0c08..bae7f9bb3 100644 --- a/vendor/github.com/mattn/go-isatty/isatty_plan9.go +++ b/vendor/github.com/mattn/go-isatty/isatty_plan9.go @@ -1,3 +1,4 @@ +//go:build plan9 // +build plan9 package isatty diff --git a/vendor/github.com/mattn/go-isatty/isatty_solaris.go b/vendor/github.com/mattn/go-isatty/isatty_solaris.go index bdd5c79a0..0c3acf2dc 100644 --- a/vendor/github.com/mattn/go-isatty/isatty_solaris.go +++ b/vendor/github.com/mattn/go-isatty/isatty_solaris.go @@ -1,5 +1,5 @@ -// +build solaris -// +build !appengine +//go:build solaris && !appengine +// +build solaris,!appengine package isatty @@ -8,10 +8,9 @@ import ( ) // IsTerminal returns true if the given file descriptor is a terminal. -// see: http://src.illumos.org/source/xref/illumos-gate/usr/src/lib/libbc/libc/gen/common/isatty.c +// see: https://src.illumos.org/source/xref/illumos-gate/usr/src/lib/libc/port/gen/isatty.c func IsTerminal(fd uintptr) bool { - var termio unix.Termio - err := unix.IoctlSetTermio(int(fd), unix.TCGETA, &termio) + _, err := unix.IoctlGetTermio(int(fd), unix.TCGETA) return err == nil } diff --git a/vendor/github.com/mattn/go-isatty/isatty_tcgets.go b/vendor/github.com/mattn/go-isatty/isatty_tcgets.go index 31a1ca973..67787657f 100644 --- a/vendor/github.com/mattn/go-isatty/isatty_tcgets.go +++ b/vendor/github.com/mattn/go-isatty/isatty_tcgets.go @@ -1,4 +1,5 @@ -// +build linux aix +//go:build (linux || aix || zos) && !appengine +// +build linux aix zos // +build !appengine package isatty diff --git a/vendor/github.com/mattn/go-isatty/isatty_windows.go b/vendor/github.com/mattn/go-isatty/isatty_windows.go index 1fa869154..8e3c99171 100644 --- a/vendor/github.com/mattn/go-isatty/isatty_windows.go +++ b/vendor/github.com/mattn/go-isatty/isatty_windows.go @@ -1,5 +1,5 @@ -// +build windows -// +build !appengine +//go:build windows && !appengine +// +build windows,!appengine package isatty @@ -76,7 +76,7 @@ func isCygwinPipeName(name string) bool { } // getFileNameByHandle use the undocomented ntdll NtQueryObject to get file full name from file handler -// since GetFileInformationByHandleEx is not avilable under windows Vista and still some old fashion +// since GetFileInformationByHandleEx is not available under windows Vista and still some old fashion // guys are using Windows XP, this is a workaround for those guys, it will also work on system from // Windows vista to 10 // see https://stackoverflow.com/a/18792477 for details diff --git a/vendor/github.com/mattn/go-isatty/renovate.json b/vendor/github.com/mattn/go-isatty/renovate.json deleted file mode 100644 index 5ae9d96b7..000000000 --- a/vendor/github.com/mattn/go-isatty/renovate.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "extends": [ - "config:base" - ], - "postUpdateOptions": [ - "gomodTidy" - ] -} diff --git a/vendor/modules.txt b/vendor/modules.txt index 1974ce038..687e172b4 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -25,12 +25,14 @@ github.com/docker/go-units # github.com/fatih/color v1.10.0 ## explicit; go 1.13 github.com/fatih/color -# github.com/getkin/kin-openapi v0.49.0 +# github.com/getkin/kin-openapi v0.83.0 ## explicit; go 1.14 github.com/getkin/kin-openapi/jsoninfo github.com/getkin/kin-openapi/openapi3 github.com/getkin/kin-openapi/openapi3filter -github.com/getkin/kin-openapi/pathpattern +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 ## explicit github.com/ghodss/yaml @@ -88,10 +90,10 @@ github.com/mailru/easyjson/jwriter # github.com/mattermost/xml-roundtrip-validator v0.0.0-20201208211235-fe770d50d911 ## explicit; go 1.14 github.com/mattermost/xml-roundtrip-validator -# github.com/mattn/go-colorable v0.1.8 +# github.com/mattn/go-colorable v0.1.9 ## explicit; go 1.13 github.com/mattn/go-colorable -# github.com/mattn/go-isatty v0.0.12 +# github.com/mattn/go-isatty v0.0.14 ## explicit; go 1.12 github.com/mattn/go-isatty # github.com/matttproud/golang_protobuf_extensions v1.0.1 From 644593d535e507c99a5b9ceabb3d38cd67110f5f Mon Sep 17 00:00:00 2001 From: Alex Schneider Date: Mon, 29 Nov 2021 11:05:45 +0100 Subject: [PATCH 2/6] Fix muxer --- server/mux.go | 27 +++++++++++++++------------ 1 file changed, 15 insertions(+), 12 deletions(-) diff --git a/server/mux.go b/server/mux.go index c5b77df5e..7e1b134df 100644 --- a/server/mux.go +++ b/server/mux.go @@ -7,8 +7,8 @@ import ( "strings" "github.com/getkin/kin-openapi/openapi3" - "github.com/getkin/kin-openapi/openapi3filter" - "github.com/getkin/kin-openapi/pathpattern" + "github.com/getkin/kin-openapi/routers" + "github.com/getkin/kin-openapi/routers/legacy/pathpattern" "github.com/avenga/couper/config" "github.com/avenga/couper/config/request" @@ -24,6 +24,7 @@ import ( type Mux struct { endpointRoot *pathpattern.Node fileRoot *pathpattern.Node + handler map[*routers.Route]http.Handler opts *runtime.MuxOptions spaRoot *pathpattern.Node } @@ -57,6 +58,7 @@ func NewMux(options *runtime.MuxOptions) *Mux { endpointRoot: &pathpattern.Node{}, fileRoot: &pathpattern.Node{}, spaRoot: &pathpattern.Node{}, + handler: make(map[*routers.Route]http.Handler), } for path, h := range opts.EndpointRoutes { @@ -118,21 +120,22 @@ func (m *Mux) mustAddRoute(root *pathpattern.Node, methods []string, path string serverOpts = optsHandler.Options() } - node.Value = &openapi3filter.Route{ - Method: method, - Path: path, - Handler: handler, + node.Value = &routers.Route{ + Method: method, + Path: path, Server: &openapi3.Server{Variables: map[string]*openapi3.ServerVariable{ - serverOptionsKey: {Default: serverOpts}, + serverOptionsKey: {Default: fmt.Sprintf("%#v", serverOpts)}, }}, } + + m.handler[node.Value.(*routers.Route)] = handler } return m } func (m *Mux) FindHandler(req *http.Request) http.Handler { - var route *openapi3filter.Route + var route *routers.Route node, paramValues := m.match(m.endpointRoot, req) if node == nil { @@ -160,7 +163,7 @@ func (m *Mux) FindHandler(req *http.Request) http.Handler { } } - route, _ = node.Value.(*openapi3filter.Route) + route, _ = node.Value.(*routers.Route) pathParams := make(request.PathParameter, len(paramValues)) paramKeys := node.VariableNames @@ -181,7 +184,7 @@ func (m *Mux) FindHandler(req *http.Request) http.Handler { ctx = context.WithValue(ctx, request.PathParams, pathParams) *req = *req.WithContext(ctx) - return route.Handler + return m.handler[route] } func (m *Mux) match(root *pathpattern.Node, req *http.Request) (*pathpattern.Node, []string) { @@ -196,8 +199,8 @@ func (m *Mux) hasFileResponse(req *http.Request) (http.Handler, bool) { return nil, false } - route := node.Value.(*openapi3filter.Route) - fileHandler := route.Handler + route := node.Value.(*routers.Route) + fileHandler := m.handler[route] unprotectedHandler := getChildHandler(fileHandler) if fh, ok := unprotectedHandler.(*handler.File); ok { return fileHandler, fh.HasResponse(req) From cb6f0ce9c377cffc31e602ca3f10107c3fb93a79 Mon Sep 17 00:00:00 2001 From: Alex Schneider Date: Mon, 29 Nov 2021 11:06:11 +0100 Subject: [PATCH 3/6] Fix validation --- handler/validation/openapi.go | 22 ++++++++++++---------- handler/validation/openapi_options.go | 6 +++--- 2 files changed, 15 insertions(+), 13 deletions(-) diff --git a/handler/validation/openapi.go b/handler/validation/openapi.go index e431d8e40..96ac4a015 100644 --- a/handler/validation/openapi.go +++ b/handler/validation/openapi.go @@ -11,12 +11,14 @@ import ( "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/avenga/couper/config/request" "github.com/avenga/couper/eval" ) -var routers sync.Map +var routersStore sync.Map type OpenAPI struct { options *OpenAPIOptions @@ -31,10 +33,10 @@ func NewOpenAPI(opts *OpenAPIOptions) *OpenAPI { } } -func (v *OpenAPI) getRouter(key, origin string) (*openapi3filter.Router, error) { - router, exists := routers.Load(key) +func (v *OpenAPI) getRouter(key, origin string) (routers.Router, error) { + router, exists := routersStore.Load(key) if !exists { - clonedSwagger := cloneSwagger(v.options.swagger) + clonedSwagger := cloneSwagger(v.options.doc) var newServers []string for _, s := range clonedSwagger.Servers { @@ -69,16 +71,16 @@ func (v *OpenAPI) getRouter(key, origin string) (*openapi3filter.Router, error) clonedSwagger.AddServer(&openapi3.Server{URL: ns}) } - r := openapi3filter.NewRouter() - if err := r.AddSwagger(clonedSwagger); err != nil { + r, err := legacy.NewRouter(clonedSwagger) + if err != nil { return nil, err } - routers.Store(key, r) + routersStore.Store(key, r) return r, nil } - return router.(*openapi3filter.Router), nil + return router.(routers.Router), nil } func (v *OpenAPI) ValidateRequest(req *http.Request, key string) (*openapi3filter.RequestValidationInput, error) { @@ -101,7 +103,7 @@ func (v *OpenAPI) ValidateRequest(req *http.Request, key string) (*openapi3filte return nil, nil } - route, pathParams, err := router.FindRoute(req.Method, &serverURL) + route, pathParams, err := router.FindRoute(req) if err != nil { err = fmt.Errorf("'%s %s': %w", req.Method, req.URL.Path, err) if ctx, ok := req.Context().Value(request.OpenAPI).(*OpenAPIContext); ok { @@ -185,7 +187,7 @@ func (v *OpenAPI) ValidateResponse(beresp *http.Response, requestValidationInput return nil } -func cloneSwagger(s *openapi3.Swagger) *openapi3.Swagger { +func cloneSwagger(s *openapi3.T) *openapi3.T { sw := *s // this is not a deep clone; we only want to add servers sw.Servers = s.Servers[:] diff --git a/handler/validation/openapi_options.go b/handler/validation/openapi_options.go index 59d5e6e35..b3cf457d9 100644 --- a/handler/validation/openapi_options.go +++ b/handler/validation/openapi_options.go @@ -17,7 +17,7 @@ type OpenAPIOptions struct { ignoreRequestViolations bool ignoreResponseViolations bool filterOptions *openapi3filter.Options - swagger *openapi3.Swagger + doc *openapi3.T } // NewOpenAPIOptions takes a list of openAPI configuration due to merging configurations. @@ -45,7 +45,7 @@ func NewOpenAPIOptionsFromBytes(openapi *config.OpenAPI, bytes []byte) (*OpenAPI return nil, nil } - swagger, err := openapi3.NewSwaggerLoader().LoadSwaggerFromData(bytes) + doc, err := openapi3.NewLoader().LoadFromData(bytes) if err != nil { return nil, fmt.Errorf("error loading openapi file: %w", err) } @@ -63,6 +63,6 @@ func NewOpenAPIOptionsFromBytes(openapi *config.OpenAPI, bytes []byte) (*OpenAPI }, ignoreRequestViolations: openapi.IgnoreRequestViolations, ignoreResponseViolations: openapi.IgnoreResponseViolations, - swagger: swagger, + doc: doc, }, nil } From 74d887731a2cc504702a1c6b7dd569882fdead47 Mon Sep 17 00:00:00 2001 From: Alex Schneider Date: Mon, 29 Nov 2021 11:06:30 +0100 Subject: [PATCH 4/6] Update error messages in the tests --- handler/transport/backend_test.go | 4 ++-- handler/validation/openapi_test.go | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/handler/transport/backend_test.go b/handler/transport/backend_test.go index 3821f004e..98935d9ac 100644 --- a/handler/transport/backend_test.go +++ b/handler/transport/backend_test.go @@ -192,7 +192,7 @@ func TestBackend_RoundTrip_Validation(t *testing.T) { http.MethodPost, "/get", "backend validation error", - "'POST /get': Path doesn't support the HTTP method", + "'POST /get': method not allowed", }, { "invalid request, IgnoreRequestViolations", @@ -200,7 +200,7 @@ func TestBackend_RoundTrip_Validation(t *testing.T) { http.MethodPost, "/get", "", - "'POST /get': Path doesn't support the HTTP method", + "'POST /get': method not allowed", }, { "invalid response", diff --git a/handler/validation/openapi_test.go b/handler/validation/openapi_test.go index d136bf0d2..ea2ccd1db 100644 --- a/handler/validation/openapi_test.go +++ b/handler/validation/openapi_test.go @@ -67,10 +67,10 @@ func TestOpenAPIValidator_ValidateRequest(t *testing.T) { wantBody bool wantErrLog string }{ - {"GET without required query", "/a?b", nil, false, "backend validation error: Parameter 'b' in query has an error: must have a value: must have a value"}, + {"GET without required query", "/a?b", nil, false, `backend validation error: parameter "b" in query has an error: value is required but missing: value is required but missing`}, {"GET with required query", "/a?b=value", nil, false, ""}, {"GET with required path", "/a/value", nil, false, ""}, - {"GET with required path missing", "/a//", nil, false, "backend validation error: Parameter 'b' in query has an error: must have a value: must have a value"}, + {"GET with required path missing", "/a//", nil, false, `backend validation error: parameter "b" in query has an error: value is required but missing: value is required but missing`}, {"GET with optional query", "/b", nil, false, ""}, {"GET with optional path param", "/b/a", nil, false, ""}, {"GET with required json body", "/json", strings.NewReader(`["hans", "wurst"]`), true, ""}, From dd68ac450e822447e2e29841b2e857da680c890e Mon Sep 17 00:00:00 2001 From: Alex Schneider Date: Mon, 29 Nov 2021 11:06:47 +0100 Subject: [PATCH 5/6] Update test due to better validation --- handler/validation/testdata/backend_04_openapi.yaml | 4 ---- 1 file changed, 4 deletions(-) diff --git a/handler/validation/testdata/backend_04_openapi.yaml b/handler/validation/testdata/backend_04_openapi.yaml index 572554eb6..b9b89ed9c 100644 --- a/handler/validation/testdata/backend_04_openapi.yaml +++ b/handler/validation/testdata/backend_04_openapi.yaml @@ -8,10 +8,6 @@ servers: variables: sub: default: 'api' - - url: 'http://{broken/v1' - variables: - broken: - default: https://api.example.com - url: 'https://{sub}.example.com/anything' variables: sub: From f1db9b634b0ae8abcefa2977690d1f0e6ff745ba Mon Sep 17 00:00:00 2001 From: Alex Schneider Date: Tue, 30 Nov 2021 10:06:04 +0100 Subject: [PATCH 6/6] Changelog --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index c78d95fc5..7d9c1c441 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,10 @@ Unreleased changes are available as `avenga/couper:edge` container. * exclude file descriptor limit startup-logs for Windows ([#396](https://github.com/avenga/couper/pull/396), [#383](https://github.com/avenga/couper/pull/383)) * possible race conditions while updating JWKS for the [JWT access control](./docs/REFERENCE.md#jwt-block) ([#398](https://github.com/avenga/couper/pull/398)) +* **Dependencies** + * Update modules for [OpenAPI](./docs/REFERENCE.md#openapi-block) validation ([#399](https://github.com/avenga/couper/pull/399)) + * `github.com/getkin/kin-openapi v0.49.0` => `github.com/getkin/kin-openapi v0.83.0` + --- ## [1.6](https://github.com/avenga/couper/releases/tag/1.6)