From 85a53dcd65cfa93c6172c085a3ca13d4f6e69f8e Mon Sep 17 00:00:00 2001 From: Brian Dwyer Date: Mon, 15 Jul 2024 15:04:33 -0400 Subject: [PATCH] Fix and add other TF versions to the matrix --- .github/workflows/test.yml | 4 + go.mod | 5 +- go.sum | 10 + vendor/github.com/getkin/kin-openapi/LICENSE | 21 + .../getkin/kin-openapi/openapi3/callback.go | 54 + .../getkin/kin-openapi/openapi3/components.go | 370 +++ .../getkin/kin-openapi/openapi3/contact.go | 68 + .../getkin/kin-openapi/openapi3/content.go | 124 + .../kin-openapi/openapi3/discriminator.go | 61 + .../getkin/kin-openapi/openapi3/doc.go | 4 + .../getkin/kin-openapi/openapi3/encoding.go | 148 ++ .../getkin/kin-openapi/openapi3/errors.go | 59 + .../getkin/kin-openapi/openapi3/example.go | 85 + .../openapi3/example_validation.go | 16 + .../getkin/kin-openapi/openapi3/extension.go | 32 + .../kin-openapi/openapi3/external_docs.go | 73 + .../getkin/kin-openapi/openapi3/header.go | 96 + .../getkin/kin-openapi/openapi3/helpers.go | 261 ++ .../getkin/kin-openapi/openapi3/info.go | 103 + .../kin-openapi/openapi3/internalize_refs.go | 546 ++++ .../getkin/kin-openapi/openapi3/license.go | 66 + .../getkin/kin-openapi/openapi3/link.go | 94 + .../getkin/kin-openapi/openapi3/loader.go | 1192 +++++++++ .../kin-openapi/openapi3/loader_uri_reader.go | 116 + .../getkin/kin-openapi/openapi3/maplike.go | 402 +++ .../getkin/kin-openapi/openapi3/marsh.go | 34 + .../getkin/kin-openapi/openapi3/media_type.go | 179 ++ .../getkin/kin-openapi/openapi3/openapi3.go | 205 ++ .../getkin/kin-openapi/openapi3/operation.go | 222 ++ .../getkin/kin-openapi/openapi3/parameter.go | 416 +++ .../getkin/kin-openapi/openapi3/path_item.go | 248 ++ .../getkin/kin-openapi/openapi3/paths.go | 268 ++ .../getkin/kin-openapi/openapi3/ref.go | 9 + .../getkin/kin-openapi/openapi3/refs.go | 1247 +++++++++ .../getkin/kin-openapi/openapi3/refs.tmpl | 152 ++ .../kin-openapi/openapi3/refs_test.tmpl | 54 + .../kin-openapi/openapi3/request_body.go | 138 + .../getkin/kin-openapi/openapi3/response.go | 227 ++ .../getkin/kin-openapi/openapi3/schema.go | 2247 +++++++++++++++++ .../kin-openapi/openapi3/schema_formats.go | 169 ++ .../kin-openapi/openapi3/schema_pattern.go | 29 + .../openapi3/schema_validation_settings.go | 79 + .../openapi3/security_requirements.go | 51 + .../kin-openapi/openapi3/security_scheme.go | 429 ++++ .../openapi3/serialization_method.go | 17 + .../getkin/kin-openapi/openapi3/server.go | 302 +++ .../getkin/kin-openapi/openapi3/tag.go | 99 + .../openapi3/validation_options.go | 133 + .../getkin/kin-openapi/openapi3/visited.go | 41 + .../getkin/kin-openapi/openapi3/xml.go | 78 + vendor/github.com/invopop/yaml/.gitignore | 20 + vendor/github.com/invopop/yaml/.golangci.toml | 16 + vendor/github.com/invopop/yaml/LICENSE | 50 + vendor/github.com/invopop/yaml/README.md | 128 + vendor/github.com/invopop/yaml/fields.go | 499 ++++ vendor/github.com/invopop/yaml/yaml.go | 312 +++ vendor/github.com/mohae/deepcopy/.gitignore | 26 + vendor/github.com/mohae/deepcopy/.travis.yml | 11 + vendor/github.com/mohae/deepcopy/LICENSE | 21 + vendor/github.com/mohae/deepcopy/README.md | 8 + vendor/github.com/mohae/deepcopy/deepcopy.go | 125 + .../perimeterx/marshmallow/.gitignore | 4 + .../perimeterx/marshmallow/CHANGELOG.md | 49 + .../perimeterx/marshmallow/CODE_OF_CONDUCT.md | 133 + .../perimeterx/marshmallow/CONTRIBUTING.md | 47 + .../github.com/perimeterx/marshmallow/LICENSE | 21 + .../perimeterx/marshmallow/README.md | 205 ++ .../perimeterx/marshmallow/cache.go | 63 + .../github.com/perimeterx/marshmallow/doc.go | 10 + .../perimeterx/marshmallow/errors.go | 101 + .../perimeterx/marshmallow/options.go | 96 + .../perimeterx/marshmallow/reflection.go | 197 ++ .../perimeterx/marshmallow/unmarshal.go | 383 +++ .../marshmallow/unmarshal_from_json_map.go | 295 +++ vendor/modules.txt | 14 +- 75 files changed, 13914 insertions(+), 3 deletions(-) create mode 100644 vendor/github.com/getkin/kin-openapi/LICENSE create mode 100644 vendor/github.com/getkin/kin-openapi/openapi3/callback.go create mode 100644 vendor/github.com/getkin/kin-openapi/openapi3/components.go create mode 100644 vendor/github.com/getkin/kin-openapi/openapi3/contact.go create mode 100644 vendor/github.com/getkin/kin-openapi/openapi3/content.go create mode 100644 vendor/github.com/getkin/kin-openapi/openapi3/discriminator.go create mode 100644 vendor/github.com/getkin/kin-openapi/openapi3/doc.go create mode 100644 vendor/github.com/getkin/kin-openapi/openapi3/encoding.go create mode 100644 vendor/github.com/getkin/kin-openapi/openapi3/errors.go create mode 100644 vendor/github.com/getkin/kin-openapi/openapi3/example.go create mode 100644 vendor/github.com/getkin/kin-openapi/openapi3/example_validation.go create mode 100644 vendor/github.com/getkin/kin-openapi/openapi3/extension.go create mode 100644 vendor/github.com/getkin/kin-openapi/openapi3/external_docs.go create mode 100644 vendor/github.com/getkin/kin-openapi/openapi3/header.go create mode 100644 vendor/github.com/getkin/kin-openapi/openapi3/helpers.go create mode 100644 vendor/github.com/getkin/kin-openapi/openapi3/info.go create mode 100644 vendor/github.com/getkin/kin-openapi/openapi3/internalize_refs.go create mode 100644 vendor/github.com/getkin/kin-openapi/openapi3/license.go create mode 100644 vendor/github.com/getkin/kin-openapi/openapi3/link.go create mode 100644 vendor/github.com/getkin/kin-openapi/openapi3/loader.go create mode 100644 vendor/github.com/getkin/kin-openapi/openapi3/loader_uri_reader.go create mode 100644 vendor/github.com/getkin/kin-openapi/openapi3/maplike.go create mode 100644 vendor/github.com/getkin/kin-openapi/openapi3/marsh.go create mode 100644 vendor/github.com/getkin/kin-openapi/openapi3/media_type.go create mode 100644 vendor/github.com/getkin/kin-openapi/openapi3/openapi3.go create mode 100644 vendor/github.com/getkin/kin-openapi/openapi3/operation.go create mode 100644 vendor/github.com/getkin/kin-openapi/openapi3/parameter.go create mode 100644 vendor/github.com/getkin/kin-openapi/openapi3/path_item.go create mode 100644 vendor/github.com/getkin/kin-openapi/openapi3/paths.go create mode 100644 vendor/github.com/getkin/kin-openapi/openapi3/ref.go create mode 100644 vendor/github.com/getkin/kin-openapi/openapi3/refs.go create mode 100644 vendor/github.com/getkin/kin-openapi/openapi3/refs.tmpl create mode 100644 vendor/github.com/getkin/kin-openapi/openapi3/refs_test.tmpl create mode 100644 vendor/github.com/getkin/kin-openapi/openapi3/request_body.go create mode 100644 vendor/github.com/getkin/kin-openapi/openapi3/response.go create mode 100644 vendor/github.com/getkin/kin-openapi/openapi3/schema.go create mode 100644 vendor/github.com/getkin/kin-openapi/openapi3/schema_formats.go create mode 100644 vendor/github.com/getkin/kin-openapi/openapi3/schema_pattern.go create mode 100644 vendor/github.com/getkin/kin-openapi/openapi3/schema_validation_settings.go create mode 100644 vendor/github.com/getkin/kin-openapi/openapi3/security_requirements.go create mode 100644 vendor/github.com/getkin/kin-openapi/openapi3/security_scheme.go create mode 100644 vendor/github.com/getkin/kin-openapi/openapi3/serialization_method.go create mode 100644 vendor/github.com/getkin/kin-openapi/openapi3/server.go create mode 100644 vendor/github.com/getkin/kin-openapi/openapi3/tag.go create mode 100644 vendor/github.com/getkin/kin-openapi/openapi3/validation_options.go create mode 100644 vendor/github.com/getkin/kin-openapi/openapi3/visited.go create mode 100644 vendor/github.com/getkin/kin-openapi/openapi3/xml.go create mode 100644 vendor/github.com/invopop/yaml/.gitignore create mode 100644 vendor/github.com/invopop/yaml/.golangci.toml create mode 100644 vendor/github.com/invopop/yaml/LICENSE create mode 100644 vendor/github.com/invopop/yaml/README.md create mode 100644 vendor/github.com/invopop/yaml/fields.go create mode 100644 vendor/github.com/invopop/yaml/yaml.go create mode 100644 vendor/github.com/mohae/deepcopy/.gitignore create mode 100644 vendor/github.com/mohae/deepcopy/.travis.yml create mode 100644 vendor/github.com/mohae/deepcopy/LICENSE create mode 100644 vendor/github.com/mohae/deepcopy/README.md create mode 100644 vendor/github.com/mohae/deepcopy/deepcopy.go create mode 100644 vendor/github.com/perimeterx/marshmallow/.gitignore create mode 100644 vendor/github.com/perimeterx/marshmallow/CHANGELOG.md create mode 100644 vendor/github.com/perimeterx/marshmallow/CODE_OF_CONDUCT.md create mode 100644 vendor/github.com/perimeterx/marshmallow/CONTRIBUTING.md create mode 100644 vendor/github.com/perimeterx/marshmallow/LICENSE create mode 100644 vendor/github.com/perimeterx/marshmallow/README.md create mode 100644 vendor/github.com/perimeterx/marshmallow/cache.go create mode 100644 vendor/github.com/perimeterx/marshmallow/doc.go create mode 100644 vendor/github.com/perimeterx/marshmallow/errors.go create mode 100644 vendor/github.com/perimeterx/marshmallow/options.go create mode 100644 vendor/github.com/perimeterx/marshmallow/reflection.go create mode 100644 vendor/github.com/perimeterx/marshmallow/unmarshal.go create mode 100644 vendor/github.com/perimeterx/marshmallow/unmarshal_from_json_map.go diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 7b7529f6..a1cbc600 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -65,6 +65,10 @@ jobs: - '1.3.*' - '1.4.*' - '1.5.*' + - '1.6.*' + - '1.7.*' + - '1.8.*' + - '1.9.*' steps: - name: Check out code into the Go module directory uses: actions/checkout@v4 diff --git a/go.mod b/go.mod index 08295ae9..6a249b8f 100644 --- a/go.mod +++ b/go.mod @@ -6,6 +6,7 @@ require ( github.com/aws/aws-sdk-go-v2/feature/dynamodb/attributevalue v1.14.9 github.com/aws/aws-sdk-go-v2/service/dynamodb v1.34.3 github.com/aws/smithy-go v1.20.3 + github.com/getkin/kin-openapi v0.126.0 github.com/go-openapi/spec v0.21.0 github.com/go-openapi/strfmt v0.23.0 github.com/go-openapi/validate v0.24.0 @@ -36,7 +37,6 @@ require ( github.com/go-openapi/jsonreference v0.21.0 // indirect github.com/go-openapi/loads v0.22.0 // indirect github.com/go-openapi/swag v0.23.0 // indirect - github.com/go-test/deep v1.0.8 // indirect github.com/golang/protobuf v1.5.4 // indirect github.com/google/go-cmp v0.6.0 // indirect github.com/google/uuid v1.6.0 // indirect @@ -61,6 +61,7 @@ require ( github.com/hashicorp/yamux v0.1.1 // indirect github.com/huandu/xstrings v1.4.0 // indirect github.com/imdario/mergo v0.3.16 // indirect + github.com/invopop/yaml v0.3.1 // indirect github.com/josharian/intern v1.0.0 // indirect github.com/mailru/easyjson v0.7.7 // indirect github.com/mattn/go-colorable v0.1.13 // indirect @@ -71,8 +72,10 @@ require ( github.com/mitchellh/go-wordwrap v1.0.1 // indirect github.com/mitchellh/mapstructure v1.5.0 // indirect github.com/mitchellh/reflectwalk v1.0.2 // indirect + github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 // indirect github.com/oklog/run v1.1.0 // indirect github.com/oklog/ulid v1.3.1 // indirect + github.com/perimeterx/marshmallow v1.1.5 // indirect github.com/posener/complete v1.2.3 // indirect github.com/shopspring/decimal v1.3.1 // indirect github.com/spf13/cast v1.6.0 // indirect diff --git a/go.sum b/go.sum index c5491864..f6cd83e2 100644 --- a/go.sum +++ b/go.sum @@ -54,6 +54,8 @@ github.com/fatih/color v1.17.0 h1:GlRw1BRJxkpqUCBKzKOw098ed57fEsKeNjpTe3cSjK4= github.com/fatih/color v1.17.0/go.mod h1:YZ7TlrGPkiz6ku9fK3TLD/pl3CpsiFyu8N92HLgmosI= github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= +github.com/getkin/kin-openapi v0.126.0 h1:c2cSgLnAsS0xYfKsgt5oBV6MYRM/giU8/RtwUY4wyfY= +github.com/getkin/kin-openapi v0.126.0/go.mod h1:7mONz8IwmSRg6RttPu6v8U/OJ+gr+J99qSFNjPGSQqw= github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 h1:+zs/tPmkDkHx3U66DAb0lQFJrpS6731Oaa12ikc+DiI= github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376/go.mod h1:an3vInlBmSxCcxctByoQdvwPiA7DTK7jaaFDBTtu0ic= github.com/go-git/go-billy/v5 v5.5.0 h1:yEY4yhzCDuMGSv83oGxiBotRzhwhNr8VZyphhiu+mTU= @@ -148,6 +150,8 @@ github.com/huandu/xstrings v1.4.0/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq github.com/imdario/mergo v0.3.11/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= github.com/imdario/mergo v0.3.16 h1:wwQJbIsHYGMUyLSPrEq1CT16AhnhNJQ51+4fdHUnCl4= github.com/imdario/mergo v0.3.16/go.mod h1:WBLT9ZmE3lPoWsEzCh9LPo3TiwVN+ZKEjmz+hD27ysY= +github.com/invopop/yaml v0.3.1 h1:f0+ZpmhfBSS4MhG+4HYseMdJhoeeopbSKbq5Rpeelso= +github.com/invopop/yaml v0.3.1/go.mod h1:PMOp3nn4/12yEZUFfmOuNHJsZToEEOwoWsT+D81KkeA= github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A= github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo= github.com/jhump/protoreflect v1.15.1 h1:HUMERORf3I3ZdX05WaQ6MIpd/NJ434hTp5YiKgfCL6c= @@ -188,10 +192,14 @@ github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RR github.com/mitchellh/reflectwalk v1.0.0/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ= github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= +github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 h1:RWengNIwukTxcDr9M+97sNutRR1RKhG96O6jWumTTnw= +github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826/go.mod h1:TaXosZuwdSHYgviHp1DAtfrULt5eUgsSMsZf+YrPgl8= github.com/oklog/run v1.1.0 h1:GEenZ1cK0+q0+wsJew9qUg/DyD8k3JzYsZAi5gYi2mA= github.com/oklog/run v1.1.0/go.mod h1:sVPdnTZT1zYwAJeCMu2Th4T21pA3FPOQRfWjQlk7DVU= github.com/oklog/ulid v1.3.1 h1:EGfNDEx6MqHz8B3uNV6QAib1UR2Lm97sHi3ocA6ESJ4= github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= +github.com/perimeterx/marshmallow v1.1.5 h1:a2LALqQ1BlHM8PZblsDdidgv1mWi1DgC2UmX50IvK2s= +github.com/perimeterx/marshmallow v1.1.5/go.mod h1:dsXbUu8CRzfYP5a87xpp0xq9S3u0Vchtcl8we9tYaXw= github.com/pjbgf/sha1cd v0.3.0 h1:4D5XXmUUBUl/xQ6IjCkEAbqXskkq/4O7LmGn0AqMDs4= github.com/pjbgf/sha1cd v0.3.0/go.mod h1:nZ1rrWOcGJ5uZgEEVL1VUM9iRQiZvWdbZjkKyFzPPsI= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= @@ -217,6 +225,8 @@ github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5 github.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1FQKckRals= github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/ugorji/go/codec v1.2.7 h1:YPXUKf7fYbp/y8xloBqZOw2qaVggbfwMlI8WM3wZUJ0= +github.com/ugorji/go/codec v1.2.7/go.mod h1:WGN1fab3R1fzQlVQTkfxVtIBhWDRqOviHU95kRgeqEY= github.com/vmihailenco/msgpack v3.3.3+incompatible/go.mod h1:fy3FlTQTDXWkZ7Bh6AcGMlsjHatGryHQYUTf1ShIgkk= github.com/vmihailenco/msgpack v4.0.4+incompatible h1:dSLoQfGFAo3F6OoNhwUmLwVgaUXK79GlxNBwueZn0xI= github.com/vmihailenco/msgpack v4.0.4+incompatible/go.mod h1:fy3FlTQTDXWkZ7Bh6AcGMlsjHatGryHQYUTf1ShIgkk= diff --git a/vendor/github.com/getkin/kin-openapi/LICENSE b/vendor/github.com/getkin/kin-openapi/LICENSE new file mode 100644 index 00000000..992b9831 --- /dev/null +++ b/vendor/github.com/getkin/kin-openapi/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2017-2018 the project authors. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/vendor/github.com/getkin/kin-openapi/openapi3/callback.go b/vendor/github.com/getkin/kin-openapi/openapi3/callback.go new file mode 100644 index 00000000..34a6bea3 --- /dev/null +++ b/vendor/github.com/getkin/kin-openapi/openapi3/callback.go @@ -0,0 +1,54 @@ +package openapi3 + +import ( + "context" + "sort" +) + +// Callback is specified by OpenAPI/Swagger standard version 3. +// See https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#callback-object +type Callback struct { + Extensions map[string]any `json:"-" yaml:"-"` + + m map[string]*PathItem +} + +// NewCallback builds a Callback object with path items in insertion order. +func NewCallback(opts ...NewCallbackOption) *Callback { + Callback := NewCallbackWithCapacity(len(opts)) + for _, opt := range opts { + opt(Callback) + } + return Callback +} + +// NewCallbackOption describes options to NewCallback func +type NewCallbackOption func(*Callback) + +// WithCallback adds Callback as an option to NewCallback +func WithCallback(cb string, pathItem *PathItem) NewCallbackOption { + return func(callback *Callback) { + if p := pathItem; p != nil && cb != "" { + callback.Set(cb, p) + } + } +} + +// Validate returns an error if Callback does not comply with the OpenAPI spec. +func (callback *Callback) Validate(ctx context.Context, opts ...ValidationOption) error { + ctx = WithValidationOptions(ctx, opts...) + + keys := make([]string, 0, callback.Len()) + for key := range callback.Map() { + keys = append(keys, key) + } + sort.Strings(keys) + for _, key := range keys { + v := callback.Value(key) + if err := v.Validate(ctx); err != nil { + return err + } + } + + return validateExtensions(ctx, callback.Extensions) +} diff --git a/vendor/github.com/getkin/kin-openapi/openapi3/components.go b/vendor/github.com/getkin/kin-openapi/openapi3/components.go new file mode 100644 index 00000000..98c4b96c --- /dev/null +++ b/vendor/github.com/getkin/kin-openapi/openapi3/components.go @@ -0,0 +1,370 @@ +package openapi3 + +import ( + "context" + "encoding/json" + "fmt" + "sort" + + "github.com/go-openapi/jsonpointer" +) + +type ( + Callbacks map[string]*CallbackRef + Examples map[string]*ExampleRef + Headers map[string]*HeaderRef + Links map[string]*LinkRef + ParametersMap map[string]*ParameterRef + RequestBodies map[string]*RequestBodyRef + ResponseBodies map[string]*ResponseRef + Schemas map[string]*SchemaRef + SecuritySchemes map[string]*SecuritySchemeRef +) + +// Components is specified by OpenAPI/Swagger standard version 3. +// See https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#components-object +type Components struct { + Extensions map[string]any `json:"-" yaml:"-"` + + Schemas Schemas `json:"schemas,omitempty" yaml:"schemas,omitempty"` + Parameters ParametersMap `json:"parameters,omitempty" yaml:"parameters,omitempty"` + Headers Headers `json:"headers,omitempty" yaml:"headers,omitempty"` + RequestBodies RequestBodies `json:"requestBodies,omitempty" yaml:"requestBodies,omitempty"` + Responses ResponseBodies `json:"responses,omitempty" yaml:"responses,omitempty"` + SecuritySchemes SecuritySchemes `json:"securitySchemes,omitempty" yaml:"securitySchemes,omitempty"` + Examples Examples `json:"examples,omitempty" yaml:"examples,omitempty"` + Links Links `json:"links,omitempty" yaml:"links,omitempty"` + Callbacks Callbacks `json:"callbacks,omitempty" yaml:"callbacks,omitempty"` +} + +func NewComponents() Components { + return Components{} +} + +// MarshalJSON returns the JSON encoding of Components. +func (components Components) MarshalJSON() ([]byte, error) { + x, err := components.MarshalYAML() + if err != nil { + return nil, err + } + return json.Marshal(x) +} + +// MarshalYAML returns the YAML encoding of Components. +func (components Components) MarshalYAML() (any, error) { + m := make(map[string]any, 9+len(components.Extensions)) + for k, v := range components.Extensions { + m[k] = v + } + if x := components.Schemas; len(x) != 0 { + m["schemas"] = x + } + if x := components.Parameters; len(x) != 0 { + m["parameters"] = x + } + if x := components.Headers; len(x) != 0 { + m["headers"] = x + } + if x := components.RequestBodies; len(x) != 0 { + m["requestBodies"] = x + } + if x := components.Responses; len(x) != 0 { + m["responses"] = x + } + if x := components.SecuritySchemes; len(x) != 0 { + m["securitySchemes"] = x + } + if x := components.Examples; len(x) != 0 { + m["examples"] = x + } + if x := components.Links; len(x) != 0 { + m["links"] = x + } + if x := components.Callbacks; len(x) != 0 { + m["callbacks"] = x + } + return m, nil +} + +// UnmarshalJSON sets Components to a copy of data. +func (components *Components) UnmarshalJSON(data []byte) error { + type ComponentsBis Components + var x ComponentsBis + if err := json.Unmarshal(data, &x); err != nil { + return unmarshalError(err) + } + _ = json.Unmarshal(data, &x.Extensions) + delete(x.Extensions, "schemas") + delete(x.Extensions, "parameters") + delete(x.Extensions, "headers") + delete(x.Extensions, "requestBodies") + delete(x.Extensions, "responses") + delete(x.Extensions, "securitySchemes") + delete(x.Extensions, "examples") + delete(x.Extensions, "links") + delete(x.Extensions, "callbacks") + if len(x.Extensions) == 0 { + x.Extensions = nil + } + *components = Components(x) + return nil +} + +// Validate returns an error if Components does not comply with the OpenAPI spec. +func (components *Components) Validate(ctx context.Context, opts ...ValidationOption) (err error) { + ctx = WithValidationOptions(ctx, opts...) + + schemas := make([]string, 0, len(components.Schemas)) + for name := range components.Schemas { + schemas = append(schemas, name) + } + sort.Strings(schemas) + for _, k := range schemas { + v := components.Schemas[k] + if err = ValidateIdentifier(k); err != nil { + return fmt.Errorf("schema %q: %w", k, err) + } + if err = v.Validate(ctx); err != nil { + return fmt.Errorf("schema %q: %w", k, err) + } + } + + parameters := make([]string, 0, len(components.Parameters)) + for name := range components.Parameters { + parameters = append(parameters, name) + } + sort.Strings(parameters) + for _, k := range parameters { + v := components.Parameters[k] + if err = ValidateIdentifier(k); err != nil { + return fmt.Errorf("parameter %q: %w", k, err) + } + if err = v.Validate(ctx); err != nil { + return fmt.Errorf("parameter %q: %w", k, err) + } + } + + requestBodies := make([]string, 0, len(components.RequestBodies)) + for name := range components.RequestBodies { + requestBodies = append(requestBodies, name) + } + sort.Strings(requestBodies) + for _, k := range requestBodies { + v := components.RequestBodies[k] + if err = ValidateIdentifier(k); err != nil { + return fmt.Errorf("request body %q: %w", k, err) + } + if err = v.Validate(ctx); err != nil { + return fmt.Errorf("request body %q: %w", k, err) + } + } + + responses := make([]string, 0, len(components.Responses)) + for name := range components.Responses { + responses = append(responses, name) + } + sort.Strings(responses) + for _, k := range responses { + if err = ValidateIdentifier(k); err != nil { + return fmt.Errorf("response %q: %w", k, err) + } + v := components.Responses[k] + if err = v.Validate(ctx); err != nil { + return fmt.Errorf("response %q: %w", k, err) + } + } + + headers := make([]string, 0, len(components.Headers)) + for name := range components.Headers { + headers = append(headers, name) + } + sort.Strings(headers) + for _, k := range headers { + v := components.Headers[k] + if err = ValidateIdentifier(k); err != nil { + return fmt.Errorf("header %q: %w", k, err) + } + if err = v.Validate(ctx); err != nil { + return fmt.Errorf("header %q: %w", k, err) + } + } + + securitySchemes := make([]string, 0, len(components.SecuritySchemes)) + for name := range components.SecuritySchemes { + securitySchemes = append(securitySchemes, name) + } + sort.Strings(securitySchemes) + for _, k := range securitySchemes { + v := components.SecuritySchemes[k] + if err = ValidateIdentifier(k); err != nil { + return fmt.Errorf("security scheme %q: %w", k, err) + } + if err = v.Validate(ctx); err != nil { + return fmt.Errorf("security scheme %q: %w", k, err) + } + } + + examples := make([]string, 0, len(components.Examples)) + for name := range components.Examples { + examples = append(examples, name) + } + sort.Strings(examples) + for _, k := range examples { + v := components.Examples[k] + if err = ValidateIdentifier(k); err != nil { + return fmt.Errorf("example %q: %w", k, err) + } + if err = v.Validate(ctx); err != nil { + return fmt.Errorf("example %q: %w", k, err) + } + } + + links := make([]string, 0, len(components.Links)) + for name := range components.Links { + links = append(links, name) + } + sort.Strings(links) + for _, k := range links { + v := components.Links[k] + if err = ValidateIdentifier(k); err != nil { + return fmt.Errorf("link %q: %w", k, err) + } + if err = v.Validate(ctx); err != nil { + return fmt.Errorf("link %q: %w", k, err) + } + } + + callbacks := make([]string, 0, len(components.Callbacks)) + for name := range components.Callbacks { + callbacks = append(callbacks, name) + } + sort.Strings(callbacks) + for _, k := range callbacks { + v := components.Callbacks[k] + if err = ValidateIdentifier(k); err != nil { + return fmt.Errorf("callback %q: %w", k, err) + } + if err = v.Validate(ctx); err != nil { + return fmt.Errorf("callback %q: %w", k, err) + } + } + + return validateExtensions(ctx, components.Extensions) +} + +var _ jsonpointer.JSONPointable = (*Schemas)(nil) + +// JSONLookup implements https://pkg.go.dev/github.com/go-openapi/jsonpointer#JSONPointable +func (m Schemas) JSONLookup(token string) (any, error) { + if v, ok := m[token]; !ok || v == nil { + return nil, fmt.Errorf("no schema %q", token) + } else if ref := v.Ref; ref != "" { + return &Ref{Ref: ref}, nil + } else { + return v.Value, nil + } +} + +var _ jsonpointer.JSONPointable = (*ParametersMap)(nil) + +// JSONLookup implements https://pkg.go.dev/github.com/go-openapi/jsonpointer#JSONPointable +func (m ParametersMap) JSONLookup(token string) (any, error) { + if v, ok := m[token]; !ok || v == nil { + return nil, fmt.Errorf("no parameter %q", token) + } else if ref := v.Ref; ref != "" { + return &Ref{Ref: ref}, nil + } else { + return v.Value, nil + } +} + +var _ jsonpointer.JSONPointable = (*Headers)(nil) + +// JSONLookup implements https://pkg.go.dev/github.com/go-openapi/jsonpointer#JSONPointable +func (m Headers) JSONLookup(token string) (any, error) { + if v, ok := m[token]; !ok || v == nil { + return nil, fmt.Errorf("no header %q", token) + } else if ref := v.Ref; ref != "" { + return &Ref{Ref: ref}, nil + } else { + return v.Value, nil + } +} + +var _ jsonpointer.JSONPointable = (*RequestBodyRef)(nil) + +// JSONLookup implements https://pkg.go.dev/github.com/go-openapi/jsonpointer#JSONPointable +func (m RequestBodies) JSONLookup(token string) (any, error) { + if v, ok := m[token]; !ok || v == nil { + return nil, fmt.Errorf("no request body %q", token) + } else if ref := v.Ref; ref != "" { + return &Ref{Ref: ref}, nil + } else { + return v.Value, nil + } +} + +var _ jsonpointer.JSONPointable = (*ResponseRef)(nil) + +// JSONLookup implements https://pkg.go.dev/github.com/go-openapi/jsonpointer#JSONPointable +func (m ResponseBodies) JSONLookup(token string) (any, error) { + if v, ok := m[token]; !ok || v == nil { + return nil, fmt.Errorf("no response body %q", token) + } else if ref := v.Ref; ref != "" { + return &Ref{Ref: ref}, nil + } else { + return v.Value, nil + } +} + +var _ jsonpointer.JSONPointable = (*SecuritySchemes)(nil) + +// JSONLookup implements https://pkg.go.dev/github.com/go-openapi/jsonpointer#JSONPointable +func (m SecuritySchemes) JSONLookup(token string) (any, error) { + if v, ok := m[token]; !ok || v == nil { + return nil, fmt.Errorf("no security scheme body %q", token) + } else if ref := v.Ref; ref != "" { + return &Ref{Ref: ref}, nil + } else { + return v.Value, nil + } +} + +var _ jsonpointer.JSONPointable = (*Examples)(nil) + +// JSONLookup implements https://pkg.go.dev/github.com/go-openapi/jsonpointer#JSONPointable +func (m Examples) JSONLookup(token string) (any, error) { + if v, ok := m[token]; !ok || v == nil { + return nil, fmt.Errorf("no example body %q", token) + } else if ref := v.Ref; ref != "" { + return &Ref{Ref: ref}, nil + } else { + return v.Value, nil + } +} + +var _ jsonpointer.JSONPointable = (*Links)(nil) + +// JSONLookup implements https://pkg.go.dev/github.com/go-openapi/jsonpointer#JSONPointable +func (m Links) JSONLookup(token string) (any, error) { + if v, ok := m[token]; !ok || v == nil { + return nil, fmt.Errorf("no link body %q", token) + } else if ref := v.Ref; ref != "" { + return &Ref{Ref: ref}, nil + } else { + return v.Value, nil + } +} + +var _ jsonpointer.JSONPointable = (*Callbacks)(nil) + +// JSONLookup implements https://pkg.go.dev/github.com/go-openapi/jsonpointer#JSONPointable +func (m Callbacks) JSONLookup(token string) (any, error) { + if v, ok := m[token]; !ok || v == nil { + return nil, fmt.Errorf("no callback body %q", token) + } else if ref := v.Ref; ref != "" { + return &Ref{Ref: ref}, nil + } else { + return v.Value, nil + } +} diff --git a/vendor/github.com/getkin/kin-openapi/openapi3/contact.go b/vendor/github.com/getkin/kin-openapi/openapi3/contact.go new file mode 100644 index 00000000..6c76a6fb --- /dev/null +++ b/vendor/github.com/getkin/kin-openapi/openapi3/contact.go @@ -0,0 +1,68 @@ +package openapi3 + +import ( + "context" + "encoding/json" +) + +// Contact is specified by OpenAPI/Swagger standard version 3. +// See https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#contact-object +type Contact struct { + Extensions map[string]any `json:"-" yaml:"-"` + + Name string `json:"name,omitempty" yaml:"name,omitempty"` + URL string `json:"url,omitempty" yaml:"url,omitempty"` + Email string `json:"email,omitempty" yaml:"email,omitempty"` +} + +// MarshalJSON returns the JSON encoding of Contact. +func (contact Contact) MarshalJSON() ([]byte, error) { + x, err := contact.MarshalYAML() + if err != nil { + return nil, err + } + return json.Marshal(x) +} + +// MarshalYAML returns the YAML encoding of Contact. +func (contact Contact) MarshalYAML() (any, error) { + m := make(map[string]any, 3+len(contact.Extensions)) + for k, v := range contact.Extensions { + m[k] = v + } + if x := contact.Name; x != "" { + m["name"] = x + } + if x := contact.URL; x != "" { + m["url"] = x + } + if x := contact.Email; x != "" { + m["email"] = x + } + return m, nil +} + +// UnmarshalJSON sets Contact to a copy of data. +func (contact *Contact) UnmarshalJSON(data []byte) error { + type ContactBis Contact + var x ContactBis + if err := json.Unmarshal(data, &x); err != nil { + return unmarshalError(err) + } + _ = json.Unmarshal(data, &x.Extensions) + delete(x.Extensions, "name") + delete(x.Extensions, "url") + delete(x.Extensions, "email") + if len(x.Extensions) == 0 { + x.Extensions = nil + } + *contact = Contact(x) + return nil +} + +// Validate returns an error if Contact does not comply with the OpenAPI spec. +func (contact *Contact) Validate(ctx context.Context, opts ...ValidationOption) error { + ctx = WithValidationOptions(ctx, opts...) + + return validateExtensions(ctx, contact.Extensions) +} diff --git a/vendor/github.com/getkin/kin-openapi/openapi3/content.go b/vendor/github.com/getkin/kin-openapi/openapi3/content.go new file mode 100644 index 00000000..81b070ee --- /dev/null +++ b/vendor/github.com/getkin/kin-openapi/openapi3/content.go @@ -0,0 +1,124 @@ +package openapi3 + +import ( + "context" + "sort" + "strings" +) + +// Content is specified by OpenAPI/Swagger 3.0 standard. +type Content map[string]*MediaType + +func NewContent() Content { + return make(map[string]*MediaType) +} + +func NewContentWithSchema(schema *Schema, consumes []string) Content { + if len(consumes) == 0 { + return Content{ + "*/*": NewMediaType().WithSchema(schema), + } + } + content := make(map[string]*MediaType, len(consumes)) + for _, mediaType := range consumes { + content[mediaType] = NewMediaType().WithSchema(schema) + } + return content +} + +func NewContentWithSchemaRef(schema *SchemaRef, consumes []string) Content { + if len(consumes) == 0 { + return Content{ + "*/*": NewMediaType().WithSchemaRef(schema), + } + } + content := make(map[string]*MediaType, len(consumes)) + for _, mediaType := range consumes { + content[mediaType] = NewMediaType().WithSchemaRef(schema) + } + return content +} + +func NewContentWithJSONSchema(schema *Schema) Content { + return Content{ + "application/json": NewMediaType().WithSchema(schema), + } +} +func NewContentWithJSONSchemaRef(schema *SchemaRef) Content { + return Content{ + "application/json": NewMediaType().WithSchemaRef(schema), + } +} + +func NewContentWithFormDataSchema(schema *Schema) Content { + return Content{ + "multipart/form-data": NewMediaType().WithSchema(schema), + } +} + +func NewContentWithFormDataSchemaRef(schema *SchemaRef) Content { + return Content{ + "multipart/form-data": NewMediaType().WithSchemaRef(schema), + } +} + +func (content Content) Get(mime string) *MediaType { + // If the mime is empty then short-circuit to the wildcard. + // We do this here so that we catch only the specific case of + // and empty mime rather than a present, but invalid, mime type. + if mime == "" { + return content["*/*"] + } + // Start by making the most specific match possible + // by using the mime type in full. + if v := content[mime]; v != nil { + return v + } + // If an exact match is not found then we strip all + // metadata from the mime type and only use the x/y + // portion. + i := strings.IndexByte(mime, ';') + if i < 0 { + // If there is no metadata then preserve the full mime type + // string for later wildcard searches. + i = len(mime) + } + mime = mime[:i] + if v := content[mime]; v != nil { + return v + } + // If the x/y pattern has no specific match then we + // try the x/* pattern. + i = strings.IndexByte(mime, '/') + if i < 0 { + // In the case that the given mime type is not valid because it is + // missing the subtype we return nil so that this does not accidentally + // resolve with the wildcard. + return nil + } + mime = mime[:i] + "/*" + if v := content[mime]; v != nil { + return v + } + // Finally, the most generic match of */* is returned + // as a catch-all. + return content["*/*"] +} + +// Validate returns an error if Content does not comply with the OpenAPI spec. +func (content Content) Validate(ctx context.Context, opts ...ValidationOption) error { + ctx = WithValidationOptions(ctx, opts...) + + keys := make([]string, 0, len(content)) + for key := range content { + keys = append(keys, key) + } + sort.Strings(keys) + for _, k := range keys { + v := content[k] + if err := v.Validate(ctx); err != nil { + return err + } + } + return nil +} diff --git a/vendor/github.com/getkin/kin-openapi/openapi3/discriminator.go b/vendor/github.com/getkin/kin-openapi/openapi3/discriminator.go new file mode 100644 index 00000000..e8193bd9 --- /dev/null +++ b/vendor/github.com/getkin/kin-openapi/openapi3/discriminator.go @@ -0,0 +1,61 @@ +package openapi3 + +import ( + "context" + "encoding/json" +) + +// Discriminator is specified by OpenAPI/Swagger standard version 3. +// See https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#discriminator-object +type Discriminator struct { + Extensions map[string]any `json:"-" yaml:"-"` + + PropertyName string `json:"propertyName" yaml:"propertyName"` // required + Mapping map[string]string `json:"mapping,omitempty" yaml:"mapping,omitempty"` +} + +// MarshalJSON returns the JSON encoding of Discriminator. +func (discriminator Discriminator) MarshalJSON() ([]byte, error) { + x, err := discriminator.MarshalYAML() + if err != nil { + return nil, err + } + return json.Marshal(x) +} + +// MarshalYAML returns the YAML encoding of Discriminator. +func (discriminator Discriminator) MarshalYAML() (any, error) { + m := make(map[string]any, 2+len(discriminator.Extensions)) + for k, v := range discriminator.Extensions { + m[k] = v + } + m["propertyName"] = discriminator.PropertyName + if x := discriminator.Mapping; len(x) != 0 { + m["mapping"] = x + } + return m, nil +} + +// UnmarshalJSON sets Discriminator to a copy of data. +func (discriminator *Discriminator) UnmarshalJSON(data []byte) error { + type DiscriminatorBis Discriminator + var x DiscriminatorBis + if err := json.Unmarshal(data, &x); err != nil { + return unmarshalError(err) + } + _ = json.Unmarshal(data, &x.Extensions) + delete(x.Extensions, "propertyName") + delete(x.Extensions, "mapping") + if len(x.Extensions) == 0 { + x.Extensions = nil + } + *discriminator = Discriminator(x) + return nil +} + +// Validate returns an error if Discriminator does not comply with the OpenAPI spec. +func (discriminator *Discriminator) Validate(ctx context.Context, opts ...ValidationOption) error { + ctx = WithValidationOptions(ctx, opts...) + + return validateExtensions(ctx, discriminator.Extensions) +} diff --git a/vendor/github.com/getkin/kin-openapi/openapi3/doc.go b/vendor/github.com/getkin/kin-openapi/openapi3/doc.go new file mode 100644 index 00000000..41c9965c --- /dev/null +++ b/vendor/github.com/getkin/kin-openapi/openapi3/doc.go @@ -0,0 +1,4 @@ +// Package openapi3 parses and writes OpenAPI 3 specification documents. +// +// See https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.3.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 new file mode 100644 index 00000000..1bcdaea5 --- /dev/null +++ b/vendor/github.com/getkin/kin-openapi/openapi3/encoding.go @@ -0,0 +1,148 @@ +package openapi3 + +import ( + "context" + "encoding/json" + "fmt" + "sort" +) + +// Encoding is specified by OpenAPI/Swagger 3.0 standard. +// See https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#encoding-object +type Encoding struct { + Extensions map[string]any `json:"-" yaml:"-"` + + ContentType string `json:"contentType,omitempty" yaml:"contentType,omitempty"` + Headers Headers `json:"headers,omitempty" yaml:"headers,omitempty"` + Style string `json:"style,omitempty" yaml:"style,omitempty"` + Explode *bool `json:"explode,omitempty" yaml:"explode,omitempty"` + AllowReserved bool `json:"allowReserved,omitempty" yaml:"allowReserved,omitempty"` +} + +func NewEncoding() *Encoding { + return &Encoding{} +} + +func (encoding *Encoding) WithHeader(name string, header *Header) *Encoding { + return encoding.WithHeaderRef(name, &HeaderRef{ + Value: header, + }) +} + +func (encoding *Encoding) WithHeaderRef(name string, ref *HeaderRef) *Encoding { + headers := encoding.Headers + if headers == nil { + headers = make(map[string]*HeaderRef) + encoding.Headers = headers + } + headers[name] = ref + return encoding +} + +// MarshalJSON returns the JSON encoding of Encoding. +func (encoding Encoding) MarshalJSON() ([]byte, error) { + x, err := encoding.MarshalYAML() + if err != nil { + return nil, err + } + return json.Marshal(x) +} + +// MarshalYAML returns the YAML encoding of Encoding. +func (encoding Encoding) MarshalYAML() (any, error) { + m := make(map[string]any, 5+len(encoding.Extensions)) + for k, v := range encoding.Extensions { + m[k] = v + } + if x := encoding.ContentType; x != "" { + m["contentType"] = x + } + if x := encoding.Headers; len(x) != 0 { + m["headers"] = x + } + if x := encoding.Style; x != "" { + m["style"] = x + } + if x := encoding.Explode; x != nil { + m["explode"] = x + } + if x := encoding.AllowReserved; x { + m["allowReserved"] = x + } + return m, nil +} + +// UnmarshalJSON sets Encoding to a copy of data. +func (encoding *Encoding) UnmarshalJSON(data []byte) error { + type EncodingBis Encoding + var x EncodingBis + if err := json.Unmarshal(data, &x); err != nil { + return unmarshalError(err) + } + _ = json.Unmarshal(data, &x.Extensions) + delete(x.Extensions, "contentType") + delete(x.Extensions, "headers") + delete(x.Extensions, "style") + delete(x.Extensions, "explode") + delete(x.Extensions, "allowReserved") + if len(x.Extensions) == 0 { + x.Extensions = nil + } + *encoding = Encoding(x) + return nil +} + +// SerializationMethod returns a serialization method of request body. +// When serialization method is not defined the method returns the default serialization method. +func (encoding *Encoding) SerializationMethod() *SerializationMethod { + sm := &SerializationMethod{Style: SerializationForm, Explode: true} + if encoding != nil { + if encoding.Style != "" { + sm.Style = encoding.Style + } + if encoding.Explode != nil { + sm.Explode = *encoding.Explode + } + } + return sm +} + +// Validate returns an error if Encoding does not comply with the OpenAPI spec. +func (encoding *Encoding) Validate(ctx context.Context, opts ...ValidationOption) error { + ctx = WithValidationOptions(ctx, opts...) + + if encoding == nil { + return nil + } + + headers := make([]string, 0, len(encoding.Headers)) + for k := range encoding.Headers { + headers = append(headers, k) + } + sort.Strings(headers) + for _, k := range headers { + v := encoding.Headers[k] + if err := ValidateIdentifier(k); err != nil { + return nil + } + if err := v.Validate(ctx); err != nil { + return nil + } + } + + // Validate a media types's serialization method. + sm := encoding.SerializationMethod() + switch { + case sm.Style == SerializationForm && sm.Explode, + sm.Style == SerializationForm && !sm.Explode, + sm.Style == SerializationSpaceDelimited && sm.Explode, + sm.Style == SerializationSpaceDelimited && !sm.Explode, + sm.Style == SerializationPipeDelimited && sm.Explode, + sm.Style == SerializationPipeDelimited && !sm.Explode, + sm.Style == SerializationDeepObject && sm.Explode: + default: + return fmt.Errorf("serialization method with style=%q and explode=%v is not supported by media type", sm.Style, sm.Explode) + } + + return validateExtensions(ctx, encoding.Extensions) +} diff --git a/vendor/github.com/getkin/kin-openapi/openapi3/errors.go b/vendor/github.com/getkin/kin-openapi/openapi3/errors.go new file mode 100644 index 00000000..010dc889 --- /dev/null +++ b/vendor/github.com/getkin/kin-openapi/openapi3/errors.go @@ -0,0 +1,59 @@ +package openapi3 + +import ( + "bytes" + "errors" +) + +// MultiError is a collection of errors, intended for when +// multiple issues need to be reported upstream +type MultiError []error + +func (me MultiError) Error() string { + return spliceErr(" | ", me) +} + +func spliceErr(sep string, errs []error) string { + buff := &bytes.Buffer{} + for i, e := range errs { + buff.WriteString(e.Error()) + if i != len(errs)-1 { + buff.WriteString(sep) + } + } + return buff.String() +} + +// Is allows you to determine if a generic error is in fact a MultiError using `errors.Is()` +// It will also return true if any of the contained errors match target +func (me MultiError) Is(target error) bool { + if _, ok := target.(MultiError); ok { + return true + } + for _, e := range me { + if errors.Is(e, target) { + return true + } + } + return false +} + +// As allows you to use `errors.As()` to set target to the first error within the multi error that matches the target type +func (me MultiError) As(target any) bool { + for _, e := range me { + if errors.As(e, target) { + return true + } + } + return false +} + +type multiErrorForOneOf MultiError + +func (meo multiErrorForOneOf) Error() string { + return spliceErr(" Or ", meo) +} + +func (meo multiErrorForOneOf) Unwrap() error { + return MultiError(meo) +} diff --git a/vendor/github.com/getkin/kin-openapi/openapi3/example.go b/vendor/github.com/getkin/kin-openapi/openapi3/example.go new file mode 100644 index 00000000..f9a7a6b0 --- /dev/null +++ b/vendor/github.com/getkin/kin-openapi/openapi3/example.go @@ -0,0 +1,85 @@ +package openapi3 + +import ( + "context" + "encoding/json" + "errors" +) + +// Example is specified by OpenAPI/Swagger 3.0 standard. +// See https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#example-object +type Example struct { + Extensions map[string]any `json:"-" yaml:"-"` + + Summary string `json:"summary,omitempty" yaml:"summary,omitempty"` + Description string `json:"description,omitempty" yaml:"description,omitempty"` + Value any `json:"value,omitempty" yaml:"value,omitempty"` + ExternalValue string `json:"externalValue,omitempty" yaml:"externalValue,omitempty"` +} + +func NewExample(value any) *Example { + return &Example{Value: value} +} + +// MarshalJSON returns the JSON encoding of Example. +func (example Example) MarshalJSON() ([]byte, error) { + x, err := example.MarshalYAML() + if err != nil { + return nil, err + } + return json.Marshal(x) +} + +// MarshalYAML returns the YAML encoding of Example. +func (example Example) MarshalYAML() (any, error) { + m := make(map[string]any, 4+len(example.Extensions)) + for k, v := range example.Extensions { + m[k] = v + } + if x := example.Summary; x != "" { + m["summary"] = x + } + if x := example.Description; x != "" { + m["description"] = x + } + if x := example.Value; x != nil { + m["value"] = x + } + if x := example.ExternalValue; x != "" { + m["externalValue"] = x + } + return m, nil +} + +// UnmarshalJSON sets Example to a copy of data. +func (example *Example) UnmarshalJSON(data []byte) error { + type ExampleBis Example + var x ExampleBis + if err := json.Unmarshal(data, &x); err != nil { + return unmarshalError(err) + } + _ = json.Unmarshal(data, &x.Extensions) + delete(x.Extensions, "summary") + delete(x.Extensions, "description") + delete(x.Extensions, "value") + delete(x.Extensions, "externalValue") + if len(x.Extensions) == 0 { + x.Extensions = nil + } + *example = Example(x) + return nil +} + +// Validate returns an error if Example does not comply with the OpenAPI spec. +func (example *Example) Validate(ctx context.Context, opts ...ValidationOption) error { + ctx = WithValidationOptions(ctx, opts...) + + if example.Value != nil && example.ExternalValue != "" { + return errors.New("value and externalValue are mutually exclusive") + } + if example.Value == nil && example.ExternalValue == "" { + return errors.New("no value or externalValue field") + } + + return validateExtensions(ctx, example.Extensions) +} diff --git a/vendor/github.com/getkin/kin-openapi/openapi3/example_validation.go b/vendor/github.com/getkin/kin-openapi/openapi3/example_validation.go new file mode 100644 index 00000000..0d105c92 --- /dev/null +++ b/vendor/github.com/getkin/kin-openapi/openapi3/example_validation.go @@ -0,0 +1,16 @@ +package openapi3 + +import "context" + +func validateExampleValue(ctx context.Context, input any, schema *Schema) error { + opts := make([]SchemaValidationOption, 0, 2) + + if vo := getValidationOptions(ctx); vo.examplesValidationAsReq { + opts = append(opts, VisitAsRequest()) + } else if vo.examplesValidationAsRes { + opts = append(opts, VisitAsResponse()) + } + opts = append(opts, MultiErrors()) + + return schema.VisitJSON(input, opts...) +} diff --git a/vendor/github.com/getkin/kin-openapi/openapi3/extension.go b/vendor/github.com/getkin/kin-openapi/openapi3/extension.go new file mode 100644 index 00000000..ca86078f --- /dev/null +++ b/vendor/github.com/getkin/kin-openapi/openapi3/extension.go @@ -0,0 +1,32 @@ +package openapi3 + +import ( + "context" + "fmt" + "sort" + "strings" +) + +func validateExtensions(ctx context.Context, extensions map[string]any) error { // FIXME: newtype + Validate(...) + allowed := getValidationOptions(ctx).extraSiblingFieldsAllowed + + var unknowns []string + for k := range extensions { + if strings.HasPrefix(k, "x-") { + continue + } + if allowed != nil { + if _, ok := allowed[k]; ok { + continue + } + } + unknowns = append(unknowns, k) + } + + if len(unknowns) != 0 { + sort.Strings(unknowns) + return fmt.Errorf("extra sibling fields: %+v", unknowns) + } + + return nil +} diff --git a/vendor/github.com/getkin/kin-openapi/openapi3/external_docs.go b/vendor/github.com/getkin/kin-openapi/openapi3/external_docs.go new file mode 100644 index 00000000..bd99511a --- /dev/null +++ b/vendor/github.com/getkin/kin-openapi/openapi3/external_docs.go @@ -0,0 +1,73 @@ +package openapi3 + +import ( + "context" + "encoding/json" + "errors" + "fmt" + "net/url" +) + +// ExternalDocs is specified by OpenAPI/Swagger standard version 3. +// See https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#external-documentation-object +type ExternalDocs struct { + Extensions map[string]any `json:"-" yaml:"-"` + + Description string `json:"description,omitempty" yaml:"description,omitempty"` + URL string `json:"url,omitempty" yaml:"url,omitempty"` +} + +// MarshalJSON returns the JSON encoding of ExternalDocs. +func (e ExternalDocs) MarshalJSON() ([]byte, error) { + x, err := e.MarshalYAML() + if err != nil { + return nil, err + } + return json.Marshal(x) +} + +// MarshalYAML returns the YAML encoding of ExternalDocs. +func (e ExternalDocs) MarshalYAML() (any, error) { + m := make(map[string]any, 2+len(e.Extensions)) + for k, v := range e.Extensions { + m[k] = v + } + if x := e.Description; x != "" { + m["description"] = x + } + if x := e.URL; x != "" { + m["url"] = x + } + return m, nil +} + +// UnmarshalJSON sets ExternalDocs to a copy of data. +func (e *ExternalDocs) UnmarshalJSON(data []byte) error { + type ExternalDocsBis ExternalDocs + var x ExternalDocsBis + if err := json.Unmarshal(data, &x); err != nil { + return unmarshalError(err) + } + _ = json.Unmarshal(data, &x.Extensions) + delete(x.Extensions, "description") + delete(x.Extensions, "url") + if len(x.Extensions) == 0 { + x.Extensions = nil + } + *e = ExternalDocs(x) + return nil +} + +// Validate returns an error if ExternalDocs does not comply with the OpenAPI spec. +func (e *ExternalDocs) Validate(ctx context.Context, opts ...ValidationOption) error { + ctx = WithValidationOptions(ctx, opts...) + + if e.URL == "" { + return errors.New("url is required") + } + if _, err := url.Parse(e.URL); err != nil { + return fmt.Errorf("url is incorrect: %w", err) + } + + return validateExtensions(ctx, e.Extensions) +} diff --git a/vendor/github.com/getkin/kin-openapi/openapi3/header.go b/vendor/github.com/getkin/kin-openapi/openapi3/header.go new file mode 100644 index 00000000..dc542874 --- /dev/null +++ b/vendor/github.com/getkin/kin-openapi/openapi3/header.go @@ -0,0 +1,96 @@ +package openapi3 + +import ( + "context" + "errors" + "fmt" + + "github.com/go-openapi/jsonpointer" +) + +// Header is specified by OpenAPI/Swagger 3.0 standard. +// See https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#header-object +type Header struct { + Parameter +} + +var _ jsonpointer.JSONPointable = (*Header)(nil) + +// JSONLookup implements https://pkg.go.dev/github.com/go-openapi/jsonpointer#JSONPointable +func (header Header) JSONLookup(token string) (any, error) { + return header.Parameter.JSONLookup(token) +} + +// MarshalJSON returns the JSON encoding of Header. +func (header Header) MarshalJSON() ([]byte, error) { + return header.Parameter.MarshalJSON() +} + +// UnmarshalJSON sets Header to a copy of data. +func (header *Header) UnmarshalJSON(data []byte) error { + return header.Parameter.UnmarshalJSON(data) +} + +// MarshalYAML returns the JSON encoding of Header. +func (header Header) MarshalYAML() (any, error) { + return header.Parameter, nil +} + +// SerializationMethod returns a header's serialization method. +func (header *Header) SerializationMethod() (*SerializationMethod, error) { + style := header.Style + if style == "" { + style = SerializationSimple + } + explode := false + if header.Explode != nil { + explode = *header.Explode + } + return &SerializationMethod{Style: style, Explode: explode}, nil +} + +// Validate returns an error if Header does not comply with the OpenAPI spec. +func (header *Header) Validate(ctx context.Context, opts ...ValidationOption) error { + ctx = WithValidationOptions(ctx, opts...) + + if header.Name != "" { + return errors.New("header 'name' MUST NOT be specified, it is given in the corresponding headers map") + } + if header.In != "" { + return errors.New("header 'in' MUST NOT be specified, it is implicitly in header") + } + + // Validate a parameter's serialization method. + sm, err := header.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: %w", e) + } + + if (header.Schema == nil) == (len(header.Content) == 0) { + e := fmt.Errorf("parameter must contain exactly one of content and schema: %v", header) + return fmt.Errorf("header schema is invalid: %w", e) + } + if schema := header.Schema; schema != nil { + if err := schema.Validate(ctx); err != nil { + return fmt.Errorf("header schema is invalid: %w", err) + } + } + + if content := header.Content; content != nil { + e := errors.New("parameter content must only contain one entry") + if len(content) > 1 { + return fmt.Errorf("header content is invalid: %w", e) + } + + if err := content.Validate(ctx); err != nil { + return fmt.Errorf("header content is invalid: %w", err) + } + } + return nil +} diff --git a/vendor/github.com/getkin/kin-openapi/openapi3/helpers.go b/vendor/github.com/getkin/kin-openapi/openapi3/helpers.go new file mode 100644 index 00000000..cb1ed3a9 --- /dev/null +++ b/vendor/github.com/getkin/kin-openapi/openapi3/helpers.go @@ -0,0 +1,261 @@ +package openapi3 + +import ( + "fmt" + "net/url" + "path" + "reflect" + "regexp" + "sort" + "strings" + + "github.com/go-openapi/jsonpointer" +) + +const identifierChars = `a-zA-Z0-9._-` + +// IdentifierRegExp verifies whether Component object key matches contains just 'identifierChars', according to OpenAPI v3.x. +// InvalidIdentifierCharRegExp matches all characters not contained in 'identifierChars'. +// However, to be able supporting legacy OpenAPI v2.x, there is a need to customize above pattern in order not to fail +// converted v2-v3 validation +var ( + IdentifierRegExp = regexp.MustCompile(`^[` + identifierChars + `]+$`) + InvalidIdentifierCharRegExp = regexp.MustCompile(`[^` + identifierChars + `]`) +) + +// ValidateIdentifier returns an error if the given component name does not match [IdentifierRegExp]. +func ValidateIdentifier(value string) error { + if IdentifierRegExp.MatchString(value) { + return nil + } + return fmt.Errorf("identifier %q is not supported by OpenAPIv3 standard (charset: [%q])", value, identifierChars) +} + +// Float64Ptr is a helper for defining OpenAPI schemas. +func Float64Ptr(value float64) *float64 { + return &value +} + +// BoolPtr is a helper for defining OpenAPI schemas. +func BoolPtr(value bool) *bool { + return &value +} + +// Int64Ptr is a helper for defining OpenAPI schemas. +func Int64Ptr(value int64) *int64 { + return &value +} + +// Uint64Ptr is a helper for defining OpenAPI schemas. +func Uint64Ptr(value uint64) *uint64 { + return &value +} + +// componentNames returns the map keys in a sorted slice. +func componentNames[E any](s map[string]E) []string { + out := make([]string, 0, len(s)) + for i := range s { + out = append(out, i) + } + sort.Strings(out) + return out +} + +// copyURI makes a copy of the pointer. +func copyURI(u *url.URL) *url.URL { + if u == nil { + return nil + } + + c := *u // shallow-copy + return &c +} + +type componentRef interface { + RefString() string + RefPath() *url.URL + CollectionName() string +} + +// refersToSameDocument returns if the $ref refers to the same document. +// +// Documents in different directories will have distinct $ref values that resolve to +// the same document. +// For example, consider the 3 files: +// +// /records.yaml +// /root.yaml $ref: records.yaml +// /schema/other.yaml $ref: ../records.yaml +// +// The records.yaml reference in the 2 latter refers to the same document. +func refersToSameDocument(o1 componentRef, o2 componentRef) bool { + if o1 == nil || o2 == nil { + return false + } + + r1 := o1.RefPath() + r2 := o2.RefPath() + + if r1 == nil || r2 == nil { + return false + } + + // refURL is relative to the working directory & base spec file. + return referenceURIMatch(r1, r2) +} + +// referencesRootDocument returns if the $ref points to the root document of the OpenAPI spec. +// +// If the document has no location, perhaps loaded from data in memory, it always returns false. +func referencesRootDocument(doc *T, ref componentRef) bool { + if doc.url == nil || ref == nil || ref.RefPath() == nil { + return false + } + + refURL := *ref.RefPath() + refURL.Fragment = "" + + // Check referenced element was in the root document. + return referenceURIMatch(doc.url, &refURL) +} + +func referenceURIMatch(u1 *url.URL, u2 *url.URL) bool { + s1, s2 := *u1, *u2 + if s1.Scheme == "" { + s1.Scheme = "file" + } + if s2.Scheme == "" { + s2.Scheme = "file" + } + + return s1.String() == s2.String() +} + +// ReferencesComponentInRootDocument returns if the given component reference references +// the same document or element as another component reference in the root document's +// '#/components/'. If it does, it returns the name of it in the form +// '#/components//NameXXX' +// +// Of course given a component from the root document will always match itself. +// +// https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#reference-object +// https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#relative-references-in-urls +// +// Example. Take the spec with directory structure: +// +// openapi.yaml +// schemas/ +// ├─ record.yaml +// ├─ records.yaml +// +// In openapi.yaml we have: +// +// components: +// schemas: +// Record: +// $ref: schemas/record.yaml +// +// Case 1: records.yml references a component in the root document +// +// $ref: ../openapi.yaml#/components/schemas/Record +// +// This would return... +// +// #/components/schemas/Record +// +// Case 2: records.yml indirectly refers to the same schema +// as a schema the root document's '#/components/schemas'. +// +// $ref: ./record.yaml +// +// This would also return... +// +// #/components/schemas/Record +func ReferencesComponentInRootDocument(doc *T, ref componentRef) (string, bool) { + if ref == nil || ref.RefString() == "" { + return "", false + } + + // Case 1: + // Something like: ../another-folder/document.json#/myElement + if isRemoteReference(ref.RefString()) && isRootComponentReference(ref.RefString(), ref.CollectionName()) { + // Determine if it is *this* root doc. + if referencesRootDocument(doc, ref) { + _, name, _ := strings.Cut(ref.RefString(), path.Join("#/components/", ref.CollectionName())) + + return path.Join("#/components/", ref.CollectionName(), name), true + } + } + + // If there are no schemas defined in the root document return early. + if doc.Components == nil { + return "", false + } + + collection, _, err := jsonpointer.GetForToken(doc.Components, ref.CollectionName()) + if err != nil { + panic(err) // unreachable + } + + var components map[string]componentRef + + componentRefType := reflect.TypeOf(new(componentRef)).Elem() + if t := reflect.TypeOf(collection); t.Kind() == reflect.Map && + t.Key().Kind() == reflect.String && + t.Elem().AssignableTo(componentRefType) { + v := reflect.ValueOf(collection) + + components = make(map[string]componentRef, v.Len()) + for _, key := range v.MapKeys() { + strct := v.MapIndex(key) + // Type assertion safe, already checked via reflection above. + components[key.Interface().(string)] = strct.Interface().(componentRef) + } + } else { + return "", false + } + + // Case 2: + // Something like: ../openapi.yaml#/components/schemas/myElement + for name, s := range components { + // Must be a reference to a YAML file. + if !isWholeDocumentReference(s.RefString()) { + continue + } + + // Is the schema a ref to the same resource. + if !refersToSameDocument(s, ref) { + continue + } + + // Transform the remote ref to the equivalent schema in the root document. + return path.Join("#/components/", ref.CollectionName(), name), true + } + + return "", false +} + +// isElementReference takes a $ref value and checks if it references a specific element. +func isElementReference(ref string) bool { + return ref != "" && !isWholeDocumentReference(ref) +} + +// isSchemaReference takes a $ref value and checks if it references a schema element. +func isRootComponentReference(ref string, compType string) bool { + return isElementReference(ref) && strings.Contains(ref, path.Join("#/components/", compType)) +} + +// isWholeDocumentReference takes a $ref value and checks if it is whole document reference. +func isWholeDocumentReference(ref string) bool { + return ref != "" && !strings.ContainsAny(ref, "#") +} + +// isRemoteReference takes a $ref value and checks if it is remote reference. +func isRemoteReference(ref string) bool { + return ref != "" && !strings.HasPrefix(ref, "#") && !isURLReference(ref) +} + +// isURLReference takes a $ref value and checks if it is URL reference. +func isURLReference(ref string) bool { + return strings.HasPrefix(ref, "http://") || strings.HasPrefix(ref, "https://") || strings.HasPrefix(ref, "//") +} diff --git a/vendor/github.com/getkin/kin-openapi/openapi3/info.go b/vendor/github.com/getkin/kin-openapi/openapi3/info.go new file mode 100644 index 00000000..e2468285 --- /dev/null +++ b/vendor/github.com/getkin/kin-openapi/openapi3/info.go @@ -0,0 +1,103 @@ +package openapi3 + +import ( + "context" + "encoding/json" + "errors" +) + +// Info is specified by OpenAPI/Swagger standard version 3. +// See https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#info-object +type Info struct { + Extensions map[string]any `json:"-" yaml:"-"` + + Title string `json:"title" yaml:"title"` // Required + Description string `json:"description,omitempty" yaml:"description,omitempty"` + TermsOfService string `json:"termsOfService,omitempty" yaml:"termsOfService,omitempty"` + Contact *Contact `json:"contact,omitempty" yaml:"contact,omitempty"` + License *License `json:"license,omitempty" yaml:"license,omitempty"` + Version string `json:"version" yaml:"version"` // Required +} + +// MarshalJSON returns the JSON encoding of Info. +func (info Info) MarshalJSON() ([]byte, error) { + x, err := info.MarshalYAML() + if err != nil { + return nil, err + } + return json.Marshal(x) +} + +// MarshalYAML returns the YAML encoding of Info. +func (info *Info) MarshalYAML() (any, error) { + if info == nil { + return nil, nil + } + m := make(map[string]any, 6+len(info.Extensions)) + for k, v := range info.Extensions { + m[k] = v + } + m["title"] = info.Title + if x := info.Description; x != "" { + m["description"] = x + } + if x := info.TermsOfService; x != "" { + m["termsOfService"] = x + } + if x := info.Contact; x != nil { + m["contact"] = x + } + if x := info.License; x != nil { + m["license"] = x + } + m["version"] = info.Version + return m, nil +} + +// UnmarshalJSON sets Info to a copy of data. +func (info *Info) UnmarshalJSON(data []byte) error { + type InfoBis Info + var x InfoBis + if err := json.Unmarshal(data, &x); err != nil { + return unmarshalError(err) + } + _ = json.Unmarshal(data, &x.Extensions) + delete(x.Extensions, "title") + delete(x.Extensions, "description") + delete(x.Extensions, "termsOfService") + delete(x.Extensions, "contact") + delete(x.Extensions, "license") + delete(x.Extensions, "version") + if len(x.Extensions) == 0 { + x.Extensions = nil + } + *info = Info(x) + return nil +} + +// Validate returns an error if Info does not comply with the OpenAPI spec. +func (info *Info) Validate(ctx context.Context, opts ...ValidationOption) error { + ctx = WithValidationOptions(ctx, opts...) + + if contact := info.Contact; contact != nil { + if err := contact.Validate(ctx); err != nil { + return err + } + } + + if license := info.License; license != nil { + if err := license.Validate(ctx); err != nil { + return err + } + } + + if info.Version == "" { + return errors.New("value of version must be a non-empty string") + } + + if info.Title == "" { + return errors.New("value of title must be a non-empty string") + } + + return validateExtensions(ctx, info.Extensions) +} 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 00000000..b4742864 --- /dev/null +++ b/vendor/github.com/getkin/kin-openapi/openapi3/internalize_refs.go @@ -0,0 +1,546 @@ +package openapi3 + +import ( + "context" + "path" + "strings" +) + +// RefNameResolver maps a component to an name that is used as it's internalized name. +// +// The function should avoid name collisions (i.e. be a injective mapping). +// It must only contain characters valid for fixed field names: [IdentifierRegExp]. +type RefNameResolver func(*T, componentRef) string + +// DefaultRefResolver is a default implementation of refNameResolver for the +// InternalizeRefs function. +// +// The external reference is internalized to (hopefully) a unique name. If +// the external reference matches (by path) to another reference in the root +// document then the name of that component is used. +// +// The transformation involves: +// - Cutting the "#/components/" part. +// - Cutting the file extensions (.yaml/.json) from documents. +// - Trimming the common directory with the root spec. +// - Replace invalid characters with with underscores. +// +// This is an injective mapping over a "reasonable" amount of the possible openapi +// spec domain space but is not perfect. There might be edge cases. +func DefaultRefNameResolver(doc *T, ref componentRef) string { + if ref.RefString() == "" || ref.RefPath() == nil { + panic("unable to resolve reference to name") + } + + name := ref.RefPath() + + // If refering to a component in the root spec, no need to internalize just use + // the existing component. + // XXX(percivalalb): since this function call is iterating over components behind the + // scenes during an internalization call it actually starts interating over + // new & replaced internalized components. This might caused some edge cases, + // haven't found one yet but this might need to actually be used on a frozen copy + // of doc. + if nameInRoot, found := ReferencesComponentInRootDocument(doc, ref); found { + nameInRoot = strings.TrimPrefix(nameInRoot, "#") + + rootCompURI := copyURI(doc.url) + rootCompURI.Fragment = nameInRoot + name = rootCompURI + } + + filePath, componentPath := name.Path, name.Fragment + + // Cut out the "#/components/" to make the names shorter. + // XXX(percivalalb): This might cause collisions but is worth the brevity. + if b, a, ok := strings.Cut(componentPath, path.Join("components", ref.CollectionName(), "")); ok { + componentPath = path.Join(b, a) + } + + if filePath != "" { + // If the path is the same as the root doc, just remove. + if doc.url != nil && filePath == doc.url.Path { + filePath = "" + } + + // Remove the path extentions to make this JSON/YAML agnostic. + for ext := path.Ext(filePath); len(ext) > 0; ext = path.Ext(filePath) { + filePath = strings.TrimSuffix(filePath, ext) + } + + // Trim the common prefix with the root doc path. + if doc.url != nil { + commonDir := path.Dir(doc.url.Path) + for { + if commonDir == "." { // no common prefix + break + } + + if p, found := cutDirectories(filePath, commonDir); found { + filePath = p + break + } + + commonDir = path.Dir(commonDir) + } + } + } + + var internalizedName string + + // Trim .'s & slashes from start e.g. otherwise ./doc.yaml would end up as __doc + if filePath != "" { + internalizedName = strings.TrimLeft(filePath, "./") + } + + if componentPath != "" { + if internalizedName != "" { + internalizedName += "_" + } + + internalizedName += strings.TrimLeft(componentPath, "./") + } + + // Replace invalid characters in component fixed field names. + internalizedName = InvalidIdentifierCharRegExp.ReplaceAllString(internalizedName, "_") + + return internalizedName +} + +// cutDirectories removes the given directories from the start of the path if +// the path is a child. +func cutDirectories(p, dirs string) (string, bool) { + if dirs == "" || p == "" { + return p, false + } + + p = strings.TrimRight(p, "/") + dirs = strings.TrimRight(dirs, "/") + + var sb strings.Builder + sb.Grow(len(ParameterInHeader)) + for _, segments := range strings.Split(p, "/") { + sb.WriteString(segments) + + if sb.String() == p { + return strings.TrimPrefix(p, dirs), true + } + + sb.WriteRune('/') + } + + return p, false +} + +func isExternalRef(ref string, parentIsExternal bool) bool { + return ref != "" && (!strings.HasPrefix(ref, "#/components/") || parentIsExternal) +} + +func (doc *T) addSchemaToSpec(s *SchemaRef, refNameResolver RefNameResolver, parentIsExternal bool) bool { + if s == nil || !isExternalRef(s.Ref, parentIsExternal) { + return false + } + + name := refNameResolver(doc, s) + if doc.Components != nil { + if _, ok := doc.Components.Schemas[name]; ok { + s.Ref = "#/components/schemas/" + name + return true + } + } + + if doc.Components == nil { + doc.Components = &Components{} + } + if doc.Components.Schemas == nil { + doc.Components.Schemas = make(Schemas) + } + doc.Components.Schemas[name] = s.Value.NewRef() + s.Ref = "#/components/schemas/" + name + return true +} + +func (doc *T) addParameterToSpec(p *ParameterRef, refNameResolver RefNameResolver, parentIsExternal bool) bool { + if p == nil || !isExternalRef(p.Ref, parentIsExternal) { + return false + } + name := refNameResolver(doc, p) + if doc.Components != nil { + if _, ok := doc.Components.Parameters[name]; ok { + p.Ref = "#/components/parameters/" + name + return true + } + } + + if doc.Components == nil { + doc.Components = &Components{} + } + if doc.Components.Parameters == nil { + doc.Components.Parameters = make(ParametersMap) + } + doc.Components.Parameters[name] = &ParameterRef{Value: p.Value} + p.Ref = "#/components/parameters/" + name + return true +} + +func (doc *T) addHeaderToSpec(h *HeaderRef, refNameResolver RefNameResolver, parentIsExternal bool) bool { + if h == nil || !isExternalRef(h.Ref, parentIsExternal) { + return false + } + name := refNameResolver(doc, h) + if doc.Components != nil { + if _, ok := doc.Components.Headers[name]; ok { + h.Ref = "#/components/headers/" + name + return true + } + } + + if doc.Components == nil { + doc.Components = &Components{} + } + if doc.Components.Headers == nil { + doc.Components.Headers = make(Headers) + } + doc.Components.Headers[name] = &HeaderRef{Value: h.Value} + h.Ref = "#/components/headers/" + name + return true +} + +func (doc *T) addRequestBodyToSpec(r *RequestBodyRef, refNameResolver RefNameResolver, parentIsExternal bool) bool { + if r == nil || !isExternalRef(r.Ref, parentIsExternal) { + return false + } + name := refNameResolver(doc, r) + if doc.Components != nil { + if _, ok := doc.Components.RequestBodies[name]; ok { + r.Ref = "#/components/requestBodies/" + name + return true + } + } + + if doc.Components == nil { + doc.Components = &Components{} + } + if doc.Components.RequestBodies == nil { + doc.Components.RequestBodies = make(RequestBodies) + } + doc.Components.RequestBodies[name] = &RequestBodyRef{Value: r.Value} + r.Ref = "#/components/requestBodies/" + name + return true +} + +func (doc *T) addResponseToSpec(r *ResponseRef, refNameResolver RefNameResolver, parentIsExternal bool) bool { + if r == nil || !isExternalRef(r.Ref, parentIsExternal) { + return false + } + name := refNameResolver(doc, r) + if doc.Components != nil { + if _, ok := doc.Components.Responses[name]; ok { + r.Ref = "#/components/responses/" + name + return true + } + } + + if doc.Components == nil { + doc.Components = &Components{} + } + if doc.Components.Responses == nil { + doc.Components.Responses = make(ResponseBodies) + } + doc.Components.Responses[name] = &ResponseRef{Value: r.Value} + r.Ref = "#/components/responses/" + name + return true +} + +func (doc *T) addSecuritySchemeToSpec(ss *SecuritySchemeRef, refNameResolver RefNameResolver, parentIsExternal bool) { + if ss == nil || !isExternalRef(ss.Ref, parentIsExternal) { + return + } + name := refNameResolver(doc, ss) + if doc.Components != nil { + if _, ok := doc.Components.SecuritySchemes[name]; ok { + ss.Ref = "#/components/securitySchemes/" + name + return + } + } + + if doc.Components == nil { + doc.Components = &Components{} + } + 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, parentIsExternal bool) { + if e == nil || !isExternalRef(e.Ref, parentIsExternal) { + return + } + name := refNameResolver(doc, e) + if doc.Components != nil { + if _, ok := doc.Components.Examples[name]; ok { + e.Ref = "#/components/examples/" + name + return + } + } + + if doc.Components == nil { + doc.Components = &Components{} + } + 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, parentIsExternal bool) { + if l == nil || !isExternalRef(l.Ref, parentIsExternal) { + return + } + name := refNameResolver(doc, l) + if doc.Components != nil { + if _, ok := doc.Components.Links[name]; ok { + l.Ref = "#/components/links/" + name + return + } + } + + if doc.Components == nil { + doc.Components = &Components{} + } + 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, parentIsExternal bool) bool { + if c == nil || !isExternalRef(c.Ref, parentIsExternal) { + return false + } + name := refNameResolver(doc, c) + + if doc.Components == nil { + doc.Components = &Components{} + } + if doc.Components.Callbacks == nil { + doc.Components.Callbacks = make(Callbacks) + } + c.Ref = "#/components/callbacks/" + name + doc.Components.Callbacks[name] = &CallbackRef{Value: c.Value} + return true +} + +func (doc *T) derefSchema(s *Schema, refNameResolver RefNameResolver, parentIsExternal bool) { + if s == nil || doc.isVisitedSchema(s) { + return + } + + for _, list := range []SchemaRefs{s.AllOf, s.AnyOf, s.OneOf} { + for _, s2 := range list { + isExternal := doc.addSchemaToSpec(s2, refNameResolver, parentIsExternal) + if s2 != nil { + doc.derefSchema(s2.Value, refNameResolver, isExternal || parentIsExternal) + } + } + } + + for _, name := range componentNames(s.Properties) { + s2 := s.Properties[name] + isExternal := doc.addSchemaToSpec(s2, refNameResolver, parentIsExternal) + if s2 != nil { + doc.derefSchema(s2.Value, refNameResolver, isExternal || parentIsExternal) + } + } + for _, ref := range []*SchemaRef{s.Not, s.AdditionalProperties.Schema, s.Items} { + isExternal := doc.addSchemaToSpec(ref, refNameResolver, parentIsExternal) + if ref != nil { + doc.derefSchema(ref.Value, refNameResolver, isExternal || parentIsExternal) + } + } +} + +func (doc *T) derefHeaders(hs Headers, refNameResolver RefNameResolver, parentIsExternal bool) { + for _, name := range componentNames(hs) { + h := hs[name] + isExternal := doc.addHeaderToSpec(h, refNameResolver, parentIsExternal) + if doc.isVisitedHeader(h.Value) { + continue + } + doc.derefParameter(h.Value.Parameter, refNameResolver, parentIsExternal || isExternal) + } +} + +func (doc *T) derefExamples(es Examples, refNameResolver RefNameResolver, parentIsExternal bool) { + for _, name := range componentNames(es) { + e := es[name] + doc.addExampleToSpec(e, refNameResolver, parentIsExternal) + } +} + +func (doc *T) derefContent(c Content, refNameResolver RefNameResolver, parentIsExternal bool) { + for _, name := range componentNames(c) { + mediatype := c[name] + isExternal := doc.addSchemaToSpec(mediatype.Schema, refNameResolver, parentIsExternal) + if mediatype.Schema != nil { + doc.derefSchema(mediatype.Schema.Value, refNameResolver, isExternal || parentIsExternal) + } + doc.derefExamples(mediatype.Examples, refNameResolver, parentIsExternal) + for _, name := range componentNames(mediatype.Encoding) { + e := mediatype.Encoding[name] + doc.derefHeaders(e.Headers, refNameResolver, parentIsExternal) + } + } +} + +func (doc *T) derefLinks(ls Links, refNameResolver RefNameResolver, parentIsExternal bool) { + for _, name := range componentNames(ls) { + l := ls[name] + doc.addLinkToSpec(l, refNameResolver, parentIsExternal) + } +} + +func (doc *T) derefResponse(r *ResponseRef, refNameResolver RefNameResolver, parentIsExternal bool) { + isExternal := doc.addResponseToSpec(r, refNameResolver, parentIsExternal) + if v := r.Value; v != nil { + doc.derefHeaders(v.Headers, refNameResolver, isExternal || parentIsExternal) + doc.derefContent(v.Content, refNameResolver, isExternal || parentIsExternal) + doc.derefLinks(v.Links, refNameResolver, isExternal || parentIsExternal) + } +} + +func (doc *T) derefResponses(rs *Responses, refNameResolver RefNameResolver, parentIsExternal bool) { + doc.derefResponseBodies(rs.Map(), refNameResolver, parentIsExternal) +} + +func (doc *T) derefResponseBodies(es ResponseBodies, refNameResolver RefNameResolver, parentIsExternal bool) { + for _, name := range componentNames(es) { + e := es[name] + doc.derefResponse(e, refNameResolver, parentIsExternal) + } +} + +func (doc *T) derefParameter(p Parameter, refNameResolver RefNameResolver, parentIsExternal bool) { + isExternal := doc.addSchemaToSpec(p.Schema, refNameResolver, parentIsExternal) + doc.derefContent(p.Content, refNameResolver, parentIsExternal) + if p.Schema != nil { + doc.derefSchema(p.Schema.Value, refNameResolver, isExternal || parentIsExternal) + } +} + +func (doc *T) derefRequestBody(r RequestBody, refNameResolver RefNameResolver, parentIsExternal bool) { + doc.derefContent(r.Content, refNameResolver, parentIsExternal) +} + +func (doc *T) derefPaths(paths map[string]*PathItem, refNameResolver RefNameResolver, parentIsExternal bool) { + for _, name := range componentNames(paths) { + ops := paths[name] + pathIsExternal := isExternalRef(ops.Ref, parentIsExternal) + // inline full operations + ops.Ref = "" + + for _, param := range ops.Parameters { + isExternal := doc.addParameterToSpec(param, refNameResolver, pathIsExternal) + if param.Value != nil { + doc.derefParameter(*param.Value, refNameResolver, pathIsExternal || isExternal) + } + } + + opsWithMethod := ops.Operations() + for _, name := range componentNames(opsWithMethod) { + op := opsWithMethod[name] + isExternal := doc.addRequestBodyToSpec(op.RequestBody, refNameResolver, pathIsExternal) + if op.RequestBody != nil && op.RequestBody.Value != nil { + doc.derefRequestBody(*op.RequestBody.Value, refNameResolver, pathIsExternal || isExternal) + } + for _, name := range componentNames(op.Callbacks) { + cb := op.Callbacks[name] + isExternal := doc.addCallbackToSpec(cb, refNameResolver, pathIsExternal) + if cb.Value != nil { + cbValue := (*cb.Value).Map() + doc.derefPaths(cbValue, refNameResolver, pathIsExternal || isExternal) + } + } + doc.derefResponses(op.Responses, refNameResolver, pathIsExternal) + for _, param := range op.Parameters { + isExternal := doc.addParameterToSpec(param, refNameResolver, pathIsExternal) + if param.Value != nil { + doc.derefParameter(*param.Value, refNameResolver, pathIsExternal || isExternal) + } + } + } + } +} + +// 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 +// documentation for more details. +// +// Example: +// +// doc.InternalizeRefs(context.Background(), nil) +func (doc *T) InternalizeRefs(ctx context.Context, refNameResolver func(*T, componentRef) string) { + doc.resetVisited() + + if refNameResolver == nil { + refNameResolver = DefaultRefNameResolver + } + + if components := doc.Components; components != nil { + for _, name := range componentNames(components.Schemas) { + schema := components.Schemas[name] + isExternal := doc.addSchemaToSpec(schema, refNameResolver, false) + if schema != nil { + schema.Ref = "" // always dereference the top level + doc.derefSchema(schema.Value, refNameResolver, isExternal) + } + } + for _, name := range componentNames(components.Parameters) { + p := components.Parameters[name] + isExternal := doc.addParameterToSpec(p, refNameResolver, false) + if p != nil && p.Value != nil { + p.Ref = "" // always dereference the top level + doc.derefParameter(*p.Value, refNameResolver, isExternal) + } + } + doc.derefHeaders(components.Headers, refNameResolver, false) + for _, name := range componentNames(components.RequestBodies) { + req := components.RequestBodies[name] + isExternal := doc.addRequestBodyToSpec(req, refNameResolver, false) + if req != nil && req.Value != nil { + req.Ref = "" // always dereference the top level + doc.derefRequestBody(*req.Value, refNameResolver, isExternal) + } + } + doc.derefResponseBodies(components.Responses, refNameResolver, false) + for _, name := range componentNames(components.SecuritySchemes) { + ss := components.SecuritySchemes[name] + doc.addSecuritySchemeToSpec(ss, refNameResolver, false) + } + doc.derefExamples(components.Examples, refNameResolver, false) + doc.derefLinks(components.Links, refNameResolver, false) + + for _, name := range componentNames(components.Callbacks) { + cb := components.Callbacks[name] + isExternal := doc.addCallbackToSpec(cb, refNameResolver, false) + if cb != nil && cb.Value != nil { + cb.Ref = "" // always dereference the top level + cbValue := (*cb.Value).Map() + doc.derefPaths(cbValue, refNameResolver, isExternal) + } + } + } + + doc.derefPaths(doc.Paths.Map(), refNameResolver, false) +} diff --git a/vendor/github.com/getkin/kin-openapi/openapi3/license.go b/vendor/github.com/getkin/kin-openapi/openapi3/license.go new file mode 100644 index 00000000..c4f6c8dc --- /dev/null +++ b/vendor/github.com/getkin/kin-openapi/openapi3/license.go @@ -0,0 +1,66 @@ +package openapi3 + +import ( + "context" + "encoding/json" + "errors" +) + +// License is specified by OpenAPI/Swagger standard version 3. +// See https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#license-object +type License struct { + Extensions map[string]any `json:"-" yaml:"-"` + + Name string `json:"name" yaml:"name"` // Required + URL string `json:"url,omitempty" yaml:"url,omitempty"` +} + +// MarshalJSON returns the JSON encoding of License. +func (license License) MarshalJSON() ([]byte, error) { + x, err := license.MarshalYAML() + if err != nil { + return nil, err + } + return json.Marshal(x) +} + +// MarshalYAML returns the YAML encoding of License. +func (license License) MarshalYAML() (any, error) { + m := make(map[string]any, 2+len(license.Extensions)) + for k, v := range license.Extensions { + m[k] = v + } + m["name"] = license.Name + if x := license.URL; x != "" { + m["url"] = x + } + return m, nil +} + +// UnmarshalJSON sets License to a copy of data. +func (license *License) UnmarshalJSON(data []byte) error { + type LicenseBis License + var x LicenseBis + if err := json.Unmarshal(data, &x); err != nil { + return unmarshalError(err) + } + _ = json.Unmarshal(data, &x.Extensions) + delete(x.Extensions, "name") + delete(x.Extensions, "url") + if len(x.Extensions) == 0 { + x.Extensions = nil + } + *license = License(x) + return nil +} + +// Validate returns an error if License does not comply with the OpenAPI spec. +func (license *License) Validate(ctx context.Context, opts ...ValidationOption) error { + ctx = WithValidationOptions(ctx, opts...) + + if license.Name == "" { + return errors.New("value of license name must be a non-empty string") + } + + return validateExtensions(ctx, license.Extensions) +} diff --git a/vendor/github.com/getkin/kin-openapi/openapi3/link.go b/vendor/github.com/getkin/kin-openapi/openapi3/link.go new file mode 100644 index 00000000..132f6780 --- /dev/null +++ b/vendor/github.com/getkin/kin-openapi/openapi3/link.go @@ -0,0 +1,94 @@ +package openapi3 + +import ( + "context" + "encoding/json" + "errors" + "fmt" +) + +// Link is specified by OpenAPI/Swagger standard version 3. +// See https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#link-object +type Link struct { + Extensions map[string]any `json:"-" yaml:"-"` + + OperationRef string `json:"operationRef,omitempty" yaml:"operationRef,omitempty"` + OperationID string `json:"operationId,omitempty" yaml:"operationId,omitempty"` + Description string `json:"description,omitempty" yaml:"description,omitempty"` + Parameters map[string]any `json:"parameters,omitempty" yaml:"parameters,omitempty"` + Server *Server `json:"server,omitempty" yaml:"server,omitempty"` + RequestBody any `json:"requestBody,omitempty" yaml:"requestBody,omitempty"` +} + +// MarshalJSON returns the JSON encoding of Link. +func (link Link) MarshalJSON() ([]byte, error) { + x, err := link.MarshalYAML() + if err != nil { + return nil, err + } + return json.Marshal(x) +} + +// MarshalYAML returns the YAML encoding of Link. +func (link Link) MarshalYAML() (any, error) { + m := make(map[string]any, 6+len(link.Extensions)) + for k, v := range link.Extensions { + m[k] = v + } + + if x := link.OperationRef; x != "" { + m["operationRef"] = x + } + if x := link.OperationID; x != "" { + m["operationId"] = x + } + if x := link.Description; x != "" { + m["description"] = x + } + if x := link.Parameters; len(x) != 0 { + m["parameters"] = x + } + if x := link.Server; x != nil { + m["server"] = x + } + if x := link.RequestBody; x != nil { + m["requestBody"] = x + } + + return m, nil +} + +// UnmarshalJSON sets Link to a copy of data. +func (link *Link) UnmarshalJSON(data []byte) error { + type LinkBis Link + var x LinkBis + if err := json.Unmarshal(data, &x); err != nil { + return unmarshalError(err) + } + _ = json.Unmarshal(data, &x.Extensions) + delete(x.Extensions, "operationRef") + delete(x.Extensions, "operationId") + delete(x.Extensions, "description") + delete(x.Extensions, "parameters") + delete(x.Extensions, "server") + delete(x.Extensions, "requestBody") + if len(x.Extensions) == 0 { + x.Extensions = nil + } + *link = Link(x) + return nil +} + +// Validate returns an error if Link does not comply with the OpenAPI spec. +func (link *Link) Validate(ctx context.Context, opts ...ValidationOption) error { + ctx = WithValidationOptions(ctx, opts...) + + if link.OperationID == "" && link.OperationRef == "" { + return errors.New("missing operationId or operationRef on link") + } + if link.OperationID != "" && link.OperationRef != "" { + return fmt.Errorf("operationId %q and operationRef %q are mutually exclusive", link.OperationID, link.OperationRef) + } + + return validateExtensions(ctx, link.Extensions) +} 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 00000000..4f2766a0 --- /dev/null +++ b/vendor/github.com/getkin/kin-openapi/openapi3/loader.go @@ -0,0 +1,1192 @@ +package openapi3 + +import ( + "context" + "encoding/json" + "errors" + "fmt" + "io" + "net/url" + "os" + "path" + "path/filepath" + "reflect" + "strconv" + "strings" +) + +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 ReadFromURIFunc + + Context context.Context + + rootDir string + rootLocation string + + visitedPathItemRefs map[string]struct{} + + visitedDocuments map[string]*T + + visitedRefs map[string]struct{} + visitedPath []string + backtrack map[string][]func(value any) +} + +// NewLoader returns an empty Loader +func NewLoader() *Loader { + return &Loader{ + Context: context.Background(), + } +} + +func (loader *Loader) resetVisitedPathItemRefs() { + loader.visitedPathItemRefs = make(map[string]struct{}) + loader.visitedRefs = make(map[string]struct{}) + loader.visitedPath = nil + loader.backtrack = make(map[string][]func(value any)) +} + +// 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 +} + +func (loader *Loader) loadSingleElementFromURI(ref string, rootPath *url.URL, element any) (*url.URL, error) { + if err := loader.allowsExternalRefs(ref); err != nil { + return nil, err + } + + resolvedPath, err := resolvePathWithRef(ref, rootPath) + if err != nil { + return nil, err + } + if frag := resolvedPath.Fragment; frag != "" { + return nil, fmt.Errorf("unexpected ref fragment %q", frag) + } + + data, err := loader.readURL(resolvedPath) + if err != nil { + return nil, err + } + if err := 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) + } + return DefaultReadFromURI(loader, location) +} + +// LoadFromStdin loads a spec from stdin +func (loader *Loader) LoadFromStdin() (*T, error) { + return loader.LoadFromIoReader(os.Stdin) +} + +// LoadFromStdin loads a spec from io.Reader +func (loader *Loader) LoadFromIoReader(reader io.Reader) (*T, error) { + if reader == nil { + return nil, fmt.Errorf("invalid reader: %v", reader) + } + + data, err := io.ReadAll(reader) + if err != nil { + return nil, err + } + return loader.LoadFromData(data) +} + +// LoadFromData loads a spec from a byte array +func (loader *Loader) LoadFromData(data []byte) (*T, error) { + loader.resetVisitedPathItemRefs() + doc := &T{} + if err := 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) + loader.rootLocation = location.Path + } + uri := location.String() + if doc, ok := loader.visitedDocuments[uri]; ok { + return doc, nil + } + + doc := &T{} + loader.visitedDocuments[uri] = doc + + if err := unmarshal(data, doc); err != nil { + return nil, err + } + + doc.url = copyURI(location) + + if err := loader.ResolveRefsIn(doc, location); err != nil { + return nil, err + } + + return doc, nil +} + +// ResolveRefsIn expands references if for instance spec was just unmarshaled +func (loader *Loader) ResolveRefsIn(doc *T, location *url.URL) (err error) { + if loader.Context == nil { + loader.Context = context.Background() + } + + if loader.visitedPathItemRefs == nil { + loader.resetVisitedPathItemRefs() + } + + if components := doc.Components; components != nil { + for _, name := range componentNames(components.Headers) { + component := components.Headers[name] + if err = loader.resolveHeaderRef(doc, component, location); err != nil { + return + } + } + for _, name := range componentNames(components.Parameters) { + component := components.Parameters[name] + if err = loader.resolveParameterRef(doc, component, location); err != nil { + return + } + } + for _, name := range componentNames(components.RequestBodies) { + component := components.RequestBodies[name] + if err = loader.resolveRequestBodyRef(doc, component, location); err != nil { + return + } + } + for _, name := range componentNames(components.Responses) { + component := components.Responses[name] + if err = loader.resolveResponseRef(doc, component, location); err != nil { + return + } + } + for _, name := range componentNames(components.Schemas) { + component := components.Schemas[name] + if err = loader.resolveSchemaRef(doc, component, location, []string{}); err != nil { + return + } + } + for _, name := range componentNames(components.SecuritySchemes) { + component := components.SecuritySchemes[name] + if err = loader.resolveSecuritySchemeRef(doc, component, location); err != nil { + return + } + } + for _, name := range componentNames(components.Examples) { + component := components.Examples[name] + if err = loader.resolveExampleRef(doc, component, location); err != nil { + return + } + } + for _, name := range componentNames(components.Callbacks) { + component := components.Callbacks[name] + if err = loader.resolveCallbackRef(doc, component, location); err != nil { + return + } + } + } + + // Visit all operations + pathItems := doc.Paths.Map() + for _, name := range componentNames(pathItems) { + pathItem := pathItems[name] + if pathItem == nil { + continue + } + if err = loader.resolvePathItemRef(doc, pathItem, location); err != nil { + return + } + } + + return +} + +func join(basePath *url.URL, relativePath *url.URL) *url.URL { + if basePath == nil { + return relativePath + } + newPath := *basePath + newPath.Path = path.Join(path.Dir(newPath.Path), relativePath.Path) + return &newPath +} + +func resolvePath(basePath *url.URL, componentPath *url.URL) *url.URL { + if is_file(componentPath) { + // support absolute paths + if filepath.IsAbs(componentPath.Path) { + return componentPath + } + return join(basePath, componentPath) + } + return componentPath +} + +func resolvePathWithRef(ref string, rootPath *url.URL) (*url.URL, error) { + parsedURL, err := url.Parse(ref) + if err != nil { + return nil, fmt.Errorf("cannot parse reference: %q: %w", ref, err) + } + + resolvedPath := resolvePath(rootPath, parsedURL) + resolvedPath.Fragment = parsedURL.Fragment + return resolvedPath, nil +} + +func isSingleRefElement(ref string) bool { + return !strings.Contains(ref, "#") +} + +func (loader *Loader) visitRef(ref string) { + if loader.visitedRefs == nil { + loader.visitedRefs = make(map[string]struct{}) + loader.backtrack = make(map[string][]func(value any)) + } + loader.visitedPath = append(loader.visitedPath, ref) + loader.visitedRefs[ref] = struct{}{} +} + +func (loader *Loader) unvisitRef(ref string, value any) { + if value != nil { + for _, fn := range loader.backtrack[ref] { + fn(value) + } + } + delete(loader.visitedRefs, ref) + delete(loader.backtrack, ref) + loader.visitedPath = loader.visitedPath[:len(loader.visitedPath)-1] +} + +func (loader *Loader) shouldVisitRef(ref string, fn func(value any)) bool { + if _, ok := loader.visitedRefs[ref]; ok { + loader.backtrack[ref] = append(loader.backtrack[ref], fn) + return false + } + return true +} + +func (loader *Loader) resolveComponent(doc *T, ref string, path *url.URL, resolved any) ( + componentDoc *T, + componentPath *url.URL, + err error, +) { + if componentDoc, ref, componentPath, err = loader.resolveRef(doc, ref, path); 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 + if fragment == "" { + fragment = "/" + } + if fragment[0] != '/' { + return nil, nil, fmt.Errorf("expected fragment prefix '#/' in URI %q", ref) + } + + drill := func(cursor any) (any, error) { + for _, pathPart := range strings.Split(fragment[1:], "/") { + pathPart = unescapeRefString(pathPart) + attempted := false + + switch c := cursor.(type) { + // Special case of T + // See issue856: a ref to doc => we assume that doc is a T => things live in T.Extensions + case *T: + if pathPart == "" { + cursor = c.Extensions + attempted = true + } + + // Special case due to multijson + case *SchemaRef: + if pathPart == "additionalProperties" { + if ap := c.Value.AdditionalProperties.Has; ap != nil { + cursor = *ap + } else { + cursor = c.Value.AdditionalProperties.Schema + } + attempted = true + } + + case *Responses: + cursor = c.m // m map[string]*ResponseRef + case *Callback: + cursor = c.m // m map[string]*PathItem + case *Paths: + cursor = c.m // m map[string]*PathItem + } + + if !attempted { + if cursor, err = drillIntoField(cursor, pathPart); err != nil { + e := failedToResolveRefFragmentPart(ref, pathPart) + return nil, fmt.Errorf("%s: %w", e, err) + } + } + + if cursor == nil { + return nil, failedToResolveRefFragmentPart(ref, pathPart) + } + } + return cursor, nil + } + var cursor any + if cursor, err = drill(componentDoc); err != nil { + if path == nil { + return nil, nil, err + } + var err2 error + data, err2 := loader.readURL(path) + if err2 != nil { + return nil, nil, err + } + if err2 = unmarshal(data, &cursor); err2 != nil { + return nil, nil, err + } + if cursor, err2 = drill(cursor); err2 != nil || cursor == nil { + return nil, nil, err + } + err = nil + } + + setComponent := func(target any) { + if componentPath != nil { + if i, ok := target.(interface { + setRefPath(*url.URL) + }); ok { + copy := *componentPath + copy.Fragment = parsedURL.Fragment + i.setRefPath(©) + } + } + } + + switch { + case reflect.TypeOf(cursor) == reflect.TypeOf(resolved): + setComponent(cursor) + + reflect.ValueOf(resolved).Elem().Set(reflect.ValueOf(cursor).Elem()) + return componentDoc, componentPath, nil + + case reflect.TypeOf(cursor) == reflect.TypeOf(map[string]any{}): + codec := func(got, expect any) error { + enc, err := json.Marshal(got) + if err != nil { + return err + } + if err = json.Unmarshal(enc, expect); err != nil { + return err + } + + setComponent(expect) + return nil + } + if err := codec(cursor, resolved); err != nil { + return nil, nil, fmt.Errorf("bad data in %q (expecting %s)", ref, readableType(resolved)) + } + return componentDoc, componentPath, nil + + default: + return nil, nil, fmt.Errorf("bad data in %q (expecting %s)", ref, readableType(resolved)) + } +} + +func readableType(x any) string { + switch x.(type) { + case *Callback: + return "callback object" + case *CallbackRef: + return "ref to callback object" + case *ExampleRef: + return "ref to example object" + case *HeaderRef: + return "ref to header object" + case *LinkRef: + return "ref to link object" + case *ParameterRef: + return "ref to parameter object" + case *PathItem: + return "pathItem object" + case *RequestBodyRef: + return "ref to requestBody object" + case *ResponseRef: + return "ref to response object" + case *SchemaRef: + return "ref to schema object" + case *SecuritySchemeRef: + return "ref to securityScheme object" + default: + panic(fmt.Sprintf("unreachable %T", x)) + } +} + +func drillIntoField(cursor any, fieldName string) (any, 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 + if yamlTag := val.Type().Field(i).Tag.Get("yaml"); yamlTag != "-" { + if tagName := strings.Split(yamlTag, ",")[0]; tagName != "" { + if fieldName == tagName { + 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 == "Extensions" { + extensions := val.Field(0).Interface().(map[string]any) + if enc, ok := extensions[fieldName]; ok { + return enc, 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) resolveRef(doc *T, ref string, path *url.URL) (*T, string, *url.URL, error) { + if ref != "" && ref[0] == '#' { + return doc, ref, path, nil + } + + fragment, resolvedPath, err := loader.resolveRefPath(ref, path) + if err != nil { + return nil, "", nil, err + } + + if doc, err = loader.loadFromURIInternal(resolvedPath); err != nil { + return nil, "", nil, fmt.Errorf("error resolving reference %q: %w", ref, err) + } + + return doc, fragment, resolvedPath, nil +} + +func (loader *Loader) resolveRefPath(ref string, path *url.URL) (string, *url.URL, error) { + if ref != "" && ref[0] == '#' { + return ref, path, nil + } + + if err := loader.allowsExternalRefs(ref); err != nil { + return "", nil, err + } + + resolvedPath, err := resolvePathWithRef(ref, path) + if err != nil { + return "", nil, err + } + + fragment := "#" + resolvedPath.Fragment + resolvedPath.Fragment = "" + return fragment, resolvedPath, nil +} + +var ( + errMUSTCallback = errors.New("invalid callback: value MUST be an object") + errMUSTExample = errors.New("invalid example: value MUST be an object") + errMUSTHeader = errors.New("invalid header: value MUST be an object") + errMUSTLink = errors.New("invalid link: value MUST be an object") + errMUSTParameter = errors.New("invalid parameter: value MUST be an object") + errMUSTPathItem = errors.New("invalid path item: value MUST be an object") + errMUSTRequestBody = errors.New("invalid requestBody: value MUST be an object") + errMUSTResponse = errors.New("invalid response: value MUST be an object") + errMUSTSchema = errors.New("invalid schema: value MUST be an object") + errMUSTSecurityScheme = errors.New("invalid securityScheme: value MUST be an object") +) + +func (loader *Loader) resolveHeaderRef(doc *T, component *HeaderRef, documentPath *url.URL) (err error) { + if component.isEmpty() { + return errMUSTHeader + } + + if ref := component.Ref; ref != "" { + if component.Value != nil { + return nil + } + if !loader.shouldVisitRef(ref, func(value any) { + component.Value = value.(*Header) + _, refDocPath, _ := loader.resolveRefPath(ref, documentPath) + component.setRefPath(refDocPath) + }) { + return nil + } + loader.visitRef(ref) + if isSingleRefElement(ref) { + var header Header + if documentPath, err = loader.loadSingleElementFromURI(ref, documentPath, &header); err != nil { + return err + } + component.Value = &header + component.setRefPath(documentPath) + } else { + var resolved HeaderRef + doc, componentPath, err := loader.resolveComponent(doc, ref, documentPath, &resolved) + if err != nil { + return err + } + if err := loader.resolveHeaderRef(doc, &resolved, componentPath); err != nil { + if err == errMUSTHeader { + return nil + } + return err + } + component.Value = resolved.Value + component.setRefPath(resolved.RefPath()) + } + defer loader.unvisitRef(ref, component.Value) + } + value := component.Value + if value == nil { + return nil + } + + if schema := value.Schema; schema != nil { + if err := loader.resolveSchemaRef(doc, schema, documentPath, []string{}); err != nil { + return err + } + } + return nil +} + +func (loader *Loader) resolveParameterRef(doc *T, component *ParameterRef, documentPath *url.URL) (err error) { + if component.isEmpty() { + return errMUSTParameter + } + + if ref := component.Ref; ref != "" { + if component.Value != nil { + return nil + } + if !loader.shouldVisitRef(ref, func(value any) { + component.Value = value.(*Parameter) + _, refDocPath, _ := loader.resolveRefPath(ref, documentPath) + component.setRefPath(refDocPath) + }) { + return nil + } + loader.visitRef(ref) + if isSingleRefElement(ref) { + var param Parameter + if documentPath, err = loader.loadSingleElementFromURI(ref, documentPath, ¶m); err != nil { + return err + } + component.Value = ¶m + component.setRefPath(documentPath) + } else { + var resolved ParameterRef + doc, componentPath, err := loader.resolveComponent(doc, ref, documentPath, &resolved) + if err != nil { + return err + } + if err := loader.resolveParameterRef(doc, &resolved, componentPath); err != nil { + if err == errMUSTParameter { + return nil + } + return err + } + component.Value = resolved.Value + component.setRefPath(resolved.RefPath()) + } + defer loader.unvisitRef(ref, component.Value) + } + 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 _, name := range componentNames(value.Content) { + contentType := value.Content[name] + if schema := contentType.Schema; schema != nil { + if err := loader.resolveSchemaRef(doc, schema, documentPath, []string{}); err != nil { + return err + } + } + } + if schema := value.Schema; schema != nil { + if err := loader.resolveSchemaRef(doc, schema, documentPath, []string{}); err != nil { + return err + } + } + return nil +} + +func (loader *Loader) resolveRequestBodyRef(doc *T, component *RequestBodyRef, documentPath *url.URL) (err error) { + if component.isEmpty() { + return errMUSTRequestBody + } + + if ref := component.Ref; ref != "" { + if component.Value != nil { + return nil + } + if !loader.shouldVisitRef(ref, func(value any) { + component.Value = value.(*RequestBody) + _, refDocPath, _ := loader.resolveRefPath(ref, documentPath) + component.setRefPath(refDocPath) + }) { + return nil + } + loader.visitRef(ref) + if isSingleRefElement(ref) { + var requestBody RequestBody + if documentPath, err = loader.loadSingleElementFromURI(ref, documentPath, &requestBody); err != nil { + return err + } + component.Value = &requestBody + component.setRefPath(documentPath) + } else { + var resolved RequestBodyRef + doc, componentPath, err := loader.resolveComponent(doc, ref, documentPath, &resolved) + if err != nil { + return err + } + if err = loader.resolveRequestBodyRef(doc, &resolved, componentPath); err != nil { + if err == errMUSTRequestBody { + return nil + } + return err + } + component.Value = resolved.Value + component.setRefPath(resolved.RefPath()) + } + defer loader.unvisitRef(ref, component.Value) + } + value := component.Value + if value == nil { + return nil + } + + for _, name := range componentNames(value.Content) { + contentType := value.Content[name] + if contentType == nil { + continue + } + for _, name := range componentNames(contentType.Examples) { + example := contentType.Examples[name] + 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, []string{}); err != nil { + return err + } + } + } + return nil +} + +func (loader *Loader) resolveResponseRef(doc *T, component *ResponseRef, documentPath *url.URL) (err error) { + if component.isEmpty() { + return errMUSTResponse + } + + if ref := component.Ref; ref != "" { + if component.Value != nil { + return nil + } + if !loader.shouldVisitRef(ref, func(value any) { + component.Value = value.(*Response) + _, refDocPath, _ := loader.resolveRefPath(ref, documentPath) + component.setRefPath(refDocPath) + }) { + return nil + } + loader.visitRef(ref) + if isSingleRefElement(ref) { + var resp Response + if documentPath, err = loader.loadSingleElementFromURI(ref, documentPath, &resp); err != nil { + return err + } + component.Value = &resp + component.setRefPath(documentPath) + } else { + var resolved ResponseRef + doc, componentPath, err := loader.resolveComponent(doc, ref, documentPath, &resolved) + if err != nil { + return err + } + if err := loader.resolveResponseRef(doc, &resolved, componentPath); err != nil { + if err == errMUSTResponse { + return nil + } + return err + } + component.Value = resolved.Value + component.setRefPath(resolved.RefPath()) + } + defer loader.unvisitRef(ref, component.Value) + } + value := component.Value + if value == nil { + return nil + } + + for _, name := range componentNames(value.Headers) { + header := value.Headers[name] + if err := loader.resolveHeaderRef(doc, header, documentPath); err != nil { + return err + } + } + for _, name := range componentNames(value.Content) { + contentType := value.Content[name] + if contentType == nil { + continue + } + for _, name := range componentNames(contentType.Examples) { + example := contentType.Examples[name] + 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, []string{}); err != nil { + return err + } + contentType.Schema = schema + } + } + for _, name := range componentNames(value.Links) { + link := value.Links[name] + if err := loader.resolveLinkRef(doc, link, documentPath); err != nil { + return err + } + } + return nil +} + +func (loader *Loader) resolveSchemaRef(doc *T, component *SchemaRef, documentPath *url.URL, visited []string) (err error) { + if component.isEmpty() { + return errMUSTSchema + } + + if ref := component.Ref; ref != "" { + if component.Value != nil { + return nil + } + if !loader.shouldVisitRef(ref, func(value any) { + component.Value = value.(*Schema) + _, refDocPath, _ := loader.resolveRefPath(ref, documentPath) + component.setRefPath(refDocPath) + }) { + return nil + } + loader.visitRef(ref) + if isSingleRefElement(ref) { + var schema Schema + if documentPath, err = loader.loadSingleElementFromURI(ref, documentPath, &schema); err != nil { + return err + } + component.Value = &schema + component.setRefPath(documentPath) + } else { + var resolved SchemaRef + doc, componentPath, err := loader.resolveComponent(doc, ref, documentPath, &resolved) + if err != nil { + return err + } + if err := loader.resolveSchemaRef(doc, &resolved, componentPath, visited); err != nil { + if err == errMUSTSchema { + return nil + } + return err + } + component.Value = resolved.Value + component.setRefPath(resolved.RefPath()) + } + defer loader.unvisitRef(ref, component.Value) + } + value := component.Value + if value == nil { + return nil + } + + // ResolveRefs referred schemas + if v := value.Items; v != nil { + if err := loader.resolveSchemaRef(doc, v, documentPath, visited); err != nil { + return err + } + } + for _, name := range componentNames(value.Properties) { + v := value.Properties[name] + if err := loader.resolveSchemaRef(doc, v, documentPath, visited); err != nil { + return err + } + } + if v := value.AdditionalProperties.Schema; v != nil { + if err := loader.resolveSchemaRef(doc, v, documentPath, visited); err != nil { + return err + } + } + if v := value.Not; v != nil { + if err := loader.resolveSchemaRef(doc, v, documentPath, visited); err != nil { + return err + } + } + for _, v := range value.AllOf { + if err := loader.resolveSchemaRef(doc, v, documentPath, visited); err != nil { + return err + } + } + for _, v := range value.AnyOf { + if err := loader.resolveSchemaRef(doc, v, documentPath, visited); err != nil { + return err + } + } + for _, v := range value.OneOf { + if err := loader.resolveSchemaRef(doc, v, documentPath, visited); err != nil { + return err + } + } + return nil +} + +func (loader *Loader) resolveSecuritySchemeRef(doc *T, component *SecuritySchemeRef, documentPath *url.URL) (err error) { + if component.isEmpty() { + return errMUSTSecurityScheme + } + + if ref := component.Ref; ref != "" { + if component.Value != nil { + return nil + } + if !loader.shouldVisitRef(ref, func(value any) { + component.Value = value.(*SecurityScheme) + _, refDocPath, _ := loader.resolveRefPath(ref, documentPath) + component.setRefPath(refDocPath) + }) { + return nil + } + loader.visitRef(ref) + if isSingleRefElement(ref) { + var scheme SecurityScheme + if _, err = loader.loadSingleElementFromURI(ref, documentPath, &scheme); err != nil { + return err + } + component.Value = &scheme + component.setRefPath(documentPath) + } else { + var resolved SecuritySchemeRef + doc, componentPath, err := loader.resolveComponent(doc, ref, documentPath, &resolved) + if err != nil { + return err + } + if err := loader.resolveSecuritySchemeRef(doc, &resolved, componentPath); err != nil { + if err == errMUSTSecurityScheme { + return nil + } + return err + } + component.Value = resolved.Value + component.setRefPath(resolved.RefPath()) + } + defer loader.unvisitRef(ref, component.Value) + } + return nil +} + +func (loader *Loader) resolveExampleRef(doc *T, component *ExampleRef, documentPath *url.URL) (err error) { + if ref := component.Ref; ref != "" { + if component.Value != nil { + return nil + } + if !loader.shouldVisitRef(ref, func(value any) { + component.Value = value.(*Example) + _, refDocPath, _ := loader.resolveRefPath(ref, documentPath) + component.setRefPath(refDocPath) + }) { + return nil + } + loader.visitRef(ref) + if isSingleRefElement(ref) { + var example Example + if _, err = loader.loadSingleElementFromURI(ref, documentPath, &example); err != nil { + return err + } + component.Value = &example + component.setRefPath(documentPath) + } else { + var resolved ExampleRef + doc, componentPath, err := loader.resolveComponent(doc, ref, documentPath, &resolved) + if err != nil { + return err + } + if err := loader.resolveExampleRef(doc, &resolved, componentPath); err != nil { + if err == errMUSTExample { + return nil + } + return err + } + component.Value = resolved.Value + component.setRefPath(resolved.RefPath()) + } + defer loader.unvisitRef(ref, component.Value) + } + return nil +} + +func (loader *Loader) resolveCallbackRef(doc *T, component *CallbackRef, documentPath *url.URL) (err error) { + if component.isEmpty() { + return errMUSTCallback + } + + if ref := component.Ref; ref != "" { + if component.Value != nil { + return nil + } + if !loader.shouldVisitRef(ref, func(value any) { + component.Value = value.(*Callback) + _, refDocPath, _ := loader.resolveRefPath(ref, documentPath) + component.setRefPath(refDocPath) + }) { + return nil + } + loader.visitRef(ref) + if isSingleRefElement(ref) { + var resolved Callback + if documentPath, err = loader.loadSingleElementFromURI(ref, documentPath, &resolved); err != nil { + return err + } + component.Value = &resolved + component.setRefPath(documentPath) + } else { + var resolved CallbackRef + doc, componentPath, err := loader.resolveComponent(doc, ref, documentPath, &resolved) + if err != nil { + return err + } + if err = loader.resolveCallbackRef(doc, &resolved, componentPath); err != nil { + if err == errMUSTCallback { + return nil + } + return err + } + component.Value = resolved.Value + component.setRefPath(resolved.RefPath()) + } + defer loader.unvisitRef(ref, component.Value) + } + value := component.Value + if value == nil { + return nil + } + + pathItems := value.Map() + for _, name := range componentNames(pathItems) { + pathItem := pathItems[name] + if err = loader.resolvePathItemRef(doc, pathItem, documentPath); err != nil { + return err + } + } + return nil +} + +func (loader *Loader) resolveLinkRef(doc *T, component *LinkRef, documentPath *url.URL) (err error) { + if component.isEmpty() { + return errMUSTLink + } + + if ref := component.Ref; ref != "" { + if component.Value != nil { + return nil + } + if !loader.shouldVisitRef(ref, func(value any) { + component.Value = value.(*Link) + _, refDocPath, _ := loader.resolveRefPath(ref, documentPath) + component.setRefPath(refDocPath) + }) { + return nil + } + loader.visitRef(ref) + if isSingleRefElement(ref) { + var link Link + if _, err = loader.loadSingleElementFromURI(ref, documentPath, &link); err != nil { + return err + } + component.Value = &link + component.setRefPath(documentPath) + } else { + var resolved LinkRef + doc, componentPath, err := loader.resolveComponent(doc, ref, documentPath, &resolved) + if err != nil { + return err + } + if err := loader.resolveLinkRef(doc, &resolved, componentPath); err != nil { + if err == errMUSTLink { + return nil + } + return err + } + component.Value = resolved.Value + component.setRefPath(resolved.RefPath()) + } + defer loader.unvisitRef(ref, component.Value) + } + return nil +} + +func (loader *Loader) resolvePathItemRef(doc *T, pathItem *PathItem, documentPath *url.URL) (err error) { + if pathItem == nil { + err = errMUSTPathItem + return + } + + if ref := pathItem.Ref; ref != "" { + if !pathItem.isEmpty() { + return + } + if !loader.shouldVisitRef(ref, func(value any) { + *pathItem = *value.(*PathItem) + }) { + return nil + } + loader.visitRef(ref) + if isSingleRefElement(ref) { + var p PathItem + if documentPath, err = loader.loadSingleElementFromURI(ref, documentPath, &p); err != nil { + return + } + *pathItem = p + } else { + var resolved PathItem + if doc, documentPath, err = loader.resolveComponent(doc, ref, documentPath, &resolved); err != nil { + if err == errMUSTPathItem { + return nil + } + return + } + *pathItem = resolved + } + pathItem.Ref = ref + defer loader.unvisitRef(ref, pathItem) + } + + for _, parameter := range pathItem.Parameters { + if err = loader.resolveParameterRef(doc, parameter, documentPath); err != nil { + return + } + } + operations := pathItem.Operations() + for _, name := range componentNames(operations) { + operation := operations[name] + 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 + } + } + responses := operation.Responses.Map() + for _, name := range componentNames(responses) { + response := responses[name] + if err = loader.resolveResponseRef(doc, response, documentPath); err != nil { + return + } + } + for _, name := range componentNames(operation.Callbacks) { + callback := operation.Callbacks[name] + 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/loader_uri_reader.go b/vendor/github.com/getkin/kin-openapi/openapi3/loader_uri_reader.go new file mode 100644 index 00000000..b023dfb2 --- /dev/null +++ b/vendor/github.com/getkin/kin-openapi/openapi3/loader_uri_reader.go @@ -0,0 +1,116 @@ +package openapi3 + +import ( + "errors" + "fmt" + "io" + "net/http" + "net/url" + "os" + "path/filepath" + "sync" +) + +// ReadFromURIFunc defines a function which reads the contents of a resource +// located at a URI. +type ReadFromURIFunc func(loader *Loader, url *url.URL) ([]byte, error) + +var uriMu = &sync.RWMutex{} + +// ErrURINotSupported indicates the ReadFromURIFunc does not know how to handle a +// given URI. +var ErrURINotSupported = errors.New("unsupported URI") + +// ReadFromURIs returns a ReadFromURIFunc which tries to read a URI using the +// given reader functions, in the same order. If a reader function does not +// support the URI and returns ErrURINotSupported, the next function is checked +// until a match is found, or the URI is not supported by any. +func ReadFromURIs(readers ...ReadFromURIFunc) ReadFromURIFunc { + return func(loader *Loader, url *url.URL) ([]byte, error) { + for i := range readers { + buf, err := readers[i](loader, url) + if err == ErrURINotSupported { + continue + } else if err != nil { + return nil, err + } + return buf, nil + } + return nil, ErrURINotSupported + } +} + +// DefaultReadFromURI returns a caching ReadFromURIFunc which can read remote +// HTTP URIs and local file URIs. +var DefaultReadFromURI = URIMapCache(ReadFromURIs(ReadFromHTTP(http.DefaultClient), ReadFromFile)) + +// ReadFromHTTP returns a ReadFromURIFunc which uses the given http.Client to +// read the contents from a remote HTTP URI. This client may be customized to +// implement timeouts, RFC 7234 caching, etc. +func ReadFromHTTP(cl *http.Client) ReadFromURIFunc { + return func(loader *Loader, location *url.URL) ([]byte, error) { + if location.Scheme == "" || location.Host == "" { + return nil, ErrURINotSupported + } + req, err := http.NewRequest("GET", location.String(), nil) + if err != nil { + return nil, err + } + resp, err := cl.Do(req) + if err != nil { + return nil, err + } + defer resp.Body.Close() + if resp.StatusCode > 399 { + return nil, fmt.Errorf("error loading %q: request returned status code %d", location.String(), resp.StatusCode) + } + return io.ReadAll(resp.Body) + } +} + +func is_file(location *url.URL) bool { + return location.Path != "" && + location.Host == "" && + (location.Scheme == "" || location.Scheme == "file") +} + +// ReadFromFile is a ReadFromURIFunc which reads local file URIs. +func ReadFromFile(loader *Loader, location *url.URL) ([]byte, error) { + if !is_file(location) { + return nil, ErrURINotSupported + } + return os.ReadFile(filepath.FromSlash(location.Path)) +} + +// URIMapCache returns a ReadFromURIFunc that caches the contents read from URI +// locations in a simple map. This cache implementation is suitable for +// short-lived processes such as command-line tools which process OpenAPI +// documents. +func URIMapCache(reader ReadFromURIFunc) ReadFromURIFunc { + cache := map[string][]byte{} + return func(loader *Loader, location *url.URL) (buf []byte, err error) { + if location.Scheme == "" || location.Scheme == "file" { + if !filepath.IsAbs(location.Path) { + // Do not cache relative file paths; this can cause trouble if + // the current working directory changes when processing + // multiple top-level documents. + return reader(loader, location) + } + } + uri := location.String() + var ok bool + uriMu.RLock() + if buf, ok = cache[uri]; ok { + uriMu.RUnlock() + return + } + uriMu.RUnlock() + if buf, err = reader(loader, location); err != nil { + return + } + uriMu.Lock() + defer uriMu.Unlock() + cache[uri] = buf + return + } +} diff --git a/vendor/github.com/getkin/kin-openapi/openapi3/maplike.go b/vendor/github.com/getkin/kin-openapi/openapi3/maplike.go new file mode 100644 index 00000000..7b8045c6 --- /dev/null +++ b/vendor/github.com/getkin/kin-openapi/openapi3/maplike.go @@ -0,0 +1,402 @@ +package openapi3 + +import ( + "encoding/json" + "sort" + "strings" + + "github.com/go-openapi/jsonpointer" +) + +// NewResponsesWithCapacity builds a responses object of the given capacity. +func NewResponsesWithCapacity(cap int) *Responses { + if cap == 0 { + return &Responses{m: make(map[string]*ResponseRef)} + } + return &Responses{m: make(map[string]*ResponseRef, cap)} +} + +// Value returns the responses for key or nil +func (responses *Responses) Value(key string) *ResponseRef { + if responses.Len() == 0 { + return nil + } + return responses.m[key] +} + +// Set adds or replaces key 'key' of 'responses' with 'value'. +// Note: 'responses' MUST be non-nil +func (responses *Responses) Set(key string, value *ResponseRef) { + if responses.m == nil { + responses.m = make(map[string]*ResponseRef) + } + responses.m[key] = value +} + +// Len returns the amount of keys in responses excluding responses.Extensions. +func (responses *Responses) Len() int { + if responses == nil || responses.m == nil { + return 0 + } + return len(responses.m) +} + +// Delete removes the entry associated with key 'key' from 'responses'. +func (responses *Responses) Delete(key string) { + if responses != nil && responses.m != nil { + delete(responses.m, key) + } +} + +// Map returns responses as a 'map'. +// Note: iteration on Go maps is not ordered. +func (responses *Responses) Map() (m map[string]*ResponseRef) { + if responses == nil || len(responses.m) == 0 { + return make(map[string]*ResponseRef) + } + m = make(map[string]*ResponseRef, len(responses.m)) + for k, v := range responses.m { + m[k] = v + } + return +} + +var _ jsonpointer.JSONPointable = (*Responses)(nil) + +// JSONLookup implements https://github.com/go-openapi/jsonpointer#JSONPointable +func (responses Responses) JSONLookup(token string) (any, error) { + if v := responses.Value(token); v == nil { + vv, _, err := jsonpointer.GetForToken(responses.Extensions, token) + return vv, err + } else if ref := v.Ref; ref != "" { + return &Ref{Ref: ref}, nil + } else { + var vv *Response = v.Value + return vv, nil + } +} + +// MarshalYAML returns the YAML encoding of Responses. +func (responses *Responses) MarshalYAML() (any, error) { + if responses == nil { + return nil, nil + } + m := make(map[string]any, responses.Len()+len(responses.Extensions)) + for k, v := range responses.Extensions { + m[k] = v + } + for k, v := range responses.Map() { + m[k] = v + } + return m, nil +} + +// MarshalJSON returns the JSON encoding of Responses. +func (responses *Responses) MarshalJSON() ([]byte, error) { + responsesYaml, err := responses.MarshalYAML() + if err != nil { + return nil, err + } + return json.Marshal(responsesYaml) +} + +// UnmarshalJSON sets Responses to a copy of data. +func (responses *Responses) UnmarshalJSON(data []byte) (err error) { + var m map[string]any + if err = json.Unmarshal(data, &m); err != nil { + return + } + + ks := make([]string, 0, len(m)) + for k := range m { + ks = append(ks, k) + } + sort.Strings(ks) + + x := Responses{ + Extensions: make(map[string]any), + m: make(map[string]*ResponseRef, len(m)), + } + + for _, k := range ks { + v := m[k] + if strings.HasPrefix(k, "x-") { + x.Extensions[k] = v + continue + } + + var data []byte + if data, err = json.Marshal(v); err != nil { + return + } + var vv ResponseRef + if err = vv.UnmarshalJSON(data); err != nil { + return + } + x.m[k] = &vv + } + *responses = x + return +} + +// NewCallbackWithCapacity builds a callback object of the given capacity. +func NewCallbackWithCapacity(cap int) *Callback { + if cap == 0 { + return &Callback{m: make(map[string]*PathItem)} + } + return &Callback{m: make(map[string]*PathItem, cap)} +} + +// Value returns the callback for key or nil +func (callback *Callback) Value(key string) *PathItem { + if callback.Len() == 0 { + return nil + } + return callback.m[key] +} + +// Set adds or replaces key 'key' of 'callback' with 'value'. +// Note: 'callback' MUST be non-nil +func (callback *Callback) Set(key string, value *PathItem) { + if callback.m == nil { + callback.m = make(map[string]*PathItem) + } + callback.m[key] = value +} + +// Len returns the amount of keys in callback excluding callback.Extensions. +func (callback *Callback) Len() int { + if callback == nil || callback.m == nil { + return 0 + } + return len(callback.m) +} + +// Delete removes the entry associated with key 'key' from 'callback'. +func (callback *Callback) Delete(key string) { + if callback != nil && callback.m != nil { + delete(callback.m, key) + } +} + +// Map returns callback as a 'map'. +// Note: iteration on Go maps is not ordered. +func (callback *Callback) Map() (m map[string]*PathItem) { + if callback == nil || len(callback.m) == 0 { + return make(map[string]*PathItem) + } + m = make(map[string]*PathItem, len(callback.m)) + for k, v := range callback.m { + m[k] = v + } + return +} + +var _ jsonpointer.JSONPointable = (*Callback)(nil) + +// JSONLookup implements https://github.com/go-openapi/jsonpointer#JSONPointable +func (callback Callback) JSONLookup(token string) (any, error) { + if v := callback.Value(token); v == nil { + vv, _, err := jsonpointer.GetForToken(callback.Extensions, token) + return vv, err + } else if ref := v.Ref; ref != "" { + return &Ref{Ref: ref}, nil + } else { + var vv *PathItem = v + return vv, nil + } +} + +// MarshalYAML returns the YAML encoding of Callback. +func (callback *Callback) MarshalYAML() (any, error) { + if callback == nil { + return nil, nil + } + m := make(map[string]any, callback.Len()+len(callback.Extensions)) + for k, v := range callback.Extensions { + m[k] = v + } + for k, v := range callback.Map() { + m[k] = v + } + return m, nil +} + +// MarshalJSON returns the JSON encoding of Callback. +func (callback *Callback) MarshalJSON() ([]byte, error) { + callbackYaml, err := callback.MarshalYAML() + if err != nil { + return nil, err + } + return json.Marshal(callbackYaml) +} + +// UnmarshalJSON sets Callback to a copy of data. +func (callback *Callback) UnmarshalJSON(data []byte) (err error) { + var m map[string]any + if err = json.Unmarshal(data, &m); err != nil { + return + } + + ks := make([]string, 0, len(m)) + for k := range m { + ks = append(ks, k) + } + sort.Strings(ks) + + x := Callback{ + Extensions: make(map[string]any), + m: make(map[string]*PathItem, len(m)), + } + + for _, k := range ks { + v := m[k] + if strings.HasPrefix(k, "x-") { + x.Extensions[k] = v + continue + } + + var data []byte + if data, err = json.Marshal(v); err != nil { + return + } + var vv PathItem + if err = vv.UnmarshalJSON(data); err != nil { + return + } + x.m[k] = &vv + } + *callback = x + return +} + +// NewPathsWithCapacity builds a paths object of the given capacity. +func NewPathsWithCapacity(cap int) *Paths { + if cap == 0 { + return &Paths{m: make(map[string]*PathItem)} + } + return &Paths{m: make(map[string]*PathItem, cap)} +} + +// Value returns the paths for key or nil +func (paths *Paths) Value(key string) *PathItem { + if paths.Len() == 0 { + return nil + } + return paths.m[key] +} + +// Set adds or replaces key 'key' of 'paths' with 'value'. +// Note: 'paths' MUST be non-nil +func (paths *Paths) Set(key string, value *PathItem) { + if paths.m == nil { + paths.m = make(map[string]*PathItem) + } + paths.m[key] = value +} + +// Len returns the amount of keys in paths excluding paths.Extensions. +func (paths *Paths) Len() int { + if paths == nil || paths.m == nil { + return 0 + } + return len(paths.m) +} + +// Delete removes the entry associated with key 'key' from 'paths'. +func (paths *Paths) Delete(key string) { + if paths != nil && paths.m != nil { + delete(paths.m, key) + } +} + +// Map returns paths as a 'map'. +// Note: iteration on Go maps is not ordered. +func (paths *Paths) Map() (m map[string]*PathItem) { + if paths == nil || len(paths.m) == 0 { + return make(map[string]*PathItem) + } + m = make(map[string]*PathItem, len(paths.m)) + for k, v := range paths.m { + m[k] = v + } + return +} + +var _ jsonpointer.JSONPointable = (*Paths)(nil) + +// JSONLookup implements https://github.com/go-openapi/jsonpointer#JSONPointable +func (paths Paths) JSONLookup(token string) (any, error) { + if v := paths.Value(token); v == nil { + vv, _, err := jsonpointer.GetForToken(paths.Extensions, token) + return vv, err + } else if ref := v.Ref; ref != "" { + return &Ref{Ref: ref}, nil + } else { + var vv *PathItem = v + return vv, nil + } +} + +// MarshalYAML returns the YAML encoding of Paths. +func (paths *Paths) MarshalYAML() (any, error) { + if paths == nil { + return nil, nil + } + m := make(map[string]any, paths.Len()+len(paths.Extensions)) + for k, v := range paths.Extensions { + m[k] = v + } + for k, v := range paths.Map() { + m[k] = v + } + return m, nil +} + +// MarshalJSON returns the JSON encoding of Paths. +func (paths *Paths) MarshalJSON() ([]byte, error) { + pathsYaml, err := paths.MarshalYAML() + if err != nil { + return nil, err + } + return json.Marshal(pathsYaml) +} + +// UnmarshalJSON sets Paths to a copy of data. +func (paths *Paths) UnmarshalJSON(data []byte) (err error) { + var m map[string]any + if err = json.Unmarshal(data, &m); err != nil { + return + } + + ks := make([]string, 0, len(m)) + for k := range m { + ks = append(ks, k) + } + sort.Strings(ks) + + x := Paths{ + Extensions: make(map[string]any), + m: make(map[string]*PathItem, len(m)), + } + + for _, k := range ks { + v := m[k] + if strings.HasPrefix(k, "x-") { + x.Extensions[k] = v + continue + } + + var data []byte + if data, err = json.Marshal(v); err != nil { + return + } + var vv PathItem + if err = vv.UnmarshalJSON(data); err != nil { + return + } + x.m[k] = &vv + } + *paths = x + return +} diff --git a/vendor/github.com/getkin/kin-openapi/openapi3/marsh.go b/vendor/github.com/getkin/kin-openapi/openapi3/marsh.go new file mode 100644 index 00000000..daa93755 --- /dev/null +++ b/vendor/github.com/getkin/kin-openapi/openapi3/marsh.go @@ -0,0 +1,34 @@ +package openapi3 + +import ( + "encoding/json" + "fmt" + "strings" + + "github.com/invopop/yaml" +) + +func unmarshalError(jsonUnmarshalErr error) error { + if before, after, found := strings.Cut(jsonUnmarshalErr.Error(), "Bis"); found && before != "" && after != "" { + before = strings.ReplaceAll(before, " Go struct ", " ") + return fmt.Errorf("%s%s", before, strings.ReplaceAll(after, "Bis", "")) + } + return jsonUnmarshalErr +} + +func unmarshal(data []byte, v any) error { + var jsonErr, yamlErr error + + // See https://github.com/getkin/kin-openapi/issues/680 + if jsonErr = json.Unmarshal(data, v); jsonErr == nil { + return nil + } + + // UnmarshalStrict(data, v) TODO: investigate how ymlv3 handles duplicate map keys + if yamlErr = yaml.Unmarshal(data, v); yamlErr == nil { + return nil + } + + // If both unmarshaling attempts fail, return a new error that includes both errors + return fmt.Errorf("failed to unmarshal data: json error: %v, yaml error: %v", jsonErr, yamlErr) +} diff --git a/vendor/github.com/getkin/kin-openapi/openapi3/media_type.go b/vendor/github.com/getkin/kin-openapi/openapi3/media_type.go new file mode 100644 index 00000000..d4466bcf --- /dev/null +++ b/vendor/github.com/getkin/kin-openapi/openapi3/media_type.go @@ -0,0 +1,179 @@ +package openapi3 + +import ( + "context" + "encoding/json" + "errors" + "fmt" + "sort" + + "github.com/go-openapi/jsonpointer" +) + +// MediaType is specified by OpenAPI/Swagger 3.0 standard. +// See https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#media-type-object +type MediaType struct { + Extensions map[string]any `json:"-" yaml:"-"` + + Schema *SchemaRef `json:"schema,omitempty" yaml:"schema,omitempty"` + Example any `json:"example,omitempty" yaml:"example,omitempty"` + Examples Examples `json:"examples,omitempty" yaml:"examples,omitempty"` + Encoding map[string]*Encoding `json:"encoding,omitempty" yaml:"encoding,omitempty"` +} + +var _ jsonpointer.JSONPointable = (*MediaType)(nil) + +func NewMediaType() *MediaType { + return &MediaType{} +} + +func (mediaType *MediaType) WithSchema(schema *Schema) *MediaType { + if schema == nil { + mediaType.Schema = nil + } else { + mediaType.Schema = &SchemaRef{Value: schema} + } + return mediaType +} + +func (mediaType *MediaType) WithSchemaRef(schema *SchemaRef) *MediaType { + mediaType.Schema = schema + return mediaType +} + +func (mediaType *MediaType) WithExample(name string, value any) *MediaType { + example := mediaType.Examples + if example == nil { + example = make(map[string]*ExampleRef) + mediaType.Examples = example + } + example[name] = &ExampleRef{ + Value: NewExample(value), + } + return mediaType +} + +func (mediaType *MediaType) WithEncoding(name string, enc *Encoding) *MediaType { + encoding := mediaType.Encoding + if encoding == nil { + encoding = make(map[string]*Encoding) + mediaType.Encoding = encoding + } + encoding[name] = enc + return mediaType +} + +// MarshalJSON returns the JSON encoding of MediaType. +func (mediaType MediaType) MarshalJSON() ([]byte, error) { + x, err := mediaType.MarshalYAML() + if err != nil { + return nil, err + } + return json.Marshal(x) +} + +// MarshalYAML returns the YAML encoding of MediaType. +func (mediaType MediaType) MarshalYAML() (any, error) { + m := make(map[string]any, 4+len(mediaType.Extensions)) + for k, v := range mediaType.Extensions { + m[k] = v + } + if x := mediaType.Schema; x != nil { + m["schema"] = x + } + if x := mediaType.Example; x != nil { + m["example"] = x + } + if x := mediaType.Examples; len(x) != 0 { + m["examples"] = x + } + if x := mediaType.Encoding; len(x) != 0 { + m["encoding"] = x + } + return m, nil +} + +// UnmarshalJSON sets MediaType to a copy of data. +func (mediaType *MediaType) UnmarshalJSON(data []byte) error { + type MediaTypeBis MediaType + var x MediaTypeBis + if err := json.Unmarshal(data, &x); err != nil { + return unmarshalError(err) + } + _ = json.Unmarshal(data, &x.Extensions) + delete(x.Extensions, "schema") + delete(x.Extensions, "example") + delete(x.Extensions, "examples") + delete(x.Extensions, "encoding") + if len(x.Extensions) == 0 { + x.Extensions = nil + } + *mediaType = MediaType(x) + return nil +} + +// Validate returns an error if MediaType does not comply with the OpenAPI spec. +func (mediaType *MediaType) Validate(ctx context.Context, opts ...ValidationOption) error { + ctx = WithValidationOptions(ctx, opts...) + + if mediaType == nil { + return nil + } + if schema := mediaType.Schema; schema != nil { + if err := schema.Validate(ctx); err != nil { + return err + } + + if mediaType.Example != nil && mediaType.Examples != nil { + return errors.New("example and examples are mutually exclusive") + } + + if vo := getValidationOptions(ctx); !vo.examplesValidationDisabled { + if example := mediaType.Example; example != nil { + if err := validateExampleValue(ctx, example, schema.Value); err != nil { + return fmt.Errorf("invalid example: %w", err) + } + } + + if examples := mediaType.Examples; examples != nil { + names := make([]string, 0, len(examples)) + for name := range examples { + names = append(names, name) + } + sort.Strings(names) + for _, k := range names { + v := examples[k] + if err := v.Validate(ctx); err != nil { + return fmt.Errorf("example %s: %w", k, err) + } + if err := validateExampleValue(ctx, v.Value.Value, schema.Value); err != nil { + return fmt.Errorf("example %s: %w", k, err) + } + } + } + } + } + + return validateExtensions(ctx, mediaType.Extensions) +} + +// JSONLookup implements https://pkg.go.dev/github.com/go-openapi/jsonpointer#JSONPointable +func (mediaType MediaType) JSONLookup(token string) (any, error) { + switch token { + case "schema": + if mediaType.Schema != nil { + if mediaType.Schema.Ref != "" { + return &Ref{Ref: mediaType.Schema.Ref}, nil + } + return mediaType.Schema.Value, nil + } + case "example": + return mediaType.Example, nil + case "examples": + return mediaType.Examples, nil + case "encoding": + return mediaType.Encoding, nil + } + v, _, err := jsonpointer.GetForToken(mediaType.Extensions, token) + return v, err +} diff --git a/vendor/github.com/getkin/kin-openapi/openapi3/openapi3.go b/vendor/github.com/getkin/kin-openapi/openapi3/openapi3.go new file mode 100644 index 00000000..ef1592e8 --- /dev/null +++ b/vendor/github.com/getkin/kin-openapi/openapi3/openapi3.go @@ -0,0 +1,205 @@ +package openapi3 + +import ( + "context" + "encoding/json" + "errors" + "fmt" + "net/url" + + "github.com/go-openapi/jsonpointer" +) + +// T is the root of an OpenAPI v3 document +// See https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#openapi-object +type T struct { + Extensions map[string]any `json:"-" yaml:"-"` + + OpenAPI string `json:"openapi" yaml:"openapi"` // Required + Components *Components `json:"components,omitempty" yaml:"components,omitempty"` + Info *Info `json:"info" yaml:"info"` // Required + Paths *Paths `json:"paths" yaml:"paths"` // Required + Security SecurityRequirements `json:"security,omitempty" yaml:"security,omitempty"` + Servers Servers `json:"servers,omitempty" yaml:"servers,omitempty"` + Tags Tags `json:"tags,omitempty" yaml:"tags,omitempty"` + ExternalDocs *ExternalDocs `json:"externalDocs,omitempty" yaml:"externalDocs,omitempty"` + + visited visitedComponent + url *url.URL +} + +var _ jsonpointer.JSONPointable = (*T)(nil) + +// JSONLookup implements https://pkg.go.dev/github.com/go-openapi/jsonpointer#JSONPointable +func (doc *T) JSONLookup(token string) (any, error) { + switch token { + case "openapi": + return doc.OpenAPI, nil + case "components": + return doc.Components, nil + case "info": + return doc.Info, nil + case "paths": + return doc.Paths, nil + case "security": + return doc.Security, nil + case "servers": + return doc.Servers, nil + case "tags": + return doc.Tags, nil + case "externalDocs": + return doc.ExternalDocs, nil + } + + v, _, err := jsonpointer.GetForToken(doc.Extensions, token) + return v, err +} + +// MarshalJSON returns the JSON encoding of T. +func (doc *T) MarshalJSON() ([]byte, error) { + x, err := doc.MarshalYAML() + if err != nil { + return nil, err + } + return json.Marshal(x) +} + +// MarshalYAML returns the YAML encoding of T. +func (doc *T) MarshalYAML() (any, error) { + if doc == nil { + return nil, nil + } + m := make(map[string]any, 4+len(doc.Extensions)) + for k, v := range doc.Extensions { + m[k] = v + } + m["openapi"] = doc.OpenAPI + if x := doc.Components; x != nil { + m["components"] = x + } + m["info"] = doc.Info + m["paths"] = doc.Paths + if x := doc.Security; len(x) != 0 { + m["security"] = x + } + if x := doc.Servers; len(x) != 0 { + m["servers"] = x + } + if x := doc.Tags; len(x) != 0 { + m["tags"] = x + } + if x := doc.ExternalDocs; x != nil { + m["externalDocs"] = x + } + return m, nil +} + +// UnmarshalJSON sets T to a copy of data. +func (doc *T) UnmarshalJSON(data []byte) error { + type TBis T + var x TBis + if err := json.Unmarshal(data, &x); err != nil { + return unmarshalError(err) + } + _ = json.Unmarshal(data, &x.Extensions) + delete(x.Extensions, "openapi") + delete(x.Extensions, "components") + delete(x.Extensions, "info") + delete(x.Extensions, "paths") + delete(x.Extensions, "security") + delete(x.Extensions, "servers") + delete(x.Extensions, "tags") + delete(x.Extensions, "externalDocs") + if len(x.Extensions) == 0 { + x.Extensions = nil + } + *doc = T(x) + return nil +} + +func (doc *T) AddOperation(path string, method string, operation *Operation) { + if doc.Paths == nil { + doc.Paths = NewPaths() + } + pathItem := doc.Paths.Value(path) + if pathItem == nil { + pathItem = &PathItem{} + doc.Paths.Set(path, pathItem) + } + pathItem.SetOperation(method, operation) +} + +func (doc *T) AddServer(server *Server) { + doc.Servers = append(doc.Servers, server) +} + +func (doc *T) AddServers(servers ...*Server) { + doc.Servers = append(doc.Servers, servers...) +} + +// Validate returns an error if T does not comply with the OpenAPI spec. +// Validations Options can be provided to modify the validation behavior. +func (doc *T) Validate(ctx context.Context, opts ...ValidationOption) error { + ctx = WithValidationOptions(ctx, opts...) + + if doc.OpenAPI == "" { + return errors.New("value of openapi must be a non-empty string") + } + + var wrap func(error) error + + wrap = func(e error) error { return fmt.Errorf("invalid components: %w", e) } + if v := doc.Components; v != nil { + if err := v.Validate(ctx); err != nil { + return wrap(err) + } + } + + wrap = func(e error) error { return fmt.Errorf("invalid info: %w", e) } + if v := doc.Info; v != nil { + if err := v.Validate(ctx); err != nil { + return wrap(err) + } + } else { + return wrap(errors.New("must be an object")) + } + + wrap = func(e error) error { return fmt.Errorf("invalid paths: %w", e) } + if v := doc.Paths; v != nil { + if err := v.Validate(ctx); err != nil { + return wrap(err) + } + } else { + return wrap(errors.New("must be an object")) + } + + wrap = func(e error) error { return fmt.Errorf("invalid security: %w", e) } + if v := doc.Security; v != nil { + if err := v.Validate(ctx); err != nil { + return wrap(err) + } + } + + wrap = func(e error) error { return fmt.Errorf("invalid servers: %w", e) } + if v := doc.Servers; v != nil { + if err := v.Validate(ctx); err != nil { + return wrap(err) + } + } + + wrap = func(e error) error { return fmt.Errorf("invalid tags: %w", e) } + if v := doc.Tags; v != nil { + if err := v.Validate(ctx); err != nil { + return wrap(err) + } + } + + wrap = func(e error) error { return fmt.Errorf("invalid external docs: %w", e) } + if v := doc.ExternalDocs; v != nil { + if err := v.Validate(ctx); err != nil { + return wrap(err) + } + } + + return validateExtensions(ctx, doc.Extensions) +} diff --git a/vendor/github.com/getkin/kin-openapi/openapi3/operation.go b/vendor/github.com/getkin/kin-openapi/openapi3/operation.go new file mode 100644 index 00000000..40abf73c --- /dev/null +++ b/vendor/github.com/getkin/kin-openapi/openapi3/operation.go @@ -0,0 +1,222 @@ +package openapi3 + +import ( + "context" + "encoding/json" + "errors" + "fmt" + "strconv" + + "github.com/go-openapi/jsonpointer" +) + +// Operation represents "operation" specified by" OpenAPI/Swagger 3.0 standard. +// See https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#operation-object +type Operation struct { + Extensions map[string]any `json:"-" yaml:"-"` + + // Optional tags for documentation. + Tags []string `json:"tags,omitempty" yaml:"tags,omitempty"` + + // Optional short summary. + Summary string `json:"summary,omitempty" yaml:"summary,omitempty"` + + // Optional description. Should use CommonMark syntax. + Description string `json:"description,omitempty" yaml:"description,omitempty"` + + // Optional operation ID. + OperationID string `json:"operationId,omitempty" yaml:"operationId,omitempty"` + + // Optional parameters. + Parameters Parameters `json:"parameters,omitempty" yaml:"parameters,omitempty"` + + // Optional body parameter. + RequestBody *RequestBodyRef `json:"requestBody,omitempty" yaml:"requestBody,omitempty"` + + // Responses. + Responses *Responses `json:"responses" yaml:"responses"` // Required + + // Optional callbacks + Callbacks Callbacks `json:"callbacks,omitempty" yaml:"callbacks,omitempty"` + + Deprecated bool `json:"deprecated,omitempty" yaml:"deprecated,omitempty"` + + // Optional security requirements that overrides top-level security. + Security *SecurityRequirements `json:"security,omitempty" yaml:"security,omitempty"` + + // Optional servers that overrides top-level servers. + Servers *Servers `json:"servers,omitempty" yaml:"servers,omitempty"` + + ExternalDocs *ExternalDocs `json:"externalDocs,omitempty" yaml:"externalDocs,omitempty"` +} + +var _ jsonpointer.JSONPointable = (*Operation)(nil) + +func NewOperation() *Operation { + return &Operation{} +} + +// MarshalJSON returns the JSON encoding of Operation. +func (operation Operation) MarshalJSON() ([]byte, error) { + x, err := operation.MarshalYAML() + if err != nil { + return nil, err + } + return json.Marshal(x) +} + +// MarshalYAML returns the YAML encoding of Operation. +func (operation Operation) MarshalYAML() (any, error) { + m := make(map[string]any, 12+len(operation.Extensions)) + for k, v := range operation.Extensions { + m[k] = v + } + if x := operation.Tags; len(x) != 0 { + m["tags"] = x + } + if x := operation.Summary; x != "" { + m["summary"] = x + } + if x := operation.Description; x != "" { + m["description"] = x + } + if x := operation.OperationID; x != "" { + m["operationId"] = x + } + if x := operation.Parameters; len(x) != 0 { + m["parameters"] = x + } + if x := operation.RequestBody; x != nil { + m["requestBody"] = x + } + m["responses"] = operation.Responses + if x := operation.Callbacks; len(x) != 0 { + m["callbacks"] = x + } + if x := operation.Deprecated; x { + m["deprecated"] = x + } + if x := operation.Security; x != nil { + m["security"] = x + } + if x := operation.Servers; x != nil { + m["servers"] = x + } + if x := operation.ExternalDocs; x != nil { + m["externalDocs"] = x + } + return m, nil +} + +// UnmarshalJSON sets Operation to a copy of data. +func (operation *Operation) UnmarshalJSON(data []byte) error { + type OperationBis Operation + var x OperationBis + if err := json.Unmarshal(data, &x); err != nil { + return unmarshalError(err) + } + _ = json.Unmarshal(data, &x.Extensions) + delete(x.Extensions, "tags") + delete(x.Extensions, "summary") + delete(x.Extensions, "description") + delete(x.Extensions, "operationId") + delete(x.Extensions, "parameters") + delete(x.Extensions, "requestBody") + delete(x.Extensions, "responses") + delete(x.Extensions, "callbacks") + delete(x.Extensions, "deprecated") + delete(x.Extensions, "security") + delete(x.Extensions, "servers") + delete(x.Extensions, "externalDocs") + if len(x.Extensions) == 0 { + x.Extensions = nil + } + *operation = Operation(x) + return nil +} + +// JSONLookup implements https://pkg.go.dev/github.com/go-openapi/jsonpointer#JSONPointable +func (operation Operation) JSONLookup(token string) (any, error) { + switch token { + case "requestBody": + if operation.RequestBody != nil { + if operation.RequestBody.Ref != "" { + return &Ref{Ref: operation.RequestBody.Ref}, nil + } + return operation.RequestBody.Value, nil + } + case "tags": + return operation.Tags, nil + case "summary": + return operation.Summary, nil + case "description": + return operation.Description, nil + case "operationID": + return operation.OperationID, nil + case "parameters": + return operation.Parameters, nil + case "responses": + return operation.Responses, nil + case "callbacks": + return operation.Callbacks, nil + case "deprecated": + return operation.Deprecated, nil + case "security": + return operation.Security, nil + case "servers": + return operation.Servers, nil + case "externalDocs": + return operation.ExternalDocs, nil + } + + v, _, err := jsonpointer.GetForToken(operation.Extensions, token) + return v, err +} + +func (operation *Operation) AddParameter(p *Parameter) { + operation.Parameters = append(operation.Parameters, &ParameterRef{Value: p}) +} + +func (operation *Operation) AddResponse(status int, response *Response) { + code := "default" + if 0 < status && status < 1000 { + code = strconv.FormatInt(int64(status), 10) + } + if operation.Responses == nil { + operation.Responses = NewResponses() + } + operation.Responses.Set(code, &ResponseRef{Value: response}) +} + +// Validate returns an error if Operation does not comply with the OpenAPI spec. +func (operation *Operation) Validate(ctx context.Context, opts ...ValidationOption) error { + ctx = WithValidationOptions(ctx, opts...) + + if v := operation.Parameters; v != nil { + if err := v.Validate(ctx); err != nil { + return err + } + } + + if v := operation.RequestBody; v != nil { + if err := v.Validate(ctx); err != nil { + return err + } + } + + if v := operation.Responses; v != nil { + if err := v.Validate(ctx); err != nil { + return err + } + } else { + return errors.New("value of responses must be an object") + } + + if v := operation.ExternalDocs; v != nil { + if err := v.Validate(ctx); err != nil { + return fmt.Errorf("invalid external docs: %w", err) + } + } + + return validateExtensions(ctx, operation.Extensions) +} diff --git a/vendor/github.com/getkin/kin-openapi/openapi3/parameter.go b/vendor/github.com/getkin/kin-openapi/openapi3/parameter.go new file mode 100644 index 00000000..34fe2911 --- /dev/null +++ b/vendor/github.com/getkin/kin-openapi/openapi3/parameter.go @@ -0,0 +1,416 @@ +package openapi3 + +import ( + "context" + "encoding/json" + "errors" + "fmt" + "sort" + "strconv" + + "github.com/go-openapi/jsonpointer" +) + +// Parameters is specified by OpenAPI/Swagger 3.0 standard. +type Parameters []*ParameterRef + +var _ jsonpointer.JSONPointable = (*Parameters)(nil) + +// JSONLookup implements https://pkg.go.dev/github.com/go-openapi/jsonpointer#JSONPointable +func (p Parameters) JSONLookup(token string) (any, error) { + index, err := strconv.Atoi(token) + if err != nil { + return nil, err + } + if index < 0 || index >= len(p) { + return nil, fmt.Errorf("index %d out of bounds of array of length %d", index, len(p)) + } + + ref := p[index] + if ref != nil && ref.Ref != "" { + return &Ref{Ref: ref.Ref}, nil + } + return ref.Value, nil +} + +func NewParameters() Parameters { + return make(Parameters, 0, 4) +} + +func (parameters Parameters) GetByInAndName(in string, name string) *Parameter { + for _, item := range parameters { + if v := item.Value; v != nil { + if v.Name == name && v.In == in { + return v + } + } + } + return nil +} + +// Validate returns an error if Parameters does not comply with the OpenAPI spec. +func (parameters Parameters) Validate(ctx context.Context, opts ...ValidationOption) error { + ctx = WithValidationOptions(ctx, opts...) + + dupes := make(map[string]struct{}) + for _, parameterRef := range parameters { + if v := parameterRef.Value; v != nil { + key := v.In + ":" + v.Name + if _, ok := dupes[key]; ok { + return fmt.Errorf("more than one %q parameter has name %q", v.In, v.Name) + } + dupes[key] = struct{}{} + } + + if err := parameterRef.Validate(ctx); err != nil { + return err + } + } + return nil +} + +// Parameter is specified by OpenAPI/Swagger 3.0 standard. +// See https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#parameter-object +type Parameter struct { + Extensions map[string]any `json:"-" yaml:"-"` + + Name string `json:"name,omitempty" yaml:"name,omitempty"` + In string `json:"in,omitempty" yaml:"in,omitempty"` + Description string `json:"description,omitempty" yaml:"description,omitempty"` + Style string `json:"style,omitempty" yaml:"style,omitempty"` + Explode *bool `json:"explode,omitempty" yaml:"explode,omitempty"` + AllowEmptyValue bool `json:"allowEmptyValue,omitempty" yaml:"allowEmptyValue,omitempty"` + AllowReserved bool `json:"allowReserved,omitempty" yaml:"allowReserved,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 any `json:"example,omitempty" yaml:"example,omitempty"` + Examples Examples `json:"examples,omitempty" yaml:"examples,omitempty"` + Content Content `json:"content,omitempty" yaml:"content,omitempty"` +} + +var _ jsonpointer.JSONPointable = (*Parameter)(nil) + +const ( + ParameterInPath = "path" + ParameterInQuery = "query" + ParameterInHeader = "header" + ParameterInCookie = "cookie" +) + +func NewPathParameter(name string) *Parameter { + return &Parameter{ + Name: name, + In: ParameterInPath, + Required: true, + } +} + +func NewQueryParameter(name string) *Parameter { + return &Parameter{ + Name: name, + In: ParameterInQuery, + } +} + +func NewHeaderParameter(name string) *Parameter { + return &Parameter{ + Name: name, + In: ParameterInHeader, + } +} + +func NewCookieParameter(name string) *Parameter { + return &Parameter{ + Name: name, + In: ParameterInCookie, + } +} + +func (parameter *Parameter) WithDescription(value string) *Parameter { + parameter.Description = value + return parameter +} + +func (parameter *Parameter) WithRequired(value bool) *Parameter { + parameter.Required = value + return parameter +} + +func (parameter *Parameter) WithSchema(value *Schema) *Parameter { + if value == nil { + parameter.Schema = nil + } else { + parameter.Schema = &SchemaRef{ + Value: value, + } + } + return parameter +} + +// MarshalJSON returns the JSON encoding of Parameter. +func (parameter Parameter) MarshalJSON() ([]byte, error) { + x, err := parameter.MarshalYAML() + if err != nil { + return nil, err + } + return json.Marshal(x) +} + +// MarshalYAML returns the YAML encoding of Parameter. +func (parameter Parameter) MarshalYAML() (any, error) { + m := make(map[string]any, 13+len(parameter.Extensions)) + for k, v := range parameter.Extensions { + m[k] = v + } + + if x := parameter.Name; x != "" { + m["name"] = x + } + if x := parameter.In; x != "" { + m["in"] = x + } + if x := parameter.Description; x != "" { + m["description"] = x + } + if x := parameter.Style; x != "" { + m["style"] = x + } + if x := parameter.Explode; x != nil { + m["explode"] = x + } + if x := parameter.AllowEmptyValue; x { + m["allowEmptyValue"] = x + } + if x := parameter.AllowReserved; x { + m["allowReserved"] = x + } + if x := parameter.Deprecated; x { + m["deprecated"] = x + } + if x := parameter.Required; x { + m["required"] = x + } + if x := parameter.Schema; x != nil { + m["schema"] = x + } + if x := parameter.Example; x != nil { + m["example"] = x + } + if x := parameter.Examples; len(x) != 0 { + m["examples"] = x + } + if x := parameter.Content; len(x) != 0 { + m["content"] = x + } + + return m, nil +} + +// UnmarshalJSON sets Parameter to a copy of data. +func (parameter *Parameter) UnmarshalJSON(data []byte) error { + type ParameterBis Parameter + var x ParameterBis + if err := json.Unmarshal(data, &x); err != nil { + return unmarshalError(err) + } + _ = json.Unmarshal(data, &x.Extensions) + + delete(x.Extensions, "name") + delete(x.Extensions, "in") + delete(x.Extensions, "description") + delete(x.Extensions, "style") + delete(x.Extensions, "explode") + delete(x.Extensions, "allowEmptyValue") + delete(x.Extensions, "allowReserved") + delete(x.Extensions, "deprecated") + delete(x.Extensions, "required") + delete(x.Extensions, "schema") + delete(x.Extensions, "example") + delete(x.Extensions, "examples") + delete(x.Extensions, "content") + if len(x.Extensions) == 0 { + x.Extensions = nil + } + + *parameter = Parameter(x) + return nil +} + +// JSONLookup implements https://pkg.go.dev/github.com/go-openapi/jsonpointer#JSONPointable +func (parameter Parameter) JSONLookup(token string) (any, error) { + switch token { + case "schema": + if parameter.Schema != nil { + if parameter.Schema.Ref != "" { + return &Ref{Ref: parameter.Schema.Ref}, nil + } + return parameter.Schema.Value, nil + } + case "name": + return parameter.Name, nil + case "in": + return parameter.In, nil + case "description": + return parameter.Description, nil + case "style": + return parameter.Style, nil + case "explode": + return parameter.Explode, nil + case "allowEmptyValue": + return parameter.AllowEmptyValue, nil + case "allowReserved": + return parameter.AllowReserved, nil + case "deprecated": + return parameter.Deprecated, nil + case "required": + return parameter.Required, nil + case "example": + return parameter.Example, nil + case "examples": + return parameter.Examples, nil + case "content": + return parameter.Content, nil + } + + v, _, err := jsonpointer.GetForToken(parameter.Extensions, token) + return v, err +} + +// SerializationMethod returns a parameter's serialization method. +// When a parameter's serialization method is not defined the method returns +// the default serialization method corresponding to a parameter's location. +func (parameter *Parameter) SerializationMethod() (*SerializationMethod, error) { + switch parameter.In { + case ParameterInPath, ParameterInHeader: + style := parameter.Style + if style == "" { + style = SerializationSimple + } + explode := false + if parameter.Explode != nil { + explode = *parameter.Explode + } + return &SerializationMethod{Style: style, Explode: explode}, nil + case ParameterInQuery, ParameterInCookie: + style := parameter.Style + if style == "" { + style = SerializationForm + } + explode := true + if parameter.Explode != nil { + explode = *parameter.Explode + } + return &SerializationMethod{Style: style, Explode: explode}, nil + default: + return nil, fmt.Errorf("unexpected parameter's 'in': %q", parameter.In) + } +} + +// Validate returns an error if Parameter does not comply with the OpenAPI spec. +func (parameter *Parameter) Validate(ctx context.Context, opts ...ValidationOption) error { + ctx = WithValidationOptions(ctx, opts...) + + if parameter.Name == "" { + return errors.New("parameter name can't be blank") + } + in := parameter.In + switch in { + case + ParameterInPath, + ParameterInQuery, + ParameterInHeader, + ParameterInCookie: + default: + return fmt.Errorf("parameter can't have 'in' value %q", parameter.In) + } + + if in == ParameterInPath && !parameter.Required { + return fmt.Errorf("path parameter %q must be required", parameter.Name) + } + + // Validate a parameter's serialization method. + sm, err := parameter.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: + 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: %w", parameter.Name, e) + } + + if (parameter.Schema == nil) == (len(parameter.Content) == 0) { + e := errors.New("parameter must contain exactly one of content and schema") + return fmt.Errorf("parameter %q schema is invalid: %w", parameter.Name, e) + } + + if content := parameter.Content; content != nil { + e := errors.New("parameter content must only contain one entry") + if len(content) > 1 { + return fmt.Errorf("parameter %q content is invalid: %w", parameter.Name, e) + } + + if err := content.Validate(ctx); err != nil { + return fmt.Errorf("parameter %q content is invalid: %w", parameter.Name, err) + } + } + + if schema := parameter.Schema; schema != nil { + if err := schema.Validate(ctx); err != nil { + return fmt.Errorf("parameter %q schema is invalid: %w", parameter.Name, err) + } + if parameter.Example != nil && parameter.Examples != nil { + return fmt.Errorf("parameter %q example and examples are mutually exclusive", parameter.Name) + } + + if vo := getValidationOptions(ctx); vo.examplesValidationDisabled { + return nil + } + if example := parameter.Example; example != nil { + if err := validateExampleValue(ctx, example, schema.Value); err != nil { + return fmt.Errorf("invalid example: %w", err) + } + } else if examples := parameter.Examples; examples != nil { + names := make([]string, 0, len(examples)) + for name := range examples { + names = append(names, name) + } + sort.Strings(names) + for _, k := range names { + v := examples[k] + if err := v.Validate(ctx); err != nil { + return fmt.Errorf("%s: %w", k, err) + } + if err := validateExampleValue(ctx, v.Value.Value, schema.Value); err != nil { + return fmt.Errorf("%s: %w", k, err) + } + } + } + } + + return validateExtensions(ctx, parameter.Extensions) +} diff --git a/vendor/github.com/getkin/kin-openapi/openapi3/path_item.go b/vendor/github.com/getkin/kin-openapi/openapi3/path_item.go new file mode 100644 index 00000000..859634fe --- /dev/null +++ b/vendor/github.com/getkin/kin-openapi/openapi3/path_item.go @@ -0,0 +1,248 @@ +package openapi3 + +import ( + "context" + "encoding/json" + "fmt" + "net/http" + "sort" +) + +// PathItem is specified by OpenAPI/Swagger standard version 3. +// See https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#path-item-object +type PathItem struct { + Extensions map[string]any `json:"-" yaml:"-"` + + Ref string `json:"$ref,omitempty" yaml:"$ref,omitempty"` + Summary string `json:"summary,omitempty" yaml:"summary,omitempty"` + Description string `json:"description,omitempty" yaml:"description,omitempty"` + Connect *Operation `json:"connect,omitempty" yaml:"connect,omitempty"` + Delete *Operation `json:"delete,omitempty" yaml:"delete,omitempty"` + Get *Operation `json:"get,omitempty" yaml:"get,omitempty"` + Head *Operation `json:"head,omitempty" yaml:"head,omitempty"` + Options *Operation `json:"options,omitempty" yaml:"options,omitempty"` + Patch *Operation `json:"patch,omitempty" yaml:"patch,omitempty"` + Post *Operation `json:"post,omitempty" yaml:"post,omitempty"` + Put *Operation `json:"put,omitempty" yaml:"put,omitempty"` + Trace *Operation `json:"trace,omitempty" yaml:"trace,omitempty"` + Servers Servers `json:"servers,omitempty" yaml:"servers,omitempty"` + Parameters Parameters `json:"parameters,omitempty" yaml:"parameters,omitempty"` +} + +// MarshalJSON returns the JSON encoding of PathItem. +func (pathItem PathItem) MarshalJSON() ([]byte, error) { + x, err := pathItem.MarshalYAML() + if err != nil { + return nil, err + } + return json.Marshal(x) +} + +// MarshalYAML returns the YAML encoding of PathItem. +func (pathItem PathItem) MarshalYAML() (any, error) { + if ref := pathItem.Ref; ref != "" { + return Ref{Ref: ref}, nil + } + + m := make(map[string]any, 13+len(pathItem.Extensions)) + for k, v := range pathItem.Extensions { + m[k] = v + } + if x := pathItem.Summary; x != "" { + m["summary"] = x + } + if x := pathItem.Description; x != "" { + m["description"] = x + } + if x := pathItem.Connect; x != nil { + m["connect"] = x + } + if x := pathItem.Delete; x != nil { + m["delete"] = x + } + if x := pathItem.Get; x != nil { + m["get"] = x + } + if x := pathItem.Head; x != nil { + m["head"] = x + } + if x := pathItem.Options; x != nil { + m["options"] = x + } + if x := pathItem.Patch; x != nil { + m["patch"] = x + } + if x := pathItem.Post; x != nil { + m["post"] = x + } + if x := pathItem.Put; x != nil { + m["put"] = x + } + if x := pathItem.Trace; x != nil { + m["trace"] = x + } + if x := pathItem.Servers; len(x) != 0 { + m["servers"] = x + } + if x := pathItem.Parameters; len(x) != 0 { + m["parameters"] = x + } + return m, nil +} + +// UnmarshalJSON sets PathItem to a copy of data. +func (pathItem *PathItem) UnmarshalJSON(data []byte) error { + type PathItemBis PathItem + var x PathItemBis + if err := json.Unmarshal(data, &x); err != nil { + return unmarshalError(err) + } + _ = json.Unmarshal(data, &x.Extensions) + delete(x.Extensions, "$ref") + delete(x.Extensions, "summary") + delete(x.Extensions, "description") + delete(x.Extensions, "connect") + delete(x.Extensions, "delete") + delete(x.Extensions, "get") + delete(x.Extensions, "head") + delete(x.Extensions, "options") + delete(x.Extensions, "patch") + delete(x.Extensions, "post") + delete(x.Extensions, "put") + delete(x.Extensions, "trace") + delete(x.Extensions, "servers") + delete(x.Extensions, "parameters") + if len(x.Extensions) == 0 { + x.Extensions = nil + } + *pathItem = PathItem(x) + return nil +} + +func (pathItem *PathItem) Operations() map[string]*Operation { + operations := make(map[string]*Operation) + if v := pathItem.Connect; v != nil { + operations[http.MethodConnect] = v + } + if v := pathItem.Delete; v != nil { + operations[http.MethodDelete] = v + } + if v := pathItem.Get; v != nil { + operations[http.MethodGet] = v + } + if v := pathItem.Head; v != nil { + operations[http.MethodHead] = v + } + if v := pathItem.Options; v != nil { + operations[http.MethodOptions] = v + } + if v := pathItem.Patch; v != nil { + operations[http.MethodPatch] = v + } + if v := pathItem.Post; v != nil { + operations[http.MethodPost] = v + } + if v := pathItem.Put; v != nil { + operations[http.MethodPut] = v + } + if v := pathItem.Trace; v != nil { + operations[http.MethodTrace] = v + } + return operations +} + +func (pathItem *PathItem) GetOperation(method string) *Operation { + switch method { + case http.MethodConnect: + return pathItem.Connect + case http.MethodDelete: + return pathItem.Delete + case http.MethodGet: + return pathItem.Get + case http.MethodHead: + return pathItem.Head + case http.MethodOptions: + return pathItem.Options + case http.MethodPatch: + return pathItem.Patch + case http.MethodPost: + return pathItem.Post + case http.MethodPut: + return pathItem.Put + case http.MethodTrace: + return pathItem.Trace + default: + panic(fmt.Errorf("unsupported HTTP method %q", method)) + } +} + +func (pathItem *PathItem) SetOperation(method string, operation *Operation) { + switch method { + case http.MethodConnect: + pathItem.Connect = operation + case http.MethodDelete: + pathItem.Delete = operation + case http.MethodGet: + pathItem.Get = operation + case http.MethodHead: + pathItem.Head = operation + case http.MethodOptions: + pathItem.Options = operation + case http.MethodPatch: + pathItem.Patch = operation + case http.MethodPost: + pathItem.Post = operation + case http.MethodPut: + pathItem.Put = operation + case http.MethodTrace: + pathItem.Trace = operation + default: + panic(fmt.Errorf("unsupported HTTP method %q", method)) + } +} + +// Validate returns an error if PathItem does not comply with the OpenAPI spec. +func (pathItem *PathItem) Validate(ctx context.Context, opts ...ValidationOption) error { + ctx = WithValidationOptions(ctx, opts...) + + operations := pathItem.Operations() + + methods := make([]string, 0, len(operations)) + for method := range operations { + methods = append(methods, method) + } + sort.Strings(methods) + for _, method := range methods { + operation := operations[method] + if err := operation.Validate(ctx); err != nil { + return fmt.Errorf("invalid operation %s: %v", method, err) + } + } + + if v := pathItem.Parameters; v != nil { + if err := v.Validate(ctx); err != nil { + return err + } + } + + return validateExtensions(ctx, pathItem.Extensions) +} + +// isEmpty's introduced in 546590b1 +func (pathItem *PathItem) isEmpty() bool { + // NOTE: ignores pathItem.Extensions + // NOTE: ignores pathItem.Ref + return pathItem.Summary == "" && + pathItem.Description == "" && + pathItem.Connect == nil && + pathItem.Delete == nil && + pathItem.Get == nil && + pathItem.Head == nil && + pathItem.Options == nil && + pathItem.Patch == nil && + pathItem.Post == nil && + pathItem.Put == nil && + pathItem.Trace == nil && + len(pathItem.Servers) == 0 && + len(pathItem.Parameters) == 0 +} diff --git a/vendor/github.com/getkin/kin-openapi/openapi3/paths.go b/vendor/github.com/getkin/kin-openapi/openapi3/paths.go new file mode 100644 index 00000000..76747412 --- /dev/null +++ b/vendor/github.com/getkin/kin-openapi/openapi3/paths.go @@ -0,0 +1,268 @@ +package openapi3 + +import ( + "context" + "fmt" + "sort" + "strings" +) + +// Paths is specified by OpenAPI/Swagger standard version 3. +// See https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#paths-object +type Paths struct { + Extensions map[string]any `json:"-" yaml:"-"` + + m map[string]*PathItem +} + +// NewPaths builds a paths object with path items in insertion order. +func NewPaths(opts ...NewPathsOption) *Paths { + paths := NewPathsWithCapacity(len(opts)) + for _, opt := range opts { + opt(paths) + } + return paths +} + +// NewPathsOption describes options to NewPaths func +type NewPathsOption func(*Paths) + +// WithPath adds a named path item +func WithPath(path string, pathItem *PathItem) NewPathsOption { + return func(paths *Paths) { + if p := pathItem; p != nil && path != "" { + paths.Set(path, p) + } + } +} + +// Validate returns an error if Paths does not comply with the OpenAPI spec. +func (paths *Paths) Validate(ctx context.Context, opts ...ValidationOption) error { + ctx = WithValidationOptions(ctx, opts...) + + normalizedPaths := make(map[string]string, paths.Len()) + + keys := make([]string, 0, paths.Len()) + for key := range paths.Map() { + keys = append(keys, key) + } + sort.Strings(keys) + for _, path := range keys { + pathItem := paths.Value(path) + if path == "" || path[0] != '/' { + return fmt.Errorf("path %q does not start with a forward slash (/)", path) + } + + if pathItem == nil { + pathItem = &PathItem{} + paths.Set(path, pathItem) + } + + normalizedPath, _, varsInPath := normalizeTemplatedPath(path) + if oldPath, ok := normalizedPaths[normalizedPath]; ok { + return fmt.Errorf("conflicting paths %q and %q", path, oldPath) + } + normalizedPaths[path] = path + + var commonParams []string + for _, parameterRef := range pathItem.Parameters { + if parameterRef != nil { + if parameter := parameterRef.Value; parameter != nil && parameter.In == ParameterInPath { + commonParams = append(commonParams, parameter.Name) + } + } + } + operations := pathItem.Operations() + methods := make([]string, 0, len(operations)) + for method := range operations { + methods = append(methods, method) + } + sort.Strings(methods) + for _, method := range methods { + operation := operations[method] + var setParams []string + for _, parameterRef := range operation.Parameters { + if parameterRef != nil { + if parameter := parameterRef.Value; parameter != nil && parameter.In == ParameterInPath { + setParams = append(setParams, parameter.Name) + } + } + } + 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(ctx); err != nil { + return fmt.Errorf("invalid path %s: %v", path, err) + } + } + + if err := paths.validateUniqueOperationIDs(); err != nil { + return err + } + + return validateExtensions(ctx, paths.Extensions) +} + +// InMatchingOrder returns paths in the order they are matched against URLs. +// See https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#paths-object +// When matching URLs, concrete (non-templated) paths would be matched +// before their templated counterparts. +func (paths *Paths) InMatchingOrder() []string { + // NOTE: sorting by number of variables ASC then by descending lexicographical + // order seems to be a good heuristic. + if paths.Len() == 0 { + return nil + } + + vars := make(map[int][]string) + max := 0 + for path := range paths.Map() { + count := strings.Count(path, "}") + vars[count] = append(vars[count], path) + if count > max { + max = count + } + } + + ordered := make([]string, 0, paths.Len()) + for c := 0; c <= max; c++ { + if ps, ok := vars[c]; ok { + sort.Sort(sort.Reverse(sort.StringSlice(ps))) + ordered = append(ordered, ps...) + } + } + return ordered +} + +// Find returns a path that matches the key. +// +// The method ignores differences in template variable names (except possible "*" suffix). +// +// For example: +// +// paths := openapi3.Paths { +// "/person/{personName}": &openapi3.PathItem{}, +// } +// pathItem := path.Find("/person/{name}") +// +// would return the correct path item. +func (paths *Paths) Find(key string) *PathItem { + // Try directly access the map + pathItem := paths.Value(key) + if pathItem != nil { + return pathItem + } + + normalizedPath, expected, _ := normalizeTemplatedPath(key) + for path, pathItem := range paths.Map() { + pathNormalized, got, _ := normalizeTemplatedPath(path) + if got == expected && pathNormalized == normalizedPath { + return pathItem + } + } + return nil +} + +func (paths *Paths) validateUniqueOperationIDs() error { + operationIDs := make(map[string]string) + for urlPath, pathItem := range paths.Map() { + if pathItem == nil { + continue + } + for httpMethod, operation := range pathItem.Operations() { + if operation == nil || operation.OperationID == "" { + continue + } + endpoint := httpMethod + " " + urlPath + if endpointDup, ok := operationIDs[operation.OperationID]; ok { + if endpoint > endpointDup { // For make error message a bit more deterministic. May be useful for tests. + endpoint, endpointDup = endpointDup, endpoint + } + return fmt.Errorf("operations %q and %q have the same operation id %q", + endpoint, endpointDup, operation.OperationID) + } + operationIDs[operation.OperationID] = endpoint + } + } + return nil +} + +func normalizeTemplatedPath(path string) (string, uint, map[string]struct{}) { + if strings.IndexByte(path, '{') < 0 { + return path, 0, nil + } + + 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 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 == '*' { + buffTpl.WriteRune(cc) + } + } else { + buffVar.WriteRune(c) + continue + } + + } else if c == '{' { + // Begin path variable + isVariable = true + + // The character '{' will be appended + count++ + } + + // Append the character + buffTpl.WriteRune(c) + cc = c + } + return buffTpl.String(), count, vars +} diff --git a/vendor/github.com/getkin/kin-openapi/openapi3/ref.go b/vendor/github.com/getkin/kin-openapi/openapi3/ref.go new file mode 100644 index 00000000..07060731 --- /dev/null +++ b/vendor/github.com/getkin/kin-openapi/openapi3/ref.go @@ -0,0 +1,9 @@ +package openapi3 + +//go:generate go run refsgenerator.go + +// Ref is specified by OpenAPI/Swagger 3.0 standard. +// See https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#reference-object +type Ref struct { + Ref string `json:"$ref" yaml:"$ref"` +} diff --git a/vendor/github.com/getkin/kin-openapi/openapi3/refs.go b/vendor/github.com/getkin/kin-openapi/openapi3/refs.go new file mode 100644 index 00000000..846dc55a --- /dev/null +++ b/vendor/github.com/getkin/kin-openapi/openapi3/refs.go @@ -0,0 +1,1247 @@ +// Code generated by go generate; DO NOT EDIT. +package openapi3 + +import ( + "context" + "encoding/json" + "fmt" + "net/url" + "sort" + "strings" + + "github.com/go-openapi/jsonpointer" + "github.com/perimeterx/marshmallow" +) + +// CallbackRef represents either a Callback or a $ref to a Callback. +// When serializing and both fields are set, Ref is preferred over Value. +type CallbackRef struct { + // Extensions only captures fields starting with 'x-' as no other fields + // are allowed by the openapi spec. + Extensions map[string]any + + Ref string + Value *Callback + extra []string + + refPath url.URL +} + +var _ jsonpointer.JSONPointable = (*CallbackRef)(nil) + +func (x *CallbackRef) isEmpty() bool { return x == nil || x.Ref == "" && x.Value == nil } + +// RefString returns the $ref value. +func (x *CallbackRef) RefString() string { return x.Ref } + +// CollectionName returns the JSON string used for a collection of these components. +func (x *CallbackRef) CollectionName() string { return "callbacks" } + +// RefPath returns the path of the $ref relative to the root document. +func (x *CallbackRef) RefPath() *url.URL { return &x.refPath } + +func (x *CallbackRef) setRefPath(u *url.URL) { + // Do not set to null or override a path already set. + // References can be loaded multiple times not all with access + // to the correct path info. + if u == nil || x.refPath != (url.URL{}) { + return + } + + x.refPath = *u +} + +// MarshalYAML returns the YAML encoding of CallbackRef. +func (x CallbackRef) MarshalYAML() (any, error) { + if ref := x.Ref; ref != "" { + return &Ref{Ref: ref}, nil + } + return x.Value.MarshalYAML() +} + +// MarshalJSON returns the JSON encoding of CallbackRef. +func (x CallbackRef) MarshalJSON() ([]byte, error) { + y, err := x.MarshalYAML() + if err != nil { + return nil, err + } + return json.Marshal(y) +} + +// UnmarshalJSON sets CallbackRef to a copy of data. +func (x *CallbackRef) UnmarshalJSON(data []byte) error { + var refOnly Ref + if extra, err := marshmallow.Unmarshal(data, &refOnly, marshmallow.WithExcludeKnownFieldsFromMap(true)); err == nil && refOnly.Ref != "" { + x.Ref = refOnly.Ref + if len(extra) != 0 { + x.extra = make([]string, 0, len(extra)) + for key := range extra { + x.extra = append(x.extra, key) + } + sort.Strings(x.extra) + for k := range extra { + if !strings.HasPrefix(k, "x-") { + delete(extra, k) + } + } + if len(extra) != 0 { + x.Extensions = extra + } + } + return nil + } + return json.Unmarshal(data, &x.Value) +} + +// Validate returns an error if CallbackRef does not comply with the OpenAPI spec. +func (x *CallbackRef) Validate(ctx context.Context, opts ...ValidationOption) error { + ctx = WithValidationOptions(ctx, opts...) + exProhibited := getValidationOptions(ctx).schemaExtensionsInRefProhibited + var extras []string + if extra := x.extra; len(extra) != 0 { + allowed := getValidationOptions(ctx).extraSiblingFieldsAllowed + for _, ex := range extra { + if allowed != nil { + if _, ok := allowed[ex]; ok { + continue + } + } + // extras in the Extensions checked below + if _, ok := x.Extensions[ex]; !ok { + extras = append(extras, ex) + } + } + } + + if extra := x.Extensions; exProhibited && len(extra) != 0 { + allowed := getValidationOptions(ctx).extraSiblingFieldsAllowed + for ex := range extra { + if allowed != nil { + if _, ok := allowed[ex]; ok { + continue + } + } + extras = append(extras, ex) + } + } + + if len(extras) != 0 { + return fmt.Errorf("extra sibling fields: %+v", extras) + } + + if v := x.Value; v != nil { + return v.Validate(ctx) + } + + return foundUnresolvedRef(x.Ref) +} + +// JSONLookup implements https://pkg.go.dev/github.com/go-openapi/jsonpointer#JSONPointable +func (x *CallbackRef) JSONLookup(token string) (any, error) { + if token == "$ref" { + return x.Ref, nil + } + + if v, ok := x.Extensions[token]; ok { + return v, nil + } + + ptr, _, err := jsonpointer.GetForToken(x.Value, token) + return ptr, err +} + +// ExampleRef represents either a Example or a $ref to a Example. +// When serializing and both fields are set, Ref is preferred over Value. +type ExampleRef struct { + // Extensions only captures fields starting with 'x-' as no other fields + // are allowed by the openapi spec. + Extensions map[string]any + + Ref string + Value *Example + extra []string + + refPath url.URL +} + +var _ jsonpointer.JSONPointable = (*ExampleRef)(nil) + +func (x *ExampleRef) isEmpty() bool { return x == nil || x.Ref == "" && x.Value == nil } + +// RefString returns the $ref value. +func (x *ExampleRef) RefString() string { return x.Ref } + +// CollectionName returns the JSON string used for a collection of these components. +func (x *ExampleRef) CollectionName() string { return "examples" } + +// RefPath returns the path of the $ref relative to the root document. +func (x *ExampleRef) RefPath() *url.URL { return &x.refPath } + +func (x *ExampleRef) setRefPath(u *url.URL) { + // Do not set to null or override a path already set. + // References can be loaded multiple times not all with access + // to the correct path info. + if u == nil || x.refPath != (url.URL{}) { + return + } + + x.refPath = *u +} + +// MarshalYAML returns the YAML encoding of ExampleRef. +func (x ExampleRef) MarshalYAML() (any, error) { + if ref := x.Ref; ref != "" { + return &Ref{Ref: ref}, nil + } + return x.Value.MarshalYAML() +} + +// MarshalJSON returns the JSON encoding of ExampleRef. +func (x ExampleRef) MarshalJSON() ([]byte, error) { + y, err := x.MarshalYAML() + if err != nil { + return nil, err + } + return json.Marshal(y) +} + +// UnmarshalJSON sets ExampleRef to a copy of data. +func (x *ExampleRef) UnmarshalJSON(data []byte) error { + var refOnly Ref + if extra, err := marshmallow.Unmarshal(data, &refOnly, marshmallow.WithExcludeKnownFieldsFromMap(true)); err == nil && refOnly.Ref != "" { + x.Ref = refOnly.Ref + if len(extra) != 0 { + x.extra = make([]string, 0, len(extra)) + for key := range extra { + x.extra = append(x.extra, key) + } + sort.Strings(x.extra) + for k := range extra { + if !strings.HasPrefix(k, "x-") { + delete(extra, k) + } + } + if len(extra) != 0 { + x.Extensions = extra + } + } + return nil + } + return json.Unmarshal(data, &x.Value) +} + +// Validate returns an error if ExampleRef does not comply with the OpenAPI spec. +func (x *ExampleRef) Validate(ctx context.Context, opts ...ValidationOption) error { + ctx = WithValidationOptions(ctx, opts...) + exProhibited := getValidationOptions(ctx).schemaExtensionsInRefProhibited + var extras []string + if extra := x.extra; len(extra) != 0 { + allowed := getValidationOptions(ctx).extraSiblingFieldsAllowed + for _, ex := range extra { + if allowed != nil { + if _, ok := allowed[ex]; ok { + continue + } + } + // extras in the Extensions checked below + if _, ok := x.Extensions[ex]; !ok { + extras = append(extras, ex) + } + } + } + + if extra := x.Extensions; exProhibited && len(extra) != 0 { + allowed := getValidationOptions(ctx).extraSiblingFieldsAllowed + for ex := range extra { + if allowed != nil { + if _, ok := allowed[ex]; ok { + continue + } + } + extras = append(extras, ex) + } + } + + if len(extras) != 0 { + return fmt.Errorf("extra sibling fields: %+v", extras) + } + + if v := x.Value; v != nil { + return v.Validate(ctx) + } + + return foundUnresolvedRef(x.Ref) +} + +// JSONLookup implements https://pkg.go.dev/github.com/go-openapi/jsonpointer#JSONPointable +func (x *ExampleRef) JSONLookup(token string) (any, error) { + if token == "$ref" { + return x.Ref, nil + } + + if v, ok := x.Extensions[token]; ok { + return v, nil + } + + ptr, _, err := jsonpointer.GetForToken(x.Value, token) + return ptr, err +} + +// HeaderRef represents either a Header or a $ref to a Header. +// When serializing and both fields are set, Ref is preferred over Value. +type HeaderRef struct { + // Extensions only captures fields starting with 'x-' as no other fields + // are allowed by the openapi spec. + Extensions map[string]any + + Ref string + Value *Header + extra []string + + refPath url.URL +} + +var _ jsonpointer.JSONPointable = (*HeaderRef)(nil) + +func (x *HeaderRef) isEmpty() bool { return x == nil || x.Ref == "" && x.Value == nil } + +// RefString returns the $ref value. +func (x *HeaderRef) RefString() string { return x.Ref } + +// CollectionName returns the JSON string used for a collection of these components. +func (x *HeaderRef) CollectionName() string { return "headers" } + +// RefPath returns the path of the $ref relative to the root document. +func (x *HeaderRef) RefPath() *url.URL { return &x.refPath } + +func (x *HeaderRef) setRefPath(u *url.URL) { + // Do not set to null or override a path already set. + // References can be loaded multiple times not all with access + // to the correct path info. + if u == nil || x.refPath != (url.URL{}) { + return + } + + x.refPath = *u +} + +// MarshalYAML returns the YAML encoding of HeaderRef. +func (x HeaderRef) MarshalYAML() (any, error) { + if ref := x.Ref; ref != "" { + return &Ref{Ref: ref}, nil + } + return x.Value.MarshalYAML() +} + +// MarshalJSON returns the JSON encoding of HeaderRef. +func (x HeaderRef) MarshalJSON() ([]byte, error) { + y, err := x.MarshalYAML() + if err != nil { + return nil, err + } + return json.Marshal(y) +} + +// UnmarshalJSON sets HeaderRef to a copy of data. +func (x *HeaderRef) UnmarshalJSON(data []byte) error { + var refOnly Ref + if extra, err := marshmallow.Unmarshal(data, &refOnly, marshmallow.WithExcludeKnownFieldsFromMap(true)); err == nil && refOnly.Ref != "" { + x.Ref = refOnly.Ref + if len(extra) != 0 { + x.extra = make([]string, 0, len(extra)) + for key := range extra { + x.extra = append(x.extra, key) + } + sort.Strings(x.extra) + for k := range extra { + if !strings.HasPrefix(k, "x-") { + delete(extra, k) + } + } + if len(extra) != 0 { + x.Extensions = extra + } + } + return nil + } + return json.Unmarshal(data, &x.Value) +} + +// Validate returns an error if HeaderRef does not comply with the OpenAPI spec. +func (x *HeaderRef) Validate(ctx context.Context, opts ...ValidationOption) error { + ctx = WithValidationOptions(ctx, opts...) + exProhibited := getValidationOptions(ctx).schemaExtensionsInRefProhibited + var extras []string + if extra := x.extra; len(extra) != 0 { + allowed := getValidationOptions(ctx).extraSiblingFieldsAllowed + for _, ex := range extra { + if allowed != nil { + if _, ok := allowed[ex]; ok { + continue + } + } + // extras in the Extensions checked below + if _, ok := x.Extensions[ex]; !ok { + extras = append(extras, ex) + } + } + } + + if extra := x.Extensions; exProhibited && len(extra) != 0 { + allowed := getValidationOptions(ctx).extraSiblingFieldsAllowed + for ex := range extra { + if allowed != nil { + if _, ok := allowed[ex]; ok { + continue + } + } + extras = append(extras, ex) + } + } + + if len(extras) != 0 { + return fmt.Errorf("extra sibling fields: %+v", extras) + } + + if v := x.Value; v != nil { + return v.Validate(ctx) + } + + return foundUnresolvedRef(x.Ref) +} + +// JSONLookup implements https://pkg.go.dev/github.com/go-openapi/jsonpointer#JSONPointable +func (x *HeaderRef) JSONLookup(token string) (any, error) { + if token == "$ref" { + return x.Ref, nil + } + + if v, ok := x.Extensions[token]; ok { + return v, nil + } + + ptr, _, err := jsonpointer.GetForToken(x.Value, token) + return ptr, err +} + +// LinkRef represents either a Link or a $ref to a Link. +// When serializing and both fields are set, Ref is preferred over Value. +type LinkRef struct { + // Extensions only captures fields starting with 'x-' as no other fields + // are allowed by the openapi spec. + Extensions map[string]any + + Ref string + Value *Link + extra []string + + refPath url.URL +} + +var _ jsonpointer.JSONPointable = (*LinkRef)(nil) + +func (x *LinkRef) isEmpty() bool { return x == nil || x.Ref == "" && x.Value == nil } + +// RefString returns the $ref value. +func (x *LinkRef) RefString() string { return x.Ref } + +// CollectionName returns the JSON string used for a collection of these components. +func (x *LinkRef) CollectionName() string { return "links" } + +// RefPath returns the path of the $ref relative to the root document. +func (x *LinkRef) RefPath() *url.URL { return &x.refPath } + +func (x *LinkRef) setRefPath(u *url.URL) { + // Do not set to null or override a path already set. + // References can be loaded multiple times not all with access + // to the correct path info. + if u == nil || x.refPath != (url.URL{}) { + return + } + + x.refPath = *u +} + +// MarshalYAML returns the YAML encoding of LinkRef. +func (x LinkRef) MarshalYAML() (any, error) { + if ref := x.Ref; ref != "" { + return &Ref{Ref: ref}, nil + } + return x.Value.MarshalYAML() +} + +// MarshalJSON returns the JSON encoding of LinkRef. +func (x LinkRef) MarshalJSON() ([]byte, error) { + y, err := x.MarshalYAML() + if err != nil { + return nil, err + } + return json.Marshal(y) +} + +// UnmarshalJSON sets LinkRef to a copy of data. +func (x *LinkRef) UnmarshalJSON(data []byte) error { + var refOnly Ref + if extra, err := marshmallow.Unmarshal(data, &refOnly, marshmallow.WithExcludeKnownFieldsFromMap(true)); err == nil && refOnly.Ref != "" { + x.Ref = refOnly.Ref + if len(extra) != 0 { + x.extra = make([]string, 0, len(extra)) + for key := range extra { + x.extra = append(x.extra, key) + } + sort.Strings(x.extra) + for k := range extra { + if !strings.HasPrefix(k, "x-") { + delete(extra, k) + } + } + if len(extra) != 0 { + x.Extensions = extra + } + } + return nil + } + return json.Unmarshal(data, &x.Value) +} + +// Validate returns an error if LinkRef does not comply with the OpenAPI spec. +func (x *LinkRef) Validate(ctx context.Context, opts ...ValidationOption) error { + ctx = WithValidationOptions(ctx, opts...) + exProhibited := getValidationOptions(ctx).schemaExtensionsInRefProhibited + var extras []string + if extra := x.extra; len(extra) != 0 { + allowed := getValidationOptions(ctx).extraSiblingFieldsAllowed + for _, ex := range extra { + if allowed != nil { + if _, ok := allowed[ex]; ok { + continue + } + } + // extras in the Extensions checked below + if _, ok := x.Extensions[ex]; !ok { + extras = append(extras, ex) + } + } + } + + if extra := x.Extensions; exProhibited && len(extra) != 0 { + allowed := getValidationOptions(ctx).extraSiblingFieldsAllowed + for ex := range extra { + if allowed != nil { + if _, ok := allowed[ex]; ok { + continue + } + } + extras = append(extras, ex) + } + } + + if len(extras) != 0 { + return fmt.Errorf("extra sibling fields: %+v", extras) + } + + if v := x.Value; v != nil { + return v.Validate(ctx) + } + + return foundUnresolvedRef(x.Ref) +} + +// JSONLookup implements https://pkg.go.dev/github.com/go-openapi/jsonpointer#JSONPointable +func (x *LinkRef) JSONLookup(token string) (any, error) { + if token == "$ref" { + return x.Ref, nil + } + + if v, ok := x.Extensions[token]; ok { + return v, nil + } + + ptr, _, err := jsonpointer.GetForToken(x.Value, token) + return ptr, err +} + +// ParameterRef represents either a Parameter or a $ref to a Parameter. +// When serializing and both fields are set, Ref is preferred over Value. +type ParameterRef struct { + // Extensions only captures fields starting with 'x-' as no other fields + // are allowed by the openapi spec. + Extensions map[string]any + + Ref string + Value *Parameter + extra []string + + refPath url.URL +} + +var _ jsonpointer.JSONPointable = (*ParameterRef)(nil) + +func (x *ParameterRef) isEmpty() bool { return x == nil || x.Ref == "" && x.Value == nil } + +// RefString returns the $ref value. +func (x *ParameterRef) RefString() string { return x.Ref } + +// CollectionName returns the JSON string used for a collection of these components. +func (x *ParameterRef) CollectionName() string { return "parameters" } + +// RefPath returns the path of the $ref relative to the root document. +func (x *ParameterRef) RefPath() *url.URL { return &x.refPath } + +func (x *ParameterRef) setRefPath(u *url.URL) { + // Do not set to null or override a path already set. + // References can be loaded multiple times not all with access + // to the correct path info. + if u == nil || x.refPath != (url.URL{}) { + return + } + + x.refPath = *u +} + +// MarshalYAML returns the YAML encoding of ParameterRef. +func (x ParameterRef) MarshalYAML() (any, error) { + if ref := x.Ref; ref != "" { + return &Ref{Ref: ref}, nil + } + return x.Value.MarshalYAML() +} + +// MarshalJSON returns the JSON encoding of ParameterRef. +func (x ParameterRef) MarshalJSON() ([]byte, error) { + y, err := x.MarshalYAML() + if err != nil { + return nil, err + } + return json.Marshal(y) +} + +// UnmarshalJSON sets ParameterRef to a copy of data. +func (x *ParameterRef) UnmarshalJSON(data []byte) error { + var refOnly Ref + if extra, err := marshmallow.Unmarshal(data, &refOnly, marshmallow.WithExcludeKnownFieldsFromMap(true)); err == nil && refOnly.Ref != "" { + x.Ref = refOnly.Ref + if len(extra) != 0 { + x.extra = make([]string, 0, len(extra)) + for key := range extra { + x.extra = append(x.extra, key) + } + sort.Strings(x.extra) + for k := range extra { + if !strings.HasPrefix(k, "x-") { + delete(extra, k) + } + } + if len(extra) != 0 { + x.Extensions = extra + } + } + return nil + } + return json.Unmarshal(data, &x.Value) +} + +// Validate returns an error if ParameterRef does not comply with the OpenAPI spec. +func (x *ParameterRef) Validate(ctx context.Context, opts ...ValidationOption) error { + ctx = WithValidationOptions(ctx, opts...) + exProhibited := getValidationOptions(ctx).schemaExtensionsInRefProhibited + var extras []string + if extra := x.extra; len(extra) != 0 { + allowed := getValidationOptions(ctx).extraSiblingFieldsAllowed + for _, ex := range extra { + if allowed != nil { + if _, ok := allowed[ex]; ok { + continue + } + } + // extras in the Extensions checked below + if _, ok := x.Extensions[ex]; !ok { + extras = append(extras, ex) + } + } + } + + if extra := x.Extensions; exProhibited && len(extra) != 0 { + allowed := getValidationOptions(ctx).extraSiblingFieldsAllowed + for ex := range extra { + if allowed != nil { + if _, ok := allowed[ex]; ok { + continue + } + } + extras = append(extras, ex) + } + } + + if len(extras) != 0 { + return fmt.Errorf("extra sibling fields: %+v", extras) + } + + if v := x.Value; v != nil { + return v.Validate(ctx) + } + + return foundUnresolvedRef(x.Ref) +} + +// JSONLookup implements https://pkg.go.dev/github.com/go-openapi/jsonpointer#JSONPointable +func (x *ParameterRef) JSONLookup(token string) (any, error) { + if token == "$ref" { + return x.Ref, nil + } + + if v, ok := x.Extensions[token]; ok { + return v, nil + } + + ptr, _, err := jsonpointer.GetForToken(x.Value, token) + return ptr, err +} + +// RequestBodyRef represents either a RequestBody or a $ref to a RequestBody. +// When serializing and both fields are set, Ref is preferred over Value. +type RequestBodyRef struct { + // Extensions only captures fields starting with 'x-' as no other fields + // are allowed by the openapi spec. + Extensions map[string]any + + Ref string + Value *RequestBody + extra []string + + refPath url.URL +} + +var _ jsonpointer.JSONPointable = (*RequestBodyRef)(nil) + +func (x *RequestBodyRef) isEmpty() bool { return x == nil || x.Ref == "" && x.Value == nil } + +// RefString returns the $ref value. +func (x *RequestBodyRef) RefString() string { return x.Ref } + +// CollectionName returns the JSON string used for a collection of these components. +func (x *RequestBodyRef) CollectionName() string { return "requestBodies" } + +// RefPath returns the path of the $ref relative to the root document. +func (x *RequestBodyRef) RefPath() *url.URL { return &x.refPath } + +func (x *RequestBodyRef) setRefPath(u *url.URL) { + // Do not set to null or override a path already set. + // References can be loaded multiple times not all with access + // to the correct path info. + if u == nil || x.refPath != (url.URL{}) { + return + } + + x.refPath = *u +} + +// MarshalYAML returns the YAML encoding of RequestBodyRef. +func (x RequestBodyRef) MarshalYAML() (any, error) { + if ref := x.Ref; ref != "" { + return &Ref{Ref: ref}, nil + } + return x.Value.MarshalYAML() +} + +// MarshalJSON returns the JSON encoding of RequestBodyRef. +func (x RequestBodyRef) MarshalJSON() ([]byte, error) { + y, err := x.MarshalYAML() + if err != nil { + return nil, err + } + return json.Marshal(y) +} + +// UnmarshalJSON sets RequestBodyRef to a copy of data. +func (x *RequestBodyRef) UnmarshalJSON(data []byte) error { + var refOnly Ref + if extra, err := marshmallow.Unmarshal(data, &refOnly, marshmallow.WithExcludeKnownFieldsFromMap(true)); err == nil && refOnly.Ref != "" { + x.Ref = refOnly.Ref + if len(extra) != 0 { + x.extra = make([]string, 0, len(extra)) + for key := range extra { + x.extra = append(x.extra, key) + } + sort.Strings(x.extra) + for k := range extra { + if !strings.HasPrefix(k, "x-") { + delete(extra, k) + } + } + if len(extra) != 0 { + x.Extensions = extra + } + } + return nil + } + return json.Unmarshal(data, &x.Value) +} + +// Validate returns an error if RequestBodyRef does not comply with the OpenAPI spec. +func (x *RequestBodyRef) Validate(ctx context.Context, opts ...ValidationOption) error { + ctx = WithValidationOptions(ctx, opts...) + exProhibited := getValidationOptions(ctx).schemaExtensionsInRefProhibited + var extras []string + if extra := x.extra; len(extra) != 0 { + allowed := getValidationOptions(ctx).extraSiblingFieldsAllowed + for _, ex := range extra { + if allowed != nil { + if _, ok := allowed[ex]; ok { + continue + } + } + // extras in the Extensions checked below + if _, ok := x.Extensions[ex]; !ok { + extras = append(extras, ex) + } + } + } + + if extra := x.Extensions; exProhibited && len(extra) != 0 { + allowed := getValidationOptions(ctx).extraSiblingFieldsAllowed + for ex := range extra { + if allowed != nil { + if _, ok := allowed[ex]; ok { + continue + } + } + extras = append(extras, ex) + } + } + + if len(extras) != 0 { + return fmt.Errorf("extra sibling fields: %+v", extras) + } + + if v := x.Value; v != nil { + return v.Validate(ctx) + } + + return foundUnresolvedRef(x.Ref) +} + +// JSONLookup implements https://pkg.go.dev/github.com/go-openapi/jsonpointer#JSONPointable +func (x *RequestBodyRef) JSONLookup(token string) (any, error) { + if token == "$ref" { + return x.Ref, nil + } + + if v, ok := x.Extensions[token]; ok { + return v, nil + } + + ptr, _, err := jsonpointer.GetForToken(x.Value, token) + return ptr, err +} + +// ResponseRef represents either a Response or a $ref to a Response. +// When serializing and both fields are set, Ref is preferred over Value. +type ResponseRef struct { + // Extensions only captures fields starting with 'x-' as no other fields + // are allowed by the openapi spec. + Extensions map[string]any + + Ref string + Value *Response + extra []string + + refPath url.URL +} + +var _ jsonpointer.JSONPointable = (*ResponseRef)(nil) + +func (x *ResponseRef) isEmpty() bool { return x == nil || x.Ref == "" && x.Value == nil } + +// RefString returns the $ref value. +func (x *ResponseRef) RefString() string { return x.Ref } + +// CollectionName returns the JSON string used for a collection of these components. +func (x *ResponseRef) CollectionName() string { return "responses" } + +// RefPath returns the path of the $ref relative to the root document. +func (x *ResponseRef) RefPath() *url.URL { return &x.refPath } + +func (x *ResponseRef) setRefPath(u *url.URL) { + // Do not set to null or override a path already set. + // References can be loaded multiple times not all with access + // to the correct path info. + if u == nil || x.refPath != (url.URL{}) { + return + } + + x.refPath = *u +} + +// MarshalYAML returns the YAML encoding of ResponseRef. +func (x ResponseRef) MarshalYAML() (any, error) { + if ref := x.Ref; ref != "" { + return &Ref{Ref: ref}, nil + } + return x.Value.MarshalYAML() +} + +// MarshalJSON returns the JSON encoding of ResponseRef. +func (x ResponseRef) MarshalJSON() ([]byte, error) { + y, err := x.MarshalYAML() + if err != nil { + return nil, err + } + return json.Marshal(y) +} + +// UnmarshalJSON sets ResponseRef to a copy of data. +func (x *ResponseRef) UnmarshalJSON(data []byte) error { + var refOnly Ref + if extra, err := marshmallow.Unmarshal(data, &refOnly, marshmallow.WithExcludeKnownFieldsFromMap(true)); err == nil && refOnly.Ref != "" { + x.Ref = refOnly.Ref + if len(extra) != 0 { + x.extra = make([]string, 0, len(extra)) + for key := range extra { + x.extra = append(x.extra, key) + } + sort.Strings(x.extra) + for k := range extra { + if !strings.HasPrefix(k, "x-") { + delete(extra, k) + } + } + if len(extra) != 0 { + x.Extensions = extra + } + } + return nil + } + return json.Unmarshal(data, &x.Value) +} + +// Validate returns an error if ResponseRef does not comply with the OpenAPI spec. +func (x *ResponseRef) Validate(ctx context.Context, opts ...ValidationOption) error { + ctx = WithValidationOptions(ctx, opts...) + exProhibited := getValidationOptions(ctx).schemaExtensionsInRefProhibited + var extras []string + if extra := x.extra; len(extra) != 0 { + allowed := getValidationOptions(ctx).extraSiblingFieldsAllowed + for _, ex := range extra { + if allowed != nil { + if _, ok := allowed[ex]; ok { + continue + } + } + // extras in the Extensions checked below + if _, ok := x.Extensions[ex]; !ok { + extras = append(extras, ex) + } + } + } + + if extra := x.Extensions; exProhibited && len(extra) != 0 { + allowed := getValidationOptions(ctx).extraSiblingFieldsAllowed + for ex := range extra { + if allowed != nil { + if _, ok := allowed[ex]; ok { + continue + } + } + extras = append(extras, ex) + } + } + + if len(extras) != 0 { + return fmt.Errorf("extra sibling fields: %+v", extras) + } + + if v := x.Value; v != nil { + return v.Validate(ctx) + } + + return foundUnresolvedRef(x.Ref) +} + +// JSONLookup implements https://pkg.go.dev/github.com/go-openapi/jsonpointer#JSONPointable +func (x *ResponseRef) JSONLookup(token string) (any, error) { + if token == "$ref" { + return x.Ref, nil + } + + if v, ok := x.Extensions[token]; ok { + return v, nil + } + + ptr, _, err := jsonpointer.GetForToken(x.Value, token) + return ptr, err +} + +// SchemaRef represents either a Schema or a $ref to a Schema. +// When serializing and both fields are set, Ref is preferred over Value. +type SchemaRef struct { + // Extensions only captures fields starting with 'x-' as no other fields + // are allowed by the openapi spec. + Extensions map[string]any + + Ref string + Value *Schema + extra []string + + refPath url.URL +} + +var _ jsonpointer.JSONPointable = (*SchemaRef)(nil) + +func (x *SchemaRef) isEmpty() bool { return x == nil || x.Ref == "" && x.Value == nil } + +// RefString returns the $ref value. +func (x *SchemaRef) RefString() string { return x.Ref } + +// CollectionName returns the JSON string used for a collection of these components. +func (x *SchemaRef) CollectionName() string { return "schemas" } + +// RefPath returns the path of the $ref relative to the root document. +func (x *SchemaRef) RefPath() *url.URL { return &x.refPath } + +func (x *SchemaRef) setRefPath(u *url.URL) { + // Do not set to null or override a path already set. + // References can be loaded multiple times not all with access + // to the correct path info. + if u == nil || x.refPath != (url.URL{}) { + return + } + + x.refPath = *u +} + +// MarshalYAML returns the YAML encoding of SchemaRef. +func (x SchemaRef) MarshalYAML() (any, error) { + if ref := x.Ref; ref != "" { + return &Ref{Ref: ref}, nil + } + return x.Value.MarshalYAML() +} + +// MarshalJSON returns the JSON encoding of SchemaRef. +func (x SchemaRef) MarshalJSON() ([]byte, error) { + y, err := x.MarshalYAML() + if err != nil { + return nil, err + } + return json.Marshal(y) +} + +// UnmarshalJSON sets SchemaRef to a copy of data. +func (x *SchemaRef) UnmarshalJSON(data []byte) error { + var refOnly Ref + if extra, err := marshmallow.Unmarshal(data, &refOnly, marshmallow.WithExcludeKnownFieldsFromMap(true)); err == nil && refOnly.Ref != "" { + x.Ref = refOnly.Ref + if len(extra) != 0 { + x.extra = make([]string, 0, len(extra)) + for key := range extra { + x.extra = append(x.extra, key) + } + sort.Strings(x.extra) + for k := range extra { + if !strings.HasPrefix(k, "x-") { + delete(extra, k) + } + } + if len(extra) != 0 { + x.Extensions = extra + } + } + return nil + } + return json.Unmarshal(data, &x.Value) +} + +// Validate returns an error if SchemaRef does not comply with the OpenAPI spec. +func (x *SchemaRef) Validate(ctx context.Context, opts ...ValidationOption) error { + ctx = WithValidationOptions(ctx, opts...) + exProhibited := getValidationOptions(ctx).schemaExtensionsInRefProhibited + var extras []string + if extra := x.extra; len(extra) != 0 { + allowed := getValidationOptions(ctx).extraSiblingFieldsAllowed + for _, ex := range extra { + if allowed != nil { + if _, ok := allowed[ex]; ok { + continue + } + } + // extras in the Extensions checked below + if _, ok := x.Extensions[ex]; !ok { + extras = append(extras, ex) + } + } + } + + if extra := x.Extensions; exProhibited && len(extra) != 0 { + allowed := getValidationOptions(ctx).extraSiblingFieldsAllowed + for ex := range extra { + if allowed != nil { + if _, ok := allowed[ex]; ok { + continue + } + } + extras = append(extras, ex) + } + } + + if len(extras) != 0 { + return fmt.Errorf("extra sibling fields: %+v", extras) + } + + if v := x.Value; v != nil { + return v.Validate(ctx) + } + + return foundUnresolvedRef(x.Ref) +} + +// JSONLookup implements https://pkg.go.dev/github.com/go-openapi/jsonpointer#JSONPointable +func (x *SchemaRef) JSONLookup(token string) (any, error) { + if token == "$ref" { + return x.Ref, nil + } + + if v, ok := x.Extensions[token]; ok { + return v, nil + } + + ptr, _, err := jsonpointer.GetForToken(x.Value, token) + return ptr, err +} + +// SecuritySchemeRef represents either a SecurityScheme or a $ref to a SecurityScheme. +// When serializing and both fields are set, Ref is preferred over Value. +type SecuritySchemeRef struct { + // Extensions only captures fields starting with 'x-' as no other fields + // are allowed by the openapi spec. + Extensions map[string]any + + Ref string + Value *SecurityScheme + extra []string + + refPath url.URL +} + +var _ jsonpointer.JSONPointable = (*SecuritySchemeRef)(nil) + +func (x *SecuritySchemeRef) isEmpty() bool { return x == nil || x.Ref == "" && x.Value == nil } + +// RefString returns the $ref value. +func (x *SecuritySchemeRef) RefString() string { return x.Ref } + +// CollectionName returns the JSON string used for a collection of these components. +func (x *SecuritySchemeRef) CollectionName() string { return "securitySchemes" } + +// RefPath returns the path of the $ref relative to the root document. +func (x *SecuritySchemeRef) RefPath() *url.URL { return &x.refPath } + +func (x *SecuritySchemeRef) setRefPath(u *url.URL) { + // Do not set to null or override a path already set. + // References can be loaded multiple times not all with access + // to the correct path info. + if u == nil || x.refPath != (url.URL{}) { + return + } + + x.refPath = *u +} + +// MarshalYAML returns the YAML encoding of SecuritySchemeRef. +func (x SecuritySchemeRef) MarshalYAML() (any, error) { + if ref := x.Ref; ref != "" { + return &Ref{Ref: ref}, nil + } + return x.Value.MarshalYAML() +} + +// MarshalJSON returns the JSON encoding of SecuritySchemeRef. +func (x SecuritySchemeRef) MarshalJSON() ([]byte, error) { + y, err := x.MarshalYAML() + if err != nil { + return nil, err + } + return json.Marshal(y) +} + +// UnmarshalJSON sets SecuritySchemeRef to a copy of data. +func (x *SecuritySchemeRef) UnmarshalJSON(data []byte) error { + var refOnly Ref + if extra, err := marshmallow.Unmarshal(data, &refOnly, marshmallow.WithExcludeKnownFieldsFromMap(true)); err == nil && refOnly.Ref != "" { + x.Ref = refOnly.Ref + if len(extra) != 0 { + x.extra = make([]string, 0, len(extra)) + for key := range extra { + x.extra = append(x.extra, key) + } + sort.Strings(x.extra) + for k := range extra { + if !strings.HasPrefix(k, "x-") { + delete(extra, k) + } + } + if len(extra) != 0 { + x.Extensions = extra + } + } + return nil + } + return json.Unmarshal(data, &x.Value) +} + +// Validate returns an error if SecuritySchemeRef does not comply with the OpenAPI spec. +func (x *SecuritySchemeRef) Validate(ctx context.Context, opts ...ValidationOption) error { + ctx = WithValidationOptions(ctx, opts...) + exProhibited := getValidationOptions(ctx).schemaExtensionsInRefProhibited + var extras []string + if extra := x.extra; len(extra) != 0 { + allowed := getValidationOptions(ctx).extraSiblingFieldsAllowed + for _, ex := range extra { + if allowed != nil { + if _, ok := allowed[ex]; ok { + continue + } + } + // extras in the Extensions checked below + if _, ok := x.Extensions[ex]; !ok { + extras = append(extras, ex) + } + } + } + + if extra := x.Extensions; exProhibited && len(extra) != 0 { + allowed := getValidationOptions(ctx).extraSiblingFieldsAllowed + for ex := range extra { + if allowed != nil { + if _, ok := allowed[ex]; ok { + continue + } + } + extras = append(extras, ex) + } + } + + if len(extras) != 0 { + return fmt.Errorf("extra sibling fields: %+v", extras) + } + + if v := x.Value; v != nil { + return v.Validate(ctx) + } + + return foundUnresolvedRef(x.Ref) +} + +// JSONLookup implements https://pkg.go.dev/github.com/go-openapi/jsonpointer#JSONPointable +func (x *SecuritySchemeRef) JSONLookup(token string) (any, error) { + if token == "$ref" { + return x.Ref, nil + } + + if v, ok := x.Extensions[token]; ok { + return v, nil + } + + ptr, _, err := jsonpointer.GetForToken(x.Value, token) + return ptr, err +} diff --git a/vendor/github.com/getkin/kin-openapi/openapi3/refs.tmpl b/vendor/github.com/getkin/kin-openapi/openapi3/refs.tmpl new file mode 100644 index 00000000..f9ed1e6e --- /dev/null +++ b/vendor/github.com/getkin/kin-openapi/openapi3/refs.tmpl @@ -0,0 +1,152 @@ +// Code generated by go generate; DO NOT EDIT. +package {{ .Package }} + +import ( + "context" + "encoding/json" + "fmt" + "net/url" + "sort" + "strings" + + "github.com/go-openapi/jsonpointer" + "github.com/perimeterx/marshmallow" +) +{{ range $type := .Types }} +// {{ $type.Name }}Ref represents either a {{ $type.Name }} or a $ref to a {{ $type.Name }}. +// When serializing and both fields are set, Ref is preferred over Value. +type {{ $type.Name }}Ref struct { + // Extensions only captures fields starting with 'x-' as no other fields + // are allowed by the openapi spec. + Extensions map[string]any + + Ref string + Value *{{ $type.Name }} + extra []string + + refPath url.URL +} + +var _ jsonpointer.JSONPointable = (*{{ $type.Name }}Ref)(nil) + +func (x *{{ $type.Name }}Ref) isEmpty() bool { return x == nil || x.Ref == "" && x.Value == nil } + +// RefString returns the $ref value. +func (x *{{ $type.Name }}Ref) RefString() string { return x.Ref } + +// CollectionName returns the JSON string used for a collection of these components. +func (x *{{ $type.Name }}Ref) CollectionName() string { return "{{ $type.CollectionName }}" } + +// RefPath returns the path of the $ref relative to the root document. +func (x *{{ $type.Name }}Ref) RefPath() *url.URL { return &x.refPath } + +func (x *{{ $type.Name }}Ref) setRefPath(u *url.URL) { + // Do not set to null or override a path already set. + // References can be loaded multiple times not all with access + // to the correct path info. + if u == nil || x.refPath != (url.URL{}) { + return + } + + x.refPath = *u +} + +// MarshalYAML returns the YAML encoding of {{ $type.Name }}Ref. +func (x {{ $type.Name }}Ref) MarshalYAML() (any, error) { + if ref := x.Ref; ref != "" { + return &Ref{Ref: ref}, nil + } + return x.Value.MarshalYAML() +} + +// MarshalJSON returns the JSON encoding of {{ $type.Name }}Ref. +func (x {{ $type.Name }}Ref) MarshalJSON() ([]byte, error) { + y, err := x.MarshalYAML() + if err != nil { + return nil, err + } + return json.Marshal(y) +} + +// UnmarshalJSON sets {{ $type.Name }}Ref to a copy of data. +func (x *{{ $type.Name }}Ref) UnmarshalJSON(data []byte) error { + var refOnly Ref + if extra, err := marshmallow.Unmarshal(data, &refOnly, marshmallow.WithExcludeKnownFieldsFromMap(true)); err == nil && refOnly.Ref != "" { + x.Ref = refOnly.Ref + if len(extra) != 0 { + x.extra = make([]string, 0, len(extra)) + for key := range extra { + x.extra = append(x.extra, key) + } + sort.Strings(x.extra) + for k := range extra { + if !strings.HasPrefix(k, "x-") { + delete(extra, k) + } + } + if len(extra) != 0 { + x.Extensions = extra + } + } + return nil + } + return json.Unmarshal(data, &x.Value) +} + +// Validate returns an error if {{ $type.Name }}Ref does not comply with the OpenAPI spec. +func (x *{{ $type.Name }}Ref) Validate(ctx context.Context, opts ...ValidationOption) error { + ctx = WithValidationOptions(ctx, opts...) + exProhibited := getValidationOptions(ctx).schemaExtensionsInRefProhibited + var extras []string + if extra := x.extra; len(extra) != 0 { + allowed := getValidationOptions(ctx).extraSiblingFieldsAllowed + for _, ex := range extra { + if allowed != nil { + if _, ok := allowed[ex]; ok { + continue + } + } + // extras in the Extensions checked below + if _, ok := x.Extensions[ex]; !ok { + extras = append(extras, ex) + } + } + } + + if extra := x.Extensions; exProhibited && len(extra) != 0 { + allowed := getValidationOptions(ctx).extraSiblingFieldsAllowed + for ex := range extra { + if allowed != nil { + if _, ok := allowed[ex]; ok { + continue + } + } + extras = append(extras, ex) + } + } + + if len(extras) != 0 { + return fmt.Errorf("extra sibling fields: %+v", extras) + } + + if v := x.Value; v != nil { + return v.Validate(ctx) + } + + return foundUnresolvedRef(x.Ref) +} + +// JSONLookup implements https://pkg.go.dev/github.com/go-openapi/jsonpointer#JSONPointable +func (x *{{ $type.Name }}Ref) JSONLookup(token string) (any, error) { + if token == "$ref" { + return x.Ref, nil + } + + if v, ok := x.Extensions[token]; ok { + return v, nil + } + + ptr, _, err := jsonpointer.GetForToken(x.Value, token) + return ptr, err +} +{{ end -}} diff --git a/vendor/github.com/getkin/kin-openapi/openapi3/refs_test.tmpl b/vendor/github.com/getkin/kin-openapi/openapi3/refs_test.tmpl new file mode 100644 index 00000000..634fccf6 --- /dev/null +++ b/vendor/github.com/getkin/kin-openapi/openapi3/refs_test.tmpl @@ -0,0 +1,54 @@ +// Code generated by go generate; DO NOT EDIT. +package {{ .Package }} + +import ( + "context" + "encoding/json" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) +{{ range $type := .Types }} +func Test{{ $type.Name }}Ref_Extensions(t *testing.T) { + data := []byte(`{"$ref":"#/components/schemas/Pet","something":"integer","x-order":1}`) + + ref := {{ $type.Name }}Ref{} + err := json.Unmarshal(data, &ref) + assert.NoError(t, err) + + // captures extension + assert.Equal(t, "#/components/schemas/Pet", ref.Ref) + assert.Equal(t, float64(1), ref.Extensions["x-order"]) + + // does not capture non-extensions + assert.Nil(t, ref.Extensions["something"]) + + // validation + err = ref.Validate(context.Background()) + require.EqualError(t, err, "extra sibling fields: [something]") + + err = ref.Validate(context.Background(), ProhibitExtensionsWithRef()) + require.EqualError(t, err, "extra sibling fields: [something x-order]") + + err = ref.Validate(context.Background(), AllowExtraSiblingFields("something")) + assert.ErrorContains(t, err, "found unresolved ref") // expected since value not defined + + // non-extension not json lookable + _, err = ref.JSONLookup("something") + assert.Error(t, err) +{{ if ne $type.Name "Header" }} + t.Run("extentions in value", func(t *testing.T) { + ref.Value = &{{ $type.Name }}{Extensions: map[string]any{}} + ref.Value.Extensions["x-order"] = 2.0 + + // prefers the value next to the \$ref over the one in the \$ref. + v, err := ref.JSONLookup("x-order") + assert.NoError(t, err) + assert.Equal(t, float64(1), v) + }) +{{ else }} + // Header does not have its own extensions. +{{ end -}} +} +{{ end -}} diff --git a/vendor/github.com/getkin/kin-openapi/openapi3/request_body.go b/vendor/github.com/getkin/kin-openapi/openapi3/request_body.go new file mode 100644 index 00000000..6d4b8185 --- /dev/null +++ b/vendor/github.com/getkin/kin-openapi/openapi3/request_body.go @@ -0,0 +1,138 @@ +package openapi3 + +import ( + "context" + "encoding/json" + "errors" +) + +// RequestBody is specified by OpenAPI/Swagger 3.0 standard. +// See https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#request-body-object +type RequestBody struct { + Extensions map[string]any `json:"-" yaml:"-"` + + Description string `json:"description,omitempty" yaml:"description,omitempty"` + Required bool `json:"required,omitempty" yaml:"required,omitempty"` + Content Content `json:"content" yaml:"content"` +} + +func NewRequestBody() *RequestBody { + return &RequestBody{} +} + +func (requestBody *RequestBody) WithDescription(value string) *RequestBody { + requestBody.Description = value + return requestBody +} + +func (requestBody *RequestBody) WithRequired(value bool) *RequestBody { + requestBody.Required = value + return requestBody +} + +func (requestBody *RequestBody) WithContent(content Content) *RequestBody { + requestBody.Content = content + return requestBody +} + +func (requestBody *RequestBody) WithSchemaRef(value *SchemaRef, consumes []string) *RequestBody { + requestBody.Content = NewContentWithSchemaRef(value, consumes) + return requestBody +} + +func (requestBody *RequestBody) WithSchema(value *Schema, consumes []string) *RequestBody { + requestBody.Content = NewContentWithSchema(value, consumes) + return requestBody +} + +func (requestBody *RequestBody) WithJSONSchemaRef(value *SchemaRef) *RequestBody { + requestBody.Content = NewContentWithJSONSchemaRef(value) + return requestBody +} + +func (requestBody *RequestBody) WithJSONSchema(value *Schema) *RequestBody { + requestBody.Content = NewContentWithJSONSchema(value) + return requestBody +} + +func (requestBody *RequestBody) WithFormDataSchemaRef(value *SchemaRef) *RequestBody { + requestBody.Content = NewContentWithFormDataSchemaRef(value) + return requestBody +} + +func (requestBody *RequestBody) WithFormDataSchema(value *Schema) *RequestBody { + requestBody.Content = NewContentWithFormDataSchema(value) + return requestBody +} + +func (requestBody *RequestBody) GetMediaType(mediaType string) *MediaType { + m := requestBody.Content + if m == nil { + return nil + } + return m[mediaType] +} + +// MarshalJSON returns the JSON encoding of RequestBody. +func (requestBody RequestBody) MarshalJSON() ([]byte, error) { + x, err := requestBody.MarshalYAML() + if err != nil { + return nil, err + } + return json.Marshal(x) +} + +// MarshalYAML returns the YAML encoding of RequestBody. +func (requestBody RequestBody) MarshalYAML() (any, error) { + m := make(map[string]any, 3+len(requestBody.Extensions)) + for k, v := range requestBody.Extensions { + m[k] = v + } + if x := requestBody.Description; x != "" { + m["description"] = requestBody.Description + } + if x := requestBody.Required; x { + m["required"] = x + } + if x := requestBody.Content; true { + m["content"] = x + } + return m, nil +} + +// UnmarshalJSON sets RequestBody to a copy of data. +func (requestBody *RequestBody) UnmarshalJSON(data []byte) error { + type RequestBodyBis RequestBody + var x RequestBodyBis + if err := json.Unmarshal(data, &x); err != nil { + return unmarshalError(err) + } + _ = json.Unmarshal(data, &x.Extensions) + delete(x.Extensions, "description") + delete(x.Extensions, "required") + delete(x.Extensions, "content") + if len(x.Extensions) == 0 { + x.Extensions = nil + } + *requestBody = RequestBody(x) + return nil +} + +// Validate returns an error if RequestBody does not comply with the OpenAPI spec. +func (requestBody *RequestBody) Validate(ctx context.Context, opts ...ValidationOption) error { + ctx = WithValidationOptions(ctx, opts...) + + if requestBody.Content == nil { + return errors.New("content of the request body is required") + } + + if vo := getValidationOptions(ctx); !vo.examplesValidationDisabled { + vo.examplesValidationAsReq, vo.examplesValidationAsRes = true, false + } + + if err := requestBody.Content.Validate(ctx); err != nil { + return err + } + + return validateExtensions(ctx, requestBody.Extensions) +} diff --git a/vendor/github.com/getkin/kin-openapi/openapi3/response.go b/vendor/github.com/getkin/kin-openapi/openapi3/response.go new file mode 100644 index 00000000..af8fda6f --- /dev/null +++ b/vendor/github.com/getkin/kin-openapi/openapi3/response.go @@ -0,0 +1,227 @@ +package openapi3 + +import ( + "context" + "encoding/json" + "errors" + "sort" + "strconv" +) + +// Responses is specified by OpenAPI/Swagger 3.0 standard. +// See https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#responses-object +type Responses struct { + Extensions map[string]any `json:"-" yaml:"-"` + + m map[string]*ResponseRef +} + +// NewResponses builds a responses object with response objects in insertion order. +// Given no arguments, NewResponses returns a valid responses object containing a default match-all reponse. +func NewResponses(opts ...NewResponsesOption) *Responses { + if len(opts) == 0 { + return NewResponses(WithName("default", NewResponse().WithDescription(""))) + } + responses := NewResponsesWithCapacity(len(opts)) + for _, opt := range opts { + opt(responses) + } + return responses +} + +// NewResponsesOption describes options to NewResponses func +type NewResponsesOption func(*Responses) + +// WithStatus adds a status code keyed ResponseRef +func WithStatus(status int, responseRef *ResponseRef) NewResponsesOption { + return func(responses *Responses) { + if r := responseRef; r != nil { + code := strconv.FormatInt(int64(status), 10) + responses.Set(code, r) + } + } +} + +// WithName adds a name-keyed Response +func WithName(name string, response *Response) NewResponsesOption { + return func(responses *Responses) { + if r := response; r != nil && name != "" { + responses.Set(name, &ResponseRef{Value: r}) + } + } +} + +// Default returns the default response +func (responses *Responses) Default() *ResponseRef { + return responses.Value("default") +} + +// Status returns a ResponseRef for the given status +// If an exact match isn't initially found a patterned field is checked using +// the first digit to determine the range (eg: 201 to 2XX) +// See https://spec.openapis.org/oas/v3.0.3#patterned-fields-0 +func (responses *Responses) Status(status int) *ResponseRef { + st := strconv.FormatInt(int64(status), 10) + if rref := responses.Value(st); rref != nil { + return rref + } + if 99 < status && status < 600 { + st = string(st[0]) + "XX" + switch st { + case "1XX", "2XX", "3XX", "4XX", "5XX": + return responses.Value(st) + } + } + return nil +} + +// Validate returns an error if Responses does not comply with the OpenAPI spec. +func (responses *Responses) Validate(ctx context.Context, opts ...ValidationOption) error { + ctx = WithValidationOptions(ctx, opts...) + + if responses.Len() == 0 { + return errors.New("the responses object MUST contain at least one response code") + } + + keys := make([]string, 0, responses.Len()) + for key := range responses.Map() { + keys = append(keys, key) + } + sort.Strings(keys) + for _, key := range keys { + v := responses.Value(key) + if err := v.Validate(ctx); err != nil { + return err + } + } + + return validateExtensions(ctx, responses.Extensions) +} + +// Response is specified by OpenAPI/Swagger 3.0 standard. +// See https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#response-object +type Response struct { + Extensions map[string]any `json:"-" yaml:"-"` + + Description *string `json:"description,omitempty" yaml:"description,omitempty"` + Headers Headers `json:"headers,omitempty" yaml:"headers,omitempty"` + Content Content `json:"content,omitempty" yaml:"content,omitempty"` + Links Links `json:"links,omitempty" yaml:"links,omitempty"` +} + +func NewResponse() *Response { + return &Response{} +} + +func (response *Response) WithDescription(value string) *Response { + response.Description = &value + return response +} + +func (response *Response) WithContent(content Content) *Response { + response.Content = content + return response +} + +func (response *Response) WithJSONSchema(schema *Schema) *Response { + response.Content = NewContentWithJSONSchema(schema) + return response +} + +func (response *Response) WithJSONSchemaRef(schema *SchemaRef) *Response { + response.Content = NewContentWithJSONSchemaRef(schema) + return response +} + +// MarshalJSON returns the JSON encoding of Response. +func (response Response) MarshalJSON() ([]byte, error) { + x, err := response.MarshalYAML() + if err != nil { + return nil, err + } + return json.Marshal(x) +} + +// MarshalYAML returns the YAML encoding of Response. +func (response Response) MarshalYAML() (any, error) { + m := make(map[string]any, 4+len(response.Extensions)) + for k, v := range response.Extensions { + m[k] = v + } + if x := response.Description; x != nil { + m["description"] = x + } + if x := response.Headers; len(x) != 0 { + m["headers"] = x + } + if x := response.Content; len(x) != 0 { + m["content"] = x + } + if x := response.Links; len(x) != 0 { + m["links"] = x + } + return m, nil +} + +// UnmarshalJSON sets Response to a copy of data. +func (response *Response) UnmarshalJSON(data []byte) error { + type ResponseBis Response + var x ResponseBis + if err := json.Unmarshal(data, &x); err != nil { + return unmarshalError(err) + } + _ = json.Unmarshal(data, &x.Extensions) + delete(x.Extensions, "description") + delete(x.Extensions, "headers") + delete(x.Extensions, "content") + delete(x.Extensions, "links") + if len(x.Extensions) == 0 { + x.Extensions = nil + } + *response = Response(x) + return nil +} + +// Validate returns an error if Response does not comply with the OpenAPI spec. +func (response *Response) Validate(ctx context.Context, opts ...ValidationOption) error { + ctx = WithValidationOptions(ctx, opts...) + + if response.Description == nil { + return errors.New("a short description of the response is required") + } + if vo := getValidationOptions(ctx); !vo.examplesValidationDisabled { + vo.examplesValidationAsReq, vo.examplesValidationAsRes = false, true + } + + if content := response.Content; content != nil { + if err := content.Validate(ctx); err != nil { + return err + } + } + + headers := make([]string, 0, len(response.Headers)) + for name := range response.Headers { + headers = append(headers, name) + } + sort.Strings(headers) + for _, name := range headers { + header := response.Headers[name] + if err := header.Validate(ctx); err != nil { + return err + } + } + + links := make([]string, 0, len(response.Links)) + for name := range response.Links { + links = append(links, name) + } + sort.Strings(links) + for _, name := range links { + link := response.Links[name] + if err := link.Validate(ctx); err != nil { + return err + } + } + + return validateExtensions(ctx, response.Extensions) +} diff --git a/vendor/github.com/getkin/kin-openapi/openapi3/schema.go b/vendor/github.com/getkin/kin-openapi/openapi3/schema.go new file mode 100644 index 00000000..7be6bd38 --- /dev/null +++ b/vendor/github.com/getkin/kin-openapi/openapi3/schema.go @@ -0,0 +1,2247 @@ +package openapi3 + +import ( + "bytes" + "context" + "encoding/json" + "errors" + "fmt" + "math" + "math/big" + "reflect" + "regexp" + "sort" + "strconv" + "strings" + "sync" + "unicode/utf16" + + "github.com/go-openapi/jsonpointer" + "github.com/mohae/deepcopy" +) + +const ( + TypeArray = "array" + TypeBoolean = "boolean" + TypeInteger = "integer" + TypeNumber = "number" + TypeObject = "object" + TypeString = "string" + TypeNull = "null" +) + +var ( + // SchemaErrorDetailsDisabled disables printing of details about schema errors. + SchemaErrorDetailsDisabled = false + + 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("floating point NaN is not allowed") + // ErrSchemaInputInf may be returned when validating a number + ErrSchemaInputInf = errors.New("floating point Inf is not allowed") + + compiledPatterns sync.Map +) + +// NewSchemaRef simply builds a SchemaRef +func NewSchemaRef(ref string, value *Schema) *SchemaRef { + return &SchemaRef{ + Ref: ref, + Value: value, + } +} + +type SchemaRefs []*SchemaRef + +var _ jsonpointer.JSONPointable = (*SchemaRefs)(nil) + +// JSONLookup implements https://pkg.go.dev/github.com/go-openapi/jsonpointer#JSONPointable +func (s SchemaRefs) JSONLookup(token string) (any, error) { + i, err := strconv.ParseUint(token, 10, 64) + if err != nil { + return nil, err + } + + if i >= uint64(len(s)) { + return nil, fmt.Errorf("index out of range: %d", i) + } + + ref := s[i] + + if ref == nil || ref.Ref != "" { + return &Ref{Ref: ref.Ref}, nil + } + return ref.Value, nil +} + +// Schema is specified by OpenAPI/Swagger 3.0 standard. +// See https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#schema-object +type Schema struct { + Extensions map[string]any `json:"-" yaml:"-"` + + OneOf SchemaRefs `json:"oneOf,omitempty" yaml:"oneOf,omitempty"` + AnyOf SchemaRefs `json:"anyOf,omitempty" yaml:"anyOf,omitempty"` + AllOf SchemaRefs `json:"allOf,omitempty" yaml:"allOf,omitempty"` + Not *SchemaRef `json:"not,omitempty" yaml:"not,omitempty"` + Type *Types `json:"type,omitempty" yaml:"type,omitempty"` + Title string `json:"title,omitempty" yaml:"title,omitempty"` + Format string `json:"format,omitempty" yaml:"format,omitempty"` + Description string `json:"description,omitempty" yaml:"description,omitempty"` + Enum []any `json:"enum,omitempty" yaml:"enum,omitempty"` + Default any `json:"default,omitempty" yaml:"default,omitempty"` + Example any `json:"example,omitempty" yaml:"example,omitempty"` + ExternalDocs *ExternalDocs `json:"externalDocs,omitempty" yaml:"externalDocs,omitempty"` + + // Array-related, here for struct compactness + UniqueItems bool `json:"uniqueItems,omitempty" yaml:"uniqueItems,omitempty"` + // Number-related, here for struct compactness + ExclusiveMin bool `json:"exclusiveMinimum,omitempty" yaml:"exclusiveMinimum,omitempty"` + ExclusiveMax bool `json:"exclusiveMaximum,omitempty" yaml:"exclusiveMaximum,omitempty"` + // Properties + Nullable bool `json:"nullable,omitempty" yaml:"nullable,omitempty"` + ReadOnly bool `json:"readOnly,omitempty" yaml:"readOnly,omitempty"` + WriteOnly bool `json:"writeOnly,omitempty" yaml:"writeOnly,omitempty"` + AllowEmptyValue bool `json:"allowEmptyValue,omitempty" yaml:"allowEmptyValue,omitempty"` + Deprecated bool `json:"deprecated,omitempty" yaml:"deprecated,omitempty"` + XML *XML `json:"xml,omitempty" yaml:"xml,omitempty"` + + // Number + Min *float64 `json:"minimum,omitempty" yaml:"minimum,omitempty"` + Max *float64 `json:"maximum,omitempty" yaml:"maximum,omitempty"` + MultipleOf *float64 `json:"multipleOf,omitempty" yaml:"multipleOf,omitempty"` + + // String + MinLength uint64 `json:"minLength,omitempty" yaml:"minLength,omitempty"` + MaxLength *uint64 `json:"maxLength,omitempty" yaml:"maxLength,omitempty"` + Pattern string `json:"pattern,omitempty" yaml:"pattern,omitempty"` + + // Array + MinItems uint64 `json:"minItems,omitempty" yaml:"minItems,omitempty"` + MaxItems *uint64 `json:"maxItems,omitempty" yaml:"maxItems,omitempty"` + 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 AdditionalProperties `json:"additionalProperties,omitempty" yaml:"additionalProperties,omitempty"` + Discriminator *Discriminator `json:"discriminator,omitempty" yaml:"discriminator,omitempty"` +} + +type Types []string + +func (types *Types) Is(typ string) bool { + return types != nil && len(*types) == 1 && (*types)[0] == typ +} + +func (types *Types) Slice() []string { + if types == nil { + return nil + } + return *types +} + +func (pTypes *Types) Includes(typ string) bool { + if pTypes == nil { + return false + } + types := *pTypes + for _, candidate := range types { + if candidate == typ { + return true + } + } + return false +} + +func (types *Types) Permits(typ string) bool { + if types == nil { + return true + } + return types.Includes(typ) +} + +func (pTypes *Types) MarshalJSON() ([]byte, error) { + x, err := pTypes.MarshalYAML() + if err != nil { + return nil, err + } + return json.Marshal(x) +} + +func (pTypes *Types) MarshalYAML() (any, error) { + if pTypes == nil { + return nil, nil + } + types := *pTypes + switch len(types) { + case 0: + return nil, nil + case 1: + return types[0], nil + default: + return []string(types), nil + } +} + +func (types *Types) UnmarshalJSON(data []byte) error { + var strings []string + if err := json.Unmarshal(data, &strings); err != nil { + var s string + if err := json.Unmarshal(data, &s); err != nil { + return unmarshalError(err) + } + strings = []string{s} + } + *types = strings + return nil +} + +type AdditionalProperties struct { + Has *bool + Schema *SchemaRef +} + +// MarshalYAML returns the YAML encoding of AdditionalProperties. +func (addProps AdditionalProperties) MarshalYAML() (any, error) { + if x := addProps.Has; x != nil { + if *x { + return true, nil + } + return false, nil + } + if x := addProps.Schema; x != nil { + return x.MarshalYAML() + } + return nil, nil +} + +// MarshalJSON returns the JSON encoding of AdditionalProperties. +func (addProps AdditionalProperties) MarshalJSON() ([]byte, error) { + x, err := addProps.MarshalYAML() + if err != nil { + return nil, err + } + return json.Marshal(x) +} + +// UnmarshalJSON sets AdditionalProperties to a copy of data. +func (addProps *AdditionalProperties) UnmarshalJSON(data []byte) error { + var x any + if err := json.Unmarshal(data, &x); err != nil { + return unmarshalError(err) + } + switch y := x.(type) { + case nil: + case bool: + addProps.Has = &y + case map[string]any: + if len(y) == 0 { + addProps.Schema = &SchemaRef{Value: &Schema{}} + } else { + buf := new(bytes.Buffer) + json.NewEncoder(buf).Encode(y) + if err := json.NewDecoder(buf).Decode(&addProps.Schema); err != nil { + return err + } + } + default: + return errors.New("cannot unmarshal additionalProperties: value must be either a schema object or a boolean") + } + return nil +} + +var _ jsonpointer.JSONPointable = (*Schema)(nil) + +func NewSchema() *Schema { + return &Schema{} +} + +// MarshalJSON returns the JSON encoding of Schema. +func (schema Schema) MarshalJSON() ([]byte, error) { + m, err := schema.MarshalYAML() + if err != nil { + return nil, err + } + + return json.Marshal(m) +} + +// MarshalYAML returns the YAML encoding of Schema. +func (schema Schema) MarshalYAML() (any, error) { + m := make(map[string]any, 36+len(schema.Extensions)) + for k, v := range schema.Extensions { + m[k] = v + } + + if x := schema.OneOf; len(x) != 0 { + m["oneOf"] = x + } + if x := schema.AnyOf; len(x) != 0 { + m["anyOf"] = x + } + if x := schema.AllOf; len(x) != 0 { + m["allOf"] = x + } + if x := schema.Not; x != nil { + m["not"] = x + } + if x := schema.Type; x != nil { + m["type"] = x + } + if x := schema.Title; len(x) != 0 { + m["title"] = x + } + if x := schema.Format; len(x) != 0 { + m["format"] = x + } + if x := schema.Description; len(x) != 0 { + m["description"] = x + } + if x := schema.Enum; len(x) != 0 { + m["enum"] = x + } + if x := schema.Default; x != nil { + m["default"] = x + } + if x := schema.Example; x != nil { + m["example"] = x + } + if x := schema.ExternalDocs; x != nil { + m["externalDocs"] = x + } + + // Array-related + if x := schema.UniqueItems; x { + m["uniqueItems"] = x + } + // Number-related + if x := schema.ExclusiveMin; x { + m["exclusiveMinimum"] = x + } + if x := schema.ExclusiveMax; x { + m["exclusiveMaximum"] = x + } + // Properties + if x := schema.Nullable; x { + m["nullable"] = x + } + if x := schema.ReadOnly; x { + m["readOnly"] = x + } + if x := schema.WriteOnly; x { + m["writeOnly"] = x + } + if x := schema.AllowEmptyValue; x { + m["allowEmptyValue"] = x + } + if x := schema.Deprecated; x { + m["deprecated"] = x + } + if x := schema.XML; x != nil { + m["xml"] = x + } + + // Number + if x := schema.Min; x != nil { + m["minimum"] = x + } + if x := schema.Max; x != nil { + m["maximum"] = x + } + if x := schema.MultipleOf; x != nil { + m["multipleOf"] = x + } + + // String + if x := schema.MinLength; x != 0 { + m["minLength"] = x + } + if x := schema.MaxLength; x != nil { + m["maxLength"] = x + } + if x := schema.Pattern; x != "" { + m["pattern"] = x + } + + // Array + if x := schema.MinItems; x != 0 { + m["minItems"] = x + } + if x := schema.MaxItems; x != nil { + m["maxItems"] = x + } + if x := schema.Items; x != nil { + m["items"] = x + } + + // Object + if x := schema.Required; len(x) != 0 { + m["required"] = x + } + if x := schema.Properties; len(x) != 0 { + m["properties"] = x + } + if x := schema.MinProps; x != 0 { + m["minProperties"] = x + } + if x := schema.MaxProps; x != nil { + m["maxProperties"] = x + } + if x := schema.AdditionalProperties; x.Has != nil || x.Schema != nil { + m["additionalProperties"] = &x + } + if x := schema.Discriminator; x != nil { + m["discriminator"] = x + } + + return m, nil +} + +// UnmarshalJSON sets Schema to a copy of data. +func (schema *Schema) UnmarshalJSON(data []byte) error { + type SchemaBis Schema + var x SchemaBis + if err := json.Unmarshal(data, &x); err != nil { + return unmarshalError(err) + } + _ = json.Unmarshal(data, &x.Extensions) + + delete(x.Extensions, "oneOf") + delete(x.Extensions, "anyOf") + delete(x.Extensions, "allOf") + delete(x.Extensions, "not") + delete(x.Extensions, "type") + delete(x.Extensions, "title") + delete(x.Extensions, "format") + delete(x.Extensions, "description") + delete(x.Extensions, "enum") + delete(x.Extensions, "default") + delete(x.Extensions, "example") + delete(x.Extensions, "externalDocs") + + // Array-related + delete(x.Extensions, "uniqueItems") + // Number-related + delete(x.Extensions, "exclusiveMinimum") + delete(x.Extensions, "exclusiveMaximum") + // Properties + delete(x.Extensions, "nullable") + delete(x.Extensions, "readOnly") + delete(x.Extensions, "writeOnly") + delete(x.Extensions, "allowEmptyValue") + delete(x.Extensions, "deprecated") + delete(x.Extensions, "xml") + + // Number + delete(x.Extensions, "minimum") + delete(x.Extensions, "maximum") + delete(x.Extensions, "multipleOf") + + // String + delete(x.Extensions, "minLength") + delete(x.Extensions, "maxLength") + delete(x.Extensions, "pattern") + + // Array + delete(x.Extensions, "minItems") + delete(x.Extensions, "maxItems") + delete(x.Extensions, "items") + + // Object + delete(x.Extensions, "required") + delete(x.Extensions, "properties") + delete(x.Extensions, "minProperties") + delete(x.Extensions, "maxProperties") + delete(x.Extensions, "additionalProperties") + delete(x.Extensions, "discriminator") + + if len(x.Extensions) == 0 { + x.Extensions = nil + } + + *schema = Schema(x) + + if schema.Format == "date" { + // This is a fix for: https://github.com/getkin/kin-openapi/issues/697 + if eg, ok := schema.Example.(string); ok { + schema.Example = strings.TrimSuffix(eg, "T00:00:00Z") + } + } + return nil +} + +// JSONLookup implements https://pkg.go.dev/github.com/go-openapi/jsonpointer#JSONPointable +func (schema Schema) JSONLookup(token string) (any, error) { + switch token { + case "additionalProperties": + if addProps := schema.AdditionalProperties.Has; addProps != nil { + return *addProps, nil + } + if addProps := schema.AdditionalProperties.Schema; addProps != nil { + if addProps.Ref != "" { + return &Ref{Ref: addProps.Ref}, nil + } + return addProps.Value, nil + } + case "not": + if schema.Not != nil { + if schema.Not.Ref != "" { + return &Ref{Ref: schema.Not.Ref}, nil + } + return schema.Not.Value, nil + } + case "items": + if schema.Items != nil { + if schema.Items.Ref != "" { + return &Ref{Ref: schema.Items.Ref}, nil + } + return schema.Items.Value, nil + } + case "oneOf": + return schema.OneOf, nil + case "anyOf": + return schema.AnyOf, nil + case "allOf": + return schema.AllOf, nil + case "type": + return schema.Type, nil + case "title": + return schema.Title, nil + case "format": + return schema.Format, nil + case "description": + return schema.Description, nil + case "enum": + return schema.Enum, nil + case "default": + return schema.Default, nil + case "example": + return schema.Example, nil + case "externalDocs": + return schema.ExternalDocs, nil + case "uniqueItems": + return schema.UniqueItems, nil + case "exclusiveMin": + return schema.ExclusiveMin, nil + case "exclusiveMax": + return schema.ExclusiveMax, nil + case "nullable": + return schema.Nullable, nil + case "readOnly": + return schema.ReadOnly, nil + case "writeOnly": + return schema.WriteOnly, nil + case "allowEmptyValue": + return schema.AllowEmptyValue, nil + case "xml": + return schema.XML, nil + case "deprecated": + return schema.Deprecated, nil + case "min": + return schema.Min, nil + case "max": + return schema.Max, nil + case "multipleOf": + return schema.MultipleOf, nil + case "minLength": + return schema.MinLength, nil + case "maxLength": + return schema.MaxLength, nil + case "pattern": + return schema.Pattern, nil + case "minItems": + return schema.MinItems, nil + case "maxItems": + return schema.MaxItems, nil + case "required": + return schema.Required, nil + case "properties": + return schema.Properties, nil + case "minProps": + return schema.MinProps, nil + case "maxProps": + return schema.MaxProps, nil + case "discriminator": + return schema.Discriminator, nil + } + + v, _, err := jsonpointer.GetForToken(schema.Extensions, token) + return v, err +} + +func (schema *Schema) NewRef() *SchemaRef { + return &SchemaRef{ + Value: schema, + } +} + +func NewOneOfSchema(schemas ...*Schema) *Schema { + refs := make([]*SchemaRef, 0, len(schemas)) + for _, schema := range schemas { + refs = append(refs, &SchemaRef{Value: schema}) + } + return &Schema{ + OneOf: refs, + } +} + +func NewAnyOfSchema(schemas ...*Schema) *Schema { + refs := make([]*SchemaRef, 0, len(schemas)) + for _, schema := range schemas { + refs = append(refs, &SchemaRef{Value: schema}) + } + return &Schema{ + AnyOf: refs, + } +} + +func NewAllOfSchema(schemas ...*Schema) *Schema { + refs := make([]*SchemaRef, 0, len(schemas)) + for _, schema := range schemas { + refs = append(refs, &SchemaRef{Value: schema}) + } + return &Schema{ + AllOf: refs, + } +} + +func NewBoolSchema() *Schema { + return &Schema{ + Type: &Types{TypeBoolean}, + } +} + +func NewFloat64Schema() *Schema { + return &Schema{ + Type: &Types{TypeNumber}, + } +} + +func NewIntegerSchema() *Schema { + return &Schema{ + Type: &Types{TypeInteger}, + } +} + +func NewInt32Schema() *Schema { + return &Schema{ + Type: &Types{TypeInteger}, + Format: "int32", + } +} + +func NewInt64Schema() *Schema { + return &Schema{ + Type: &Types{TypeInteger}, + Format: "int64", + } +} + +func NewStringSchema() *Schema { + return &Schema{ + Type: &Types{TypeString}, + } +} + +func NewDateTimeSchema() *Schema { + return &Schema{ + Type: &Types{TypeString}, + Format: "date-time", + } +} + +func NewUUIDSchema() *Schema { + return &Schema{ + Type: &Types{TypeString}, + Format: "uuid", + } +} + +func NewBytesSchema() *Schema { + return &Schema{ + Type: &Types{TypeString}, + Format: "byte", + } +} + +func NewArraySchema() *Schema { + return &Schema{ + Type: &Types{TypeArray}, + } +} + +func NewObjectSchema() *Schema { + return &Schema{ + Type: &Types{TypeObject}, + Properties: make(Schemas), + } +} + +func (schema *Schema) WithNullable() *Schema { + schema.Nullable = true + return schema +} + +func (schema *Schema) WithMin(value float64) *Schema { + schema.Min = &value + return schema +} + +func (schema *Schema) WithMax(value float64) *Schema { + schema.Max = &value + return schema +} + +func (schema *Schema) WithExclusiveMin(value bool) *Schema { + schema.ExclusiveMin = value + return schema +} + +func (schema *Schema) WithExclusiveMax(value bool) *Schema { + schema.ExclusiveMax = value + return schema +} + +func (schema *Schema) WithEnum(values ...any) *Schema { + schema.Enum = values + return schema +} + +func (schema *Schema) WithDefault(defaultValue any) *Schema { + schema.Default = defaultValue + return schema +} + +func (schema *Schema) WithFormat(value string) *Schema { + schema.Format = value + return schema +} + +func (schema *Schema) WithLength(i int64) *Schema { + n := uint64(i) + schema.MinLength = n + schema.MaxLength = &n + return schema +} + +func (schema *Schema) WithMinLength(i int64) *Schema { + n := uint64(i) + schema.MinLength = n + return schema +} + +func (schema *Schema) WithMaxLength(i int64) *Schema { + n := uint64(i) + schema.MaxLength = &n + return schema +} + +func (schema *Schema) WithLengthDecodedBase64(i int64) *Schema { + n := uint64(i) + v := (n*8 + 5) / 6 + schema.MinLength = v + schema.MaxLength = &v + return schema +} + +func (schema *Schema) WithMinLengthDecodedBase64(i int64) *Schema { + n := uint64(i) + schema.MinLength = (n*8 + 5) / 6 + return schema +} + +func (schema *Schema) WithMaxLengthDecodedBase64(i int64) *Schema { + n := uint64(i) + schema.MinLength = (n*8 + 5) / 6 + return schema +} + +func (schema *Schema) WithPattern(pattern string) *Schema { + schema.Pattern = pattern + return schema +} + +func (schema *Schema) WithItems(value *Schema) *Schema { + schema.Items = &SchemaRef{ + Value: value, + } + return schema +} + +func (schema *Schema) WithMinItems(i int64) *Schema { + n := uint64(i) + schema.MinItems = n + return schema +} + +func (schema *Schema) WithMaxItems(i int64) *Schema { + n := uint64(i) + schema.MaxItems = &n + return schema +} + +func (schema *Schema) WithUniqueItems(unique bool) *Schema { + schema.UniqueItems = unique + return schema +} + +func (schema *Schema) WithProperty(name string, propertySchema *Schema) *Schema { + return schema.WithPropertyRef(name, &SchemaRef{ + Value: propertySchema, + }) +} + +func (schema *Schema) WithPropertyRef(name string, ref *SchemaRef) *Schema { + properties := schema.Properties + if properties == nil { + properties = make(Schemas) + schema.Properties = properties + } + properties[name] = ref + return schema +} + +func (schema *Schema) WithProperties(properties map[string]*Schema) *Schema { + result := make(Schemas, len(properties)) + for k, v := range properties { + result[k] = &SchemaRef{ + Value: v, + } + } + schema.Properties = result + return schema +} + +func (schema *Schema) WithRequired(required []string) *Schema { + schema.Required = required + return schema +} + +func (schema *Schema) WithMinProperties(i int64) *Schema { + n := uint64(i) + schema.MinProps = n + return schema +} + +func (schema *Schema) WithMaxProperties(i int64) *Schema { + n := uint64(i) + schema.MaxProps = &n + return schema +} + +func (schema *Schema) WithAnyAdditionalProperties() *Schema { + schema.AdditionalProperties = AdditionalProperties{Has: BoolPtr(true)} + return schema +} + +func (schema *Schema) WithoutAdditionalProperties() *Schema { + schema.AdditionalProperties = AdditionalProperties{Has: BoolPtr(false)} + return schema +} + +func (schema *Schema) WithAdditionalProperties(v *Schema) *Schema { + schema.AdditionalProperties = AdditionalProperties{} + if v != nil { + schema.AdditionalProperties.Schema = &SchemaRef{Value: v} + } + return schema +} + +func (schema *Schema) PermitsNull() bool { + return schema.Nullable || schema.Type.Includes("null") +} + +// IsEmpty tells whether schema is equivalent to the empty schema `{}`. +func (schema *Schema) IsEmpty() bool { + if schema.Type != nil || schema.Format != "" || len(schema.Enum) != 0 || + schema.UniqueItems || schema.ExclusiveMin || schema.ExclusiveMax || + schema.Nullable || schema.ReadOnly || schema.WriteOnly || schema.AllowEmptyValue || + schema.Min != nil || schema.Max != nil || schema.MultipleOf != nil || + schema.MinLength != 0 || schema.MaxLength != nil || schema.Pattern != "" || + schema.MinItems != 0 || schema.MaxItems != nil || + len(schema.Required) != 0 || + schema.MinProps != 0 || schema.MaxProps != nil { + return false + } + if n := schema.Not; n != nil && n.Value != nil && !n.Value.IsEmpty() { + return false + } + if ap := schema.AdditionalProperties.Schema; ap != nil && ap.Value != nil && !ap.Value.IsEmpty() { + return false + } + if apa := schema.AdditionalProperties.Has; apa != nil && !*apa { + return false + } + if items := schema.Items; items != nil && items.Value != nil && !items.Value.IsEmpty() { + return false + } + for _, s := range schema.Properties { + if ss := s.Value; ss != nil && !ss.IsEmpty() { + return false + } + } + for _, s := range schema.OneOf { + if ss := s.Value; ss != nil && !ss.IsEmpty() { + return false + } + } + for _, s := range schema.AnyOf { + if ss := s.Value; ss != nil && !ss.IsEmpty() { + return false + } + } + for _, s := range schema.AllOf { + if ss := s.Value; ss != nil && !ss.IsEmpty() { + return false + } + } + return true +} + +// Validate returns an error if Schema does not comply with the OpenAPI spec. +func (schema *Schema) Validate(ctx context.Context, opts ...ValidationOption) error { + ctx = WithValidationOptions(ctx, opts...) + _, err := schema.validate(ctx, []*Schema{}) + return err +} + +// returns the updated stack and an error if Schema does not comply with the OpenAPI spec. +func (schema *Schema) validate(ctx context.Context, stack []*Schema) ([]*Schema, error) { + validationOpts := getValidationOptions(ctx) + + for _, existing := range stack { + if existing == schema { + return stack, nil + } + } + stack = append(stack, schema) + + if schema.ReadOnly && schema.WriteOnly { + return stack, errors.New("a property MUST NOT be marked as both readOnly and writeOnly being true") + } + + for _, item := range schema.OneOf { + v := item.Value + if v == nil { + return stack, foundUnresolvedRef(item.Ref) + } + + var err error + if stack, err = v.validate(ctx, stack); err != nil { + return stack, err + } + } + + for _, item := range schema.AnyOf { + v := item.Value + if v == nil { + return stack, foundUnresolvedRef(item.Ref) + } + + var err error + if stack, err = v.validate(ctx, stack); err != nil { + return stack, err + } + } + + for _, item := range schema.AllOf { + v := item.Value + if v == nil { + return stack, foundUnresolvedRef(item.Ref) + } + + var err error + if stack, err = v.validate(ctx, stack); err != nil { + return stack, err + } + } + + if ref := schema.Not; ref != nil { + v := ref.Value + if v == nil { + return stack, foundUnresolvedRef(ref.Ref) + } + + var err error + if stack, err = v.validate(ctx, stack); err != nil { + return stack, err + } + } + + for _, schemaType := range schema.Type.Slice() { + switch schemaType { + case TypeBoolean: + case TypeNumber: + if format := schema.Format; len(format) > 0 { + switch format { + case "float", "double": + default: + if _, ok := SchemaNumberFormats[format]; !ok && validationOpts.schemaFormatValidationEnabled { + return stack, unsupportedFormat(format) + } + } + } + case TypeInteger: + if format := schema.Format; len(format) > 0 { + switch format { + case "int32", "int64": + default: + if _, ok := SchemaIntegerFormats[format]; !ok && validationOpts.schemaFormatValidationEnabled { + return stack, unsupportedFormat(format) + } + } + } + case TypeString: + if format := schema.Format; len(format) > 0 { + switch format { + // Supported by OpenAPIv3.0.3: + // https://spec.openapis.org/oas/v3.0.3 + case "byte", "binary", "date", "date-time", "password": + // In JSON Draft-07 (not validated yet though): + // https://json-schema.org/draft-07/json-schema-release-notes.html#formats + case "iri", "iri-reference", "uri-template", "idn-email", "idn-hostname": + case "json-pointer", "relative-json-pointer", "regex", "time": + // In JSON Draft 2019-09 (not validated yet though): + // https://json-schema.org/draft/2019-09/release-notes.html#format-vocabulary + case "duration", "uuid": + // Defined in some other specification + case "email", "hostname", "ipv4", "ipv6", "uri", "uri-reference": + default: + if _, ok := SchemaStringFormats[format]; !ok && validationOpts.schemaFormatValidationEnabled { + return stack, unsupportedFormat(format) + } + } + } + if !validationOpts.schemaPatternValidationDisabled && schema.Pattern != "" { + if _, err := schema.compilePattern(); err != nil { + return stack, err + } + } + case TypeArray: + if schema.Items == nil { + return stack, errors.New("when schema type is 'array', schema 'items' must be non-null") + } + case TypeObject: + default: + return stack, fmt.Errorf("unsupported 'type' value %q", schemaType) + } + } + + if ref := schema.Items; ref != nil { + v := ref.Value + if v == nil { + return stack, foundUnresolvedRef(ref.Ref) + } + + var err error + if stack, err = v.validate(ctx, stack); err != nil { + return stack, err + } + } + + properties := make([]string, 0, len(schema.Properties)) + for name := range schema.Properties { + properties = append(properties, name) + } + sort.Strings(properties) + for _, name := range properties { + ref := schema.Properties[name] + v := ref.Value + if v == nil { + return stack, foundUnresolvedRef(ref.Ref) + } + + var err error + if stack, err = v.validate(ctx, stack); err != nil { + return stack, err + } + } + + if schema.AdditionalProperties.Has != nil && schema.AdditionalProperties.Schema != nil { + return stack, errors.New("additionalProperties are set to both boolean and schema") + } + if ref := schema.AdditionalProperties.Schema; ref != nil { + v := ref.Value + if v == nil { + return stack, foundUnresolvedRef(ref.Ref) + } + + var err error + if stack, err = v.validate(ctx, stack); err != nil { + return stack, err + } + } + + if v := schema.ExternalDocs; v != nil { + if err := v.Validate(ctx); err != nil { + return stack, fmt.Errorf("invalid external docs: %w", err) + } + } + + if v := schema.Default; v != nil && !validationOpts.schemaDefaultsValidationDisabled { + if err := schema.VisitJSON(v); err != nil { + return stack, fmt.Errorf("invalid default: %w", err) + } + } + + if x := schema.Example; x != nil && !validationOpts.examplesValidationDisabled { + if err := validateExampleValue(ctx, x, schema); err != nil { + return stack, fmt.Errorf("invalid example: %w", err) + } + } + + return stack, validateExtensions(ctx, schema.Extensions) +} + +func (schema *Schema) IsMatching(value any) bool { + settings := newSchemaValidationSettings(FailFast()) + return schema.visitJSON(settings, value) == nil +} + +func (schema *Schema) IsMatchingJSONBoolean(value bool) bool { + settings := newSchemaValidationSettings(FailFast()) + return schema.visitJSON(settings, value) == nil +} + +func (schema *Schema) IsMatchingJSONNumber(value float64) bool { + settings := newSchemaValidationSettings(FailFast()) + return schema.visitJSON(settings, value) == nil +} + +func (schema *Schema) IsMatchingJSONString(value string) bool { + settings := newSchemaValidationSettings(FailFast()) + return schema.visitJSON(settings, value) == nil +} + +func (schema *Schema) IsMatchingJSONArray(value []any) bool { + settings := newSchemaValidationSettings(FailFast()) + return schema.visitJSON(settings, value) == nil +} + +func (schema *Schema) IsMatchingJSONObject(value map[string]any) bool { + settings := newSchemaValidationSettings(FailFast()) + return schema.visitJSON(settings, value) == nil +} + +func (schema *Schema) VisitJSON(value any, opts ...SchemaValidationOption) error { + settings := newSchemaValidationSettings(opts...) + return schema.visitJSON(settings, value) +} + +func (schema *Schema) visitJSON(settings *schemaValidationSettings, value any) (err error) { + switch value := value.(type) { + case nil: + // Don't use VisitJSONNull, as we still want to reach 'visitXOFOperations', since + // those could allow for a nullable value even though this one doesn't + if schema.PermitsNull() { + return + } + case float64: + if math.IsNaN(value) { + return ErrSchemaInputNaN + } + if math.IsInf(value, 0) { + return ErrSchemaInputInf + } + } + + if schema.IsEmpty() { + switch value.(type) { + case nil: + return schema.visitJSONNull(settings) + default: + return + } + } + + if err = schema.visitNotOperation(settings, value); err != nil { + return + } + var run bool + if err, run = schema.visitXOFOperations(settings, value); err != nil || !run { + return + } + if err = schema.visitEnumOperation(settings, value); err != nil { + return + } + + switch value := value.(type) { + case nil: + return schema.visitJSONNull(settings) + case bool: + return schema.visitJSONBoolean(settings, value) + case json.Number: + valueFloat64, err := value.Float64() + if err != nil { + return &SchemaError{ + Value: value, + Schema: schema, + SchemaField: "type", + Reason: "cannot convert json.Number to float64", + customizeMessageError: settings.customizeMessageError, + Origin: err, + } + } + return schema.visitJSONNumber(settings, valueFloat64) + case int: + return schema.visitJSONNumber(settings, float64(value)) + case int32: + return schema.visitJSONNumber(settings, float64(value)) + case int64: + return schema.visitJSONNumber(settings, float64(value)) + case float64: + return schema.visitJSONNumber(settings, value) + case string: + return schema.visitJSONString(settings, value) + case []any: + return schema.visitJSONArray(settings, value) + case map[string]any: + return schema.visitJSONObject(settings, value) + case map[any]any: // for YAML cf. issue https://github.com/getkin/kin-openapi/issues/444 + values := make(map[string]any, 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) + } + } + + // Catch slice of non-empty interface type + if reflect.TypeOf(value).Kind() == reflect.Slice { + valueR := reflect.ValueOf(value) + newValue := make([]any, 0, valueR.Len()) + for i := 0; i < valueR.Len(); i++ { + newValue = append(newValue, valueR.Index(i).Interface()) + } + return schema.visitJSONArray(settings, newValue) + } + + return &SchemaError{ + Value: value, + Schema: schema, + SchemaField: "type", + Reason: fmt.Sprintf("unhandled value of type %T", value), + customizeMessageError: settings.customizeMessageError, + } +} + +func (schema *Schema) visitEnumOperation(settings *schemaValidationSettings, value any) (err error) { + if enum := schema.Enum; len(enum) != 0 { + for _, v := range enum { + switch c := value.(type) { + case json.Number: + var f float64 + if f, err = strconv.ParseFloat(c.String(), 64); err != nil { + return err + } + if v == f { + return + } + case int64: + if v == float64(c) { + return + } + default: + if reflect.DeepEqual(v, value) { + return + } + } + } + if settings.failfast { + return errSchema + } + allowedValues, _ := json.Marshal(enum) + return &SchemaError{ + Value: value, + Schema: schema, + SchemaField: "enum", + Reason: fmt.Sprintf("value is not one of the allowed values %s", string(allowedValues)), + customizeMessageError: settings.customizeMessageError, + } + } + return +} + +func (schema *Schema) visitNotOperation(settings *schemaValidationSettings, value any) (err error) { + if ref := schema.Not; ref != nil { + v := ref.Value + if v == nil { + return foundUnresolvedRef(ref.Ref) + } + if err := v.visitJSON(settings, value); err == nil { + if settings.failfast { + return errSchema + } + return &SchemaError{ + Value: value, + Schema: schema, + SchemaField: "not", + customizeMessageError: settings.customizeMessageError, + } + } + } + return +} + +// If the XOF operations pass successfully, abort further run of validation, as they will already be satisfied (unless the schema +// itself is badly specified +func (schema *Schema) visitXOFOperations(settings *schemaValidationSettings, value any) (err error, run bool) { + var visitedOneOf, visitedAnyOf, visitedAllOf bool + if v := schema.OneOf; len(v) > 0 { + var discriminatorRef string + if schema.Discriminator != nil { + pn := schema.Discriminator.PropertyName + if valuemap, okcheck := value.(map[string]any); okcheck { + discriminatorVal, okcheck := valuemap[pn] + if !okcheck { + return &SchemaError{ + Schema: schema, + SchemaField: "discriminator", + Reason: fmt.Sprintf("input does not contain the discriminator property %q", pn), + }, false + } + + discriminatorValString, okcheck := discriminatorVal.(string) + if !okcheck { + return &SchemaError{ + Value: discriminatorVal, + Schema: schema, + SchemaField: "discriminator", + Reason: fmt.Sprintf("value of discriminator property %q is not a string", pn), + }, false + } + + if discriminatorRef, okcheck = schema.Discriminator.Mapping[discriminatorValString]; len(schema.Discriminator.Mapping) > 0 && !okcheck { + return &SchemaError{ + Value: discriminatorVal, + Schema: schema, + SchemaField: "discriminator", + Reason: fmt.Sprintf("discriminator property %q has invalid value", pn), + }, false + } + } + } + + var ( + ok = 0 + validationErrors = multiErrorForOneOf{} + matchedOneOfIndices = make([]int, 0) + tempValue = value + ) + for idx, item := range v { + v := item.Value + if v == nil { + return foundUnresolvedRef(item.Ref), false + } + + if discriminatorRef != "" && discriminatorRef != item.Ref { + continue + } + + // make a deep copy to protect origin value from being injected default value that defined in mismatched oneOf schema + if settings.asreq || settings.asrep { + tempValue = deepcopy.Copy(value) + } + + if err := v.visitJSON(settings, tempValue); err != nil { + validationErrors = append(validationErrors, err) + continue + } + + matchedOneOfIndices = append(matchedOneOfIndices, idx) + ok++ + } + + if ok != 1 { + if settings.failfast { + return errSchema, false + } + e := &SchemaError{ + Value: value, + Schema: schema, + SchemaField: "oneOf", + customizeMessageError: settings.customizeMessageError, + } + if ok > 1 { + e.Origin = ErrOneOfConflict + e.Reason = fmt.Sprintf(`value matches more than one schema from "oneOf" (matches schemas at indices %v)`, matchedOneOfIndices) + } else { + e.Origin = fmt.Errorf("doesn't match schema due to: %w", validationErrors) + e.Reason = `value doesn't match any schema from "oneOf"` + } + + return e, false + } + + // run again to inject default value that defined in matched oneOf schema + if settings.asreq || settings.asrep { + _ = v[matchedOneOfIndices[0]].Value.visitJSON(settings, value) + } + visitedOneOf = true + } + + if v := schema.AnyOf; len(v) > 0 { + var ( + ok = false + matchedAnyOfIdx = 0 + tempValue = value + ) + for idx, item := range v { + v := item.Value + if v == nil { + return foundUnresolvedRef(item.Ref), false + } + // make a deep copy to protect origin value from being injected default value that defined in mismatched anyOf schema + if settings.asreq || settings.asrep { + tempValue = deepcopy.Copy(value) + } + if err := v.visitJSON(settings, tempValue); err == nil { + ok = true + matchedAnyOfIdx = idx + break + } + } + if !ok { + if settings.failfast { + return errSchema, false + } + return &SchemaError{ + Value: value, + Schema: schema, + SchemaField: "anyOf", + Reason: `doesn't match any schema from "anyOf"`, + customizeMessageError: settings.customizeMessageError, + }, false + } + + _ = v[matchedAnyOfIdx].Value.visitJSON(settings, value) + visitedAnyOf = true + } + + for _, item := range schema.AllOf { + v := item.Value + if v == nil { + return foundUnresolvedRef(item.Ref), false + } + if err := v.visitJSON(settings, value); err != nil { + if settings.failfast { + return errSchema, false + } + return &SchemaError{ + Value: value, + Schema: schema, + SchemaField: "allOf", + Reason: `doesn't match all schemas from "allOf"`, + Origin: err, + customizeMessageError: settings.customizeMessageError, + }, false + } + visitedAllOf = true + } + + run = !((visitedOneOf || visitedAnyOf || visitedAllOf) && value == nil) + return +} + +// The value is not considered in visitJSONNull because according to the spec +// "null is not supported as a type" unless `nullable` is also set to true +// https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#data-types +// https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#schema-object +func (schema *Schema) visitJSONNull(settings *schemaValidationSettings) (err error) { + if schema.PermitsNull() { + return + } + if settings.failfast { + return errSchema + } + return &SchemaError{ + Value: nil, + Schema: schema, + SchemaField: "nullable", + Reason: "Value is not nullable", + customizeMessageError: settings.customizeMessageError, + } +} + +func (schema *Schema) VisitJSONBoolean(value bool) error { + settings := newSchemaValidationSettings() + return schema.visitJSONBoolean(settings, value) +} + +func (schema *Schema) visitJSONBoolean(settings *schemaValidationSettings, value bool) (err error) { + if !schema.Type.Permits(TypeBoolean) { + return schema.expectedType(settings, value) + } + return +} + +func (schema *Schema) VisitJSONNumber(value float64) error { + settings := newSchemaValidationSettings() + return schema.visitJSONNumber(settings, value) +} + +func (schema *Schema) visitJSONNumber(settings *schemaValidationSettings, value float64) error { + var me MultiError + schemaType := schema.Type + requireInteger := false + if schemaType.Permits(TypeInteger) && !schemaType.Permits(TypeNumber) { + requireInteger = true + if bigFloat := big.NewFloat(value); !bigFloat.IsInt() { + if settings.failfast { + return errSchema + } + err := &SchemaError{ + Value: value, + Schema: schema, + SchemaField: "type", + Reason: "value must be an integer", + customizeMessageError: settings.customizeMessageError, + } + if !settings.multiError { + return err + } + me = append(me, err) + } + } else if !(schemaType.Permits(TypeInteger) || schemaType.Permits(TypeNumber)) { + return schema.expectedType(settings, value) + } + + // formats + var formatStrErr string + var formatErr error + format := schema.Format + if format != "" { + if requireInteger { + if f, ok := SchemaIntegerFormats[format]; ok { + if err := f.Validate(int64(value)); err != nil { + var reason string + schemaErr := &SchemaError{} + if errors.As(err, &schemaErr) { + reason = schemaErr.Reason + } else { + reason = err.Error() + } + formatStrErr = fmt.Sprintf(`integer doesn't match the format %q (%v)`, format, reason) + formatErr = fmt.Errorf("integer doesn't match the format %q: %w", format, err) + } + } + } else { + if f, ok := SchemaNumberFormats[format]; ok { + if err := f.Validate(value); err != nil { + var reason string + schemaErr := &SchemaError{} + if errors.As(err, &schemaErr) { + reason = schemaErr.Reason + } else { + reason = err.Error() + } + formatStrErr = fmt.Sprintf(`number doesn't match the format %q (%v)`, format, reason) + formatErr = fmt.Errorf("number doesn't match the format %q: %w", format, err) + } + } + } + } + + if formatStrErr != "" || formatErr != nil { + err := &SchemaError{ + Value: value, + Schema: schema, + SchemaField: "format", + Reason: formatStrErr, + Origin: formatErr, + customizeMessageError: settings.customizeMessageError, + } + if !settings.multiError { + return err + } + me = append(me, err) + } + + // "exclusiveMinimum" + if v := schema.ExclusiveMin; v && !(*schema.Min < value) { + if settings.failfast { + return errSchema + } + err := &SchemaError{ + Value: value, + Schema: schema, + SchemaField: "exclusiveMinimum", + Reason: fmt.Sprintf("number must be more than %g", *schema.Min), + customizeMessageError: settings.customizeMessageError, + } + if !settings.multiError { + return err + } + me = append(me, err) + } + + // "exclusiveMaximum" + if v := schema.ExclusiveMax; v && !(*schema.Max > value) { + if settings.failfast { + return errSchema + } + err := &SchemaError{ + Value: value, + Schema: schema, + SchemaField: "exclusiveMaximum", + Reason: fmt.Sprintf("number must be less than %g", *schema.Max), + customizeMessageError: settings.customizeMessageError, + } + if !settings.multiError { + return err + } + me = append(me, err) + } + + // "minimum" + if v := schema.Min; v != nil && !(*v <= value) { + if settings.failfast { + return errSchema + } + err := &SchemaError{ + Value: value, + Schema: schema, + SchemaField: "minimum", + Reason: fmt.Sprintf("number must be at least %g", *v), + customizeMessageError: settings.customizeMessageError, + } + if !settings.multiError { + return err + } + me = append(me, err) + } + + // "maximum" + if v := schema.Max; v != nil && !(*v >= value) { + if settings.failfast { + return errSchema + } + err := &SchemaError{ + Value: value, + Schema: schema, + SchemaField: "maximum", + Reason: fmt.Sprintf("number must be at most %g", *v), + customizeMessageError: settings.customizeMessageError, + } + if !settings.multiError { + return err + } + me = append(me, err) + } + + // "multipleOf" + if v := schema.MultipleOf; v != nil { + // "A numeric instance is valid only if division by this keyword's + // value results in an integer." + if bigFloat := big.NewFloat(value / *v); !bigFloat.IsInt() { + if settings.failfast { + return errSchema + } + err := &SchemaError{ + Value: value, + Schema: schema, + SchemaField: "multipleOf", + Reason: fmt.Sprintf("number must be a multiple of %g", *v), + customizeMessageError: settings.customizeMessageError, + } + if !settings.multiError { + return err + } + me = append(me, err) + } + } + + if len(me) > 0 { + return me + } + + return nil +} + +func (schema *Schema) VisitJSONString(value string) error { + settings := newSchemaValidationSettings() + return schema.visitJSONString(settings, value) +} + +func (schema *Schema) visitJSONString(settings *schemaValidationSettings, value string) error { + if !schema.Type.Permits(TypeString) { + return schema.expectedType(settings, value) + } + + var me MultiError + + // "minLength" and "maxLength" + minLength := schema.MinLength + maxLength := schema.MaxLength + if minLength != 0 || maxLength != nil { + // JSON schema string lengths are UTF-16, not UTF-8! + length := int64(0) + for _, r := range value { + if utf16.IsSurrogate(r) { + length += 2 + } else { + length++ + } + } + if minLength != 0 && length < int64(minLength) { + if settings.failfast { + return errSchema + } + err := &SchemaError{ + Value: value, + Schema: schema, + SchemaField: "minLength", + Reason: fmt.Sprintf("minimum string length is %d", minLength), + customizeMessageError: settings.customizeMessageError, + } + if !settings.multiError { + return err + } + me = append(me, err) + } + if maxLength != nil && length > int64(*maxLength) { + if settings.failfast { + return errSchema + } + err := &SchemaError{ + Value: value, + Schema: schema, + SchemaField: "maxLength", + Reason: fmt.Sprintf("maximum string length is %d", *maxLength), + customizeMessageError: settings.customizeMessageError, + } + if !settings.multiError { + return err + } + me = append(me, err) + } + } + + // "pattern" + if !settings.patternValidationDisabled && schema.Pattern != "" { + cpiface, _ := compiledPatterns.Load(schema.Pattern) + cp, _ := cpiface.(*regexp.Regexp) + if cp == nil { + var err error + if cp, err = schema.compilePattern(); err != nil { + if !settings.multiError { + return err + } + me = append(me, err) + } + } + if !cp.MatchString(value) { + err := &SchemaError{ + Value: value, + Schema: schema, + SchemaField: "pattern", + Reason: fmt.Sprintf(`string doesn't match the regular expression "%s"`, schema.Pattern), + customizeMessageError: settings.customizeMessageError, + } + if !settings.multiError { + return err + } + me = append(me, err) + } + } + + // "format" + var formatStrErr string + var formatErr error + if format := schema.Format; format != "" { + if f, ok := SchemaStringFormats[format]; ok { + if err := f.Validate(value); err != nil { + var reason string + schemaErr := &SchemaError{} + if errors.As(err, &schemaErr) { + reason = schemaErr.Reason + } else { + reason = err.Error() + } + formatStrErr = fmt.Sprintf(`string doesn't match the format %q (%v)`, format, reason) + formatErr = fmt.Errorf("string doesn't match the format %q: %w", format, err) + } + } + } + if formatStrErr != "" || formatErr != nil { + err := &SchemaError{ + Value: value, + Schema: schema, + SchemaField: "format", + Reason: formatStrErr, + Origin: formatErr, + customizeMessageError: settings.customizeMessageError, + } + if !settings.multiError { + return err + } + me = append(me, err) + + } + + if len(me) > 0 { + return me + } + + return nil +} + +func (schema *Schema) VisitJSONArray(value []any) error { + settings := newSchemaValidationSettings() + return schema.visitJSONArray(settings, value) +} + +func (schema *Schema) visitJSONArray(settings *schemaValidationSettings, value []any) error { + if !schema.Type.Permits(TypeArray) { + return schema.expectedType(settings, value) + } + + var me MultiError + + lenValue := int64(len(value)) + + // "minItems" + if v := schema.MinItems; v != 0 && lenValue < int64(v) { + if settings.failfast { + return errSchema + } + err := &SchemaError{ + Value: value, + Schema: schema, + SchemaField: "minItems", + Reason: fmt.Sprintf("minimum number of items is %d", v), + customizeMessageError: settings.customizeMessageError, + } + if !settings.multiError { + return err + } + me = append(me, err) + } + + // "maxItems" + if v := schema.MaxItems; v != nil && lenValue > int64(*v) { + if settings.failfast { + return errSchema + } + err := &SchemaError{ + Value: value, + Schema: schema, + SchemaField: "maxItems", + Reason: fmt.Sprintf("maximum number of items is %d", *v), + customizeMessageError: settings.customizeMessageError, + } + if !settings.multiError { + return err + } + me = append(me, err) + } + + // "uniqueItems" + if sliceUniqueItemsChecker == nil { + sliceUniqueItemsChecker = isSliceOfUniqueItems + } + if v := schema.UniqueItems; v && !sliceUniqueItemsChecker(value) { + if settings.failfast { + return errSchema + } + err := &SchemaError{ + Value: value, + Schema: schema, + SchemaField: "uniqueItems", + Reason: "duplicate items found", + customizeMessageError: settings.customizeMessageError, + } + if !settings.multiError { + return err + } + me = append(me, err) + } + + // "items" + if itemSchemaRef := schema.Items; itemSchemaRef != nil { + itemSchema := itemSchemaRef.Value + if itemSchema == nil { + return foundUnresolvedRef(itemSchemaRef.Ref) + } + for i, item := range value { + if err := itemSchema.visitJSON(settings, item); err != nil { + err = markSchemaErrorIndex(err, i) + if !settings.multiError { + return err + } + if itemMe, ok := err.(MultiError); ok { + me = append(me, itemMe...) + } else { + me = append(me, err) + } + } + } + } + + if len(me) > 0 { + return me + } + + return nil +} + +func (schema *Schema) VisitJSONObject(value map[string]any) error { + settings := newSchemaValidationSettings() + return schema.visitJSONObject(settings, value) +} + +func (schema *Schema) visitJSONObject(settings *schemaValidationSettings, value map[string]any) error { + if !schema.Type.Permits(TypeObject) { + return schema.expectedType(settings, value) + } + + var me MultiError + + if settings.asreq || settings.asrep { + properties := make([]string, 0, len(schema.Properties)) + for propName := range schema.Properties { + properties = append(properties, propName) + } + sort.Strings(properties) + for _, propName := range properties { + propSchema := schema.Properties[propName] + reqRO := settings.asreq && propSchema.Value.ReadOnly && !settings.readOnlyValidationDisabled + repWO := settings.asrep && propSchema.Value.WriteOnly && !settings.writeOnlyValidationDisabled + + if f := settings.defaultsSet; f != nil && value[propName] == nil { + if dflt := propSchema.Value.Default; dflt != nil && !reqRO && !repWO { + value[propName] = dflt + settings.onceSettingDefaults.Do(f) + } + } + + if value[propName] != nil { + if reqRO { + me = append(me, fmt.Errorf("readOnly property %q in request", propName)) + } else if repWO { + me = append(me, fmt.Errorf("writeOnly property %q in response", propName)) + } + } + } + } + + // "properties" + properties := schema.Properties + lenValue := int64(len(value)) + + // "minProperties" + if v := schema.MinProps; v != 0 && lenValue < int64(v) { + if settings.failfast { + return errSchema + } + err := &SchemaError{ + Value: value, + Schema: schema, + SchemaField: "minProperties", + Reason: fmt.Sprintf("there must be at least %d properties", v), + customizeMessageError: settings.customizeMessageError, + } + if !settings.multiError { + return err + } + me = append(me, err) + } + + // "maxProperties" + if v := schema.MaxProps; v != nil && lenValue > int64(*v) { + if settings.failfast { + return errSchema + } + err := &SchemaError{ + Value: value, + Schema: schema, + SchemaField: "maxProperties", + Reason: fmt.Sprintf("there must be at most %d properties", *v), + customizeMessageError: settings.customizeMessageError, + } + if !settings.multiError { + return err + } + me = append(me, err) + } + + // "additionalProperties" + var additionalProperties *Schema + if ref := schema.AdditionalProperties.Schema; ref != nil { + additionalProperties = ref.Value + } + keys := make([]string, 0, len(value)) + for k := range value { + keys = append(keys, k) + } + sort.Strings(keys) + for _, k := range keys { + v := value[k] + if properties != nil { + propertyRef := properties[k] + if propertyRef != nil { + p := propertyRef.Value + if p == nil { + return foundUnresolvedRef(propertyRef.Ref) + } + if err := p.visitJSON(settings, v); err != nil { + if settings.failfast { + return errSchema + } + err = markSchemaErrorKey(err, k) + if !settings.multiError { + return err + } + if v, ok := err.(MultiError); ok { + me = append(me, v...) + continue + } + me = append(me, err) + } + continue + } + } + if allowed := schema.AdditionalProperties.Has; allowed == nil || *allowed { + if additionalProperties != nil { + if err := additionalProperties.visitJSON(settings, v); err != nil { + if settings.failfast { + return errSchema + } + err = markSchemaErrorKey(err, k) + if !settings.multiError { + return err + } + if v, ok := err.(MultiError); ok { + me = append(me, v...) + continue + } + me = append(me, err) + } + } + continue + } + if settings.failfast { + return errSchema + } + err := &SchemaError{ + Value: value, + Schema: schema, + SchemaField: "properties", + Reason: fmt.Sprintf("property %q is unsupported", k), + customizeMessageError: settings.customizeMessageError, + } + if !settings.multiError { + return err + } + me = append(me, err) + } + + // "required" + for _, k := range schema.Required { + if _, ok := value[k]; !ok { + if s := schema.Properties[k]; s != nil && s.Value.ReadOnly && settings.asreq { + continue + } + if s := schema.Properties[k]; s != nil && s.Value.WriteOnly && settings.asrep { + continue + } + if settings.failfast { + return errSchema + } + err := markSchemaErrorKey(&SchemaError{ + Value: value, + Schema: schema, + SchemaField: "required", + Reason: fmt.Sprintf("property %q is missing", k), + customizeMessageError: settings.customizeMessageError, + }, k) + if !settings.multiError { + return err + } + me = append(me, err) + } + } + + if len(me) > 0 { + return me + } + + return nil +} + +func (schema *Schema) expectedType(settings *schemaValidationSettings, value any) error { + if settings.failfast { + return errSchema + } + + a := "a" + var x string + schemaTypes := (*schema.Type) + if len(schemaTypes) == 1 { + x = schemaTypes[0] + switch x { + case TypeArray, TypeObject, TypeInteger: + a = "an" + } + } else { + a = "one of" + x = strings.Join(schemaTypes, ", ") + } + return &SchemaError{ + Value: value, + Schema: schema, + SchemaField: "type", + Reason: fmt.Sprintf("value must be %s %s", a, x), + customizeMessageError: settings.customizeMessageError, + } +} + +// SchemaError is an error that occurs during schema validation. +type SchemaError struct { + // Value is the value that failed validation. + Value any + // reversePath is the path to the value that failed validation. + reversePath []string + // Schema is the schema that failed validation. + Schema *Schema + // SchemaField is the field of the schema that failed validation. + SchemaField string + // Reason is a human-readable message describing the error. + // The message should never include the original value to prevent leakage of potentially sensitive inputs in error messages. + Reason string + // Origin is the original error that caused this error. + Origin error + // customizeMessageError is a function that can be used to customize the error message. + customizeMessageError func(err *SchemaError) string +} + +var _ interface{ Unwrap() error } = SchemaError{} + +func markSchemaErrorKey(err error, key string) error { + + if v, ok := err.(*SchemaError); ok { + v.reversePath = append(v.reversePath, key) + if v.Origin != nil { + if unwrapped := errors.Unwrap(v.Origin); unwrapped != nil { + if me, ok := unwrapped.(multiErrorForOneOf); ok { + _ = markSchemaErrorKey(MultiError(me), key) + } + } + } + return v + } + if v, ok := err.(MultiError); ok { + for _, e := range v { + _ = markSchemaErrorKey(e, key) + } + return v + } + return err +} + +func markSchemaErrorIndex(err error, index int) error { + return markSchemaErrorKey(err, strconv.FormatInt(int64(index), 10)) +} + +func (err *SchemaError) JSONPointer() []string { + reversePath := err.reversePath + path := append([]string(nil), reversePath...) + for left, right := 0, len(path)-1; left < right; left, right = left+1, right-1 { + path[left], path[right] = path[right], path[left] + } + return path +} + +func (err *SchemaError) Error() string { + if err.customizeMessageError != nil { + if msg := err.customizeMessageError(err); msg != "" { + return msg + } + } + + buf := bytes.NewBuffer(make([]byte, 0, 256)) + + if len(err.reversePath) > 0 { + buf.WriteString(`Error at "`) + reversePath := err.reversePath + for i := len(reversePath) - 1; i >= 0; i-- { + buf.WriteByte('/') + buf.WriteString(reversePath[i]) + } + buf.WriteString(`": `) + } + + if err.Origin != nil { + buf.WriteString(err.Origin.Error()) + + return buf.String() + } + + reason := err.Reason + if reason == "" { + buf.WriteString(`Doesn't match schema "`) + buf.WriteString(err.SchemaField) + buf.WriteString(`"`) + } else { + buf.WriteString(reason) + } + + if !SchemaErrorDetailsDisabled { + buf.WriteString("\nSchema:\n ") + encoder := json.NewEncoder(buf) + encoder.SetIndent(" ", " ") + if err := encoder.Encode(err.Schema); err != nil { + panic(err) + } + buf.WriteString("\nValue:\n ") + if err := encoder.Encode(err.Value); err != nil { + panic(err) + } + } + + return buf.String() +} + +func (err SchemaError) Unwrap() error { + return err.Origin +} + +func isSliceOfUniqueItems(xs []any) bool { + s := len(xs) + m := make(map[string]struct{}, s) + for _, x := range xs { + // The input slice is converted from a JSON string, there shall + // have no error when convert it back. + key, _ := json.Marshal(&x) + m[string(key)] = struct{}{} + } + return s == len(m) +} + +// SliceUniqueItemsChecker is an function used to check if an given slice +// have unique items. +type SliceUniqueItemsChecker func(items []any) bool + +// By default using predefined func isSliceOfUniqueItems which make use of +// json.Marshal to generate a key for map used to check if a given slice +// have unique items. +var sliceUniqueItemsChecker SliceUniqueItemsChecker = isSliceOfUniqueItems + +// RegisterArrayUniqueItemsChecker is used to register a customized function +// used to check if JSON array have unique items. +func RegisterArrayUniqueItemsChecker(fn SliceUniqueItemsChecker) { + sliceUniqueItemsChecker = fn +} + +func unsupportedFormat(format string) error { + 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 new file mode 100644 index 00000000..023c2669 --- /dev/null +++ b/vendor/github.com/getkin/kin-openapi/openapi3/schema_formats.go @@ -0,0 +1,169 @@ +package openapi3 + +import ( + "fmt" + "math" + "net/netip" + "regexp" +) + +type ( + // FormatValidator is an interface for custom format validators. + FormatValidator[T any] interface { + Validate(value T) error + } + // StringFormatValidator is a type alias for FormatValidator[string] + StringFormatValidator = FormatValidator[string] + // NumberFormatValidator is a type alias for FormatValidator[float64] + NumberFormatValidator = FormatValidator[float64] + // IntegerFormatValidator is a type alias for FormatValidator[int64] + IntegerFormatValidator = FormatValidator[int64] +) + +var ( + // SchemaStringFormats is a map of custom string format validators. + SchemaStringFormats = make(map[string]StringFormatValidator) + // SchemaNumberFormats is a map of custom number format validators. + SchemaNumberFormats = make(map[string]NumberFormatValidator) + // SchemaIntegerFormats is a map of custom integer format validators. + SchemaIntegerFormats = make(map[string]IntegerFormatValidator) +) + +const ( + // FormatOfStringForUUIDOfRFC4122 is an optional predefined format for UUID v1-v5 as specified by RFC4122 + FormatOfStringForUUIDOfRFC4122 = `^(?:[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-5][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000)$` + + // FormatOfStringForEmail pattern catches only some suspiciously wrong-looking email addresses. + // Use DefineStringFormat(...) if you need something stricter. + FormatOfStringForEmail = `^[^@]+@[^@<>",\s]+$` + + // FormatOfStringByte is a regexp for base64-encoded characters, for example, "U3dhZ2dlciByb2Nrcw==" + FormatOfStringByte = `(^$|^[a-zA-Z0-9+/\-_]*=*$)` + + // FormatOfStringDate is a RFC3339 date format regexp, for example "2017-07-21". + FormatOfStringDate = `^[0-9]{4}-(0[0-9]|10|11|12)-([0-2][0-9]|30|31)$` + + // FormatOfStringDateTime is a RFC3339 date-time format regexp, for example "2017-07-21T17:32:28Z". + FormatOfStringDateTime = `^[0-9]{4}-(0[0-9]|10|11|12)-([0-2][0-9]|30|31)T[0-9]{2}:[0-9]{2}:[0-9]{2}(\.[0-9]+)?(Z|(\+|-)[0-9]{2}:[0-9]{2})?$` +) + +func init() { + DefineStringFormatValidator("byte", NewRegexpFormatValidator(FormatOfStringByte)) + DefineStringFormatValidator("date", NewRegexpFormatValidator(FormatOfStringDate)) + DefineStringFormatValidator("date-time", NewRegexpFormatValidator(FormatOfStringDateTime)) + DefineIntegerFormatValidator("int32", NewRangeFormatValidator(int64(math.MinInt32), int64(math.MaxInt32))) + DefineIntegerFormatValidator("int64", NewRangeFormatValidator(int64(math.MinInt64), int64(math.MaxInt64))) +} + +// DefineIPv4Format opts in ipv4 format validation on top of OAS 3 spec +func DefineIPv4Format() { + DefineStringFormatValidator("ipv4", NewIPValidator(true)) +} + +// DefineIPv6Format opts in ipv6 format validation on top of OAS 3 spec +func DefineIPv6Format() { + DefineStringFormatValidator("ipv6", NewIPValidator(false)) +} + +type stringRegexpFormatValidator struct { + re *regexp.Regexp +} + +func (s stringRegexpFormatValidator) Validate(value string) error { + if !s.re.MatchString(value) { + return fmt.Errorf(`string doesn't match pattern "%s"`, s.re.String()) + } + return nil +} + +type callbackValidator[T any] struct { + fn func(T) error +} + +func (c callbackValidator[T]) Validate(value T) error { + return c.fn(value) +} + +type rangeFormat[T int64 | float64] struct { + min, max T +} + +func (r rangeFormat[T]) Validate(value T) error { + if value < r.min || value > r.max { + return fmt.Errorf("value should be between %v and %v", r.min, r.max) + } + return nil +} + +// NewRangeFormatValidator creates a new FormatValidator that validates the value is within a given range. +func NewRangeFormatValidator[T int64 | float64](min, max T) FormatValidator[T] { + return rangeFormat[T]{min: min, max: max} +} + +// NewRegexpFormatValidator creates a new FormatValidator that uses a regular expression to validate the value. +func NewRegexpFormatValidator(pattern string) StringFormatValidator { + re, err := regexp.Compile(pattern) + if err != nil { + err := fmt.Errorf("string regexp format has invalid pattern %q: %w", pattern, err) + panic(err) + } + return stringRegexpFormatValidator{re: re} +} + +// NewCallbackValidator creates a new FormatValidator that uses a callback function to validate the value. +func NewCallbackValidator[T any](fn func(T) error) FormatValidator[T] { + return callbackValidator[T]{fn: fn} +} + +// DefineStringFormatValidator defines a custom format validator for a given string format. +func DefineStringFormatValidator(name string, validator StringFormatValidator) { + SchemaStringFormats[name] = validator +} + +// DefineNumberFormatValidator defines a custom format validator for a given number format. +func DefineNumberFormatValidator(name string, validator NumberFormatValidator) { + SchemaNumberFormats[name] = validator +} + +// DefineIntegerFormatValidator defines a custom format validator for a given integer format. +func DefineIntegerFormatValidator(name string, validator IntegerFormatValidator) { + SchemaIntegerFormats[name] = validator +} + +// DefineStringFormat defines a regexp pattern for a given string format +// Deprecated: Use openapi3.DefineStringFormatValidator(name, NewRegexpFormatValidator(pattern)) instead. +func DefineStringFormat(name string, pattern string) { + DefineStringFormatValidator(name, NewRegexpFormatValidator(pattern)) +} + +// DefineStringFormatCallback defines a callback function for a given string format +// Deprecated: Use openapi3.DefineStringFormatValidator(name, NewCallbackValidator(fn)) instead. +func DefineStringFormatCallback(name string, callback func(string) error) { + DefineStringFormatValidator(name, NewCallbackValidator(callback)) +} + +// NewIPValidator creates a new FormatValidator that validates the value is an IP address. +func NewIPValidator(isIPv4 bool) FormatValidator[string] { + return callbackValidator[string]{fn: func(ip string) error { + addr, err := netip.ParseAddr(ip) + if err != nil { + return &SchemaError{ + Value: ip, + Reason: "Not an IP address", + } + } + if isIPv4 && !addr.Is4() { + return &SchemaError{ + Value: ip, + Reason: "Not an IPv4 address (it's IPv6)", + } + } + if !isIPv4 && !addr.Is6() { + return &SchemaError{ + Value: ip, + Reason: "Not an IPv6 address (it's IPv4)", + } + } + return nil + }} +} diff --git a/vendor/github.com/getkin/kin-openapi/openapi3/schema_pattern.go b/vendor/github.com/getkin/kin-openapi/openapi3/schema_pattern.go new file mode 100644 index 00000000..4794b6a0 --- /dev/null +++ b/vendor/github.com/getkin/kin-openapi/openapi3/schema_pattern.go @@ -0,0 +1,29 @@ +package openapi3 + +import ( + "fmt" + "regexp" +) + +var patRewriteCodepoints = regexp.MustCompile(`(?P\\u)(?P[0-9A-F]{4})`) + +// See https://pkg.go.dev/regexp/syntax +func intoGoRegexp(re string) string { + return patRewriteCodepoints.ReplaceAllString(re, `\x{${code}}`) +} + +// NOTE: racey WRT [writes to schema.Pattern] vs [reads schema.Pattern then writes to compiledPatterns] +func (schema *Schema) compilePattern() (cp *regexp.Regexp, err error) { + pattern := schema.Pattern + if cp, err = regexp.Compile(intoGoRegexp(pattern)); err != nil { + err = &SchemaError{ + Schema: schema, + SchemaField: "pattern", + Origin: err, + Reason: fmt.Sprintf("cannot compile pattern %q: %v", pattern, err), + } + return + } + var _ bool = compiledPatterns.CompareAndSwap(pattern, nil, cp) + return +} diff --git a/vendor/github.com/getkin/kin-openapi/openapi3/schema_validation_settings.go b/vendor/github.com/getkin/kin-openapi/openapi3/schema_validation_settings.go new file mode 100644 index 00000000..17aad2fa --- /dev/null +++ b/vendor/github.com/getkin/kin-openapi/openapi3/schema_validation_settings.go @@ -0,0 +1,79 @@ +package openapi3 + +import ( + "sync" +) + +// SchemaValidationOption describes options a user has when validating request / response bodies. +type SchemaValidationOption func(*schemaValidationSettings) + +type schemaValidationSettings struct { + failfast bool + multiError bool + asreq, asrep bool // exclusive (XOR) fields + formatValidationEnabled bool + patternValidationDisabled bool + readOnlyValidationDisabled bool + writeOnlyValidationDisabled bool + + onceSettingDefaults sync.Once + defaultsSet func() + + customizeMessageError func(err *SchemaError) string +} + +// FailFast returns schema validation errors quicker. +func FailFast() SchemaValidationOption { + return func(s *schemaValidationSettings) { s.failfast = true } +} + +func MultiErrors() SchemaValidationOption { + return func(s *schemaValidationSettings) { s.multiError = true } +} + +func VisitAsRequest() SchemaValidationOption { + return func(s *schemaValidationSettings) { s.asreq, s.asrep = true, false } +} + +func VisitAsResponse() SchemaValidationOption { + return func(s *schemaValidationSettings) { s.asreq, s.asrep = false, true } +} + +// EnableFormatValidation setting makes Validate not return an error when validating documents that mention schema formats that are not defined by the OpenAPIv3 specification. +func EnableFormatValidation() SchemaValidationOption { + return func(s *schemaValidationSettings) { s.formatValidationEnabled = true } +} + +// DisablePatternValidation setting makes Validate not return an error when validating patterns that are not supported by the Go regexp engine. +func DisablePatternValidation() SchemaValidationOption { + return func(s *schemaValidationSettings) { s.patternValidationDisabled = true } +} + +// DisableReadOnlyValidation setting makes Validate not return an error when validating properties marked as read-only +func DisableReadOnlyValidation() SchemaValidationOption { + return func(s *schemaValidationSettings) { s.readOnlyValidationDisabled = true } +} + +// DisableWriteOnlyValidation setting makes Validate not return an error when validating properties marked as write-only +func DisableWriteOnlyValidation() SchemaValidationOption { + return func(s *schemaValidationSettings) { s.writeOnlyValidationDisabled = true } +} + +// DefaultsSet executes the given callback (once) IFF schema validation set default values. +func DefaultsSet(f func()) SchemaValidationOption { + return func(s *schemaValidationSettings) { s.defaultsSet = f } +} + +// SetSchemaErrorMessageCustomizer allows to override the schema error message. +// If the passed function returns an empty string, it returns to the previous Error() implementation. +func SetSchemaErrorMessageCustomizer(f func(err *SchemaError) string) SchemaValidationOption { + return func(s *schemaValidationSettings) { s.customizeMessageError = f } +} + +func newSchemaValidationSettings(opts ...SchemaValidationOption) *schemaValidationSettings { + settings := &schemaValidationSettings{} + for _, opt := range opts { + opt(settings) + } + return settings +} diff --git a/vendor/github.com/getkin/kin-openapi/openapi3/security_requirements.go b/vendor/github.com/getkin/kin-openapi/openapi3/security_requirements.go new file mode 100644 index 00000000..87891c95 --- /dev/null +++ b/vendor/github.com/getkin/kin-openapi/openapi3/security_requirements.go @@ -0,0 +1,51 @@ +package openapi3 + +import ( + "context" +) + +type SecurityRequirements []SecurityRequirement + +func NewSecurityRequirements() *SecurityRequirements { + return &SecurityRequirements{} +} + +func (srs *SecurityRequirements) With(securityRequirement SecurityRequirement) *SecurityRequirements { + *srs = append(*srs, securityRequirement) + return srs +} + +// Validate returns an error if SecurityRequirements does not comply with the OpenAPI spec. +func (srs SecurityRequirements) Validate(ctx context.Context, opts ...ValidationOption) error { + ctx = WithValidationOptions(ctx, opts...) + + for _, security := range srs { + if err := security.Validate(ctx); err != nil { + return err + } + } + return nil +} + +// SecurityRequirement is specified by OpenAPI/Swagger standard version 3. +// See https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#security-requirement-object +type SecurityRequirement map[string][]string + +func NewSecurityRequirement() SecurityRequirement { + return make(SecurityRequirement) +} + +func (security SecurityRequirement) Authenticate(provider string, scopes ...string) SecurityRequirement { + if len(scopes) == 0 { + scopes = []string{} // Forces the variable to be encoded as an array instead of null + } + security[provider] = scopes + return security +} + +// Validate returns an error if SecurityRequirement does not comply with the OpenAPI spec. +func (security *SecurityRequirement) Validate(ctx context.Context, opts ...ValidationOption) error { + ctx = WithValidationOptions(ctx, opts...) + + 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 new file mode 100644 index 00000000..b5c94b61 --- /dev/null +++ b/vendor/github.com/getkin/kin-openapi/openapi3/security_scheme.go @@ -0,0 +1,429 @@ +package openapi3 + +import ( + "context" + "encoding/json" + "errors" + "fmt" + "net/url" +) + +// SecurityScheme is specified by OpenAPI/Swagger standard version 3. +// See https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#security-scheme-object +type SecurityScheme struct { + Extensions map[string]any `json:"-" yaml:"-"` + + Type string `json:"type,omitempty" yaml:"type,omitempty"` + Description string `json:"description,omitempty" yaml:"description,omitempty"` + Name string `json:"name,omitempty" yaml:"name,omitempty"` + In string `json:"in,omitempty" yaml:"in,omitempty"` + Scheme string `json:"scheme,omitempty" yaml:"scheme,omitempty"` + BearerFormat string `json:"bearerFormat,omitempty" yaml:"bearerFormat,omitempty"` + Flows *OAuthFlows `json:"flows,omitempty" yaml:"flows,omitempty"` + OpenIdConnectUrl string `json:"openIdConnectUrl,omitempty" yaml:"openIdConnectUrl,omitempty"` +} + +func NewSecurityScheme() *SecurityScheme { + return &SecurityScheme{} +} + +func NewCSRFSecurityScheme() *SecurityScheme { + return &SecurityScheme{ + Type: "apiKey", + In: "header", + Name: "X-XSRF-TOKEN", + } +} + +func NewOIDCSecurityScheme(oidcUrl string) *SecurityScheme { + return &SecurityScheme{ + Type: "openIdConnect", + OpenIdConnectUrl: oidcUrl, + } +} + +func NewJWTSecurityScheme() *SecurityScheme { + return &SecurityScheme{ + Type: "http", + Scheme: "bearer", + BearerFormat: "JWT", + } +} + +// MarshalJSON returns the JSON encoding of SecurityScheme. +func (ss SecurityScheme) MarshalJSON() ([]byte, error) { + x, err := ss.MarshalYAML() + if err != nil { + return nil, err + } + return json.Marshal(x) +} + +// MarshalYAML returns the YAML encoding of SecurityScheme. +func (ss SecurityScheme) MarshalYAML() (any, error) { + m := make(map[string]any, 8+len(ss.Extensions)) + for k, v := range ss.Extensions { + m[k] = v + } + if x := ss.Type; x != "" { + m["type"] = x + } + if x := ss.Description; x != "" { + m["description"] = x + } + if x := ss.Name; x != "" { + m["name"] = x + } + if x := ss.In; x != "" { + m["in"] = x + } + if x := ss.Scheme; x != "" { + m["scheme"] = x + } + if x := ss.BearerFormat; x != "" { + m["bearerFormat"] = x + } + if x := ss.Flows; x != nil { + m["flows"] = x + } + if x := ss.OpenIdConnectUrl; x != "" { + m["openIdConnectUrl"] = x + } + return m, nil +} + +// UnmarshalJSON sets SecurityScheme to a copy of data. +func (ss *SecurityScheme) UnmarshalJSON(data []byte) error { + type SecuritySchemeBis SecurityScheme + var x SecuritySchemeBis + if err := json.Unmarshal(data, &x); err != nil { + return unmarshalError(err) + } + _ = json.Unmarshal(data, &x.Extensions) + delete(x.Extensions, "type") + delete(x.Extensions, "description") + delete(x.Extensions, "name") + delete(x.Extensions, "in") + delete(x.Extensions, "scheme") + delete(x.Extensions, "bearerFormat") + delete(x.Extensions, "flows") + delete(x.Extensions, "openIdConnectUrl") + if len(x.Extensions) == 0 { + x.Extensions = nil + } + *ss = SecurityScheme(x) + return nil +} + +func (ss *SecurityScheme) WithType(value string) *SecurityScheme { + ss.Type = value + return ss +} + +func (ss *SecurityScheme) WithDescription(value string) *SecurityScheme { + ss.Description = value + return ss +} + +func (ss *SecurityScheme) WithName(value string) *SecurityScheme { + ss.Name = value + return ss +} + +func (ss *SecurityScheme) WithIn(value string) *SecurityScheme { + ss.In = value + return ss +} + +func (ss *SecurityScheme) WithScheme(value string) *SecurityScheme { + ss.Scheme = value + return ss +} + +func (ss *SecurityScheme) WithBearerFormat(value string) *SecurityScheme { + ss.BearerFormat = value + return ss +} + +// Validate returns an error if SecurityScheme does not comply with the OpenAPI spec. +func (ss *SecurityScheme) Validate(ctx context.Context, opts ...ValidationOption) error { + ctx = WithValidationOptions(ctx, opts...) + + hasIn := false + hasBearerFormat := false + hasFlow := false + switch ss.Type { + case "apiKey": + hasIn = true + case "http": + scheme := ss.Scheme + switch scheme { + case "bearer": + hasBearerFormat = true + case "basic", "negotiate", "digest": + default: + 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) + } + default: + return fmt.Errorf("security scheme 'type' can't be %q", ss.Type) + } + + // Validate "in" and "name" + if hasIn { + switch ss.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 %q", ss.In) + } + if ss.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 %q can't have 'in'", ss.Type) + } else if len(ss.Name) > 0 { + return fmt.Errorf("security scheme of type %q can't have 'name'", ss.Type) + } + + // 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 %q can't have 'bearerFormat'", ss.Type) + } + + // Validate "flow" + if hasFlow { + flow := ss.Flows + if flow == nil { + return fmt.Errorf("security scheme of type %q should have 'flows'", ss.Type) + } + if err := flow.Validate(ctx); err != nil { + return fmt.Errorf("security scheme 'flow' is invalid: %w", err) + } + } else if ss.Flows != nil { + return fmt.Errorf("security scheme of type %q can't have 'flows'", ss.Type) + } + + return validateExtensions(ctx, ss.Extensions) +} + +// OAuthFlows is specified by OpenAPI/Swagger standard version 3. +// See https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#oauth-flows-object +type OAuthFlows struct { + Extensions map[string]any `json:"-" yaml:"-"` + + Implicit *OAuthFlow `json:"implicit,omitempty" yaml:"implicit,omitempty"` + Password *OAuthFlow `json:"password,omitempty" yaml:"password,omitempty"` + ClientCredentials *OAuthFlow `json:"clientCredentials,omitempty" yaml:"clientCredentials,omitempty"` + AuthorizationCode *OAuthFlow `json:"authorizationCode,omitempty" yaml:"authorizationCode,omitempty"` +} + +type oAuthFlowType int + +const ( + oAuthFlowTypeImplicit oAuthFlowType = iota + oAuthFlowTypePassword + oAuthFlowTypeClientCredentials + oAuthFlowAuthorizationCode +) + +// MarshalJSON returns the JSON encoding of OAuthFlows. +func (flows OAuthFlows) MarshalJSON() ([]byte, error) { + x, err := flows.MarshalYAML() + if err != nil { + return nil, err + } + return json.Marshal(x) +} + +// MarshalYAML returns the YAML encoding of OAuthFlows. +func (flows OAuthFlows) MarshalYAML() (any, error) { + m := make(map[string]any, 4+len(flows.Extensions)) + for k, v := range flows.Extensions { + m[k] = v + } + if x := flows.Implicit; x != nil { + m["implicit"] = x + } + if x := flows.Password; x != nil { + m["password"] = x + } + if x := flows.ClientCredentials; x != nil { + m["clientCredentials"] = x + } + if x := flows.AuthorizationCode; x != nil { + m["authorizationCode"] = x + } + return m, nil +} + +// UnmarshalJSON sets OAuthFlows to a copy of data. +func (flows *OAuthFlows) UnmarshalJSON(data []byte) error { + type OAuthFlowsBis OAuthFlows + var x OAuthFlowsBis + if err := json.Unmarshal(data, &x); err != nil { + return unmarshalError(err) + } + _ = json.Unmarshal(data, &x.Extensions) + delete(x.Extensions, "implicit") + delete(x.Extensions, "password") + delete(x.Extensions, "clientCredentials") + delete(x.Extensions, "authorizationCode") + if len(x.Extensions) == 0 { + x.Extensions = nil + } + *flows = OAuthFlows(x) + return nil +} + +// Validate returns an error if OAuthFlows does not comply with the OpenAPI spec. +func (flows *OAuthFlows) Validate(ctx context.Context, opts ...ValidationOption) error { + ctx = WithValidationOptions(ctx, opts...) + + if v := flows.Implicit; v != nil { + if err := v.validate(ctx, oAuthFlowTypeImplicit, opts...); err != nil { + return fmt.Errorf("the OAuth flow 'implicit' is invalid: %w", err) + } + } + + if v := flows.Password; v != nil { + if err := v.validate(ctx, oAuthFlowTypePassword, opts...); err != nil { + return fmt.Errorf("the OAuth flow 'password' is invalid: %w", err) + } + } + + if v := flows.ClientCredentials; v != nil { + if err := v.validate(ctx, oAuthFlowTypeClientCredentials, opts...); err != nil { + return fmt.Errorf("the OAuth flow 'clientCredentials' is invalid: %w", err) + } + } + + if v := flows.AuthorizationCode; v != nil { + if err := v.validate(ctx, oAuthFlowAuthorizationCode, opts...); err != nil { + return fmt.Errorf("the OAuth flow 'authorizationCode' is invalid: %w", err) + } + } + + return validateExtensions(ctx, flows.Extensions) +} + +// OAuthFlow is specified by OpenAPI/Swagger standard version 3. +// See https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#oauth-flow-object +type OAuthFlow struct { + Extensions map[string]any `json:"-" yaml:"-"` + + AuthorizationURL string `json:"authorizationUrl,omitempty" yaml:"authorizationUrl,omitempty"` + TokenURL string `json:"tokenUrl,omitempty" yaml:"tokenUrl,omitempty"` + RefreshURL string `json:"refreshUrl,omitempty" yaml:"refreshUrl,omitempty"` + Scopes map[string]string `json:"scopes" yaml:"scopes"` // required +} + +// MarshalJSON returns the JSON encoding of OAuthFlow. +func (flow OAuthFlow) MarshalJSON() ([]byte, error) { + x, err := flow.MarshalYAML() + if err != nil { + return nil, err + } + return json.Marshal(x) +} + +// MarshalYAML returns the YAML encoding of OAuthFlow. +func (flow OAuthFlow) MarshalYAML() (any, error) { + m := make(map[string]any, 4+len(flow.Extensions)) + for k, v := range flow.Extensions { + m[k] = v + } + if x := flow.AuthorizationURL; x != "" { + m["authorizationUrl"] = x + } + if x := flow.TokenURL; x != "" { + m["tokenUrl"] = x + } + if x := flow.RefreshURL; x != "" { + m["refreshUrl"] = x + } + m["scopes"] = flow.Scopes + return m, nil +} + +// UnmarshalJSON sets OAuthFlow to a copy of data. +func (flow *OAuthFlow) UnmarshalJSON(data []byte) error { + type OAuthFlowBis OAuthFlow + var x OAuthFlowBis + if err := json.Unmarshal(data, &x); err != nil { + return unmarshalError(err) + } + _ = json.Unmarshal(data, &x.Extensions) + delete(x.Extensions, "authorizationUrl") + delete(x.Extensions, "tokenUrl") + delete(x.Extensions, "refreshUrl") + delete(x.Extensions, "scopes") + if len(x.Extensions) == 0 { + x.Extensions = nil + } + *flow = OAuthFlow(x) + return nil +} + +// Validate returns an error if OAuthFlows does not comply with the OpenAPI spec. +func (flow *OAuthFlow) Validate(ctx context.Context, opts ...ValidationOption) error { + ctx = WithValidationOptions(ctx, opts...) + + if v := flow.RefreshURL; v != "" { + if _, err := url.Parse(v); err != nil { + return fmt.Errorf("field 'refreshUrl' is invalid: %w", err) + } + } + + if flow.Scopes == nil { + return errors.New("field 'scopes' is missing") + } + + return validateExtensions(ctx, flow.Extensions) +} + +func (flow *OAuthFlow) validate(ctx context.Context, typ oAuthFlowType, opts ...ValidationOption) error { + ctx = WithValidationOptions(ctx, opts...) + + typeIn := func(types ...oAuthFlowType) bool { + for _, ty := range types { + if ty == typ { + return true + } + } + return false + } + + if in := typeIn(oAuthFlowTypeImplicit, oAuthFlowAuthorizationCode); true { + switch { + case flow.AuthorizationURL == "" && in: + return errors.New("field 'authorizationUrl' is empty or missing") + case flow.AuthorizationURL != "" && !in: + return errors.New("field 'authorizationUrl' should not be set") + case flow.AuthorizationURL != "": + if _, err := url.Parse(flow.AuthorizationURL); err != nil { + return fmt.Errorf("field 'authorizationUrl' is invalid: %w", err) + } + } + } + + if in := typeIn(oAuthFlowTypePassword, oAuthFlowTypeClientCredentials, oAuthFlowAuthorizationCode); true { + switch { + case flow.TokenURL == "" && in: + return errors.New("field 'tokenUrl' is empty or missing") + case flow.TokenURL != "" && !in: + return errors.New("field 'tokenUrl' should not be set") + case flow.TokenURL != "": + if _, err := url.Parse(flow.TokenURL); err != nil { + return fmt.Errorf("field 'tokenUrl' is invalid: %w", err) + } + } + } + + return flow.Validate(ctx, opts...) +} diff --git a/vendor/github.com/getkin/kin-openapi/openapi3/serialization_method.go b/vendor/github.com/getkin/kin-openapi/openapi3/serialization_method.go new file mode 100644 index 00000000..2ec8bd2d --- /dev/null +++ b/vendor/github.com/getkin/kin-openapi/openapi3/serialization_method.go @@ -0,0 +1,17 @@ +package openapi3 + +const ( + SerializationSimple = "simple" + SerializationLabel = "label" + SerializationMatrix = "matrix" + SerializationForm = "form" + SerializationSpaceDelimited = "spaceDelimited" + SerializationPipeDelimited = "pipeDelimited" + SerializationDeepObject = "deepObject" +) + +// SerializationMethod describes a serialization method of HTTP request's parameters and body. +type SerializationMethod struct { + Style string + Explode bool +} diff --git a/vendor/github.com/getkin/kin-openapi/openapi3/server.go b/vendor/github.com/getkin/kin-openapi/openapi3/server.go new file mode 100644 index 00000000..7a2007f2 --- /dev/null +++ b/vendor/github.com/getkin/kin-openapi/openapi3/server.go @@ -0,0 +1,302 @@ +package openapi3 + +import ( + "context" + "encoding/json" + "errors" + "fmt" + "math" + "net/url" + "sort" + "strings" +) + +// Servers is specified by OpenAPI/Swagger standard version 3. +type Servers []*Server + +// Validate returns an error if Servers does not comply with the OpenAPI spec. +func (servers Servers) Validate(ctx context.Context, opts ...ValidationOption) error { + ctx = WithValidationOptions(ctx, opts...) + + for _, v := range servers { + if err := v.Validate(ctx); err != nil { + return err + } + } + return nil +} + +// BasePath returns the base path of the first server in the list, or /. +func (servers Servers) BasePath() (string, error) { + for _, server := range servers { + return server.BasePath() + } + return "/", nil +} + +func (servers Servers) MatchURL(parsedURL *url.URL) (*Server, []string, string) { + rawURL := parsedURL.String() + if i := strings.IndexByte(rawURL, '?'); i >= 0 { + rawURL = rawURL[:i] + } + for _, server := range servers { + pathParams, remaining, ok := server.MatchRawURL(rawURL) + if ok { + return server, pathParams, remaining + } + } + return nil, nil, "" +} + +// Server is specified by OpenAPI/Swagger standard version 3. +// See https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#server-object +type Server struct { + Extensions map[string]any `json:"-" yaml:"-"` + + URL string `json:"url" yaml:"url"` // Required + Description string `json:"description,omitempty" yaml:"description,omitempty"` + Variables map[string]*ServerVariable `json:"variables,omitempty" yaml:"variables,omitempty"` +} + +// BasePath returns the base path extracted from the default values of variables, if any. +// Assumes a valid struct (per Validate()). +func (server *Server) BasePath() (string, error) { + if server == nil { + return "/", nil + } + + uri := server.URL + for name, svar := range server.Variables { + uri = strings.ReplaceAll(uri, "{"+name+"}", svar.Default) + } + + u, err := url.ParseRequestURI(uri) + if err != nil { + return "", err + } + + if bp := u.Path; bp != "" { + return bp, nil + } + + return "/", nil +} + +// MarshalJSON returns the JSON encoding of Server. +func (server Server) MarshalJSON() ([]byte, error) { + x, err := server.MarshalYAML() + if err != nil { + return nil, err + } + return json.Marshal(x) +} + +// MarshalYAML returns the YAML encoding of Server. +func (server Server) MarshalYAML() (any, error) { + m := make(map[string]any, 3+len(server.Extensions)) + for k, v := range server.Extensions { + m[k] = v + } + m["url"] = server.URL + if x := server.Description; x != "" { + m["description"] = x + } + if x := server.Variables; len(x) != 0 { + m["variables"] = x + } + return m, nil +} + +// UnmarshalJSON sets Server to a copy of data. +func (server *Server) UnmarshalJSON(data []byte) error { + type ServerBis Server + var x ServerBis + if err := json.Unmarshal(data, &x); err != nil { + return unmarshalError(err) + } + _ = json.Unmarshal(data, &x.Extensions) + delete(x.Extensions, "url") + delete(x.Extensions, "description") + delete(x.Extensions, "variables") + if len(x.Extensions) == 0 { + x.Extensions = nil + } + *server = Server(x) + return nil +} + +func (server Server) ParameterNames() ([]string, error) { + pattern := server.URL + var params []string + for len(pattern) > 0 { + i := strings.IndexByte(pattern, '{') + if i < 0 { + break + } + pattern = pattern[i+1:] + i = strings.IndexByte(pattern, '}') + if i < 0 { + return nil, errors.New("missing '}'") + } + params = append(params, strings.TrimSpace(pattern[:i])) + pattern = pattern[i+1:] + } + return params, nil +} + +func (server Server) MatchRawURL(input string) ([]string, string, bool) { + pattern := server.URL + var params []string + for len(pattern) > 0 { + c := pattern[0] + if len(pattern) == 1 && c == '/' { + break + } + if c == '{' { + // Find end of pattern + i := strings.IndexByte(pattern, '}') + if i < 0 { + return nil, "", false + } + pattern = pattern[i+1:] + + // Find next matching pattern character or next '/' whichever comes first + np := -1 + if len(pattern) > 0 { + np = strings.IndexByte(input, pattern[0]) + } + ns := strings.IndexByte(input, '/') + + if np < 0 { + i = ns + } else if ns < 0 { + i = np + } else { + i = int(math.Min(float64(np), float64(ns))) + } + if i < 0 { + i = len(input) + } + params = append(params, input[:i]) + input = input[i:] + continue + } + if len(input) == 0 || input[0] != c { + return nil, "", false + } + pattern = pattern[1:] + input = input[1:] + } + if input == "" { + input = "/" + } + if input[0] != '/' { + return nil, "", false + } + return params, input, true +} + +// Validate returns an error if Server does not comply with the OpenAPI spec. +func (server *Server) Validate(ctx context.Context, opts ...ValidationOption) (err error) { + ctx = WithValidationOptions(ctx, opts...) + + if server.URL == "" { + return errors.New("value of url must be a non-empty string") + } + + opening, closing := strings.Count(server.URL, "{"), strings.Count(server.URL, "}") + if opening != closing { + return errors.New("server URL has mismatched { and }") + } + + if opening != len(server.Variables) { + return errors.New("server has undeclared variables") + } + + variables := make([]string, 0, len(server.Variables)) + for name := range server.Variables { + variables = append(variables, name) + } + sort.Strings(variables) + for _, name := range variables { + v := server.Variables[name] + if !strings.Contains(server.URL, "{"+name+"}") { + return errors.New("server has undeclared variables") + } + if err = v.Validate(ctx); err != nil { + return + } + } + + return validateExtensions(ctx, server.Extensions) +} + +// ServerVariable is specified by OpenAPI/Swagger standard version 3. +// See https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#server-variable-object +type ServerVariable struct { + Extensions map[string]any `json:"-" yaml:"-"` + + Enum []string `json:"enum,omitempty" yaml:"enum,omitempty"` + Default string `json:"default,omitempty" yaml:"default,omitempty"` + Description string `json:"description,omitempty" yaml:"description,omitempty"` +} + +// MarshalJSON returns the JSON encoding of ServerVariable. +func (serverVariable ServerVariable) MarshalJSON() ([]byte, error) { + x, err := serverVariable.MarshalYAML() + if err != nil { + return nil, err + } + return json.Marshal(x) +} + +// MarshalYAML returns the YAML encoding of ServerVariable. +func (serverVariable ServerVariable) MarshalYAML() (any, error) { + m := make(map[string]any, 4+len(serverVariable.Extensions)) + for k, v := range serverVariable.Extensions { + m[k] = v + } + if x := serverVariable.Enum; len(x) != 0 { + m["enum"] = x + } + if x := serverVariable.Default; x != "" { + m["default"] = x + } + if x := serverVariable.Description; x != "" { + m["description"] = x + } + return m, nil +} + +// UnmarshalJSON sets ServerVariable to a copy of data. +func (serverVariable *ServerVariable) UnmarshalJSON(data []byte) error { + type ServerVariableBis ServerVariable + var x ServerVariableBis + if err := json.Unmarshal(data, &x); err != nil { + return unmarshalError(err) + } + _ = json.Unmarshal(data, &x.Extensions) + delete(x.Extensions, "enum") + delete(x.Extensions, "default") + delete(x.Extensions, "description") + if len(x.Extensions) == 0 { + x.Extensions = nil + } + *serverVariable = ServerVariable(x) + return nil +} + +// Validate returns an error if ServerVariable does not comply with the OpenAPI spec. +func (serverVariable *ServerVariable) Validate(ctx context.Context, opts ...ValidationOption) error { + ctx = WithValidationOptions(ctx, opts...) + + if serverVariable.Default == "" { + data, err := serverVariable.MarshalJSON() + if err != nil { + return err + } + return fmt.Errorf("field default is required in %s", data) + } + + return validateExtensions(ctx, serverVariable.Extensions) +} diff --git a/vendor/github.com/getkin/kin-openapi/openapi3/tag.go b/vendor/github.com/getkin/kin-openapi/openapi3/tag.go new file mode 100644 index 00000000..182d0502 --- /dev/null +++ b/vendor/github.com/getkin/kin-openapi/openapi3/tag.go @@ -0,0 +1,99 @@ +package openapi3 + +import ( + "context" + "encoding/json" + "fmt" +) + +// Tags is specified by OpenAPI/Swagger 3.0 standard. +type Tags []*Tag + +func (tags Tags) Get(name string) *Tag { + for _, tag := range tags { + if tag.Name == name { + return tag + } + } + return nil +} + +// Validate returns an error if Tags does not comply with the OpenAPI spec. +func (tags Tags) Validate(ctx context.Context, opts ...ValidationOption) error { + ctx = WithValidationOptions(ctx, opts...) + + for _, v := range tags { + if err := v.Validate(ctx); err != nil { + return err + } + } + return nil +} + +// Tag is specified by OpenAPI/Swagger 3.0 standard. +// See https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#tag-object +type Tag struct { + Extensions map[string]any `json:"-" yaml:"-"` + + Name string `json:"name,omitempty" yaml:"name,omitempty"` + Description string `json:"description,omitempty" yaml:"description,omitempty"` + ExternalDocs *ExternalDocs `json:"externalDocs,omitempty" yaml:"externalDocs,omitempty"` +} + +// MarshalJSON returns the JSON encoding of Tag. +func (t Tag) MarshalJSON() ([]byte, error) { + x, err := t.MarshalYAML() + if err != nil { + return nil, err + } + return json.Marshal(x) +} + +// MarshalYAML returns the YAML encoding of Tag. +func (t Tag) MarshalYAML() (any, error) { + m := make(map[string]any, 3+len(t.Extensions)) + for k, v := range t.Extensions { + m[k] = v + } + if x := t.Name; x != "" { + m["name"] = x + } + if x := t.Description; x != "" { + m["description"] = x + } + if x := t.ExternalDocs; x != nil { + m["externalDocs"] = x + } + return m, nil +} + +// UnmarshalJSON sets Tag to a copy of data. +func (t *Tag) UnmarshalJSON(data []byte) error { + type TagBis Tag + var x TagBis + if err := json.Unmarshal(data, &x); err != nil { + return unmarshalError(err) + } + _ = json.Unmarshal(data, &x.Extensions) + delete(x.Extensions, "name") + delete(x.Extensions, "description") + delete(x.Extensions, "externalDocs") + if len(x.Extensions) == 0 { + x.Extensions = nil + } + *t = Tag(x) + return nil +} + +// Validate returns an error if Tag does not comply with the OpenAPI spec. +func (t *Tag) Validate(ctx context.Context, opts ...ValidationOption) error { + ctx = WithValidationOptions(ctx, opts...) + + if v := t.ExternalDocs; v != nil { + if err := v.Validate(ctx); err != nil { + return fmt.Errorf("invalid external docs: %w", err) + } + } + + return validateExtensions(ctx, t.Extensions) +} diff --git a/vendor/github.com/getkin/kin-openapi/openapi3/validation_options.go b/vendor/github.com/getkin/kin-openapi/openapi3/validation_options.go new file mode 100644 index 00000000..45563256 --- /dev/null +++ b/vendor/github.com/getkin/kin-openapi/openapi3/validation_options.go @@ -0,0 +1,133 @@ +package openapi3 + +import "context" + +// ValidationOption allows the modification of how the OpenAPI document is validated. +type ValidationOption func(options *ValidationOptions) + +// ValidationOptions provides configuration for validating OpenAPI documents. +type ValidationOptions struct { + examplesValidationAsReq, examplesValidationAsRes bool + examplesValidationDisabled bool + schemaDefaultsValidationDisabled bool + schemaFormatValidationEnabled bool + schemaPatternValidationDisabled bool + schemaExtensionsInRefProhibited bool + extraSiblingFieldsAllowed map[string]struct{} +} + +type validationOptionsKey struct{} + +// AllowExtraSiblingFields called as AllowExtraSiblingFields("description") makes Validate not return an error when said field appears next to a $ref. +func AllowExtraSiblingFields(fields ...string) ValidationOption { + return func(options *ValidationOptions) { + if options.extraSiblingFieldsAllowed == nil && len(fields) != 0 { + options.extraSiblingFieldsAllowed = make(map[string]struct{}, len(fields)) + } + for _, field := range fields { + options.extraSiblingFieldsAllowed[field] = struct{}{} + } + } +} + +// EnableSchemaFormatValidation makes Validate not return an error when validating documents that mention schema formats that are not defined by the OpenAPIv3 specification. +// By default, schema format validation is disabled. +func EnableSchemaFormatValidation() ValidationOption { + return func(options *ValidationOptions) { + options.schemaFormatValidationEnabled = true + } +} + +// DisableSchemaFormatValidation does the opposite of EnableSchemaFormatValidation. +// By default, schema format validation is disabled. +func DisableSchemaFormatValidation() ValidationOption { + return func(options *ValidationOptions) { + options.schemaFormatValidationEnabled = false + } +} + +// EnableSchemaPatternValidation does the opposite of DisableSchemaPatternValidation. +// By default, schema pattern validation is enabled. +func EnableSchemaPatternValidation() ValidationOption { + return func(options *ValidationOptions) { + options.schemaPatternValidationDisabled = false + } +} + +// DisableSchemaPatternValidation makes Validate not return an error when validating patterns that are not supported by the Go regexp engine. +func DisableSchemaPatternValidation() ValidationOption { + return func(options *ValidationOptions) { + options.schemaPatternValidationDisabled = true + } +} + +// EnableSchemaDefaultsValidation does the opposite of DisableSchemaDefaultsValidation. +// By default, schema default values are validated against their schema. +func EnableSchemaDefaultsValidation() ValidationOption { + return func(options *ValidationOptions) { + options.schemaDefaultsValidationDisabled = false + } +} + +// DisableSchemaDefaultsValidation disables schemas' default field validation. +// By default, schema default values are validated against their schema. +func DisableSchemaDefaultsValidation() ValidationOption { + return func(options *ValidationOptions) { + options.schemaDefaultsValidationDisabled = true + } +} + +// EnableExamplesValidation does the opposite of DisableExamplesValidation. +// By default, all schema examples are validated. +func EnableExamplesValidation() ValidationOption { + return func(options *ValidationOptions) { + options.examplesValidationDisabled = false + } +} + +// DisableExamplesValidation disables all example schema validation. +// By default, all schema examples are validated. +func DisableExamplesValidation() ValidationOption { + return func(options *ValidationOptions) { + options.examplesValidationDisabled = true + } +} + +// AllowExtensionsWithRef allows extensions (fields starting with 'x-') +// as siblings for $ref fields. This is the default. +// Non-extension fields are prohibited unless allowed explicitly with the +// AllowExtraSiblingFields option. +func AllowExtensionsWithRef() ValidationOption { + return func(options *ValidationOptions) { + options.schemaExtensionsInRefProhibited = false + } +} + +// ProhibitExtensionsWithRef causes the validation to return an +// error if extensions (fields starting with 'x-') are found as +// siblings for $ref fields. Non-extension fields are prohibited +// unless allowed explicitly with the AllowExtraSiblingFields option. +func ProhibitExtensionsWithRef() ValidationOption { + return func(options *ValidationOptions) { + options.schemaExtensionsInRefProhibited = true + } +} + +// WithValidationOptions allows adding validation options to a context object that can be used when validating any OpenAPI type. +func WithValidationOptions(ctx context.Context, opts ...ValidationOption) context.Context { + if len(opts) == 0 { + return ctx + } + options := &ValidationOptions{} + for _, opt := range opts { + opt(options) + } + return context.WithValue(ctx, validationOptionsKey{}, options) +} + +func getValidationOptions(ctx context.Context) *ValidationOptions { + if options, ok := ctx.Value(validationOptionsKey{}).(*ValidationOptions); ok { + return options + } + return &ValidationOptions{} +} diff --git a/vendor/github.com/getkin/kin-openapi/openapi3/visited.go b/vendor/github.com/getkin/kin-openapi/openapi3/visited.go new file mode 100644 index 00000000..67f970e3 --- /dev/null +++ b/vendor/github.com/getkin/kin-openapi/openapi3/visited.go @@ -0,0 +1,41 @@ +package openapi3 + +func newVisited() visitedComponent { + return visitedComponent{ + header: make(map[*Header]struct{}), + schema: make(map[*Schema]struct{}), + } +} + +type visitedComponent struct { + header map[*Header]struct{} + schema map[*Schema]struct{} +} + +// resetVisited clears visitedComponent map +// should be called before recursion over doc *T +func (doc *T) resetVisited() { + doc.visited = newVisited() +} + +// isVisitedHeader returns `true` if the *Header pointer was already visited +// otherwise it returns `false` +func (doc *T) isVisitedHeader(h *Header) bool { + if _, ok := doc.visited.header[h]; ok { + return true + } + + doc.visited.header[h] = struct{}{} + return false +} + +// isVisitedHeader returns `true` if the *Schema pointer was already visited +// otherwise it returns `false` +func (doc *T) isVisitedSchema(s *Schema) bool { + if _, ok := doc.visited.schema[s]; ok { + return true + } + + doc.visited.schema[s] = struct{}{} + return false +} diff --git a/vendor/github.com/getkin/kin-openapi/openapi3/xml.go b/vendor/github.com/getkin/kin-openapi/openapi3/xml.go new file mode 100644 index 00000000..69d1b348 --- /dev/null +++ b/vendor/github.com/getkin/kin-openapi/openapi3/xml.go @@ -0,0 +1,78 @@ +package openapi3 + +import ( + "context" + "encoding/json" +) + +// XML is specified by OpenAPI/Swagger standard version 3. +// See https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#xml-object +type XML struct { + Extensions map[string]any `json:"-" yaml:"-"` + + Name string `json:"name,omitempty" yaml:"name,omitempty"` + Namespace string `json:"namespace,omitempty" yaml:"namespace,omitempty"` + Prefix string `json:"prefix,omitempty" yaml:"prefix,omitempty"` + Attribute bool `json:"attribute,omitempty" yaml:"attribute,omitempty"` + Wrapped bool `json:"wrapped,omitempty" yaml:"wrapped,omitempty"` +} + +// MarshalJSON returns the JSON encoding of XML. +func (xml XML) MarshalJSON() ([]byte, error) { + x, err := xml.MarshalYAML() + if err != nil { + return nil, err + } + return json.Marshal(x) +} + +// MarshalYAML returns the YAML encoding of XML. +func (xml XML) MarshalYAML() (any, error) { + m := make(map[string]any, 5+len(xml.Extensions)) + for k, v := range xml.Extensions { + m[k] = v + } + if x := xml.Name; x != "" { + m["name"] = x + } + if x := xml.Namespace; x != "" { + m["namespace"] = x + } + if x := xml.Prefix; x != "" { + m["prefix"] = x + } + if x := xml.Attribute; x { + m["attribute"] = x + } + if x := xml.Wrapped; x { + m["wrapped"] = x + } + return m, nil +} + +// UnmarshalJSON sets XML to a copy of data. +func (xml *XML) UnmarshalJSON(data []byte) error { + type XMLBis XML + var x XMLBis + if err := json.Unmarshal(data, &x); err != nil { + return unmarshalError(err) + } + _ = json.Unmarshal(data, &x.Extensions) + delete(x.Extensions, "name") + delete(x.Extensions, "namespace") + delete(x.Extensions, "prefix") + delete(x.Extensions, "attribute") + delete(x.Extensions, "wrapped") + if len(x.Extensions) == 0 { + x.Extensions = nil + } + *xml = XML(x) + return nil +} + +// Validate returns an error if XML does not comply with the OpenAPI spec. +func (xml *XML) Validate(ctx context.Context, opts ...ValidationOption) error { + ctx = WithValidationOptions(ctx, opts...) + + return validateExtensions(ctx, xml.Extensions) +} diff --git a/vendor/github.com/invopop/yaml/.gitignore b/vendor/github.com/invopop/yaml/.gitignore new file mode 100644 index 00000000..e256a31e --- /dev/null +++ b/vendor/github.com/invopop/yaml/.gitignore @@ -0,0 +1,20 @@ +# OSX leaves these everywhere on SMB shares +._* + +# Eclipse files +.classpath +.project +.settings/** + +# Emacs save files +*~ + +# Vim-related files +[._]*.s[a-w][a-z] +[._]s[a-w][a-z] +*.un~ +Session.vim +.netrwhist + +# Go test binaries +*.test diff --git a/vendor/github.com/invopop/yaml/.golangci.toml b/vendor/github.com/invopop/yaml/.golangci.toml new file mode 100644 index 00000000..61b2b79a --- /dev/null +++ b/vendor/github.com/invopop/yaml/.golangci.toml @@ -0,0 +1,16 @@ +[run] +timeout = "120s" + +[output] +format = "colored-line-number" + +[linters] +enable = [ + "gocyclo", "unconvert", "goimports", "unused", "unused", + "vetshadow", "nakedret", "errcheck", "revive", "ineffassign", + "goconst", "vet", "unparam", "gofmt" +] + +[issues] +exclude-use-default = false + diff --git a/vendor/github.com/invopop/yaml/LICENSE b/vendor/github.com/invopop/yaml/LICENSE new file mode 100644 index 00000000..7805d36d --- /dev/null +++ b/vendor/github.com/invopop/yaml/LICENSE @@ -0,0 +1,50 @@ +The MIT License (MIT) + +Copyright (c) 2014 Sam Ghods + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + + +Copyright (c) 2012 The Go Authors. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the +distribution. + * Neither the name of Google Inc. nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/vendor/github.com/invopop/yaml/README.md b/vendor/github.com/invopop/yaml/README.md new file mode 100644 index 00000000..2c33dfe5 --- /dev/null +++ b/vendor/github.com/invopop/yaml/README.md @@ -0,0 +1,128 @@ +# YAML marshaling and unmarshaling support for Go + +[![Lint](https://github.com/invopop/yaml/actions/workflows/lint.yaml/badge.svg)](https://github.com/invopop/yaml/actions/workflows/lint.yaml) +[![Test Go](https://github.com/invopop/yaml/actions/workflows/test.yaml/badge.svg)](https://github.com/invopop/yaml/actions/workflows/test.yaml) +[![Go Report Card](https://goreportcard.com/badge/github.com/invopop/yaml)](https://goreportcard.com/report/github.com/invopop/yaml) +![Latest Tag](https://img.shields.io/github/v/tag/invopop/yaml) + +## Introduction + +A wrapper around [go-yaml](https://github.com/go-yaml/yaml) designed to enable a better way of handling YAML when marshaling to and from structs. + +This is a fork and split of the original [ghodss/yaml](https://github.com/ghodss/yaml) repository which no longer appears to be maintained. + +In short, this library first converts YAML to JSON using go-yaml and then uses `json.Marshal` and `json.Unmarshal` to convert to or from the struct. This means that it effectively reuses the JSON struct tags as well as the custom JSON methods `MarshalJSON` and `UnmarshalJSON` unlike go-yaml. For a detailed overview of the rationale behind this method, [see this blog post](https://web.archive.org/web/20150812020634/http://ghodss.com/2014/the-right-way-to-handle-yaml-in-golang/). + +## Compatibility + +This package uses [go-yaml](https://github.com/go-yaml/yaml) and therefore supports [everything go-yaml supports](https://github.com/go-yaml/yaml#compatibility). + +Tested against Go versions 1.14 and onwards. + +## Caveats + +**Caveat #1:** When using `yaml.Marshal` and `yaml.Unmarshal`, binary data should NOT be preceded with the `!!binary` YAML tag. If you do, go-yaml will convert the binary data from base64 to native binary data, which is not compatible with JSON. You can still use binary in your YAML files though - just store them without the `!!binary` tag and decode the base64 in your code (e.g. in the custom JSON methods `MarshalJSON` and `UnmarshalJSON`). This also has the benefit that your YAML and your JSON binary data will be decoded exactly the same way. As an example: + +``` +BAD: + exampleKey: !!binary gIGC + +GOOD: + exampleKey: gIGC +... and decode the base64 data in your code. +``` + +**Caveat #2:** When using `YAMLToJSON` directly, maps with keys that are maps will result in an error since this is not supported by JSON. This error will occur in `Unmarshal` as well since you can't unmarshal map keys anyways since struct fields can't be keys. + +## Installation and usage + +To install, run: + +``` +$ go get github.com/invopop/yaml +``` + +And import using: + +``` +import "github.com/invopop/yaml" +``` + +Usage is very similar to the JSON library: + +```go +package main + +import ( + "fmt" + + "github.com/invopop/yaml" +) + +type Person struct { + Name string `json:"name"` // Affects YAML field names too. + Age int `json:"age"` +} + +func main() { + // Marshal a Person struct to YAML. + p := Person{"John", 30} + y, err := yaml.Marshal(p) + if err != nil { + fmt.Printf("err: %v\n", err) + return + } + fmt.Println(string(y)) + /* Output: + age: 30 + name: John + */ + + // Unmarshal the YAML back into a Person struct. + var p2 Person + err = yaml.Unmarshal(y, &p2) + if err != nil { + fmt.Printf("err: %v\n", err) + return + } + fmt.Println(p2) + /* Output: + {John 30} + */ +} +``` + +`yaml.YAMLToJSON` and `yaml.JSONToYAML` methods are also available: + +```go +package main + +import ( + "fmt" + + "github.com/invopop/yaml" +) + +func main() { + j := []byte(`{"name": "John", "age": 30}`) + y, err := yaml.JSONToYAML(j) + if err != nil { + fmt.Printf("err: %v\n", err) + return + } + fmt.Println(string(y)) + /* Output: + name: John + age: 30 + */ + j2, err := yaml.YAMLToJSON(y) + if err != nil { + fmt.Printf("err: %v\n", err) + return + } + fmt.Println(string(j2)) + /* Output: + {"age":30,"name":"John"} + */ +} +``` diff --git a/vendor/github.com/invopop/yaml/fields.go b/vendor/github.com/invopop/yaml/fields.go new file mode 100644 index 00000000..3fe5f12f --- /dev/null +++ b/vendor/github.com/invopop/yaml/fields.go @@ -0,0 +1,499 @@ +// Copyright 2013 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package yaml + +import ( + "bytes" + "encoding" + "encoding/json" + "reflect" + "sort" + "strings" + "sync" + "unicode" + "unicode/utf8" +) + +// indirect walks down v allocating pointers as needed, +// until it gets to a non-pointer. +// if it encounters an Unmarshaler, indirect stops and returns that. +// if decodingNull is true, indirect stops at the last pointer so it can be set to nil. +func indirect(v reflect.Value, decodingNull bool) (json.Unmarshaler, encoding.TextUnmarshaler, reflect.Value) { + // If v is a named type and is addressable, + // start with its address, so that if the type has pointer methods, + // we find them. + if v.Kind() != reflect.Ptr && v.Type().Name() != "" && v.CanAddr() { + v = v.Addr() + } + for { + // Load value from interface, but only if the result will be + // usefully addressable. + if v.Kind() == reflect.Interface && !v.IsNil() { + e := v.Elem() + if e.Kind() == reflect.Ptr && !e.IsNil() && (!decodingNull || e.Elem().Kind() == reflect.Ptr) { + v = e + continue + } + } + + if v.Kind() != reflect.Ptr { + break + } + + if v.Elem().Kind() != reflect.Ptr && decodingNull && v.CanSet() { + break + } + if v.IsNil() { + if v.CanSet() { + v.Set(reflect.New(v.Type().Elem())) + } else { + v = reflect.New(v.Type().Elem()) + } + } + if v.Type().NumMethod() > 0 { + if u, ok := v.Interface().(json.Unmarshaler); ok { + return u, nil, reflect.Value{} + } + if u, ok := v.Interface().(encoding.TextUnmarshaler); ok { + return nil, u, reflect.Value{} + } + } + v = v.Elem() + } + return nil, nil, v +} + +// A field represents a single field found in a struct. +type field struct { + name string + nameBytes []byte // []byte(name) + equalFold func(s, t []byte) bool // bytes.EqualFold or equivalent + + tag bool + index []int + typ reflect.Type + omitEmpty bool + quoted bool +} + +func fillField(f field) field { + f.nameBytes = []byte(f.name) + f.equalFold = foldFunc(f.nameBytes) + return f +} + +// byName sorts field by name, breaking ties with depth, +// then breaking ties with "name came from json tag", then +// breaking ties with index sequence. +type byName []field + +func (x byName) Len() int { return len(x) } + +func (x byName) Swap(i, j int) { x[i], x[j] = x[j], x[i] } + +func (x byName) Less(i, j int) bool { + if x[i].name != x[j].name { + return x[i].name < x[j].name + } + if len(x[i].index) != len(x[j].index) { + return len(x[i].index) < len(x[j].index) + } + if x[i].tag != x[j].tag { + return x[i].tag + } + return byIndex(x).Less(i, j) +} + +// byIndex sorts field by index sequence. +type byIndex []field + +func (x byIndex) Len() int { return len(x) } + +func (x byIndex) Swap(i, j int) { x[i], x[j] = x[j], x[i] } + +func (x byIndex) Less(i, j int) bool { + for k, xik := range x[i].index { + if k >= len(x[j].index) { + return false + } + if xik != x[j].index[k] { + return xik < x[j].index[k] + } + } + return len(x[i].index) < len(x[j].index) +} + +// typeFields returns a list of fields that JSON should recognize for the given type. +// The algorithm is breadth-first search over the set of structs to include - the top struct +// and then any reachable anonymous structs. +func typeFields(t reflect.Type) []field { + // Anonymous fields to explore at the current level and the next. + current := []field{} + next := []field{{typ: t}} + + // Count of queued names for current level and the next. + var count, nextCount map[reflect.Type]int + + // Types already visited at an earlier level. + visited := map[reflect.Type]bool{} + + // Fields found. + var fields []field + + for len(next) > 0 { + current, next = next, current[:0] + count, nextCount = nextCount, map[reflect.Type]int{} + + for _, f := range current { + if visited[f.typ] { + continue + } + visited[f.typ] = true + + // Scan f.typ for fields to include. + for i := 0; i < f.typ.NumField(); i++ { + sf := f.typ.Field(i) + if sf.PkgPath != "" { // unexported + continue + } + tag := sf.Tag.Get("json") + if tag == "-" { + continue + } + name, opts := parseTag(tag) + if !isValidTag(name) { + name = "" + } + index := make([]int, len(f.index)+1) + copy(index, f.index) + index[len(f.index)] = i + + ft := sf.Type + if ft.Name() == "" && ft.Kind() == reflect.Ptr { + // Follow pointer. + ft = ft.Elem() + } + + // Record found field and index sequence. + if name != "" || !sf.Anonymous || ft.Kind() != reflect.Struct { + tagged := name != "" + if name == "" { + name = sf.Name + } + fields = append(fields, fillField(field{ + name: name, + tag: tagged, + index: index, + typ: ft, + omitEmpty: opts.Contains("omitempty"), + quoted: opts.Contains("string"), + })) + if count[f.typ] > 1 { + // If there were multiple instances, add a second, + // so that the annihilation code will see a duplicate. + // It only cares about the distinction between 1 or 2, + // so don't bother generating any more copies. + fields = append(fields, fields[len(fields)-1]) + } + continue + } + + // Record new anonymous struct to explore in next round. + nextCount[ft]++ + if nextCount[ft] == 1 { + next = append(next, fillField(field{name: ft.Name(), index: index, typ: ft})) + } + } + } + } + + sort.Sort(byName(fields)) + + // Delete all fields that are hidden by the Go rules for embedded fields, + // except that fields with JSON tags are promoted. + + // The fields are sorted in primary order of name, secondary order + // of field index length. Loop over names; for each name, delete + // hidden fields by choosing the one dominant field that survives. + out := fields[:0] + for advance, i := 0, 0; i < len(fields); i += advance { + // One iteration per name. + // Find the sequence of fields with the name of this first field. + fi := fields[i] + name := fi.name + for advance = 1; i+advance < len(fields); advance++ { + fj := fields[i+advance] + if fj.name != name { + break + } + } + if advance == 1 { // Only one field with this name + out = append(out, fi) + continue + } + dominant, ok := dominantField(fields[i : i+advance]) + if ok { + out = append(out, dominant) + } + } + + fields = out + sort.Sort(byIndex(fields)) + + return fields +} + +// dominantField looks through the fields, all of which are known to +// have the same name, to find the single field that dominates the +// others using Go's embedding rules, modified by the presence of +// JSON tags. If there are multiple top-level fields, the boolean +// will be false: This condition is an error in Go and we skip all +// the fields. +func dominantField(fields []field) (field, bool) { + // The fields are sorted in increasing index-length order. The winner + // must therefore be one with the shortest index length. Drop all + // longer entries, which is easy: just truncate the slice. + length := len(fields[0].index) + tagged := -1 // Index of first tagged field. + for i, f := range fields { + if len(f.index) > length { + fields = fields[:i] + break + } + if f.tag { + if tagged >= 0 { + // Multiple tagged fields at the same level: conflict. + // Return no field. + return field{}, false + } + tagged = i + } + } + if tagged >= 0 { + return fields[tagged], true + } + // All remaining fields have the same length. If there's more than one, + // we have a conflict (two fields named "X" at the same level) and we + // return no field. + if len(fields) > 1 { + return field{}, false + } + return fields[0], true +} + +var fieldCache struct { + sync.RWMutex + m map[reflect.Type][]field +} + +// cachedTypeFields is like typeFields but uses a cache to avoid repeated work. +func cachedTypeFields(t reflect.Type) []field { + fieldCache.RLock() + f := fieldCache.m[t] + fieldCache.RUnlock() + if f != nil { + return f + } + + // Compute fields without lock. + // Might duplicate effort but won't hold other computations back. + f = typeFields(t) + if f == nil { + f = []field{} + } + + fieldCache.Lock() + if fieldCache.m == nil { + fieldCache.m = map[reflect.Type][]field{} + } + fieldCache.m[t] = f + fieldCache.Unlock() + return f +} + +func isValidTag(s string) bool { + if s == "" { + return false + } + for _, c := range s { + switch { + case strings.ContainsRune("!#$%&()*+-./:<=>?@[]^_{|}~ ", c): + // Backslash and quote chars are reserved, but + // otherwise any punctuation chars are allowed + // in a tag name. + default: + if !unicode.IsLetter(c) && !unicode.IsDigit(c) { + return false + } + } + } + return true +} + +const ( + caseMask = ^byte(0x20) // Mask to ignore case in ASCII. + kelvin = '\u212a' + smallLongEss = '\u017f' +) + +// foldFunc returns one of four different case folding equivalence +// functions, from most general (and slow) to fastest: +// +// 1) bytes.EqualFold, if the key s contains any non-ASCII UTF-8 +// 2) equalFoldRight, if s contains special folding ASCII ('k', 'K', 's', 'S') +// 3) asciiEqualFold, no special, but includes non-letters (including _) +// 4) simpleLetterEqualFold, no specials, no non-letters. +// +// The letters S and K are special because they map to 3 runes, not just 2: +// - S maps to s and to U+017F 'ſ' Latin small letter long s +// - k maps to K and to U+212A 'K' Kelvin sign +// +// See http://play.golang.org/p/tTxjOc0OGo +// +// The returned function is specialized for matching against s and +// should only be given s. It's not curried for performance reasons. +func foldFunc(s []byte) func(s, t []byte) bool { + nonLetter := false + special := false // special letter + for _, b := range s { + if b >= utf8.RuneSelf { + return bytes.EqualFold + } + upper := b & caseMask + if upper < 'A' || upper > 'Z' { + nonLetter = true + } else if upper == 'K' || upper == 'S' { + // See above for why these letters are special. + special = true + } + } + if special { + return equalFoldRight + } + if nonLetter { + return asciiEqualFold + } + return simpleLetterEqualFold +} + +// equalFoldRight is a specialization of bytes.EqualFold when s is +// known to be all ASCII (including punctuation), but contains an 's', +// 'S', 'k', or 'K', requiring a Unicode fold on the bytes in t. +// See comments on foldFunc. +func equalFoldRight(s, t []byte) bool { + for _, sb := range s { + if len(t) == 0 { + return false + } + tb := t[0] + if tb < utf8.RuneSelf { + if sb != tb { + sbUpper := sb & caseMask + if 'A' <= sbUpper && sbUpper <= 'Z' { + if sbUpper != tb&caseMask { + return false + } + } else { + return false + } + } + t = t[1:] + continue + } + // sb is ASCII and t is not. t must be either kelvin + // sign or long s; sb must be s, S, k, or K. + tr, size := utf8.DecodeRune(t) + switch sb { + case 's', 'S': + if tr != smallLongEss { + return false + } + case 'k', 'K': + if tr != kelvin { + return false + } + default: + return false + } + t = t[size:] + + } + return len(t) <= 0 +} + +// asciiEqualFold is a specialization of bytes.EqualFold for use when +// s is all ASCII (but may contain non-letters) and contains no +// special-folding letters. +// See comments on foldFunc. +func asciiEqualFold(s, t []byte) bool { + if len(s) != len(t) { + return false + } + for i, sb := range s { + tb := t[i] + if sb == tb { + continue + } + if ('a' <= sb && sb <= 'z') || ('A' <= sb && sb <= 'Z') { + if sb&caseMask != tb&caseMask { + return false + } + } else { + return false + } + } + return true +} + +// simpleLetterEqualFold is a specialization of bytes.EqualFold for +// use when s is all ASCII letters (no underscores, etc) and also +// doesn't contain 'k', 'K', 's', or 'S'. +// See comments on foldFunc. +func simpleLetterEqualFold(s, t []byte) bool { + if len(s) != len(t) { + return false + } + for i, b := range s { + if b&caseMask != t[i]&caseMask { + return false + } + } + return true +} + +// tagOptions is the string following a comma in a struct field's "json" +// tag, or the empty string. It does not include the leading comma. +type tagOptions string + +// parseTag splits a struct field's json tag into its name and +// comma-separated options. +func parseTag(tag string) (string, tagOptions) { + if idx := strings.Index(tag, ","); idx != -1 { + return tag[:idx], tagOptions(tag[idx+1:]) + } + return tag, tagOptions("") +} + +// Contains reports whether a comma-separated list of options +// contains a particular substr flag. substr must be surrounded by a +// string boundary or commas. +func (o tagOptions) Contains(optionName string) bool { + if len(o) == 0 { + return false + } + s := string(o) + for s != "" { + var next string + i := strings.Index(s, ",") + if i >= 0 { + s, next = s[:i], s[i+1:] + } + if s == optionName { + return true + } + s = next + } + return false +} diff --git a/vendor/github.com/invopop/yaml/yaml.go b/vendor/github.com/invopop/yaml/yaml.go new file mode 100644 index 00000000..d57dfb10 --- /dev/null +++ b/vendor/github.com/invopop/yaml/yaml.go @@ -0,0 +1,312 @@ +// Package yaml provides a wrapper around go-yaml designed to enable a better +// way of handling YAML when marshaling to and from structs. +// +// In short, this package first converts YAML to JSON using go-yaml and then +// uses json.Marshal and json.Unmarshal to convert to or from the struct. This +// means that it effectively reuses the JSON struct tags as well as the custom +// JSON methods MarshalJSON and UnmarshalJSON unlike go-yaml. +package yaml // import "github.com/invopop/yaml" + +import ( + "bytes" + "encoding/json" + "errors" + "fmt" + "io" + "reflect" + "strconv" + + "gopkg.in/yaml.v3" +) + +// Marshal the object into JSON then converts JSON to YAML and returns the +// YAML. +func Marshal(o interface{}) ([]byte, error) { + j, err := json.Marshal(o) + if err != nil { + return nil, fmt.Errorf("error marshaling into JSON: %v", err) + } + + y, err := JSONToYAML(j) + if err != nil { + return nil, fmt.Errorf("error converting JSON to YAML: %v", err) + } + + return y, nil +} + +// JSONOpt is a decoding option for decoding from JSON format. +type JSONOpt func(*json.Decoder) *json.Decoder + +// Unmarshal converts YAML to JSON then uses JSON to unmarshal into an object, +// optionally configuring the behavior of the JSON unmarshal. +func Unmarshal(y []byte, o interface{}, opts ...JSONOpt) error { + dec := yaml.NewDecoder(bytes.NewReader(y)) + return unmarshal(dec, o, opts) +} + +func unmarshal(dec *yaml.Decoder, o interface{}, opts []JSONOpt) error { + vo := reflect.ValueOf(o) + j, err := yamlToJSON(dec, &vo) + if err != nil { + return fmt.Errorf("error converting YAML to JSON: %v", err) + } + + err = jsonUnmarshal(bytes.NewReader(j), o, opts...) + if err != nil { + return fmt.Errorf("error unmarshaling JSON: %v", err) + } + + return nil +} + +// jsonUnmarshal unmarshals the JSON byte stream from the given reader into the +// object, optionally applying decoder options prior to decoding. We are not +// using json.Unmarshal directly as we want the chance to pass in non-default +// options. +func jsonUnmarshal(r io.Reader, o interface{}, opts ...JSONOpt) error { + d := json.NewDecoder(r) + for _, opt := range opts { + d = opt(d) + } + if err := d.Decode(&o); err != nil { + return fmt.Errorf("while decoding JSON: %v", err) + } + return nil +} + +// JSONToYAML converts JSON to YAML. +func JSONToYAML(j []byte) ([]byte, error) { + // Convert the JSON to an object. + var jsonObj interface{} + // We are using yaml.Unmarshal here (instead of json.Unmarshal) because the + // Go JSON library doesn't try to pick the right number type (int, float, + // etc.) when unmarshalling to interface{}, it just picks float64 + // universally. go-yaml does go through the effort of picking the right + // number type, so we can preserve number type throughout this process. + err := yaml.Unmarshal(j, &jsonObj) + if err != nil { + return nil, err + } + + // Marshal this object into YAML. + return yaml.Marshal(jsonObj) +} + +// YAMLToJSON converts YAML to JSON. Since JSON is a subset of YAML, +// passing JSON through this method should be a no-op. +// +// Things YAML can do that are not supported by JSON: +// - In YAML you can have binary and null keys in your maps. These are invalid +// in JSON. (int and float keys are converted to strings.) +// - Binary data in YAML with the !!binary tag is not supported. If you want to +// use binary data with this library, encode the data as base64 as usual but do +// not use the !!binary tag in your YAML. This will ensure the original base64 +// encoded data makes it all the way through to the JSON. +func YAMLToJSON(y []byte) ([]byte, error) { //nolint:revive + dec := yaml.NewDecoder(bytes.NewReader(y)) + return yamlToJSON(dec, nil) +} + +func yamlToJSON(dec *yaml.Decoder, jsonTarget *reflect.Value) ([]byte, error) { + // Convert the YAML to an object. + var yamlObj interface{} + if err := dec.Decode(&yamlObj); err != nil { + // Functionality changed in v3 which means we need to ignore EOF error. + // See https://github.com/go-yaml/yaml/issues/639 + if !errors.Is(err, io.EOF) { + return nil, err + } + } + + // YAML objects are not completely compatible with JSON objects (e.g. you + // can have non-string keys in YAML). So, convert the YAML-compatible object + // to a JSON-compatible object, failing with an error if irrecoverable + // incompatibilities happen along the way. + jsonObj, err := convertToJSONableObject(yamlObj, jsonTarget) + if err != nil { + return nil, err + } + + // Convert this object to JSON and return the data. + return json.Marshal(jsonObj) +} + +func convertToJSONableObject(yamlObj interface{}, jsonTarget *reflect.Value) (interface{}, error) { //nolint:gocyclo + var err error + + // Resolve jsonTarget to a concrete value (i.e. not a pointer or an + // interface). We pass decodingNull as false because we're not actually + // decoding into the value, we're just checking if the ultimate target is a + // string. + if jsonTarget != nil { + ju, tu, pv := indirect(*jsonTarget, false) + // We have a JSON or Text Umarshaler at this level, so we can't be trying + // to decode into a string. + if ju != nil || tu != nil { + jsonTarget = nil + } else { + jsonTarget = &pv + } + } + + // go-yaml v3 changed from v2 and now will provide map[string]interface{} by + // default and map[interface{}]interface{} when none of the keys strings. + // To get around this, we run a pre-loop to convert the map. + // JSON only supports strings as keys, so we must convert. + + switch typedYAMLObj := yamlObj.(type) { + case map[interface{}]interface{}: + // From my reading of go-yaml v2 (specifically the resolve function), + // keys can only have the types string, int, int64, float64, binary + // (unsupported), or null (unsupported). + strMap := make(map[string]interface{}) + for k, v := range typedYAMLObj { + // Resolve the key to a string first. + var keyString string + switch typedKey := k.(type) { + case string: + keyString = typedKey + case int: + keyString = strconv.Itoa(typedKey) + case int64: + // go-yaml will only return an int64 as a key if the system + // architecture is 32-bit and the key's value is between 32-bit + // and 64-bit. Otherwise the key type will simply be int. + keyString = strconv.FormatInt(typedKey, 10) + case float64: + // Float64 is now supported in keys + keyString = strconv.FormatFloat(typedKey, 'g', -1, 64) + case bool: + if typedKey { + keyString = "true" + } else { + keyString = "false" + } + default: + return nil, fmt.Errorf("unsupported map key of type: %s, key: %+#v, value: %+#v", + reflect.TypeOf(k), k, v) + } + strMap[keyString] = v + } + // replace yamlObj with our new string map + yamlObj = strMap + } + + // If yamlObj is a number or a boolean, check if jsonTarget is a string - + // if so, coerce. Else return normal. + // If yamlObj is a map or array, find the field that each key is + // unmarshaling to, and when you recurse pass the reflect.Value for that + // field back into this function. + switch typedYAMLObj := yamlObj.(type) { + case map[string]interface{}: + for k, v := range typedYAMLObj { + + // jsonTarget should be a struct or a map. If it's a struct, find + // the field it's going to map to and pass its reflect.Value. If + // it's a map, find the element type of the map and pass the + // reflect.Value created from that type. If it's neither, just pass + // nil - JSON conversion will error for us if it's a real issue. + if jsonTarget != nil { + t := *jsonTarget + if t.Kind() == reflect.Struct { + keyBytes := []byte(k) + // Find the field that the JSON library would use. + var f *field + fields := cachedTypeFields(t.Type()) + for i := range fields { + ff := &fields[i] + if bytes.Equal(ff.nameBytes, keyBytes) { + f = ff + break + } + // Do case-insensitive comparison. + if f == nil && ff.equalFold(ff.nameBytes, keyBytes) { + f = ff + } + } + if f != nil { + // Find the reflect.Value of the most preferential + // struct field. + jtf := t.Field(f.index[0]) + typedYAMLObj[k], err = convertToJSONableObject(v, &jtf) + if err != nil { + return nil, err + } + continue + } + } else if t.Kind() == reflect.Map { + // Create a zero value of the map's element type to use as + // the JSON target. + jtv := reflect.Zero(t.Type().Elem()) + typedYAMLObj[k], err = convertToJSONableObject(v, &jtv) + if err != nil { + return nil, err + } + continue + } + } + typedYAMLObj[k], err = convertToJSONableObject(v, nil) + if err != nil { + return nil, err + } + } + return typedYAMLObj, nil + case []interface{}: + // We need to recurse into arrays in case there are any + // map[interface{}]interface{}'s inside and to convert any + // numbers to strings. + + // If jsonTarget is a slice (which it really should be), find the + // thing it's going to map to. If it's not a slice, just pass nil + // - JSON conversion will error for us if it's a real issue. + var jsonSliceElemValue *reflect.Value + if jsonTarget != nil { + t := *jsonTarget + if t.Kind() == reflect.Slice { + // By default slices point to nil, but we need a reflect.Value + // pointing to a value of the slice type, so we create one here. + ev := reflect.Indirect(reflect.New(t.Type().Elem())) + jsonSliceElemValue = &ev + } + } + + // Make and use a new array. + arr := make([]interface{}, len(typedYAMLObj)) + for i, v := range typedYAMLObj { + arr[i], err = convertToJSONableObject(v, jsonSliceElemValue) + if err != nil { + return nil, err + } + } + return arr, nil + default: + // If the target type is a string and the YAML type is a number, + // convert the YAML type to a string. + if jsonTarget != nil && (*jsonTarget).Kind() == reflect.String { + // Based on my reading of go-yaml, it may return int, int64, + // float64, or uint64. + var s string + switch typedVal := typedYAMLObj.(type) { + case int: + s = strconv.FormatInt(int64(typedVal), 10) + case int64: + s = strconv.FormatInt(typedVal, 10) + case float64: + s = strconv.FormatFloat(typedVal, 'g', -1, 64) + case uint64: + s = strconv.FormatUint(typedVal, 10) + case bool: + if typedVal { + s = "true" + } else { + s = "false" + } + } + if len(s) > 0 { + yamlObj = interface{}(s) + } + } + return yamlObj, nil + } +} diff --git a/vendor/github.com/mohae/deepcopy/.gitignore b/vendor/github.com/mohae/deepcopy/.gitignore new file mode 100644 index 00000000..5846dd15 --- /dev/null +++ b/vendor/github.com/mohae/deepcopy/.gitignore @@ -0,0 +1,26 @@ +# Compiled Object files, Static and Dynamic libs (Shared Objects) +*.o +*.a +*.so + +# Folders +_obj +_test + +# Architecture specific extensions/prefixes +*.[568vq] +[568vq].out + +*.cgo1.go +*.cgo2.c +_cgo_defun.c +_cgo_gotypes.go +_cgo_export.* + +_testmain.go + +*.exe +*.test +*~ +*.out +*.log diff --git a/vendor/github.com/mohae/deepcopy/.travis.yml b/vendor/github.com/mohae/deepcopy/.travis.yml new file mode 100644 index 00000000..fd47a8cf --- /dev/null +++ b/vendor/github.com/mohae/deepcopy/.travis.yml @@ -0,0 +1,11 @@ +language: go + +go: + - tip + +matrix: + allow_failures: + - go: tip + +script: + - go test ./... diff --git a/vendor/github.com/mohae/deepcopy/LICENSE b/vendor/github.com/mohae/deepcopy/LICENSE new file mode 100644 index 00000000..419673f0 --- /dev/null +++ b/vendor/github.com/mohae/deepcopy/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2014 Joel + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/vendor/github.com/mohae/deepcopy/README.md b/vendor/github.com/mohae/deepcopy/README.md new file mode 100644 index 00000000..f8184188 --- /dev/null +++ b/vendor/github.com/mohae/deepcopy/README.md @@ -0,0 +1,8 @@ +deepCopy +======== +[![GoDoc](https://godoc.org/github.com/mohae/deepcopy?status.svg)](https://godoc.org/github.com/mohae/deepcopy)[![Build Status](https://travis-ci.org/mohae/deepcopy.png)](https://travis-ci.org/mohae/deepcopy) + +DeepCopy makes deep copies of things: unexported field values are not copied. + +## Usage + cpy := deepcopy.Copy(orig) diff --git a/vendor/github.com/mohae/deepcopy/deepcopy.go b/vendor/github.com/mohae/deepcopy/deepcopy.go new file mode 100644 index 00000000..ba763ad0 --- /dev/null +++ b/vendor/github.com/mohae/deepcopy/deepcopy.go @@ -0,0 +1,125 @@ +// deepcopy makes deep copies of things. A standard copy will copy the +// pointers: deep copy copies the values pointed to. Unexported field +// values are not copied. +// +// Copyright (c)2014-2016, Joel Scoble (github.com/mohae), all rights reserved. +// License: MIT, for more details check the included LICENSE file. +package deepcopy + +import ( + "reflect" + "time" +) + +// Interface for delegating copy process to type +type Interface interface { + DeepCopy() interface{} +} + +// Iface is an alias to Copy; this exists for backwards compatibility reasons. +func Iface(iface interface{}) interface{} { + return Copy(iface) +} + +// Copy creates a deep copy of whatever is passed to it and returns the copy +// in an interface{}. The returned value will need to be asserted to the +// correct type. +func Copy(src interface{}) interface{} { + if src == nil { + return nil + } + + // Make the interface a reflect.Value + original := reflect.ValueOf(src) + + // Make a copy of the same type as the original. + cpy := reflect.New(original.Type()).Elem() + + // Recursively copy the original. + copyRecursive(original, cpy) + + // Return the copy as an interface. + return cpy.Interface() +} + +// copyRecursive does the actual copying of the interface. It currently has +// limited support for what it can handle. Add as needed. +func copyRecursive(original, cpy reflect.Value) { + // check for implement deepcopy.Interface + if original.CanInterface() { + if copier, ok := original.Interface().(Interface); ok { + cpy.Set(reflect.ValueOf(copier.DeepCopy())) + return + } + } + + // handle according to original's Kind + switch original.Kind() { + case reflect.Ptr: + // Get the actual value being pointed to. + originalValue := original.Elem() + + // if it isn't valid, return. + if !originalValue.IsValid() { + return + } + cpy.Set(reflect.New(originalValue.Type())) + copyRecursive(originalValue, cpy.Elem()) + + case reflect.Interface: + // If this is a nil, don't do anything + if original.IsNil() { + return + } + // Get the value for the interface, not the pointer. + originalValue := original.Elem() + + // Get the value by calling Elem(). + copyValue := reflect.New(originalValue.Type()).Elem() + copyRecursive(originalValue, copyValue) + cpy.Set(copyValue) + + case reflect.Struct: + t, ok := original.Interface().(time.Time) + if ok { + cpy.Set(reflect.ValueOf(t)) + return + } + // Go through each field of the struct and copy it. + for i := 0; i < original.NumField(); i++ { + // The Type's StructField for a given field is checked to see if StructField.PkgPath + // is set to determine if the field is exported or not because CanSet() returns false + // for settable fields. I'm not sure why. -mohae + if original.Type().Field(i).PkgPath != "" { + continue + } + copyRecursive(original.Field(i), cpy.Field(i)) + } + + case reflect.Slice: + if original.IsNil() { + return + } + // Make a new slice and copy each element. + cpy.Set(reflect.MakeSlice(original.Type(), original.Len(), original.Cap())) + for i := 0; i < original.Len(); i++ { + copyRecursive(original.Index(i), cpy.Index(i)) + } + + case reflect.Map: + if original.IsNil() { + return + } + cpy.Set(reflect.MakeMap(original.Type())) + for _, key := range original.MapKeys() { + originalValue := original.MapIndex(key) + copyValue := reflect.New(originalValue.Type()).Elem() + copyRecursive(originalValue, copyValue) + copyKey := Copy(key.Interface()) + cpy.SetMapIndex(reflect.ValueOf(copyKey), copyValue) + } + + default: + cpy.Set(original) + } +} diff --git a/vendor/github.com/perimeterx/marshmallow/.gitignore b/vendor/github.com/perimeterx/marshmallow/.gitignore new file mode 100644 index 00000000..cf53c0a1 --- /dev/null +++ b/vendor/github.com/perimeterx/marshmallow/.gitignore @@ -0,0 +1,4 @@ +/.idea + +coverage.out +profile.out diff --git a/vendor/github.com/perimeterx/marshmallow/CHANGELOG.md b/vendor/github.com/perimeterx/marshmallow/CHANGELOG.md new file mode 100644 index 00000000..92937d05 --- /dev/null +++ b/vendor/github.com/perimeterx/marshmallow/CHANGELOG.md @@ -0,0 +1,49 @@ +# Changelog + +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## [[1.1.5](https://github.com/PerimeterX/marshmallow/compare/v1.1.4...v1.1.5)] - 2023-07-03 + +### Added + +- Support for reporting errors from `HandleJSONData` - [info](https://github.com/PerimeterX/marshmallow/issues/27). + +## [[1.1.4](https://github.com/PerimeterX/marshmallow/compare/v1.1.3...v1.1.4)] - 2022-11-10 + +### Fixed + +- Fixed problem with nested object implementing JSONDataHandler with skipPopulateStruct - [info](https://github.com/PerimeterX/marshmallow/issues/18). +- Fixed problem with nested object implementing JSONDataHandler with skipPopulateStruct in ModeFailOverToOriginalValue - [info](https://github.com/PerimeterX/marshmallow/issues/19). + +## [[1.1.3](https://github.com/PerimeterX/marshmallow/compare/v1.1.2...v1.1.3)] - 2022-08-31 + +### Added + +- Support for excluding known fields from the result map - [info](https://github.com/PerimeterX/marshmallow/issues/16). + +## [[1.1.2](https://github.com/PerimeterX/marshmallow/compare/v1.1.1...v1.1.2)] - 2022-08-23 + +### Added + +- Support capturing nested unknown fields - [info](https://github.com/PerimeterX/marshmallow/issues/15). + +## [[1.1.1](https://github.com/PerimeterX/marshmallow/compare/v1.1.0...v1.1.1)] - 2022-08-21 + +### Fixed + +- Fix parsing bug for unknown nested fields - [info](https://github.com/PerimeterX/marshmallow/issues/12). + +## [[1.1.0](https://github.com/PerimeterX/marshmallow/compare/v0.0.1...v1.1.0)] - 2022-07-10 + +### Fixed + +- Fixed an issue with embedded fields - [info](https://github.com/PerimeterX/marshmallow/issues/9). + +## [[0.0.1](https://github.com/PerimeterX/marshmallow/tree/v0.0.1)] - 2022-04-21 + +### Added + +- All functionality from our internal repository, after it has been stabilized on production for several months - [info](https://www.perimeterx.com/tech-blog/2022/boosting-up-json-performance-of-unstructured-structs-in-go/). diff --git a/vendor/github.com/perimeterx/marshmallow/CODE_OF_CONDUCT.md b/vendor/github.com/perimeterx/marshmallow/CODE_OF_CONDUCT.md new file mode 100644 index 00000000..0f6c45e7 --- /dev/null +++ b/vendor/github.com/perimeterx/marshmallow/CODE_OF_CONDUCT.md @@ -0,0 +1,133 @@ + +# Contributor Covenant Code of Conduct + +## Our Pledge + +We as members, contributors, and leaders pledge to make participation in our +community a harassment-free experience for everyone, regardless of age, body +size, visible or invisible disability, ethnicity, sex characteristics, gender +identity and expression, level of experience, education, socio-economic status, +nationality, personal appearance, race, caste, color, religion, or sexual +identity and orientation. + +We pledge to act and interact in ways that contribute to an open, welcoming, +diverse, inclusive, and healthy community. + +## Our Standards + +Examples of behavior that contributes to a positive environment for our +community include: + +* Demonstrating empathy and kindness toward other people +* Being respectful of differing opinions, viewpoints, and experiences +* Giving and gracefully accepting constructive feedback +* Accepting responsibility and apologizing to those affected by our mistakes, + and learning from the experience +* Focusing on what is best not just for us as individuals, but for the overall + community + +Examples of unacceptable behavior include: + +* The use of sexualized language or imagery, and sexual attention or advances of + any kind +* Trolling, insulting or derogatory comments, and personal or political attacks +* Public or private harassment +* Publishing others' private information, such as a physical or email address, + without their explicit permission +* Other conduct which could reasonably be considered inappropriate in a + professional setting + +## Enforcement Responsibilities + +Community leaders are responsible for clarifying and enforcing our standards of +acceptable behavior and will take appropriate and fair corrective action in +response to any behavior that they deem inappropriate, threatening, offensive, +or harmful. + +Community leaders have the right and responsibility to remove, edit, or reject +comments, commits, code, wiki edits, issues, and other contributions that are +not aligned to this Code of Conduct, and will communicate reasons for moderation +decisions when appropriate. + +## Scope + +This Code of Conduct applies within all community spaces, and also applies when +an individual is officially representing the community in public spaces. +Examples of representing our community include using an official e-mail address, +posting via an official social media account, or acting as an appointed +representative at an online or offline event. + +## Enforcement + +Instances of abusive, harassing, or otherwise unacceptable behavior may be +reported to the community leaders responsible for enforcement at +[opensource-conduct@humansecurity.com](mailto:opensource-conduct@humansecurity.com). +All complaints will be reviewed and investigated promptly and fairly. + +All community leaders are obligated to respect the privacy and security of the +reporter of any incident. + +## Enforcement Guidelines + +Community leaders will follow these Community Impact Guidelines in determining +the consequences for any action they deem in violation of this Code of Conduct: + +### 1. Correction + +**Community Impact**: Use of inappropriate language or other behavior deemed +unprofessional or unwelcome in the community. + +**Consequence**: A private, written warning from community leaders, providing +clarity around the nature of the violation and an explanation of why the +behavior was inappropriate. A public apology may be requested. + +### 2. Warning + +**Community Impact**: A violation through a single incident or series of +actions. + +**Consequence**: A warning with consequences for continued behavior. No +interaction with the people involved, including unsolicited interaction with +those enforcing the Code of Conduct, for a specified period of time. This +includes avoiding interactions in community spaces as well as external channels +like social media. Violating these terms may lead to a temporary or permanent +ban. + +### 3. Temporary Ban + +**Community Impact**: A serious violation of community standards, including +sustained inappropriate behavior. + +**Consequence**: A temporary ban from any sort of interaction or public +communication with the community for a specified period of time. No public or +private interaction with the people involved, including unsolicited interaction +with those enforcing the Code of Conduct, is allowed during this period. +Violating these terms may lead to a permanent ban. + +### 4. Permanent Ban + +**Community Impact**: Demonstrating a pattern of violation of community +standards, including sustained inappropriate behavior, harassment of an +individual, or aggression toward or disparagement of classes of individuals. + +**Consequence**: A permanent ban from any sort of public interaction within the +community. + +## Attribution + +This Code of Conduct is adapted from the [Contributor Covenant][homepage], +version 2.1, available at +[https://www.contributor-covenant.org/version/2/1/code_of_conduct.html][v2.1]. + +Community Impact Guidelines were inspired by +[Mozilla's code of conduct enforcement ladder][Mozilla CoC]. + +For answers to common questions about this code of conduct, see the FAQ at +[https://www.contributor-covenant.org/faq][FAQ]. Translations are available at +[https://www.contributor-covenant.org/translations][translations]. + +[homepage]: https://www.contributor-covenant.org +[v2.1]: https://www.contributor-covenant.org/version/2/1/code_of_conduct.html +[Mozilla CoC]: https://github.com/mozilla/diversity +[FAQ]: https://www.contributor-covenant.org/faq +[translations]: https://www.contributor-covenant.org/translations diff --git a/vendor/github.com/perimeterx/marshmallow/CONTRIBUTING.md b/vendor/github.com/perimeterx/marshmallow/CONTRIBUTING.md new file mode 100644 index 00000000..a265c9ab --- /dev/null +++ b/vendor/github.com/perimeterx/marshmallow/CONTRIBUTING.md @@ -0,0 +1,47 @@ +# How To Contribute + +We'd love to accept your patches and contributions to this project. There are just a few guidelines you need to follow which are described in detail below. + +## 1. Fork this repo + +You should create a fork of this project in your account and work from there. You can create a fork by clicking the fork button in GitHub. + +## 2. One feature, one branch + +Work for each new feature/issue should occur in its own branch. To create a new branch from the command line: +```shell +git checkout -b my-new-feature +``` +where "my-new-feature" describes what you're working on. + +## 3. Add unit tests +If your contribution modifies existing or adds new code please add corresponding unit tests for this. + +## 4. Ensure that the build passes + +Run +```shell +go test -v +``` +and check that there are no errors. + +## 5. Add documentation for new or updated functionality + +Please review the [README.md](README.md) file in this project to see if they are impacted by your change and update them accordingly. + +## 6. Add to CHANGELOG.md + +Any notable changes should be recorded in the CHANGELOG.md following the [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) conventions. + +## 7. Submit a pull request and describe the change + +Push your changes to your branch and open a pull request against the parent repo on GitHub. The project administrators will review your pull request and respond with feedback. + +# How your contribution gets merged + +Upon pull request submission, your code will be reviewed by the maintainers. They will confirm at least the following: + +- Tests run successfully (unit, coverage, style). +- Contribution policy has been followed. + +A (human) reviewer will need to sign off on your pull request before it can be merged. diff --git a/vendor/github.com/perimeterx/marshmallow/LICENSE b/vendor/github.com/perimeterx/marshmallow/LICENSE new file mode 100644 index 00000000..8ffe8691 --- /dev/null +++ b/vendor/github.com/perimeterx/marshmallow/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2022 PerimeterX + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/vendor/github.com/perimeterx/marshmallow/README.md b/vendor/github.com/perimeterx/marshmallow/README.md new file mode 100644 index 00000000..bfa90363 --- /dev/null +++ b/vendor/github.com/perimeterx/marshmallow/README.md @@ -0,0 +1,205 @@ +# Marshmallow + +![Marshmallow Campfire](https://raw.githubusercontent.com/PerimeterX/marshmallow/assets/campfire.png) + +[![CodeQL Status](https://img.shields.io/github/actions/workflow/status/perimeterx/marshmallow/codeql.yml?branch=main&logo=github&label=CodeQL)](https://github.com/PerimeterX/marshmallow/actions/workflows/codeql.yml?query=branch%3Amain++) +[![Run Tests](https://img.shields.io/github/actions/workflow/status/perimeterx/marshmallow/go.yml?branch=main&logo=github&label=Run%20Tests)](https://github.com/PerimeterX/marshmallow/actions/workflows/go.yml?query=branch%3Amain) +[![Dependency Review](https://img.shields.io/github/actions/workflow/status/perimeterx/marshmallow/dependency-review.yml?logo=github&label=Dependency%20Review)](https://github.com/PerimeterX/marshmallow/actions/workflows/dependency-review.yml?query=branch%3Amain) +[![Go Report Card](https://goreportcard.com/badge/github.com/perimeterx/marshmallow)](https://goreportcard.com/report/github.com/perimeterx/marshmallow) +![Manual Code Coverage](https://img.shields.io/badge/coverage-92.6%25-green) +[![Go Reference](https://pkg.go.dev/badge/github.com/perimeterx/marshmallow.svg)](https://pkg.go.dev/github.com/perimeterx/marshmallow) +[![Licence](https://img.shields.io/github/license/perimeterx/marshmallow)](LICENSE) +[![Latest Release](https://img.shields.io/github/v/release/perimeterx/marshmallow)](https://github.com/PerimeterX/marshmallow/releases) +![Top Languages](https://img.shields.io/github/languages/top/perimeterx/marshmallow) +[![Issues](https://img.shields.io/github/issues-closed/perimeterx/marshmallow?color=%238250df&logo=github)](https://github.com/PerimeterX/marshmallow/issues) +[![Pull Requests](https://img.shields.io/github/issues-pr-closed-raw/perimeterx/marshmallow?color=%238250df&label=merged%20pull%20requests&logo=github)](https://github.com/PerimeterX/marshmallow/pulls) +[![Commits](https://img.shields.io/github/last-commit/perimeterx/marshmallow)](https://github.com/PerimeterX/marshmallow/commits/main) +[![Contributor Covenant](https://img.shields.io/badge/Contributor%20Covenant-2.1-4baaaa.svg)](CODE_OF_CONDUCT.md) + +marshmallow-gopher + +Marshmallow package provides a simple API to perform flexible and performant JSON unmarshalling in Go. + +Marshmallow specializes in dealing with **unstructured struct** - when some fields are known and some aren't, +with zero performance overhead nor extra coding needed. +While unmarshalling, marshmallow allows fully retaining the original data and access +it via a typed struct and a dynamic map. + +## Contents + +- [Install](#install) +- [Usage](#usage) +- [Performance Benchmark And Alternatives](#performance-benchmark-and-alternatives) +- [When Should I Use Marshmallow](#when-should-i-use-marshmallow) +- [API](#api) + +## Install + +```sh +go get -u github.com/perimeterx/marshmallow +``` + +## Usage + +```go +package main + +import ( + "fmt" + "github.com/perimeterx/marshmallow" +) + +func main() { + v := struct { + Foo string `json:"foo"` + Boo []int `json:"boo"` + }{} + result, err := marshmallow.Unmarshal([]byte(`{"foo":"bar","boo":[1,2,3],"goo":12.6}`), &v) + fmt.Printf("v=%+v, result=%+v, err=%v", v, result, err) + // Output: v={Foo:bar Boo:[1 2 3]}, result=map[boo:[1 2 3] foo:bar goo:12.6], err= +} +``` + +**Examples can be found [here](example_test.go)** + +## Performance Benchmark And Alternatives + +Marshmallow performs best when dealing with mixed data - when some fields are known and some are unknown. +More info [below](#when-should-i-use-marshmallow). +Other solutions are available for this kind of use case, each solution is explained and documented in the link below. +The full benchmark test can be found +[here](https://github.com/PerimeterX/marshmallow/blob/8c5bba9e6dc0033f4324eca554737089a99f6e5e/benchmark_test.go). + +|Benchmark|Iterations|Time/Iteration|Bytes Allocated|Allocations| +|--|--|--|--|--| +|[unmarshall twice](https://github.com/PerimeterX/marshmallow/blob/8c5bba9e6dc0033f4324eca554737089a99f6e5e/benchmark_test.go#L40)|228693|5164 ns/op|1640 B/op|51 allocs/op| +|[raw map](https://github.com/PerimeterX/marshmallow/blob/8c5bba9e6dc0033f4324eca554737089a99f6e5e/benchmark_test.go#L66)|232236|5116 ns/op|2296 B/op|53 allocs/op| +|[go codec](https://github.com/PerimeterX/marshmallow/blob/8c5bba9e6dc0033f4324eca554737089a99f6e5e/benchmark_test.go#L121)|388442|3077 ns/op|2512 B/op|37 allocs/op| +|[marshmallow](https://github.com/PerimeterX/marshmallow/blob/8c5bba9e6dc0033f4324eca554737089a99f6e5e/benchmark_test.go#L16)|626168|1853 ns/op|608 B/op|18 allocs/op| +|[marshmallow without populating struct](https://github.com/PerimeterX/marshmallow/blob/8c5bba9e6dc0033f4324eca554737089a99f6e5e/benchmark_test.go#L162)|678616|1751 ns/op|608 B/op|18 allocs/op| + +![marshmallow performance comparison](https://raw.githubusercontent.com/PerimeterX/marshmallow/e45088ca20d4ea5be4143d418d12da63a68d6dfd/performance-chart.svg) + +**Marshmallow provides the best performance (up to X3 faster) while not requiring any extra coding.** +In fact, marshmallow performs as fast as normal `json.Unmarshal` call, however, such a call causes loss of data for all +the fields that did not match the given struct. With marshmallow you never lose any data. + +|Benchmark|Iterations|Time/Iteration|Bytes Allocated|Allocations| +|--|--|--|--|--| +|[marshmallow](https://github.com/PerimeterX/marshmallow/blob/8c5bba9e6dc0033f4324eca554737089a99f6e5e/benchmark_test.go#L16)|626168|1853 ns/op|608 B/op|18 allocs/op| +|[native library](https://github.com/PerimeterX/marshmallow/blob/8c5bba9e6dc0033f4324eca554737089a99f6e5e/benchmark_test.go#L143)|652106|1845 ns/op|304 B/op|11 allocs/op| +|[marshmallow without populating struct](https://github.com/PerimeterX/marshmallow/blob/8c5bba9e6dc0033f4324eca554737089a99f6e5e/benchmark_test.go#L162)|678616|1751 ns/op|608 B/op|18 allocs/op| + +## When Should I Use Marshmallow + +Marshmallow is best suited for use cases where you are interested in all the input data, but you have predetermined +information only about a subset of it. For instance, if you plan to reference two specific fields from the data, then +iterate all the data and apply some generic logic. How does it look with the native library: + +```go +func isAllowedToDrive(data []byte) (bool, error) { + result := make(map[string]interface{}) + err := json.Unmarshal(data, &result) + if err != nil { + return false, err + } + + age, ok := result["age"] + if !ok { + return false, nil + } + a, ok := age.(float64) + if !ok { + return false, nil + } + if a < 17 { + return false, nil + } + + hasDriversLicense, ok := result["has_drivers_license"] + if !ok { + return false, nil + } + h, ok := hasDriversLicense.(bool) + if !ok { + return false, nil + } + if !h { + return false, nil + } + + for key := range result { + if strings.Contains(key, "prior_conviction") { + return false, nil + } + } + + return true, nil +} +``` + +And with marshmallow: + +```go +func isAllowedToDrive(data []byte) (bool, error) { + v := struct { + Age int `json:"age"` + HasDriversLicense bool `json:"has_drivers_license"` + }{} + result, err := marshmallow.Unmarshal(data, &v) + if err != nil { + return false, err + } + + if v.Age < 17 || !v.HasDriversLicense { + return false, nil + } + + for key := range result { + if strings.Contains(key, "prior_conviction") { + return false, nil + } + } + + return true, nil +} +``` + +## API + +Marshmallow exposes two main API functions - +[Unmarshal](https://github.com/PerimeterX/marshmallow/blob/0e0218ab860be8a4b5f57f5ff239f281c250c5da/unmarshal.go#L27) +and +[UnmarshalFromJSONMap](https://github.com/PerimeterX/marshmallow/blob/0e0218ab860be8a4b5f57f5ff239f281c250c5da/unmarshal_from_json_map.go#L37). +While unmarshalling, marshmallow supports the following optional options: + +* Setting the mode for handling invalid data using the [WithMode](https://github.com/PerimeterX/marshmallow/blob/0e0218ab860be8a4b5f57f5ff239f281c250c5da/options.go#L30) function. +* Excluding known fields from the result map using the [WithExcludeKnownFieldsFromMap](https://github.com/PerimeterX/marshmallow/blob/457669ae9973895584f2636eabfc104140d3b700/options.go#L50) function. +* Skipping struct population to boost performance using the [WithSkipPopulateStruct](https://github.com/PerimeterX/marshmallow/blob/0e0218ab860be8a4b5f57f5ff239f281c250c5da/options.go#L41) function. + +In order to capture unknown nested fields, structs must implement [JSONDataErrorHandler](https://github.com/PerimeterX/marshmallow/blob/195c994aa6e3e0852601ad9cf65bcddef0dd7479/options.go#L76). +More info [here](https://github.com/PerimeterX/marshmallow/issues/15). + +Marshmallow also supports caching of refection information using +[EnableCache](https://github.com/PerimeterX/marshmallow/blob/d3500aa5b0f330942b178b155da933c035dd3906/cache.go#L40) +and +[EnableCustomCache](https://github.com/PerimeterX/marshmallow/blob/d3500aa5b0f330942b178b155da933c035dd3906/cache.go#L35). + +## Contact and Contribute + +Reporting issues and requesting features may be done in our [GitHub issues page](https://github.com/PerimeterX/marshmallow/issues). +Discussions may be conducted in our [GitHub discussions page](https://github.com/PerimeterX/marshmallow/discussions). +For any further questions or comments you can reach us out at [open-source@humansecurity.com](mailto:open-source@humansecurity.com). + +Any type of contribution is warmly welcome and appreciated ❤️ +Please read our [contribution](CONTRIBUTING.md) guide for more info. + +If you're looking for something to get started with, tou can always follow our [issues page](https://github.com/PerimeterX/marshmallow/issues) and look for +[good first issue](https://github.com/PerimeterX/marshmallow/issues?q=is%3Aissue+is%3Aopen+label%3A%22good+first+issue%22) and +[help wanted](https://github.com/PerimeterX/marshmallow/issues?q=is%3Aissue+label%3A%22help+wanted%22+is%3Aopen) labels. + +## Marshmallow Logo + +Marshmallow logo and assets by [Adva Rom](https://www.linkedin.com/in/adva-rom-7a6738127/) are licensed under a Creative Commons Attribution 4.0 International License.
+ +![Marshmallow Logo](https://raw.githubusercontent.com/PerimeterX/marshmallow/assets/marshmallow.png) diff --git a/vendor/github.com/perimeterx/marshmallow/cache.go b/vendor/github.com/perimeterx/marshmallow/cache.go new file mode 100644 index 00000000..a67cea6d --- /dev/null +++ b/vendor/github.com/perimeterx/marshmallow/cache.go @@ -0,0 +1,63 @@ +// Copyright 2022 PerimeterX. All rights reserved. +// Use of this source code is governed by a MIT style +// license that can be found in the LICENSE file. + +package marshmallow + +import ( + "reflect" + "sync" +) + +// Cache allows unmarshalling to use a cached version of refection information about types. +// Cache interface follows the implementation of sync.Map, but you may wrap any cache implementation +// to match it. This allows you to control max cache size, eviction policies and any other caching aspect. +type Cache interface { + // Load returns the value stored in the map for a key, or nil if no value is present. + // The ok result indicates whether value was found in the map. + Load(key interface{}) (interface{}, bool) + // Store sets the value for a key. + Store(key, value interface{}) +} + +// EnableCustomCache enables unmarshalling cache. It allows reuse of refection information about types needed +// to perform the unmarshalling. A use of such cache can boost up unmarshalling by x1.4. +// Check out benchmark_test.go for an example. +// +// EnableCustomCache is not thread safe! Do not use it while performing unmarshalling, or it will +// cause an unsafe race condition. Typically, EnableCustomCache should be called once when the process boots. +// +// Caching is disabled by default. The use of this function allows enabling it and controlling the +// behavior of the cache. Typically, the use of sync.Map should be good enough. The caching mechanism +// stores a single map per struct type. If you plan to unmarshal a huge amount of distinct +// struct it may get to consume a lot of resources, in which case you have the control to choose +// the caching implementation you like and its setup. +func EnableCustomCache(c Cache) { + cache = c +} + +// EnableCache enables unmarshalling cache with default implementation. More info at EnableCustomCache. +func EnableCache() { + EnableCustomCache(&sync.Map{}) +} + +var cache Cache + +func cacheLookup(t reflect.Type) map[string]reflectionInfo { + if cache == nil { + return nil + } + value, exists := cache.Load(t) + if !exists { + return nil + } + result, _ := value.(map[string]reflectionInfo) + return result +} + +func cacheStore(t reflect.Type, fields map[string]reflectionInfo) { + if cache == nil { + return + } + cache.Store(t, fields) +} diff --git a/vendor/github.com/perimeterx/marshmallow/doc.go b/vendor/github.com/perimeterx/marshmallow/doc.go new file mode 100644 index 00000000..c179e657 --- /dev/null +++ b/vendor/github.com/perimeterx/marshmallow/doc.go @@ -0,0 +1,10 @@ +/* +Package marshmallow provides a simple API to perform flexible and performant JSON unmarshalling. +Unlike other packages, marshmallow supports unmarshalling of some known and some unknown fields +with zero performance overhead nor extra coding needed. While unmarshalling, +marshmallow allows fully retaining the original data and access it via a typed struct and a +dynamic map. + +https://github.com/perimeterx/marshmallow +*/ +package marshmallow diff --git a/vendor/github.com/perimeterx/marshmallow/errors.go b/vendor/github.com/perimeterx/marshmallow/errors.go new file mode 100644 index 00000000..c4d341cc --- /dev/null +++ b/vendor/github.com/perimeterx/marshmallow/errors.go @@ -0,0 +1,101 @@ +// Copyright 2022 PerimeterX. All rights reserved. +// Use of this source code is governed by a MIT style +// license that can be found in the LICENSE file. + +package marshmallow + +import ( + "errors" + "fmt" + "github.com/mailru/easyjson/jlexer" + "reflect" + "strings" +) + +var ( + // ErrInvalidInput indicates the input JSON is invalid + ErrInvalidInput = errors.New("invalid JSON input") + + // ErrInvalidValue indicates the target struct has invalid type + ErrInvalidValue = errors.New("unexpected non struct value") +) + +// MultipleLexerError indicates one or more unmarshalling errors during JSON bytes decode +type MultipleLexerError struct { + Errors []*jlexer.LexerError +} + +func (m *MultipleLexerError) Error() string { + errs := make([]string, len(m.Errors)) + for i, lexerError := range m.Errors { + errs[i] = lexerError.Error() + } + return strings.Join(errs, ", ") +} + +// MultipleError indicates one or more unmarshalling errors during JSON map decode +type MultipleError struct { + Errors []error +} + +func (m *MultipleError) Error() string { + errs := make([]string, len(m.Errors)) + for i, lexerError := range m.Errors { + errs[i] = lexerError.Error() + } + return strings.Join(errs, ", ") +} + +// ParseError indicates a JSON map decode error +type ParseError struct { + Reason string + Path string +} + +func (p *ParseError) Error() string { + return fmt.Sprintf("parse error: %s in %s", p.Reason, p.Path) +} + +func newUnexpectedTypeParseError(expectedType reflect.Type, path []string) *ParseError { + return &ParseError{ + Reason: fmt.Sprintf("expected type %s", externalTypeName(expectedType)), + Path: strings.Join(path, "."), + } +} + +func newUnsupportedTypeParseError(unsupportedType reflect.Type, path []string) *ParseError { + return &ParseError{ + Reason: fmt.Sprintf("unsupported type %s", externalTypeName(unsupportedType)), + Path: strings.Join(path, "."), + } +} + +func addUnexpectedTypeLexerError(lexer *jlexer.Lexer, expectedType reflect.Type) { + lexer.AddNonFatalError(fmt.Errorf("expected type %s", externalTypeName(expectedType))) +} + +func addUnsupportedTypeLexerError(lexer *jlexer.Lexer, unsupportedType reflect.Type) { + lexer.AddNonFatalError(fmt.Errorf("unsupported type %s", externalTypeName(unsupportedType))) +} + +func externalTypeName(t reflect.Type) string { + switch t.Kind() { + case reflect.String: + return "string" + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Uint, + reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr, reflect.Float32, + reflect.Float64, reflect.Complex64, reflect.Complex128: + return "number" + case reflect.Bool: + return "boolean" + case reflect.Array, reflect.Slice: + return "array" + case reflect.Interface: + return "any" + case reflect.Map, reflect.Struct: + return "object" + case reflect.Ptr: + return externalTypeName(t.Elem()) + } + return "invalid" +} diff --git a/vendor/github.com/perimeterx/marshmallow/options.go b/vendor/github.com/perimeterx/marshmallow/options.go new file mode 100644 index 00000000..ff97d336 --- /dev/null +++ b/vendor/github.com/perimeterx/marshmallow/options.go @@ -0,0 +1,96 @@ +// Copyright 2022 PerimeterX. All rights reserved. +// Use of this source code is governed by a MIT style +// license that can be found in the LICENSE file. + +package marshmallow + +// Mode dictates the unmarshalling mode. +// Each mode is self documented below. +type Mode uint8 + +const ( + // ModeFailOnFirstError is the default mode. It makes unmarshalling terminate + // immediately on any kind of error. This error will then be returned. + ModeFailOnFirstError Mode = iota + + // ModeAllowMultipleErrors mode makes unmarshalling keep decoding even if + // errors are encountered. In case of such error, the erroneous value will be omitted from the result. + // Eventually, all errors will all be returned, alongside the partial result. + ModeAllowMultipleErrors + + // ModeFailOverToOriginalValue mode makes unmarshalling keep decoding even if + // errors are encountered. In case of such error, the original external value be placed in the + // result data, even though it does not meet the schematic requirements. + // Eventually, all errors will be returned, alongside the full result. Note that the result map + // will contain values that do not match the struct schema. + ModeFailOverToOriginalValue +) + +// WithMode is an UnmarshalOption function to set the unmarshalling mode. +func WithMode(mode Mode) UnmarshalOption { + return func(options *unmarshalOptions) { + options.mode = mode + } +} + +// WithSkipPopulateStruct is an UnmarshalOption function to set the skipPopulateStruct option. +// Skipping populate struct is set to false by default. +// If you do not intend to use the struct value once unmarshalling is finished, set this +// option to true to boost performance. This would mean the struct fields will not be set +// with values, but rather it will only be used as the target schema when populating the result map. +func WithSkipPopulateStruct(skipPopulateStruct bool) UnmarshalOption { + return func(options *unmarshalOptions) { + options.skipPopulateStruct = skipPopulateStruct + } +} + +// WithExcludeKnownFieldsFromMap is an UnmarshalOption function to set the excludeKnownFieldsFromMap option. +// Exclude known fields flag is set to false by default. +// When the flag is set to true, fields specified in the input struct (known fields) will be excluded from the result map +func WithExcludeKnownFieldsFromMap(excludeKnownFields bool) UnmarshalOption { + return func(options *unmarshalOptions) { + options.excludeKnownFieldsFromMap = excludeKnownFields + } +} + +type UnmarshalOption func(*unmarshalOptions) + +type unmarshalOptions struct { + mode Mode + skipPopulateStruct bool + excludeKnownFieldsFromMap bool +} + +func buildUnmarshalOptions(options []UnmarshalOption) *unmarshalOptions { + result := &unmarshalOptions{} + for _, option := range options { + option(result) + } + return result +} + +// JSONDataErrorHandler allow types to handle JSON data as maps. +// Types should implement this interface if they wish to act on the map representation of parsed JSON input. +// This is mainly used to allow nested objects to capture unknown fields and leverage marshmallow's abilities. +// If HandleJSONData returns an error, it will be propagated as an unmarshal error +type JSONDataErrorHandler interface { + HandleJSONData(data map[string]interface{}) error +} + +// Deprecated: use JSONDataErrorHandler instead +type JSONDataHandler interface { + HandleJSONData(data map[string]interface{}) +} + +func asJSONDataHandler(value interface{}) (func(map[string]interface{}) error, bool) { + if handler, ok := value.(JSONDataErrorHandler); ok { + return handler.HandleJSONData, true + } + if handler, ok := value.(JSONDataHandler); ok { + return func(m map[string]interface{}) error { + handler.HandleJSONData(m) + return nil + }, true + } + return nil, false +} diff --git a/vendor/github.com/perimeterx/marshmallow/reflection.go b/vendor/github.com/perimeterx/marshmallow/reflection.go new file mode 100644 index 00000000..9b7d88ce --- /dev/null +++ b/vendor/github.com/perimeterx/marshmallow/reflection.go @@ -0,0 +1,197 @@ +// Copyright 2022 PerimeterX. All rights reserved. +// Use of this source code is governed by a MIT style +// license that can be found in the LICENSE file. + +package marshmallow + +import ( + "encoding/json" + "reflect" + "strings" +) + +var unmarshalerType = reflect.TypeOf((*json.Unmarshaler)(nil)).Elem() + +type reflectionInfo struct { + path []int + t reflect.Type +} + +func (r reflectionInfo) field(target reflect.Value) reflect.Value { + current := target + for _, i := range r.path { + current = current.Field(i) + } + return current +} + +func mapStructFields(target interface{}) map[string]reflectionInfo { + t := reflectStructType(target) + result := cacheLookup(t) + if result != nil { + return result + } + result = make(map[string]reflectionInfo, t.NumField()) + mapTypeFields(t, result, nil) + cacheStore(t, result) + return result +} + +func mapTypeFields(t reflect.Type, result map[string]reflectionInfo, path []int) { + num := t.NumField() + for i := 0; i < num; i++ { + field := t.Field(i) + fieldPath := append(path, i) + if field.Anonymous && field.Type.Kind() == reflect.Struct { + mapTypeFields(field.Type, result, fieldPath) + continue + } + name := field.Tag.Get("json") + if name == "" || name == "-" { + continue + } + if index := strings.Index(name, ","); index > -1 { + name = name[:index] + } + result[name] = reflectionInfo{ + path: fieldPath, + t: field.Type, + } + } +} + +func reflectStructValue(target interface{}) reflect.Value { + v := reflect.ValueOf(target) + for v.Kind() == reflect.Ptr { + v = v.Elem() + } + return v +} + +func reflectStructType(target interface{}) reflect.Type { + t := reflect.TypeOf(target) + for t.Kind() == reflect.Ptr { + t = t.Elem() + } + return t +} + +var primitiveConverters = map[reflect.Kind]func(v interface{}) (interface{}, bool){ + reflect.Bool: func(v interface{}) (interface{}, bool) { + res, ok := v.(bool) + return res, ok + }, + reflect.Int: func(v interface{}) (interface{}, bool) { + res, ok := v.(float64) + if ok { + return int(res), true + } + return v, false + }, + reflect.Int8: func(v interface{}) (interface{}, bool) { + res, ok := v.(float64) + if ok { + return int8(res), true + } + return v, false + }, + reflect.Int16: func(v interface{}) (interface{}, bool) { + res, ok := v.(float64) + if ok { + return int16(res), true + } + return v, false + }, + reflect.Int32: func(v interface{}) (interface{}, bool) { + res, ok := v.(float64) + if ok { + return int32(res), true + } + return v, false + }, + reflect.Int64: func(v interface{}) (interface{}, bool) { + res, ok := v.(float64) + if ok { + return int64(res), true + } + return v, false + }, + reflect.Uint: func(v interface{}) (interface{}, bool) { + res, ok := v.(float64) + if ok { + return uint(res), true + } + return v, false + }, + reflect.Uint8: func(v interface{}) (interface{}, bool) { + res, ok := v.(float64) + if ok { + return uint8(res), true + } + return v, false + }, + reflect.Uint16: func(v interface{}) (interface{}, bool) { + res, ok := v.(float64) + if ok { + return uint16(res), true + } + return v, false + }, + reflect.Uint32: func(v interface{}) (interface{}, bool) { + res, ok := v.(float64) + if ok { + return uint32(res), true + } + return v, false + }, + reflect.Uint64: func(v interface{}) (interface{}, bool) { + res, ok := v.(float64) + if ok { + return uint64(res), true + } + return v, false + }, + reflect.Float32: func(v interface{}) (interface{}, bool) { + res, ok := v.(float64) + if ok { + return float32(res), true + } + return v, false + }, + reflect.Float64: func(v interface{}) (interface{}, bool) { + res, ok := v.(float64) + if ok { + return res, true + } + return v, false + }, + reflect.Interface: func(v interface{}) (interface{}, bool) { + return v, true + }, + reflect.String: func(v interface{}) (interface{}, bool) { + res, ok := v.(string) + return res, ok + }, +} + +func assignValue(field reflect.Value, value interface{}) { + if value == nil { + return + } + reflectValue := reflect.ValueOf(value) + if reflectValue.Type().AssignableTo(field.Type()) { + field.Set(reflectValue) + } +} + +func isValidValue(v interface{}) bool { + value := reflect.ValueOf(v) + return value.Kind() == reflect.Ptr && value.Elem().Kind() == reflect.Struct && !value.IsNil() +} + +func safeReflectValue(t reflect.Type, v interface{}) reflect.Value { + if v == nil { + return reflect.Zero(t) + } + return reflect.ValueOf(v) +} diff --git a/vendor/github.com/perimeterx/marshmallow/unmarshal.go b/vendor/github.com/perimeterx/marshmallow/unmarshal.go new file mode 100644 index 00000000..160ea30c --- /dev/null +++ b/vendor/github.com/perimeterx/marshmallow/unmarshal.go @@ -0,0 +1,383 @@ +// Copyright 2022 PerimeterX. All rights reserved. +// Use of this source code is governed by a MIT style +// license that can be found in the LICENSE file. + +package marshmallow + +import ( + "encoding/json" + "github.com/mailru/easyjson/jlexer" + "reflect" +) + +// Unmarshal parses the JSON-encoded object in data and stores the values +// in the struct pointed to by v and in the returned map. +// If v is nil or not a pointer to a struct, Unmarshal returns an ErrInvalidValue. +// If data is not a valid JSON or not a JSON object Unmarshal returns an ErrInvalidInput. +// +// Unmarshal follows the rules of json.Unmarshal with the following exceptions: +// - All input fields are stored in the resulting map, including fields that do not exist in the +// struct pointed by v. +// - Unmarshal only operates on JSON object inputs. It will reject all other types of input +// by returning ErrInvalidInput. +// - Unmarshal only operates on struct values. It will reject all other types of v by +// returning ErrInvalidValue. +// - Unmarshal supports three types of Mode values. Each mode is self documented and affects +// how Unmarshal behaves. +func Unmarshal(data []byte, v interface{}, options ...UnmarshalOption) (map[string]interface{}, error) { + if !isValidValue(v) { + return nil, ErrInvalidValue + } + opts := buildUnmarshalOptions(options) + useMultipleErrors := opts.mode == ModeAllowMultipleErrors || opts.mode == ModeFailOverToOriginalValue + d := &decoder{options: opts, lexer: &jlexer.Lexer{Data: data, UseMultipleErrors: useMultipleErrors}} + result := make(map[string]interface{}) + if d.lexer.IsNull() { + d.lexer.Skip() + } else if !d.lexer.IsDelim('{') { + return nil, ErrInvalidInput + } else { + d.populateStruct(false, v, result) + } + d.lexer.Consumed() + if useMultipleErrors { + errors := d.lexer.GetNonFatalErrors() + if len(errors) == 0 { + return result, nil + } + return result, &MultipleLexerError{Errors: errors} + } + err := d.lexer.Error() + if err != nil { + return nil, err + } + return result, nil +} + +type decoder struct { + options *unmarshalOptions + lexer *jlexer.Lexer +} + +func (d *decoder) populateStruct(forcePopulate bool, structInstance interface{}, result map[string]interface{}) (interface{}, bool) { + doPopulate := !d.options.skipPopulateStruct || forcePopulate + var structValue reflect.Value + if doPopulate { + structValue = reflectStructValue(structInstance) + } + fields := mapStructFields(structInstance) + var clone map[string]interface{} + if d.options.mode == ModeFailOverToOriginalValue { + clone = make(map[string]interface{}, len(fields)) + } + d.lexer.Delim('{') + for !d.lexer.IsDelim('}') { + key := d.lexer.UnsafeFieldName(false) + d.lexer.WantColon() + refInfo, exists := fields[key] + if exists { + value, isValidType := d.valueByReflectType(refInfo.t) + if isValidType { + if value != nil && doPopulate { + field := refInfo.field(structValue) + assignValue(field, value) + } + if !d.options.excludeKnownFieldsFromMap { + if result != nil { + result[key] = value + } + if clone != nil { + clone[key] = value + } + } + } else { + switch d.options.mode { + case ModeFailOnFirstError: + return nil, false + case ModeFailOverToOriginalValue: + if !forcePopulate { + result[key] = value + } else { + clone[key] = value + d.lexer.WantComma() + d.drainLexerMap(clone) + return clone, false + } + } + } + } else { + value := d.lexer.Interface() + if result != nil { + result[key] = value + } + if clone != nil { + clone[key] = value + } + } + d.lexer.WantComma() + } + d.lexer.Delim('}') + return structInstance, true +} + +func (d *decoder) valueByReflectType(t reflect.Type) (interface{}, bool) { + if t.Implements(unmarshalerType) { + result := reflect.New(t.Elem()).Interface() + d.valueFromCustomUnmarshaler(result.(json.Unmarshaler)) + return result, true + } + if reflect.PtrTo(t).Implements(unmarshalerType) { + value := reflect.New(t) + d.valueFromCustomUnmarshaler(value.Interface().(json.Unmarshaler)) + return value.Elem().Interface(), true + } + kind := t.Kind() + if converter := primitiveConverters[kind]; converter != nil { + v := d.lexer.Interface() + if v == nil { + return nil, true + } + converted, ok := converter(v) + if !ok { + addUnexpectedTypeLexerError(d.lexer, t) + return v, false + } + return converted, true + } + switch kind { + case reflect.Slice: + return d.buildSlice(t) + case reflect.Array: + return d.buildArray(t) + case reflect.Map: + return d.buildMap(t) + case reflect.Struct: + value, valid := d.buildStruct(t) + if value == nil { + return nil, valid + } + if !valid { + return value, false + } + return reflect.ValueOf(value).Elem().Interface(), valid + case reflect.Ptr: + if t.Elem().Kind() == reflect.Struct { + return d.buildStruct(t.Elem()) + } + value, valid := d.valueByReflectType(t.Elem()) + if value == nil { + return nil, valid + } + if !valid { + return value, false + } + result := reflect.New(reflect.TypeOf(value)) + result.Elem().Set(reflect.ValueOf(value)) + return result.Interface(), valid + } + addUnsupportedTypeLexerError(d.lexer, t) + return nil, false +} + +func (d *decoder) buildSlice(sliceType reflect.Type) (interface{}, bool) { + if d.lexer.IsNull() { + d.lexer.Skip() + return nil, true + } + if !d.lexer.IsDelim('[') { + addUnexpectedTypeLexerError(d.lexer, sliceType) + return d.lexer.Interface(), false + } + elemType := sliceType.Elem() + d.lexer.Delim('[') + var sliceValue reflect.Value + if !d.lexer.IsDelim(']') { + sliceValue = reflect.MakeSlice(sliceType, 0, 4) + } else { + sliceValue = reflect.MakeSlice(sliceType, 0, 0) + } + for !d.lexer.IsDelim(']') { + current, valid := d.valueByReflectType(elemType) + if !valid { + if d.options.mode != ModeFailOverToOriginalValue { + d.drainLexerArray(nil) + return nil, true + } + result := d.cloneReflectArray(sliceValue, -1) + result = append(result, current) + return d.drainLexerArray(result), true + } + sliceValue = reflect.Append(sliceValue, safeReflectValue(elemType, current)) + d.lexer.WantComma() + } + d.lexer.Delim(']') + return sliceValue.Interface(), true +} + +func (d *decoder) buildArray(arrayType reflect.Type) (interface{}, bool) { + if d.lexer.IsNull() { + d.lexer.Skip() + return nil, true + } + if !d.lexer.IsDelim('[') { + addUnexpectedTypeLexerError(d.lexer, arrayType) + return d.lexer.Interface(), false + } + elemType := arrayType.Elem() + arrayValue := reflect.New(arrayType).Elem() + d.lexer.Delim('[') + for i := 0; !d.lexer.IsDelim(']'); i++ { + current, valid := d.valueByReflectType(elemType) + if !valid { + if d.options.mode != ModeFailOverToOriginalValue { + d.drainLexerArray(nil) + return nil, true + } + result := d.cloneReflectArray(arrayValue, i) + result = append(result, current) + return d.drainLexerArray(result), true + } + if current != nil { + arrayValue.Index(i).Set(reflect.ValueOf(current)) + } + d.lexer.WantComma() + } + d.lexer.Delim(']') + return arrayValue.Interface(), true +} + +func (d *decoder) buildMap(mapType reflect.Type) (interface{}, bool) { + if d.lexer.IsNull() { + d.lexer.Skip() + return nil, true + } + if !d.lexer.IsDelim('{') { + addUnexpectedTypeLexerError(d.lexer, mapType) + return d.lexer.Interface(), false + } + d.lexer.Delim('{') + keyType := mapType.Key() + valueType := mapType.Elem() + mapValue := reflect.MakeMap(mapType) + for !d.lexer.IsDelim('}') { + key, valid := d.valueByReflectType(keyType) + if !valid { + if d.options.mode != ModeFailOverToOriginalValue { + d.lexer.WantColon() + d.lexer.Interface() + d.lexer.WantComma() + d.drainLexerMap(make(map[string]interface{})) + return nil, true + } + strKey, _ := key.(string) + d.lexer.WantColon() + value := d.lexer.Interface() + result := d.cloneReflectMap(mapValue) + result[strKey] = value + d.lexer.WantComma() + d.drainLexerMap(result) + return result, true + } + d.lexer.WantColon() + value, valid := d.valueByReflectType(valueType) + if !valid { + if d.options.mode != ModeFailOverToOriginalValue { + d.lexer.WantComma() + d.drainLexerMap(make(map[string]interface{})) + return nil, true + } + strKey, _ := key.(string) + result := d.cloneReflectMap(mapValue) + result[strKey] = value + d.lexer.WantComma() + d.drainLexerMap(result) + return result, true + } + mapValue.SetMapIndex(safeReflectValue(keyType, key), safeReflectValue(valueType, value)) + d.lexer.WantComma() + } + d.lexer.Delim('}') + return mapValue.Interface(), true +} + +func (d *decoder) buildStruct(structType reflect.Type) (interface{}, bool) { + if d.lexer.IsNull() { + d.lexer.Skip() + return nil, true + } + if !d.lexer.IsDelim('{') { + addUnexpectedTypeLexerError(d.lexer, structType) + return d.lexer.Interface(), false + } + value := reflect.New(structType).Interface() + handler, ok := asJSONDataHandler(value) + if !ok { + return d.populateStruct(true, value, nil) + } + data := make(map[string]interface{}) + result, valid := d.populateStruct(true, value, data) + if !valid { + return result, false + } + err := handler(data) + if err != nil { + d.lexer.AddNonFatalError(err) + return result, false + } + return result, true +} + +func (d *decoder) valueFromCustomUnmarshaler(unmarshaler json.Unmarshaler) { + data := d.lexer.Raw() + if !d.lexer.Ok() { + return + } + err := unmarshaler.UnmarshalJSON(data) + if err != nil { + d.lexer.AddNonFatalError(err) + } +} + +func (d *decoder) cloneReflectArray(value reflect.Value, length int) []interface{} { + if length == -1 { + length = value.Len() + } + result := make([]interface{}, length) + for i := 0; i < length; i++ { + result[i] = value.Index(i).Interface() + } + return result +} + +func (d *decoder) cloneReflectMap(mapValue reflect.Value) map[string]interface{} { + l := mapValue.Len() + result := make(map[string]interface{}, l) + for _, key := range mapValue.MapKeys() { + value := mapValue.MapIndex(key) + strKey, _ := key.Interface().(string) + result[strKey] = value.Interface() + } + return result +} + +func (d *decoder) drainLexerArray(target []interface{}) interface{} { + d.lexer.WantComma() + for !d.lexer.IsDelim(']') { + current := d.lexer.Interface() + target = append(target, current) + d.lexer.WantComma() + } + d.lexer.Delim(']') + return target +} + +func (d *decoder) drainLexerMap(target map[string]interface{}) { + for !d.lexer.IsDelim('}') { + key := d.lexer.String() + d.lexer.WantColon() + value := d.lexer.Interface() + target[key] = value + d.lexer.WantComma() + } + d.lexer.Delim('}') +} diff --git a/vendor/github.com/perimeterx/marshmallow/unmarshal_from_json_map.go b/vendor/github.com/perimeterx/marshmallow/unmarshal_from_json_map.go new file mode 100644 index 00000000..0907f8f8 --- /dev/null +++ b/vendor/github.com/perimeterx/marshmallow/unmarshal_from_json_map.go @@ -0,0 +1,295 @@ +// Copyright 2022 PerimeterX. All rights reserved. +// Use of this source code is governed by a MIT style +// license that can be found in the LICENSE file. + +package marshmallow + +import ( + "reflect" +) + +// UnmarshalerFromJSONMap is the interface implemented by types +// that can unmarshal a JSON description of themselves. +// In case you want to implement custom unmarshalling, json.Unmarshaler only supports +// receiving the data as []byte. However, while unmarshalling from JSON map, +// the data is not available as a raw []byte and converting to it will significantly +// hurt performance. Thus, if you wish to implement a custom unmarshalling on a type +// that is being unmarshalled from a JSON map, you need to implement +// UnmarshalerFromJSONMap interface. +type UnmarshalerFromJSONMap interface { + UnmarshalJSONFromMap(data interface{}) error +} + +// UnmarshalFromJSONMap parses the JSON map data and stores the values +// in the struct pointed to by v and in the returned map. +// If v is nil or not a pointer to a struct, UnmarshalFromJSONMap returns an ErrInvalidValue. +// +// UnmarshalFromJSONMap follows the rules of json.Unmarshal with the following exceptions: +// - All input fields are stored in the resulting map, including fields that do not exist in the +// struct pointed by v. +// - UnmarshalFromJSONMap receive a JSON map instead of raw bytes. The given input map is assumed +// to be a JSON map, meaning it should only contain the following types: bool, string, float64, +// []interface, and map[string]interface{}. Other types will cause decoding to return unexpected results. +// - UnmarshalFromJSONMap only operates on struct values. It will reject all other types of v by +// returning ErrInvalidValue. +// - UnmarshalFromJSONMap supports three types of Mode values. Each mode is self documented and affects +// how UnmarshalFromJSONMap behaves. +func UnmarshalFromJSONMap(data map[string]interface{}, v interface{}, options ...UnmarshalOption) (map[string]interface{}, error) { + if !isValidValue(v) { + return nil, ErrInvalidValue + } + opts := buildUnmarshalOptions(options) + d := &mapDecoder{options: opts} + result := make(map[string]interface{}) + if data != nil { + d.populateStruct(false, nil, data, v, result) + } + if opts.mode == ModeAllowMultipleErrors || opts.mode == ModeFailOverToOriginalValue { + if len(d.errs) == 0 { + return result, nil + } + return result, &MultipleError{Errors: d.errs} + } + if d.err != nil { + return nil, d.err + } + return result, nil +} + +var unmarshalerFromJSONMapType = reflect.TypeOf((*UnmarshalerFromJSONMap)(nil)).Elem() + +type mapDecoder struct { + options *unmarshalOptions + err error + errs []error +} + +func (m *mapDecoder) populateStruct(forcePopulate bool, path []string, data map[string]interface{}, structInstance interface{}, result map[string]interface{}) (interface{}, bool) { + doPopulate := !m.options.skipPopulateStruct || forcePopulate + var structValue reflect.Value + if doPopulate { + structValue = reflectStructValue(structInstance) + } + fields := mapStructFields(structInstance) + for key, inputValue := range data { + refInfo, exists := fields[key] + if exists { + value, isValidType := m.valueByReflectType(append(path, key), inputValue, refInfo.t) + if isValidType { + if value != nil && doPopulate { + field := refInfo.field(structValue) + assignValue(field, value) + } + if !m.options.excludeKnownFieldsFromMap { + if result != nil { + result[key] = value + } + } + } else { + switch m.options.mode { + case ModeFailOnFirstError: + return nil, false + case ModeFailOverToOriginalValue: + if !forcePopulate { + result[key] = value + } else { + return data, false + } + } + } + } else { + if result != nil { + result[key] = inputValue + } + } + } + return structInstance, true +} + +func (m *mapDecoder) valueByReflectType(path []string, v interface{}, t reflect.Type) (interface{}, bool) { + if t.Implements(unmarshalerFromJSONMapType) { + result := reflect.New(t.Elem()).Interface() + m.valueFromCustomUnmarshaler(v, result.(UnmarshalerFromJSONMap)) + return result, true + } + if reflect.PtrTo(t).Implements(unmarshalerFromJSONMapType) { + value := reflect.New(t) + m.valueFromCustomUnmarshaler(v, value.Interface().(UnmarshalerFromJSONMap)) + return value.Elem().Interface(), true + } + kind := t.Kind() + if converter := primitiveConverters[kind]; converter != nil { + if v == nil { + return nil, true + } + converted, ok := converter(v) + if !ok { + m.addError(newUnexpectedTypeParseError(t, path)) + return v, false + } + return converted, true + } + switch kind { + case reflect.Slice: + return m.buildSlice(path, v, t) + case reflect.Array: + return m.buildArray(path, v, t) + case reflect.Map: + return m.buildMap(path, v, t) + case reflect.Struct: + value, valid := m.buildStruct(path, v, t) + if value == nil { + return nil, valid + } + if !valid { + return value, false + } + return reflect.ValueOf(value).Elem().Interface(), valid + case reflect.Ptr: + if t.Elem().Kind() == reflect.Struct { + return m.buildStruct(path, v, t.Elem()) + } + value, valid := m.valueByReflectType(path, v, t.Elem()) + if value == nil { + return nil, valid + } + if !valid { + return value, false + } + result := reflect.New(reflect.TypeOf(value)) + result.Elem().Set(reflect.ValueOf(value)) + return result.Interface(), valid + } + m.addError(newUnsupportedTypeParseError(t, path)) + return nil, false +} + +func (m *mapDecoder) buildSlice(path []string, v interface{}, sliceType reflect.Type) (interface{}, bool) { + if v == nil { + return nil, true + } + arr, ok := v.([]interface{}) + if !ok { + m.addError(newUnexpectedTypeParseError(sliceType, path)) + return v, false + } + elemType := sliceType.Elem() + var sliceValue reflect.Value + if len(arr) > 0 { + sliceValue = reflect.MakeSlice(sliceType, 0, 4) + } else { + sliceValue = reflect.MakeSlice(sliceType, 0, 0) + } + for _, element := range arr { + current, valid := m.valueByReflectType(path, element, elemType) + if !valid { + if m.options.mode != ModeFailOverToOriginalValue { + return nil, true + } + return v, true + } + sliceValue = reflect.Append(sliceValue, safeReflectValue(elemType, current)) + } + return sliceValue.Interface(), true +} + +func (m *mapDecoder) buildArray(path []string, v interface{}, arrayType reflect.Type) (interface{}, bool) { + if v == nil { + return nil, true + } + arr, ok := v.([]interface{}) + if !ok { + m.addError(newUnexpectedTypeParseError(arrayType, path)) + return v, false + } + elemType := arrayType.Elem() + arrayValue := reflect.New(arrayType).Elem() + for i, element := range arr { + current, valid := m.valueByReflectType(path, element, elemType) + if !valid { + if m.options.mode != ModeFailOverToOriginalValue { + return nil, true + } + return v, true + } + if current != nil { + arrayValue.Index(i).Set(reflect.ValueOf(current)) + } + } + return arrayValue.Interface(), true +} + +func (m *mapDecoder) buildMap(path []string, v interface{}, mapType reflect.Type) (interface{}, bool) { + if v == nil { + return nil, true + } + mp, ok := v.(map[string]interface{}) + if !ok { + m.addError(newUnexpectedTypeParseError(mapType, path)) + return v, false + } + keyType := mapType.Key() + valueType := mapType.Elem() + mapValue := reflect.MakeMap(mapType) + for inputKey, inputValue := range mp { + keyPath := append(path, inputKey) + key, valid := m.valueByReflectType(keyPath, inputKey, keyType) + if !valid { + if m.options.mode != ModeFailOverToOriginalValue { + return nil, true + } + return v, true + } + value, valid := m.valueByReflectType(keyPath, inputValue, valueType) + if !valid { + if m.options.mode != ModeFailOverToOriginalValue { + return nil, true + } + return v, true + } + mapValue.SetMapIndex(safeReflectValue(keyType, key), safeReflectValue(valueType, value)) + } + return mapValue.Interface(), true +} + +func (m *mapDecoder) buildStruct(path []string, v interface{}, structType reflect.Type) (interface{}, bool) { + if v == nil { + return nil, true + } + mp, ok := v.(map[string]interface{}) + if !ok { + m.addError(newUnexpectedTypeParseError(structType, path)) + return v, false + } + value := reflect.New(structType).Interface() + handler, ok := asJSONDataHandler(value) + if !ok { + return m.populateStruct(true, path, mp, value, nil) + } + data := make(map[string]interface{}) + result, valid := m.populateStruct(true, path, mp, value, data) + if !valid { + return result, false + } + err := handler(data) + if err != nil { + m.addError(err) + return result, false + } + return result, true +} + +func (m *mapDecoder) valueFromCustomUnmarshaler(data interface{}, unmarshaler UnmarshalerFromJSONMap) { + err := unmarshaler.UnmarshalJSONFromMap(data) + if err != nil { + m.addError(err) + } +} + +func (m *mapDecoder) addError(err error) { + if m.options.mode == ModeFailOnFirstError { + m.err = err + } else { + m.errs = append(m.errs, err) + } +} diff --git a/vendor/modules.txt b/vendor/modules.txt index cb43519c..dca54955 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -88,6 +88,9 @@ github.com/cloudflare/circl/sign/ed448 # github.com/fatih/color v1.17.0 ## explicit; go 1.17 github.com/fatih/color +# github.com/getkin/kin-openapi v0.126.0 +## explicit; go 1.20 +github.com/getkin/kin-openapi/openapi3 # github.com/go-openapi/analysis v0.23.0 ## explicit; go 1.20 github.com/go-openapi/analysis @@ -122,8 +125,6 @@ github.com/go-openapi/swag # github.com/go-openapi/validate v0.24.0 ## explicit; go 1.20 github.com/go-openapi/validate -# github.com/go-test/deep v1.0.8 -## explicit; go 1.16 # github.com/golang/protobuf v1.5.4 ## explicit; go 1.17 github.com/golang/protobuf/proto @@ -282,6 +283,9 @@ github.com/huandu/xstrings # github.com/imdario/mergo v0.3.16 ## explicit; go 1.13 github.com/imdario/mergo +# github.com/invopop/yaml v0.3.1 +## explicit; go 1.14 +github.com/invopop/yaml # github.com/josharian/intern v1.0.0 ## explicit; go 1.5 github.com/josharian/intern @@ -314,12 +318,18 @@ github.com/mitchellh/mapstructure # github.com/mitchellh/reflectwalk v1.0.2 ## explicit github.com/mitchellh/reflectwalk +# github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 +## explicit +github.com/mohae/deepcopy # github.com/oklog/run v1.1.0 ## explicit; go 1.13 github.com/oklog/run # github.com/oklog/ulid v1.3.1 ## explicit github.com/oklog/ulid +# github.com/perimeterx/marshmallow v1.1.5 +## explicit; go 1.17 +github.com/perimeterx/marshmallow # github.com/posener/complete v1.2.3 ## explicit; go 1.13 github.com/posener/complete