diff --git a/index/server/go.mod b/index/server/go.mod index 2cc7b52f1..da33814b0 100644 --- a/index/server/go.mod +++ b/index/server/go.mod @@ -7,7 +7,7 @@ require ( github.com/devfile/api/v2 v2.3.0 github.com/devfile/library/v2 v2.3.0 github.com/devfile/registry-support/index/generator v0.0.0 - github.com/getkin/kin-openapi v0.117.0 + github.com/getkin/kin-openapi v0.131.0 github.com/gin-gonic/gin v1.9.1 github.com/hashicorp/go-set v0.1.13 github.com/hashicorp/go-version v1.4.0 @@ -63,9 +63,9 @@ require ( github.com/go-git/go-git/v5 v5.13.0 // indirect github.com/go-logr/logr v1.4.1 // indirect github.com/go-logr/stdr v1.2.2 // indirect - github.com/go-openapi/jsonpointer v0.19.6 // indirect + github.com/go-openapi/jsonpointer v0.21.0 // indirect github.com/go-openapi/jsonreference v0.20.2 // indirect - github.com/go-openapi/swag v0.22.3 // indirect + github.com/go-openapi/swag v0.23.0 // indirect github.com/go-playground/locales v0.14.1 // indirect github.com/go-playground/universal-translator v0.18.1 // indirect github.com/go-playground/validator/v10 v10.14.0 // indirect @@ -84,7 +84,6 @@ require ( github.com/hashicorp/errwrap v1.1.0 // indirect github.com/hashicorp/go-multierror v1.1.1 // indirect github.com/imdario/mergo v0.3.13 // indirect - github.com/invopop/yaml v0.1.0 // indirect github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect github.com/josharian/intern v1.0.0 // indirect github.com/json-iterator/go v1.1.12 // indirect @@ -101,9 +100,11 @@ require ( github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.2 // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect + github.com/oasdiff/yaml v0.0.0-20250309154309-f31be36b4037 // indirect + github.com/oasdiff/yaml3 v0.0.0-20250309153720-d2182401db90 // indirect github.com/openshift/api v0.0.0-20200930075302-db52bc4ef99f // indirect github.com/pelletier/go-toml/v2 v2.1.1 // indirect - github.com/perimeterx/marshmallow v1.1.4 // indirect + github.com/perimeterx/marshmallow v1.1.5 // indirect github.com/peterbourgon/diskv v2.0.1+incompatible // indirect github.com/pjbgf/sha1cd v0.3.0 // indirect github.com/pkg/errors v0.9.1 // indirect diff --git a/index/server/go.sum b/index/server/go.sum index b08d7ad23..7bcf8300a 100644 --- a/index/server/go.sum +++ b/index/server/go.sum @@ -123,8 +123,8 @@ github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nos github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM= github.com/gabriel-vasile/mimetype v1.4.2 h1:w5qFW6JKBz9Y393Y4q372O9A7cUSequkh1Q7OhCmWKU= github.com/gabriel-vasile/mimetype v1.4.2/go.mod h1:zApsH/mKG4w07erKIaJPFiX0Tsq9BFQgN3qGY5GnNgA= -github.com/getkin/kin-openapi v0.117.0 h1:QT2DyGujAL09F4NrKDHJGsUoIprlIcFVHWDVDcUFE8A= -github.com/getkin/kin-openapi v0.117.0/go.mod h1:l5e9PaFUo9fyLJCPGQeXI2ML8c3P8BHOEV2VaAVf/pc= +github.com/getkin/kin-openapi v0.131.0 h1:NO2UeHnFKRYhZ8wg6Nyh5Cq7dHk4suQQr72a4pMrDxE= +github.com/getkin/kin-openapi v0.131.0/go.mod h1:3OlG51PCYNsPByuiMB0t4fjnNlIDnaEDsjiKUV8nL58= github.com/ghodss/yaml v0.0.0-20150909031657-73d445a93680/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE= github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= @@ -156,9 +156,9 @@ github.com/go-logr/zapr v1.2.3/go.mod h1:eIauM6P8qSvTw5o2ez6UEAfGjQKrxQTl5EoK+Qa github.com/go-openapi/jsonpointer v0.0.0-20160704185906-46af16f9f7b1/go.mod h1:+35s3my2LFTysnkMfxsJBAMHj/DoqoB9knIWoYG/Vk0= github.com/go-openapi/jsonpointer v0.19.2/go.mod h1:3akKfEdA7DF1sugOqz1dVQHBcuDBPKZGEoHC/NkiQRg= github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= -github.com/go-openapi/jsonpointer v0.19.5/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= -github.com/go-openapi/jsonpointer v0.19.6 h1:eCs3fxoIi3Wh6vtgmLTOjdhSpiqphQ+DaPn38N2ZdrE= github.com/go-openapi/jsonpointer v0.19.6/go.mod h1:osyAmYz/mB/C3I+WsTTSgw1ONzaLJoLCyoi6/zppojs= +github.com/go-openapi/jsonpointer v0.21.0 h1:YgdVicSA9vH5RiHs9TZW5oyafXZFc6+2Vc1rr/O9oNQ= +github.com/go-openapi/jsonpointer v0.21.0/go.mod h1:IUyH9l/+uyhIYQ/PXVA41Rexl+kOkAPDdXEYns6fzUY= github.com/go-openapi/jsonreference v0.0.0-20160704190145-13c6e3589ad9/go.mod h1:W3Z9FmVs9qj+KR4zFKmDPGiLdk1D9Rlm7cyMvf57TTg= github.com/go-openapi/jsonreference v0.19.2/go.mod h1:jMjeRr2HHw6nAVajTXJ4eiUwohSTlpa0o73RUL1owJc= github.com/go-openapi/jsonreference v0.19.3/go.mod h1:rjx6GuL8TTa9VaixXglHmQmIL98+wF9xc8zWvFonSJ8= @@ -169,8 +169,9 @@ github.com/go-openapi/spec v0.19.3/go.mod h1:FpwSN1ksY1eteniUU7X0N/BgJ7a4WvBFVA8 github.com/go-openapi/swag v0.0.0-20160704191624-1d0bd113de87/go.mod h1:DXUve3Dpr1UfpPtxFw+EFuQ41HhCWZfha5jSVRG7C7I= github.com/go-openapi/swag v0.19.2/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= -github.com/go-openapi/swag v0.22.3 h1:yMBqmnQ0gyZvEb/+KzuWZOXgllrXT4SADYbvDaXHv/g= github.com/go-openapi/swag v0.22.3/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14= +github.com/go-openapi/swag v0.23.0 h1:vsEVJDUo2hPJ2tu0/Xc+4noaxyEffXNIs3cOULZ+GrE= +github.com/go-openapi/swag v0.23.0/go.mod h1:esZ8ITTYEsH1V2trKHjAN8Ai7xHb8RV+YSZ577vPjgQ= github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s= github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA= @@ -258,8 +259,6 @@ github.com/imdario/mergo v0.3.13 h1:lFzP57bqS/wsqKssCGmtLAb8A0wKjLGrve2q3PPVcBk= github.com/imdario/mergo v0.3.13/go.mod h1:4lJ1jqUDcsbIECGy0RUJAXNIhg+6ocWgb1ALK2O4oXg= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= -github.com/invopop/yaml v0.1.0 h1:YW3WGUoJEXYfzWBjn00zIlrw7brGVD0fUKRYDPAPhrc= -github.com/invopop/yaml v0.1.0/go.mod h1:2XuRLgs/ouIrW3XNzuNj7J3Nvu/Dig5MXvbCEdiBN3Q= 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/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= @@ -338,6 +337,10 @@ github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRW github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw= github.com/nsf/jsondiff v0.0.0-20230430225905-43f6cf3098c1 h1:dOYG7LS/WK00RWZc8XGgcUTlTxpp3mKhdR2Q9z9HbXM= github.com/nsf/jsondiff v0.0.0-20230430225905-43f6cf3098c1/go.mod h1:mpRZBD8SJ55OIICQ3iWH0Yz3cjzA61JdqMLoWXeB2+8= +github.com/oasdiff/yaml v0.0.0-20250309154309-f31be36b4037 h1:G7ERwszslrBzRxj//JalHPu/3yz+De2J+4aLtSRlHiY= +github.com/oasdiff/yaml v0.0.0-20250309154309-f31be36b4037/go.mod h1:2bpvgLBZEtENV5scfDFEtB/5+1M4hkQhDQrccEJ/qGw= +github.com/oasdiff/yaml3 v0.0.0-20250309153720-d2182401db90 h1:bQx3WeLcUWy+RletIKwUIt4x3t8n2SxavmoclizMb8c= +github.com/oasdiff/yaml3 v0.0.0-20250309153720-d2182401db90/go.mod h1:y5+oSEHCPT/DGrS++Wc/479ERge0zTFxaF8PbGKcg2o= github.com/onsi/ginkgo v0.0.0-20170829012221-11459a886d9c/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.11.0 h1:JAKSXpt1YjtLA7YpPiqO9ss6sNXEsPfSGdwN0UHqzrw= @@ -357,8 +360,8 @@ github.com/openshift/api v0.0.0-20200930075302-db52bc4ef99f/go.mod h1:Si/I9UGeRR github.com/openshift/build-machinery-go v0.0.0-20200819073603-48aa266c95f7/go.mod h1:b1BuldmJlbA/xYtdZvKi+7j5YGB44qJUJDZ9zwiNCfE= github.com/pelletier/go-toml/v2 v2.1.1 h1:LWAJwfNvjQZCFIDKWYQaM62NcYeYViCmWIwmOStowAI= github.com/pelletier/go-toml/v2 v2.1.1/go.mod h1:tJU2Z3ZkXwnxa4DPO899bsyIoywizdUvyaeZurnPPDc= -github.com/perimeterx/marshmallow v1.1.4 h1:pZLDH9RjlLGGorbXhcaQLhfuV0pFMNfPO55FuFkxqLw= -github.com/perimeterx/marshmallow v1.1.4/go.mod h1:dsXbUu8CRzfYP5a87xpp0xq9S3u0Vchtcl8we9tYaXw= +github.com/perimeterx/marshmallow v1.1.5 h1:a2LALqQ1BlHM8PZblsDdidgv1mWi1DgC2UmX50IvK2s= +github.com/perimeterx/marshmallow v1.1.5/go.mod h1:dsXbUu8CRzfYP5a87xpp0xq9S3u0Vchtcl8we9tYaXw= github.com/peterbourgon/diskv v2.0.1+incompatible h1:UBdAOUP5p4RWqPBg048CAvpKN+vxiaj6gdUUzhl4XmI= github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU= github.com/phayes/freeport v0.0.0-20220201140144-74d24b5ae9f5 h1:Ii+DKncOVM8Cu1Hc+ETb5K+23HdAMvESYE3ZJ5b5cMI= @@ -391,8 +394,8 @@ github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsT github.com/prometheus/procfs v0.0.3/go.mod h1:4A/X28fw3Fc593LaREMrKMqOKvUAntwMDaekg4FpcdQ= github.com/prometheus/procfs v0.10.1 h1:kYK1Va/YMlutzCGazswoHKo//tZVlFpKYh+PymziUAg= github.com/prometheus/procfs v0.10.1/go.mod h1:nwNm2aOCAYw8uTR/9bWRREkZFxAUcWzPHWJq+XBB/FM= -github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M= -github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA= +github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8= +github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4= github.com/segmentio/backo-go v0.0.0-20200129164019-23eae7c10bd3 h1:ZuhckGJ10ulaKkdvJtiAqsLTiPrLaXSdnVgXJKJkTxE= github.com/segmentio/backo-go v0.0.0-20200129164019-23eae7c10bd3/go.mod h1:9/Rh6yILuLysoQnZ2oNooD2g7aBnvM7r/fNVxRNWfBc= github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 h1:n661drycOFuPLCN3Uc8sB6B/s6Z4t2xvBgU1htSHuq8= @@ -431,8 +434,6 @@ github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOf github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI= github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08= -github.com/ugorji/go v1.2.7/go.mod h1:nF9osbDWLy6bDVv/Rtoh6QgnvNDpmCalQV5urGCCS6M= -github.com/ugorji/go/codec v1.2.7/go.mod h1:WGN1fab3R1fzQlVQTkfxVtIBhWDRqOviHU95kRgeqEY= github.com/ugorji/go/codec v1.2.11 h1:BMaWp1Bb6fHwEtbplGBGJ498wD+LKlNSl25MjdZY4dU= github.com/ugorji/go/codec v1.2.11/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg= github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= diff --git a/index/server/vendor/github.com/getkin/kin-openapi/openapi3/callback.go b/index/server/vendor/github.com/getkin/kin-openapi/openapi3/callback.go index 62cea72d8..31f4e4be2 100644 --- a/index/server/vendor/github.com/getkin/kin-openapi/openapi3/callback.go +++ b/index/server/vendor/github.com/getkin/kin-openapi/openapi3/callback.go @@ -2,47 +2,60 @@ package openapi3 import ( "context" - "fmt" "sort" - - "github.com/go-openapi/jsonpointer" ) -type Callbacks map[string]*CallbackRef +// 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:"-"` + Origin *Origin `json:"__origin__,omitempty" yaml:"__origin__,omitempty"` -var _ jsonpointer.JSONPointable = (*Callbacks)(nil) + m map[string]*PathItem +} -// JSONLookup implements github.com/go-openapi/jsonpointer#JSONPointable -func (c Callbacks) JSONLookup(token string) (interface{}, error) { - ref, ok := c[token] - if ref == nil || !ok { - return nil, fmt.Errorf("object has no field %q", token) +// 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) - if ref.Ref != "" { - return &Ref{Ref: ref.Ref}, nil +// 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) + } } - return ref.Value, nil } -// 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 map[string]*PathItem - // Validate returns an error if Callback does not comply with the OpenAPI spec. -func (callback Callback) Validate(ctx context.Context, opts ...ValidationOption) error { +func (callback *Callback) Validate(ctx context.Context, opts ...ValidationOption) error { ctx = WithValidationOptions(ctx, opts...) - keys := make([]string, 0, len(callback)) - for key := range callback { + keys := make([]string, 0, callback.Len()) + for key := range callback.Map() { keys = append(keys, key) } sort.Strings(keys) for _, key := range keys { - v := callback[key] + v := callback.Value(key) if err := v.Validate(ctx); err != nil { return err } } - return nil + + return validateExtensions(ctx, callback.Extensions) +} + +// UnmarshalJSON sets Callbacks to a copy of data. +func (callbacks *Callbacks) UnmarshalJSON(data []byte) (err error) { + *callbacks, _, err = unmarshalStringMapP[CallbackRef](data) + return } diff --git a/index/server/vendor/github.com/getkin/kin-openapi/openapi3/components.go b/index/server/vendor/github.com/getkin/kin-openapi/openapi3/components.go index 0981e8bfe..890671aa7 100644 --- a/index/server/vendor/github.com/getkin/kin-openapi/openapi3/components.go +++ b/index/server/vendor/github.com/getkin/kin-openapi/openapi3/components.go @@ -4,20 +4,34 @@ import ( "context" "encoding/json" "fmt" - "regexp" "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]interface{} `json:"-" yaml:"-"` + Extensions map[string]any `json:"-" yaml:"-"` + Origin *Origin `json:"__origin__,omitempty" yaml:"__origin__,omitempty"` 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 Responses `json:"responses,omitempty" yaml:"responses,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"` @@ -30,7 +44,16 @@ func NewComponents() Components { // MarshalJSON returns the JSON encoding of Components. func (components Components) MarshalJSON() ([]byte, error) { - m := make(map[string]interface{}, 9+len(components.Extensions)) + 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 } @@ -61,7 +84,7 @@ func (components Components) MarshalJSON() ([]byte, error) { if x := components.Callbacks; len(x) != 0 { m["callbacks"] = x } - return json.Marshal(m) + return m, nil } // UnmarshalJSON sets Components to a copy of data. @@ -69,9 +92,10 @@ func (components *Components) UnmarshalJSON(data []byte) error { type ComponentsBis Components var x ComponentsBis if err := json.Unmarshal(data, &x); err != nil { - return err + return unmarshalError(err) } _ = json.Unmarshal(data, &x.Extensions) + delete(x.Extensions, originKey) delete(x.Extensions, "schemas") delete(x.Extensions, "parameters") delete(x.Extensions, "headers") @@ -81,6 +105,9 @@ func (components *Components) UnmarshalJSON(data []byte) error { 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 } @@ -140,10 +167,10 @@ func (components *Components) Validate(ctx context.Context, opts ...ValidationOp } sort.Strings(responses) for _, k := range responses { - v := components.Responses[k] 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) } @@ -227,16 +254,119 @@ func (components *Components) Validate(ctx context.Context, opts ...ValidationOp return validateExtensions(ctx, components.Extensions) } -const identifierPattern = `^[a-zA-Z0-9._-]+$` +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 + } +} -// IdentifierRegExp verifies whether Component object key matches 'identifierPattern' pattern, according to OapiAPI v3.x.0. -// Hovever, to be able supporting legacy OpenAPI v2.x, there is a need to customize above pattern in orde not to fail -// converted v2-v3 validation -var IdentifierRegExp = regexp.MustCompile(identifierPattern) +var _ jsonpointer.JSONPointable = (*Callbacks)(nil) -func ValidateIdentifier(value string) error { - if IdentifierRegExp.MatchString(value) { - return 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 } - return fmt.Errorf("identifier %q is not supported by OpenAPIv3 standard (regexp: %q)", value, identifierPattern) } diff --git a/index/server/vendor/github.com/getkin/kin-openapi/openapi3/contact.go b/index/server/vendor/github.com/getkin/kin-openapi/openapi3/contact.go new file mode 100644 index 000000000..2ade3c68c --- /dev/null +++ b/index/server/vendor/github.com/getkin/kin-openapi/openapi3/contact.go @@ -0,0 +1,71 @@ +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:"-"` + Origin *Origin `json:"__origin__,omitempty" yaml:"__origin__,omitempty"` + + 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, originKey) + 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/index/server/vendor/github.com/getkin/kin-openapi/openapi3/content.go b/index/server/vendor/github.com/getkin/kin-openapi/openapi3/content.go index 81b070eec..73e301e05 100644 --- a/index/server/vendor/github.com/getkin/kin-openapi/openapi3/content.go +++ b/index/server/vendor/github.com/getkin/kin-openapi/openapi3/content.go @@ -122,3 +122,9 @@ func (content Content) Validate(ctx context.Context, opts ...ValidationOption) e } return nil } + +// UnmarshalJSON sets Content to a copy of data. +func (content *Content) UnmarshalJSON(data []byte) (err error) { + *content, _, err = unmarshalStringMapP[MediaType](data) + return +} diff --git a/index/server/vendor/github.com/getkin/kin-openapi/openapi3/discriminator.go b/index/server/vendor/github.com/getkin/kin-openapi/openapi3/discriminator.go index 8b6b813f2..6934c170b 100644 --- a/index/server/vendor/github.com/getkin/kin-openapi/openapi3/discriminator.go +++ b/index/server/vendor/github.com/getkin/kin-openapi/openapi3/discriminator.go @@ -8,15 +8,25 @@ import ( // 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]interface{} `json:"-" yaml:"-"` + Extensions map[string]any `json:"-" yaml:"-"` + Origin *Origin `json:"__origin__,omitempty" yaml:"__origin__,omitempty"` - PropertyName string `json:"propertyName" yaml:"propertyName"` // required - Mapping map[string]string `json:"mapping,omitempty" yaml:"mapping,omitempty"` + PropertyName string `json:"propertyName" yaml:"propertyName"` // required + Mapping StringMap `json:"mapping,omitempty" yaml:"mapping,omitempty"` } // MarshalJSON returns the JSON encoding of Discriminator. func (discriminator Discriminator) MarshalJSON() ([]byte, error) { - m := make(map[string]interface{}, 2+len(discriminator.Extensions)) + 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 } @@ -24,7 +34,7 @@ func (discriminator Discriminator) MarshalJSON() ([]byte, error) { if x := discriminator.Mapping; len(x) != 0 { m["mapping"] = x } - return json.Marshal(m) + return m, nil } // UnmarshalJSON sets Discriminator to a copy of data. @@ -32,11 +42,16 @@ func (discriminator *Discriminator) UnmarshalJSON(data []byte) error { type DiscriminatorBis Discriminator var x DiscriminatorBis if err := json.Unmarshal(data, &x); err != nil { - return err + return unmarshalError(err) } _ = json.Unmarshal(data, &x.Extensions) + + delete(x.Extensions, originKey) delete(x.Extensions, "propertyName") delete(x.Extensions, "mapping") + if len(x.Extensions) == 0 { + x.Extensions = nil + } *discriminator = Discriminator(x) return nil } diff --git a/index/server/vendor/github.com/getkin/kin-openapi/openapi3/encoding.go b/index/server/vendor/github.com/getkin/kin-openapi/openapi3/encoding.go index dc2e54438..113eb730c 100644 --- a/index/server/vendor/github.com/getkin/kin-openapi/openapi3/encoding.go +++ b/index/server/vendor/github.com/getkin/kin-openapi/openapi3/encoding.go @@ -10,7 +10,8 @@ import ( // 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]interface{} `json:"-" yaml:"-"` + Extensions map[string]any `json:"-" yaml:"-"` + Origin *Origin `json:"__origin__,omitempty" yaml:"__origin__,omitempty"` ContentType string `json:"contentType,omitempty" yaml:"contentType,omitempty"` Headers Headers `json:"headers,omitempty" yaml:"headers,omitempty"` @@ -41,7 +42,16 @@ func (encoding *Encoding) WithHeaderRef(name string, ref *HeaderRef) *Encoding { // MarshalJSON returns the JSON encoding of Encoding. func (encoding Encoding) MarshalJSON() ([]byte, error) { - m := make(map[string]interface{}, 5+len(encoding.Extensions)) + 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 } @@ -60,7 +70,7 @@ func (encoding Encoding) MarshalJSON() ([]byte, error) { if x := encoding.AllowReserved; x { m["allowReserved"] = x } - return json.Marshal(m) + return m, nil } // UnmarshalJSON sets Encoding to a copy of data. @@ -68,14 +78,19 @@ func (encoding *Encoding) UnmarshalJSON(data []byte) error { type EncodingBis Encoding var x EncodingBis if err := json.Unmarshal(data, &x); err != nil { - return err + return unmarshalError(err) } _ = json.Unmarshal(data, &x.Extensions) + + delete(x.Extensions, originKey) 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 } diff --git a/index/server/vendor/github.com/getkin/kin-openapi/openapi3/errors.go b/index/server/vendor/github.com/getkin/kin-openapi/openapi3/errors.go index 74baab9a5..010dc889a 100644 --- a/index/server/vendor/github.com/getkin/kin-openapi/openapi3/errors.go +++ b/index/server/vendor/github.com/getkin/kin-openapi/openapi3/errors.go @@ -39,7 +39,7 @@ func (me MultiError) Is(target error) bool { } // 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 interface{}) bool { +func (me MultiError) As(target any) bool { for _, e := range me { if errors.As(e, target) { return true diff --git a/index/server/vendor/github.com/getkin/kin-openapi/openapi3/example.go b/index/server/vendor/github.com/getkin/kin-openapi/openapi3/example.go index 04338beee..44d5777e4 100644 --- a/index/server/vendor/github.com/getkin/kin-openapi/openapi3/example.go +++ b/index/server/vendor/github.com/getkin/kin-openapi/openapi3/example.go @@ -4,46 +4,36 @@ import ( "context" "encoding/json" "errors" - "fmt" - - "github.com/go-openapi/jsonpointer" ) -type Examples map[string]*ExampleRef - -var _ jsonpointer.JSONPointable = (*Examples)(nil) - -// JSONLookup implements github.com/go-openapi/jsonpointer#JSONPointable -func (e Examples) JSONLookup(token string) (interface{}, error) { - ref, ok := e[token] - if ref == nil || !ok { - return nil, fmt.Errorf("object has no field %q", token) - } - - if ref.Ref != "" { - return &Ref{Ref: ref.Ref}, nil - } - return ref.Value, nil -} - // 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]interface{} `json:"-" yaml:"-"` + Extensions map[string]any `json:"-" yaml:"-"` + Origin *Origin `json:"__origin__,omitempty" yaml:"__origin__,omitempty"` - Summary string `json:"summary,omitempty" yaml:"summary,omitempty"` - Description string `json:"description,omitempty" yaml:"description,omitempty"` - Value interface{} `json:"value,omitempty" yaml:"value,omitempty"` - ExternalValue string `json:"externalValue,omitempty" yaml:"externalValue,omitempty"` + 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 interface{}) *Example { +func NewExample(value any) *Example { return &Example{Value: value} } // MarshalJSON returns the JSON encoding of Example. func (example Example) MarshalJSON() ([]byte, error) { - m := make(map[string]interface{}, 4+len(example.Extensions)) + 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 } @@ -59,7 +49,7 @@ func (example Example) MarshalJSON() ([]byte, error) { if x := example.ExternalValue; x != "" { m["externalValue"] = x } - return json.Marshal(m) + return m, nil } // UnmarshalJSON sets Example to a copy of data. @@ -67,13 +57,17 @@ func (example *Example) UnmarshalJSON(data []byte) error { type ExampleBis Example var x ExampleBis if err := json.Unmarshal(data, &x); err != nil { - return err + return unmarshalError(err) } _ = json.Unmarshal(data, &x.Extensions) + delete(x.Extensions, originKey) 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 } @@ -91,3 +85,9 @@ func (example *Example) Validate(ctx context.Context, opts ...ValidationOption) return validateExtensions(ctx, example.Extensions) } + +// UnmarshalJSON sets Examples to a copy of data. +func (examples *Examples) UnmarshalJSON(data []byte) (err error) { + *examples, _, err = unmarshalStringMapP[ExampleRef](data) + return +} diff --git a/index/server/vendor/github.com/getkin/kin-openapi/openapi3/example_validation.go b/index/server/vendor/github.com/getkin/kin-openapi/openapi3/example_validation.go index fb7a1da16..0d105c92d 100644 --- a/index/server/vendor/github.com/getkin/kin-openapi/openapi3/example_validation.go +++ b/index/server/vendor/github.com/getkin/kin-openapi/openapi3/example_validation.go @@ -2,7 +2,7 @@ package openapi3 import "context" -func validateExampleValue(ctx context.Context, input interface{}, schema *Schema) error { +func validateExampleValue(ctx context.Context, input any, schema *Schema) error { opts := make([]SchemaValidationOption, 0, 2) if vo := getValidationOptions(ctx); vo.examplesValidationAsReq { diff --git a/index/server/vendor/github.com/getkin/kin-openapi/openapi3/extension.go b/index/server/vendor/github.com/getkin/kin-openapi/openapi3/extension.go index c29959091..ca86078f2 100644 --- a/index/server/vendor/github.com/getkin/kin-openapi/openapi3/extension.go +++ b/index/server/vendor/github.com/getkin/kin-openapi/openapi3/extension.go @@ -7,12 +7,20 @@ import ( "strings" ) -func validateExtensions(ctx context.Context, extensions map[string]interface{}) error { // FIXME: newtype + Validate(...) +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-") { - unknowns = append(unknowns, k) + if strings.HasPrefix(k, "x-") { + continue + } + if allowed != nil { + if _, ok := allowed[k]; ok { + continue + } } + unknowns = append(unknowns, k) } if len(unknowns) != 0 { diff --git a/index/server/vendor/github.com/getkin/kin-openapi/openapi3/external_docs.go b/index/server/vendor/github.com/getkin/kin-openapi/openapi3/external_docs.go index 276a36cce..7ff435ff6 100644 --- a/index/server/vendor/github.com/getkin/kin-openapi/openapi3/external_docs.go +++ b/index/server/vendor/github.com/getkin/kin-openapi/openapi3/external_docs.go @@ -11,7 +11,8 @@ import ( // 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]interface{} `json:"-" yaml:"-"` + Extensions map[string]any `json:"-" yaml:"-"` + Origin *Origin `json:"__origin__,omitempty" yaml:"__origin__,omitempty"` Description string `json:"description,omitempty" yaml:"description,omitempty"` URL string `json:"url,omitempty" yaml:"url,omitempty"` @@ -19,7 +20,16 @@ type ExternalDocs struct { // MarshalJSON returns the JSON encoding of ExternalDocs. func (e ExternalDocs) MarshalJSON() ([]byte, error) { - m := make(map[string]interface{}, 2+len(e.Extensions)) + 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 } @@ -29,7 +39,7 @@ func (e ExternalDocs) MarshalJSON() ([]byte, error) { if x := e.URL; x != "" { m["url"] = x } - return json.Marshal(m) + return m, nil } // UnmarshalJSON sets ExternalDocs to a copy of data. @@ -37,11 +47,15 @@ func (e *ExternalDocs) UnmarshalJSON(data []byte) error { type ExternalDocsBis ExternalDocs var x ExternalDocsBis if err := json.Unmarshal(data, &x); err != nil { - return err + return unmarshalError(err) } _ = json.Unmarshal(data, &x.Extensions) + delete(x.Extensions, originKey) delete(x.Extensions, "description") delete(x.Extensions, "url") + if len(x.Extensions) == 0 { + x.Extensions = nil + } *e = ExternalDocs(x) return nil } diff --git a/index/server/vendor/github.com/getkin/kin-openapi/openapi3/header.go b/index/server/vendor/github.com/getkin/kin-openapi/openapi3/header.go index 8bce69f2e..6b23db52e 100644 --- a/index/server/vendor/github.com/getkin/kin-openapi/openapi3/header.go +++ b/index/server/vendor/github.com/getkin/kin-openapi/openapi3/header.go @@ -8,23 +8,6 @@ import ( "github.com/go-openapi/jsonpointer" ) -type Headers map[string]*HeaderRef - -var _ jsonpointer.JSONPointable = (*Headers)(nil) - -// JSONLookup implements github.com/go-openapi/jsonpointer#JSONPointable -func (h Headers) JSONLookup(token string) (interface{}, error) { - ref, ok := h[token] - if ref == nil || !ok { - return nil, fmt.Errorf("object has no field %q", token) - } - - if ref.Ref != "" { - return &Ref{Ref: ref.Ref}, nil - } - return ref.Value, nil -} - // Header is specified by OpenAPI/Swagger 3.0 standard. // See https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#header-object type Header struct { @@ -33,8 +16,8 @@ type Header struct { var _ jsonpointer.JSONPointable = (*Header)(nil) -// JSONLookup implements github.com/go-openapi/jsonpointer#JSONPointable -func (header Header) JSONLookup(token string) (interface{}, error) { +// 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) } @@ -48,6 +31,11 @@ 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 @@ -84,7 +72,7 @@ func (header *Header) Validate(ctx context.Context, opts ...ValidationOption) er return fmt.Errorf("header schema is invalid: %w", e) } - if (header.Schema == nil) == (header.Content == nil) { + 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) } @@ -95,9 +83,20 @@ func (header *Header) Validate(ctx context.Context, opts ...ValidationOption) er } 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 } + +// UnmarshalJSON sets Headers to a copy of data. +func (headers *Headers) UnmarshalJSON(data []byte) (err error) { + *headers, _, err = unmarshalStringMapP[HeaderRef](data) + return +} diff --git a/index/server/vendor/github.com/getkin/kin-openapi/openapi3/helpers.go b/index/server/vendor/github.com/getkin/kin-openapi/openapi3/helpers.go new file mode 100644 index 000000000..d50b3d847 --- /dev/null +++ b/index/server/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/index/server/vendor/github.com/getkin/kin-openapi/openapi3/info.go b/index/server/vendor/github.com/getkin/kin-openapi/openapi3/info.go index 381047fca..ed5710e04 100644 --- a/index/server/vendor/github.com/getkin/kin-openapi/openapi3/info.go +++ b/index/server/vendor/github.com/getkin/kin-openapi/openapi3/info.go @@ -9,7 +9,8 @@ import ( // 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]interface{} `json:"-" yaml:"-"` + Extensions map[string]any `json:"-" yaml:"-"` + Origin *Origin `json:"__origin__,omitempty" yaml:"__origin__,omitempty"` Title string `json:"title" yaml:"title"` // Required Description string `json:"description,omitempty" yaml:"description,omitempty"` @@ -21,7 +22,19 @@ type Info struct { // MarshalJSON returns the JSON encoding of Info. func (info Info) MarshalJSON() ([]byte, error) { - m := make(map[string]interface{}, 6+len(info.Extensions)) + 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 } @@ -39,7 +52,7 @@ func (info Info) MarshalJSON() ([]byte, error) { m["license"] = x } m["version"] = info.Version - return json.Marshal(m) + return m, nil } // UnmarshalJSON sets Info to a copy of data. @@ -47,15 +60,19 @@ func (info *Info) UnmarshalJSON(data []byte) error { type InfoBis Info var x InfoBis if err := json.Unmarshal(data, &x); err != nil { - return err + return unmarshalError(err) } _ = json.Unmarshal(data, &x.Extensions) + delete(x.Extensions, originKey) 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 } @@ -86,100 +103,3 @@ func (info *Info) Validate(ctx context.Context, opts ...ValidationOption) error return validateExtensions(ctx, info.Extensions) } - -// 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]interface{} `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) { - m := make(map[string]interface{}, 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 json.Marshal(m) -} - -// 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 err - } - _ = json.Unmarshal(data, &x.Extensions) - delete(x.Extensions, "name") - delete(x.Extensions, "url") - delete(x.Extensions, "email") - *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) -} - -// 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]interface{} `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) { - m := make(map[string]interface{}, 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 json.Marshal(m) -} - -// 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 err - } - _ = json.Unmarshal(data, &x.Extensions) - delete(x.Extensions, "name") - delete(x.Extensions, "url") - *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/index/server/vendor/github.com/getkin/kin-openapi/openapi3/internalize_refs.go b/index/server/vendor/github.com/getkin/kin-openapi/openapi3/internalize_refs.go index b8506535e..01f5dad88 100644 --- a/index/server/vendor/github.com/getkin/kin-openapi/openapi3/internalize_refs.go +++ b/index/server/vendor/github.com/getkin/kin-openapi/openapi3/internalize_refs.go @@ -2,47 +2,134 @@ package openapi3 import ( "context" - "path/filepath" + "path" "strings" ) -type RefNameResolver func(string) string +// 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. // -// If a reference points to an element inside a document, it returns the last -// element in the reference using filepath.Base. Otherwise if the reference points -// to a file, it returns the file name trimmed of all extensions. -func DefaultRefNameResolver(ref string) string { - if ref == "" { - return "" - } - split := strings.SplitN(ref, "#", 2) - if len(split) == 2 { - return filepath.Base(split[1]) - } - ref = split[0] - for ext := filepath.Ext(ref); len(ext) > 0; ext = filepath.Ext(ref) { - ref = strings.TrimSuffix(ref, ext) - } - return filepath.Base(ref) -} +// 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) + } + } + } -func schemaNames(s Schemas) []string { - out := make([]string, 0, len(s)) - for i := range s { - out = append(out, i) + 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, "./") } - return out + + // Replace invalid characters in component fixed field names. + internalizedName = InvalidIdentifierCharRegExp.ReplaceAllString(internalizedName, "_") + + return internalizedName } -func parametersMapNames(s ParametersMap) []string { - out := make([]string, 0, len(s)) - for i := range s { - out = append(out, i) +// 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 out + + return p, false } func isExternalRef(ref string, parentIsExternal bool) bool { @@ -54,7 +141,7 @@ func (doc *T) addSchemaToSpec(s *SchemaRef, refNameResolver RefNameResolver, par return false } - name := refNameResolver(s.Ref) + name := refNameResolver(doc, s) if doc.Components != nil { if _, ok := doc.Components.Schemas[name]; ok { s.Ref = "#/components/schemas/" + name @@ -77,7 +164,7 @@ func (doc *T) addParameterToSpec(p *ParameterRef, refNameResolver RefNameResolve if p == nil || !isExternalRef(p.Ref, parentIsExternal) { return false } - name := refNameResolver(p.Ref) + name := refNameResolver(doc, p) if doc.Components != nil { if _, ok := doc.Components.Parameters[name]; ok { p.Ref = "#/components/parameters/" + name @@ -100,7 +187,7 @@ func (doc *T) addHeaderToSpec(h *HeaderRef, refNameResolver RefNameResolver, par if h == nil || !isExternalRef(h.Ref, parentIsExternal) { return false } - name := refNameResolver(h.Ref) + name := refNameResolver(doc, h) if doc.Components != nil { if _, ok := doc.Components.Headers[name]; ok { h.Ref = "#/components/headers/" + name @@ -123,7 +210,7 @@ func (doc *T) addRequestBodyToSpec(r *RequestBodyRef, refNameResolver RefNameRes if r == nil || !isExternalRef(r.Ref, parentIsExternal) { return false } - name := refNameResolver(r.Ref) + name := refNameResolver(doc, r) if doc.Components != nil { if _, ok := doc.Components.RequestBodies[name]; ok { r.Ref = "#/components/requestBodies/" + name @@ -146,7 +233,7 @@ func (doc *T) addResponseToSpec(r *ResponseRef, refNameResolver RefNameResolver, if r == nil || !isExternalRef(r.Ref, parentIsExternal) { return false } - name := refNameResolver(r.Ref) + name := refNameResolver(doc, r) if doc.Components != nil { if _, ok := doc.Components.Responses[name]; ok { r.Ref = "#/components/responses/" + name @@ -158,7 +245,7 @@ func (doc *T) addResponseToSpec(r *ResponseRef, refNameResolver RefNameResolver, doc.Components = &Components{} } if doc.Components.Responses == nil { - doc.Components.Responses = make(Responses) + doc.Components.Responses = make(ResponseBodies) } doc.Components.Responses[name] = &ResponseRef{Value: r.Value} r.Ref = "#/components/responses/" + name @@ -169,7 +256,7 @@ func (doc *T) addSecuritySchemeToSpec(ss *SecuritySchemeRef, refNameResolver Ref if ss == nil || !isExternalRef(ss.Ref, parentIsExternal) { return } - name := refNameResolver(ss.Ref) + name := refNameResolver(doc, ss) if doc.Components != nil { if _, ok := doc.Components.SecuritySchemes[name]; ok { ss.Ref = "#/components/securitySchemes/" + name @@ -192,7 +279,7 @@ func (doc *T) addExampleToSpec(e *ExampleRef, refNameResolver RefNameResolver, p if e == nil || !isExternalRef(e.Ref, parentIsExternal) { return } - name := refNameResolver(e.Ref) + name := refNameResolver(doc, e) if doc.Components != nil { if _, ok := doc.Components.Examples[name]; ok { e.Ref = "#/components/examples/" + name @@ -215,7 +302,7 @@ func (doc *T) addLinkToSpec(l *LinkRef, refNameResolver RefNameResolver, parentI if l == nil || !isExternalRef(l.Ref, parentIsExternal) { return } - name := refNameResolver(l.Ref) + name := refNameResolver(doc, l) if doc.Components != nil { if _, ok := doc.Components.Links[name]; ok { l.Ref = "#/components/links/" + name @@ -238,7 +325,7 @@ func (doc *T) addCallbackToSpec(c *CallbackRef, refNameResolver RefNameResolver, if c == nil || !isExternalRef(c.Ref, parentIsExternal) { return false } - name := refNameResolver(c.Ref) + name := refNameResolver(doc, c) if doc.Components == nil { doc.Components = &Components{} @@ -264,7 +351,9 @@ func (doc *T) derefSchema(s *Schema, refNameResolver RefNameResolver, parentIsEx } } } - for _, s2 := range s.Properties { + + 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) @@ -279,7 +368,8 @@ func (doc *T) derefSchema(s *Schema, refNameResolver RefNameResolver, parentIsEx } func (doc *T) derefHeaders(hs Headers, refNameResolver RefNameResolver, parentIsExternal bool) { - for _, h := range hs { + for _, name := range componentNames(hs) { + h := hs[name] isExternal := doc.addHeaderToSpec(h, refNameResolver, parentIsExternal) if doc.isVisitedHeader(h.Value) { continue @@ -289,38 +379,51 @@ func (doc *T) derefHeaders(hs Headers, refNameResolver RefNameResolver, parentIs } func (doc *T) derefExamples(es Examples, refNameResolver RefNameResolver, parentIsExternal bool) { - for _, e := range es { + for _, name := range componentNames(es) { + e := es[name] doc.addExampleToSpec(e, refNameResolver, parentIsExternal) } } func (doc *T) derefContent(c Content, refNameResolver RefNameResolver, parentIsExternal bool) { - for _, mediatype := range c { + 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 _, e := range mediatype.Encoding { + 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 _, l := range ls { + for _, name := range componentNames(ls) { + l := ls[name] doc.addLinkToSpec(l, refNameResolver, parentIsExternal) } } -func (doc *T) derefResponses(es Responses, refNameResolver RefNameResolver, parentIsExternal bool) { - for _, e := range es { - isExternal := doc.addResponseToSpec(e, refNameResolver, parentIsExternal) - if e.Value != nil { - doc.derefHeaders(e.Value.Headers, refNameResolver, isExternal || parentIsExternal) - doc.derefContent(e.Value.Content, refNameResolver, isExternal || parentIsExternal) - doc.derefLinks(e.Value.Links, refNameResolver, isExternal || 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) } } @@ -337,33 +440,39 @@ func (doc *T) derefRequestBody(r RequestBody, refNameResolver RefNameResolver, p } func (doc *T) derefPaths(paths map[string]*PathItem, refNameResolver RefNameResolver, parentIsExternal bool) { - for _, ops := range paths { - if isExternalRef(ops.Ref, parentIsExternal) { - parentIsExternal = true - } + for _, name := range componentNames(paths) { + ops := paths[name] + pathIsExternal := isExternalRef(ops.Ref, parentIsExternal) // inline full operations ops.Ref = "" for _, param := range ops.Parameters { - doc.addParameterToSpec(param, refNameResolver, parentIsExternal) + isExternal := doc.addParameterToSpec(param, refNameResolver, pathIsExternal) + if param.Value != nil { + doc.derefParameter(*param.Value, refNameResolver, pathIsExternal || isExternal) + } } - for _, op := range ops.Operations() { - isExternal := doc.addRequestBodyToSpec(op.RequestBody, refNameResolver, parentIsExternal) + 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, parentIsExternal || isExternal) + doc.derefRequestBody(*op.RequestBody.Value, refNameResolver, pathIsExternal || isExternal) } - for _, cb := range op.Callbacks { - isExternal := doc.addCallbackToSpec(cb, refNameResolver, parentIsExternal) + for _, name := range componentNames(op.Callbacks) { + cb := op.Callbacks[name] + isExternal := doc.addCallbackToSpec(cb, refNameResolver, pathIsExternal) if cb.Value != nil { - doc.derefPaths(*cb.Value, refNameResolver, parentIsExternal || isExternal) + cbValue := (*cb.Value).Map() + doc.derefPaths(cbValue, refNameResolver, pathIsExternal || isExternal) } } - doc.derefResponses(op.Responses, refNameResolver, parentIsExternal) + doc.derefResponses(op.Responses, refNameResolver, pathIsExternal) for _, param := range op.Parameters { - isExternal := doc.addParameterToSpec(param, refNameResolver, parentIsExternal) + isExternal := doc.addParameterToSpec(param, refNameResolver, pathIsExternal) if param.Value != nil { - doc.derefParameter(*param.Value, refNameResolver, parentIsExternal || isExternal) + doc.derefParameter(*param.Value, refNameResolver, pathIsExternal || isExternal) } } } @@ -376,12 +485,12 @@ func (doc *T) derefPaths(paths map[string]*PathItem, refNameResolver RefNameReso // refNameResolver takes in references to returns a name to store the reference under locally. // It MUST return a unique name for each reference type. // A default implementation is provided that will suffice for most use cases. See the function -// documention for more details. +// documentation for more details. // // Example: // // doc.InternalizeRefs(context.Background(), nil) -func (doc *T) InternalizeRefs(ctx context.Context, refNameResolver func(ref string) string) { +func (doc *T) InternalizeRefs(ctx context.Context, refNameResolver func(*T, ComponentRef) string) { doc.resetVisited() if refNameResolver == nil { @@ -389,8 +498,7 @@ func (doc *T) InternalizeRefs(ctx context.Context, refNameResolver func(ref stri } if components := doc.Components; components != nil { - names := schemaNames(components.Schemas) - for _, name := range names { + for _, name := range componentNames(components.Schemas) { schema := components.Schemas[name] isExternal := doc.addSchemaToSpec(schema, refNameResolver, false) if schema != nil { @@ -398,8 +506,7 @@ func (doc *T) InternalizeRefs(ctx context.Context, refNameResolver func(ref stri doc.derefSchema(schema.Value, refNameResolver, isExternal) } } - names = parametersMapNames(components.Parameters) - for _, name := range names { + for _, name := range componentNames(components.Parameters) { p := components.Parameters[name] isExternal := doc.addParameterToSpec(p, refNameResolver, false) if p != nil && p.Value != nil { @@ -408,27 +515,32 @@ func (doc *T) InternalizeRefs(ctx context.Context, refNameResolver func(ref stri } } doc.derefHeaders(components.Headers, refNameResolver, false) - for _, req := range components.RequestBodies { + 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.derefResponses(components.Responses, refNameResolver, false) - for _, ss := range components.SecuritySchemes { + 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 _, cb := range components.Callbacks { + + 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 - doc.derefPaths(*cb.Value, refNameResolver, isExternal) + cbValue := (*cb.Value).Map() + doc.derefPaths(cbValue, refNameResolver, isExternal) } } } - doc.derefPaths(doc.Paths, refNameResolver, false) + doc.derefPaths(doc.Paths.Map(), refNameResolver, false) } diff --git a/index/server/vendor/github.com/getkin/kin-openapi/openapi3/license.go b/index/server/vendor/github.com/getkin/kin-openapi/openapi3/license.go new file mode 100644 index 000000000..eb47fc385 --- /dev/null +++ b/index/server/vendor/github.com/getkin/kin-openapi/openapi3/license.go @@ -0,0 +1,68 @@ +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:"-"` + Origin *Origin `json:"__origin__,omitempty" yaml:"__origin__,omitempty"` + + 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, originKey) + 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/index/server/vendor/github.com/getkin/kin-openapi/openapi3/link.go b/index/server/vendor/github.com/getkin/kin-openapi/openapi3/link.go index 08dfa8d67..c76cf446d 100644 --- a/index/server/vendor/github.com/getkin/kin-openapi/openapi3/link.go +++ b/index/server/vendor/github.com/getkin/kin-openapi/openapi3/link.go @@ -5,43 +5,34 @@ import ( "encoding/json" "errors" "fmt" - - "github.com/go-openapi/jsonpointer" ) -type Links map[string]*LinkRef - -// JSONLookup implements github.com/go-openapi/jsonpointer#JSONPointable -func (links Links) JSONLookup(token string) (interface{}, error) { - ref, ok := links[token] - if ok == false { - return nil, fmt.Errorf("object has no field %q", token) - } - - if ref != nil && ref.Ref != "" { - return &Ref{Ref: ref.Ref}, nil - } - return ref.Value, nil -} - -var _ jsonpointer.JSONPointable = (*Links)(nil) - // 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]interface{} `json:"-" yaml:"-"` + Extensions map[string]any `json:"-" yaml:"-"` + Origin *Origin `json:"__origin__,omitempty" yaml:"__origin__,omitempty"` - OperationRef string `json:"operationRef,omitempty" yaml:"operationRef,omitempty"` - OperationID string `json:"operationId,omitempty" yaml:"operationId,omitempty"` - Description string `json:"description,omitempty" yaml:"description,omitempty"` - Parameters map[string]interface{} `json:"parameters,omitempty" yaml:"parameters,omitempty"` - Server *Server `json:"server,omitempty" yaml:"server,omitempty"` - RequestBody interface{} `json:"requestBody,omitempty" yaml:"requestBody,omitempty"` + OperationRef string `json:"operationRef,omitempty" yaml:"operationRef,omitempty"` + OperationID string `json:"operationId,omitempty" yaml:"operationId,omitempty"` + Description string `json:"description,omitempty" yaml:"description,omitempty"` + Parameters map[string]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) { - m := make(map[string]interface{}, 6+len(link.Extensions)) + 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 } @@ -65,7 +56,7 @@ func (link Link) MarshalJSON() ([]byte, error) { m["requestBody"] = x } - return json.Marshal(m) + return m, nil } // UnmarshalJSON sets Link to a copy of data. @@ -73,15 +64,20 @@ func (link *Link) UnmarshalJSON(data []byte) error { type LinkBis Link var x LinkBis if err := json.Unmarshal(data, &x); err != nil { - return err + return unmarshalError(err) } _ = json.Unmarshal(data, &x.Extensions) + + delete(x.Extensions, originKey) 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 } @@ -99,3 +95,9 @@ func (link *Link) Validate(ctx context.Context, opts ...ValidationOption) error return validateExtensions(ctx, link.Extensions) } + +// UnmarshalJSON sets Links to a copy of data. +func (links *Links) UnmarshalJSON(data []byte) (err error) { + *links, _, err = unmarshalStringMapP[LinkRef](data) + return +} diff --git a/index/server/vendor/github.com/getkin/kin-openapi/openapi3/loader.go b/index/server/vendor/github.com/getkin/kin-openapi/openapi3/loader.go index 4a14f67f0..436a1b3dc 100644 --- a/index/server/vendor/github.com/getkin/kin-openapi/openapi3/loader.go +++ b/index/server/vendor/github.com/getkin/kin-openapi/openapi3/loader.go @@ -5,19 +5,20 @@ import ( "encoding/json" "errors" "fmt" + "io" "net/url" + "os" "path" "path/filepath" "reflect" - "sort" "strconv" "strings" - - "github.com/invopop/yaml" ) -var CircularReferenceError = "kin-openapi bug found: circular schema reference not handled" -var CircularReferenceCounter = 3 +// IncludeOrigin specifies whether to include the origin of the OpenAPI elements +// Set this to true before loading a spec to include the origin of the OpenAPI elements +// Note it is global and affects all loaders +var IncludeOrigin = false func foundUnresolvedRef(ref string) error { return fmt.Errorf("found unresolved ref: %q", ref) @@ -44,15 +45,9 @@ type Loader struct { visitedDocuments map[string]*T - visitedCallback map[*Callback]struct{} - visitedExample map[*Example]struct{} - visitedHeader map[*Header]struct{} - visitedLink map[*Link]struct{} - visitedParameter map[*Parameter]struct{} - visitedRequestBody map[*RequestBody]struct{} - visitedResponse map[*Response]struct{} - visitedSchema map[*Schema]struct{} - visitedSecurityScheme map[*SecurityScheme]struct{} + visitedRefs map[string]struct{} + visitedPath []string + backtrack map[string][]func(value any) } // NewLoader returns an empty Loader @@ -64,6 +59,9 @@ func NewLoader() *Loader { 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 @@ -93,30 +91,24 @@ func (loader *Loader) allowsExternalRefs(ref string) (err error) { return } -// loadSingleElementFromURI reads the data from ref and unmarshals to the passed element. -func (loader *Loader) loadSingleElementFromURI(ref string, rootPath *url.URL, element interface{}) (*url.URL, error) { +func (loader *Loader) loadSingleElementFromURI(ref string, rootPath *url.URL, element any) (*url.URL, error) { if err := loader.allowsExternalRefs(ref); err != nil { return nil, err } - parsedURL, err := url.Parse(ref) + resolvedPath, err := resolvePathWithRef(ref, rootPath) if err != nil { return nil, err } - if fragment := parsedURL.Fragment; fragment != "" { - return nil, fmt.Errorf("unexpected ref fragment %q", fragment) - } - - resolvedPath, err := resolvePath(rootPath, parsedURL) - if err != nil { - return nil, fmt.Errorf("could not resolve path: %w", 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 { + if err := unmarshal(data, element, IncludeOrigin); err != nil { return nil, err } @@ -130,11 +122,29 @@ func (loader *Loader) readURL(location *url.URL) ([]byte, error) { 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 { + if err := unmarshal(data, doc, IncludeOrigin); err != nil { return nil, err } if err := loader.ResolveRefsIn(doc, nil); err != nil { @@ -163,9 +173,12 @@ func (loader *Loader) loadFromDataWithPathInternal(data []byte, location *url.UR doc := &T{} loader.visitedDocuments[uri] = doc - if err := unmarshal(data, doc); err != nil { + if err := unmarshal(data, doc, IncludeOrigin); err != nil { return nil, err } + + doc.url = copyURI(location) + if err := loader.ResolveRefsIn(doc, location); err != nil { return nil, err } @@ -173,16 +186,7 @@ func (loader *Loader) loadFromDataWithPathInternal(data []byte, location *url.UR return doc, nil } -func unmarshal(data []byte, v interface{}) error { - // See https://github.com/getkin/kin-openapi/issues/680 - if err := json.Unmarshal(data, v); err != nil { - // UnmarshalStrict(data, v) TODO: investigate how ymlv3 handles duplicate map keys - return yaml.Unmarshal(data, v) - } - return nil -} - -// ResolveRefsIn expands references if for instance spec was just unmarshalled +// 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() @@ -193,50 +197,50 @@ func (loader *Loader) ResolveRefsIn(doc *T, location *url.URL) (err error) { } if components := doc.Components; components != nil { - for _, component := range components.Headers { + for _, name := range componentNames(components.Headers) { + component := components.Headers[name] if err = loader.resolveHeaderRef(doc, component, location); err != nil { return } } - for _, component := range components.Parameters { + for _, name := range componentNames(components.Parameters) { + component := components.Parameters[name] if err = loader.resolveParameterRef(doc, component, location); err != nil { return } } - for _, component := range components.RequestBodies { + for _, name := range componentNames(components.RequestBodies) { + component := components.RequestBodies[name] if err = loader.resolveRequestBodyRef(doc, component, location); err != nil { return } } - for _, component := range components.Responses { + for _, name := range componentNames(components.Responses) { + component := components.Responses[name] if err = loader.resolveResponseRef(doc, component, location); err != nil { return } } - for _, component := range components.Schemas { + for _, name := range componentNames(components.Schemas) { + component := components.Schemas[name] if err = loader.resolveSchemaRef(doc, component, location, []string{}); err != nil { return } } - for _, component := range components.SecuritySchemes { + for _, name := range componentNames(components.SecuritySchemes) { + component := components.SecuritySchemes[name] if err = loader.resolveSecuritySchemeRef(doc, component, location); err != nil { return } } - - examples := make([]string, 0, len(components.Examples)) - for name := range components.Examples { - examples = append(examples, name) - } - sort.Strings(examples) - for _, name := range examples { + for _, name := range componentNames(components.Examples) { component := components.Examples[name] if err = loader.resolveExampleRef(doc, component, location); err != nil { return } } - - for _, component := range components.Callbacks { + for _, name := range componentNames(components.Callbacks) { + component := components.Callbacks[name] if err = loader.resolveCallbackRef(doc, component, location); err != nil { return } @@ -244,7 +248,9 @@ func (loader *Loader) ResolveRefsIn(doc *T, location *url.URL) (err error) { } // Visit all operations - for _, pathItem := range doc.Paths { + pathItems := doc.Paths.Map() + for _, name := range componentNames(pathItems) { + pathItem := pathItems[name] if pathItem == nil { continue } @@ -256,39 +262,100 @@ func (loader *Loader) ResolveRefsIn(doc *T, location *url.URL) (err error) { return } -func join(basePath *url.URL, relativePath *url.URL) (*url.URL, error) { +func join(basePath *url.URL, relativePath *url.URL) *url.URL { if basePath == nil { - return relativePath, nil - } - newPath, err := url.Parse(basePath.String()) - if err != nil { - return nil, fmt.Errorf("cannot copy path: %q", basePath.String()) + return relativePath } + newPath := *basePath newPath.Path = path.Join(path.Dir(newPath.Path), relativePath.Path) - return newPath, nil + return &newPath } -func resolvePath(basePath *url.URL, componentPath *url.URL) (*url.URL, error) { - if componentPath.Scheme == "" && componentPath.Host == "" { +func resolvePath(basePath *url.URL, componentPath *url.URL) *url.URL { + if is_file(componentPath) { // support absolute paths - if componentPath.Path[0] == '/' { - return componentPath, nil + if filepath.IsAbs(componentPath.Path) { + return componentPath } return join(basePath, componentPath) } - return componentPath, nil + 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 (loader *Loader) resolveRefPath(ref string, path *url.URL) (*url.URL, error) { + if ref != "" && ref[0] == '#' { + path = copyURI(path) + // Resolving internal refs of a doc loaded from memory + // has no path, so just set the Fragment. + if path == nil { + path = new(url.URL) + } + + path.Fragment = ref + return path, nil + } + + if err := loader.allowsExternalRefs(ref); err != nil { + return nil, err + } + + resolvedPath, err := resolvePathWithRef(ref, path) + if err != nil { + return nil, err + } + + return resolvedPath, nil } func isSingleRefElement(ref string) bool { return !strings.Contains(ref, "#") } -func (loader *Loader) resolveComponent(doc *T, ref string, path *url.URL, resolved interface{}) ( +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 { + if componentDoc, ref, componentPath, err = loader.resolveRefAndDocument(doc, ref, path); err != nil { return nil, nil, err } @@ -297,25 +364,60 @@ func (loader *Loader) resolveComponent(doc *T, ref string, path *url.URL, resolv return nil, nil, fmt.Errorf("cannot parse reference: %q: %v", ref, parsedURL) } fragment := parsedURL.Fragment - if !strings.HasPrefix(fragment, "/") { + if fragment == "" { + fragment = "/" + } + if fragment[0] != '/' { return nil, nil, fmt.Errorf("expected fragment prefix '#/' in URI %q", ref) } - drill := func(cursor interface{}) (interface{}, error) { + 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 cursor, err = drillIntoField(cursor, pathPart); err != nil { - e := failedToResolveRefFragmentPart(ref, pathPart) - return nil, fmt.Errorf("%s: %w", e, err) + 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 interface{} + var cursor any if cursor, err = drill(componentDoc); err != nil { if path == nil { return nil, nil, err @@ -325,7 +427,7 @@ func (loader *Loader) resolveComponent(doc *T, ref string, path *url.URL, resolv if err2 != nil { return nil, nil, err } - if err2 = unmarshal(data, &cursor); err2 != nil { + if err2 = unmarshal(data, &cursor, IncludeOrigin); err2 != nil { return nil, nil, err } if cursor, err2 = drill(cursor); err2 != nil || cursor == nil { @@ -334,13 +436,31 @@ func (loader *Loader) resolveComponent(doc *T, ref string, path *url.URL, resolv err = nil } + setPathRef := func(target any) { + if i, ok := target.(interface { + setRefPath(*url.URL) + }); ok { + pathRef := copyURI(componentPath) + // Resolving internal refs of a doc loaded from memory + // has no path, so just set the Fragment. + if pathRef == nil { + pathRef = new(url.URL) + } + pathRef.Fragment = fragment + + i.setRefPath(pathRef) + } + } + switch { case reflect.TypeOf(cursor) == reflect.TypeOf(resolved): + setPathRef(cursor) + reflect.ValueOf(resolved).Elem().Set(reflect.ValueOf(cursor).Elem()) return componentDoc, componentPath, nil - case reflect.TypeOf(cursor) == reflect.TypeOf(map[string]interface{}{}): - codec := func(got, expect interface{}) error { + 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 @@ -348,6 +468,8 @@ func (loader *Loader) resolveComponent(doc *T, ref string, path *url.URL, resolv if err = json.Unmarshal(enc, expect); err != nil { return err } + + setPathRef(expect) return nil } if err := codec(cursor, resolved); err != nil { @@ -360,7 +482,7 @@ func (loader *Loader) resolveComponent(doc *T, ref string, path *url.URL, resolv } } -func readableType(x interface{}) string { +func readableType(x any) string { switch x.(type) { case *Callback: return "callback object" @@ -389,16 +511,9 @@ func readableType(x interface{}) string { } } -func drillIntoField(cursor interface{}, fieldName string) (interface{}, error) { - // Special case due to multijson - if s, ok := cursor.(*SchemaRef); ok && fieldName == "additionalProperties" { - if ap := s.Value.AdditionalProperties.Has; ap != nil { - return *ap, nil - } - return s.Value.AdditionalProperties.Schema, nil - } - +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() { @@ -421,10 +536,15 @@ func drillIntoField(cursor interface{}, fieldName string) (interface{}, error) { hasFields := false for i := 0; i < val.NumField(); i++ { hasFields = true - if fieldName == strings.Split(val.Type().Field(i).Tag.Get("yaml"), ",")[0] { - return val.Field(i).Interface(), nil + 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 @@ -432,7 +552,7 @@ func drillIntoField(cursor interface{}, fieldName string) (interface{}, error) { } if hasFields { if ff := val.Type().Field(0); ff.PkgPath == "" && ff.Name == "Extensions" { - extensions := val.Field(0).Interface().(map[string]interface{}) + extensions := val.Field(0).Interface().(map[string]any) if enc, ok := extensions[fieldName]; ok { return enc, nil } @@ -445,55 +565,71 @@ func drillIntoField(cursor interface{}, fieldName string) (interface{}, error) { } } -func (loader *Loader) resolveRef(doc *T, ref string, path *url.URL) (*T, string, *url.URL, error) { +func (loader *Loader) resolveRefAndDocument(doc *T, ref string, path *url.URL) (*T, string, *url.URL, error) { if ref != "" && ref[0] == '#' { return doc, ref, path, nil } - if err := loader.allowsExternalRefs(ref); err != nil { + fragment, resolvedPath, err := loader.resolveRef(ref, path) + if 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) + if doc, err = loader.loadFromURIInternal(resolvedPath); err != nil { + return nil, "", nil, fmt.Errorf("error resolving reference %q: %w", ref, err) } - fragment := parsedURL.Fragment - parsedURL.Fragment = "" - var resolvedPath *url.URL - if resolvedPath, err = resolvePath(path, parsedURL); err != nil { - return nil, "", nil, fmt.Errorf("error resolving path: %w", err) - } + return doc, fragment, resolvedPath, nil +} - if doc, err = loader.loadFromURIInternal(resolvedPath); err != nil { - return nil, "", nil, fmt.Errorf("error resolving reference %q: %w", ref, err) +func (loader *Loader) resolveRef(ref string, path *url.URL) (string, *url.URL, error) { + resolvedPathRef, err := loader.resolveRefPath(ref, path) + if err != nil { + return "", nil, err } - return doc, "#" + fragment, resolvedPath, nil + fragment := "#" + resolvedPathRef.Fragment + resolvedPathRef.Fragment = "" + return fragment, resolvedPathRef, 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 != nil && component.Value != nil { - if loader.visitedHeader == nil { - loader.visitedHeader = make(map[*Header]struct{}) - } - if _, ok := loader.visitedHeader[component.Value]; ok { - return nil - } - loader.visitedHeader[component.Value] = struct{}{} + if component.isEmpty() { + return errMUSTHeader } - if component == nil { - return errors.New("invalid header: value MUST be an object") - } if ref := component.Ref; ref != "" { + if component.Value != nil { + return nil + } + if !loader.shouldVisitRef(ref, func(value any) { + component.Value = value.(*Header) + refPath, _ := loader.resolveRefPath(ref, documentPath) + component.setRefPath(refPath) + }) { + 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) @@ -501,10 +637,15 @@ func (loader *Loader) resolveHeaderRef(doc *T, component *HeaderRef, documentPat 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 { @@ -520,27 +661,29 @@ func (loader *Loader) resolveHeaderRef(doc *T, component *HeaderRef, documentPat } func (loader *Loader) resolveParameterRef(doc *T, component *ParameterRef, documentPath *url.URL) (err error) { - if component != nil && component.Value != nil { - if loader.visitedParameter == nil { - loader.visitedParameter = make(map[*Parameter]struct{}) + if component.isEmpty() { + return errMUSTParameter + } + + if ref := component.Ref; ref != "" { + if component.Value != nil { + return nil } - if _, ok := loader.visitedParameter[component.Value]; ok { + if !loader.shouldVisitRef(ref, func(value any) { + component.Value = value.(*Parameter) + refPath, _ := loader.resolveRefPath(ref, documentPath) + component.setRefPath(refPath) + }) { return nil } - loader.visitedParameter[component.Value] = struct{}{} - } - - if component == nil { - return errors.New("invalid parameter: value MUST be an object") - } - ref := component.Ref - if ref != "" { + 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) @@ -548,10 +691,15 @@ func (loader *Loader) resolveParameterRef(doc *T, component *ParameterRef, docum 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 { @@ -561,7 +709,8 @@ func (loader *Loader) resolveParameterRef(doc *T, component *ParameterRef, docum if value.Content != nil && value.Schema != nil { return errors.New("cannot contain both schema and content in a parameter") } - for _, contentType := range value.Content { + 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 @@ -577,26 +726,29 @@ func (loader *Loader) resolveParameterRef(doc *T, component *ParameterRef, docum } func (loader *Loader) resolveRequestBodyRef(doc *T, component *RequestBodyRef, documentPath *url.URL) (err error) { - if component != nil && component.Value != nil { - if loader.visitedRequestBody == nil { - loader.visitedRequestBody = make(map[*RequestBody]struct{}) - } - if _, ok := loader.visitedRequestBody[component.Value]; ok { - return nil - } - loader.visitedRequestBody[component.Value] = struct{}{} + if component.isEmpty() { + return errMUSTRequestBody } - if component == nil { - return errors.New("invalid requestBody: value MUST be an object") - } if ref := component.Ref; ref != "" { + if component.Value != nil { + return nil + } + if !loader.shouldVisitRef(ref, func(value any) { + component.Value = value.(*RequestBody) + refPath, _ := loader.resolveRefPath(ref, documentPath) + component.setRefPath(refPath) + }) { + 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) @@ -604,23 +756,27 @@ func (loader *Loader) resolveRequestBodyRef(doc *T, component *RequestBodyRef, d 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 _, contentType := range value.Content { - examples := make([]string, 0, len(contentType.Examples)) - for name := range contentType.Examples { - examples = append(examples, name) + for _, name := range componentNames(value.Content) { + contentType := value.Content[name] + if contentType == nil { + continue } - sort.Strings(examples) - for _, name := range examples { + for _, name := range componentNames(contentType.Examples) { example := contentType.Examples[name] if err := loader.resolveExampleRef(doc, example, documentPath); err != nil { return err @@ -637,27 +793,29 @@ func (loader *Loader) resolveRequestBodyRef(doc *T, component *RequestBodyRef, d } func (loader *Loader) resolveResponseRef(doc *T, component *ResponseRef, documentPath *url.URL) (err error) { - if component != nil && component.Value != nil { - if loader.visitedResponse == nil { - loader.visitedResponse = make(map[*Response]struct{}) + if component.isEmpty() { + return errMUSTResponse + } + + if ref := component.Ref; ref != "" { + if component.Value != nil { + return nil } - if _, ok := loader.visitedResponse[component.Value]; ok { + if !loader.shouldVisitRef(ref, func(value any) { + component.Value = value.(*Response) + refPath, _ := loader.resolveRefPath(ref, documentPath) + component.setRefPath(refPath) + }) { return nil } - loader.visitedResponse[component.Value] = struct{}{} - } - - if component == nil { - return errors.New("invalid response: value MUST be an object") - } - ref := component.Ref - if ref != "" { + 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) @@ -665,31 +823,33 @@ func (loader *Loader) resolveResponseRef(doc *T, component *ResponseRef, documen 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 _, header := range value.Headers { + for _, name := range componentNames(value.Headers) { + header := value.Headers[name] if err := loader.resolveHeaderRef(doc, header, documentPath); err != nil { return err } } - for _, contentType := range value.Content { + for _, name := range componentNames(value.Content) { + contentType := value.Content[name] if contentType == nil { continue } - examples := make([]string, 0, len(contentType.Examples)) - for name := range contentType.Examples { - examples = append(examples, name) - } - sort.Strings(examples) - for _, name := range examples { + for _, name := range componentNames(contentType.Examples) { example := contentType.Examples[name] if err := loader.resolveExampleRef(doc, example, documentPath); err != nil { return err @@ -703,7 +863,8 @@ func (loader *Loader) resolveResponseRef(doc *T, component *ResponseRef, documen contentType.Schema = schema } } - for _, link := range value.Links { + for _, name := range componentNames(value.Links) { + link := value.Links[name] if err := loader.resolveLinkRef(doc, link, documentPath); err != nil { return err } @@ -712,49 +873,45 @@ func (loader *Loader) resolveResponseRef(doc *T, component *ResponseRef, documen } func (loader *Loader) resolveSchemaRef(doc *T, component *SchemaRef, documentPath *url.URL, visited []string) (err error) { - if component == nil { - return errors.New("invalid schema: value MUST be an object") + if component.isEmpty() { + return errMUSTSchema } - if component.Value != nil { - if loader.visitedSchema == nil { - loader.visitedSchema = make(map[*Schema]struct{}) + if ref := component.Ref; ref != "" { + if component.Value != nil { + return nil } - if _, ok := loader.visitedSchema[component.Value]; ok { + if !loader.shouldVisitRef(ref, func(value any) { + component.Value = value.(*Schema) + refPath, _ := loader.resolveRefPath(ref, documentPath) + component.setRefPath(refPath) + }) { return nil } - loader.visitedSchema[component.Value] = struct{}{} - } - - ref := component.Ref - if ref != "" { + 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 { - if visitedLimit(visited, ref) { - visited = append(visited, ref) - return fmt.Errorf("%s - %s", CircularReferenceError, strings.Join(visited, " -> ")) - } - visited = append(visited, ref) - 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()) } - if loader.visitedSchema == nil { - loader.visitedSchema = make(map[*Schema]struct{}) - } - loader.visitedSchema[component.Value] = struct{}{} + defer loader.unvisitRef(ref, component.Value) } value := component.Value if value == nil { @@ -767,7 +924,8 @@ func (loader *Loader) resolveSchemaRef(doc *T, component *SchemaRef, documentPat return err } } - for _, v := range value.Properties { + for _, name := range componentNames(value.Properties) { + v := value.Properties[name] if err := loader.resolveSchemaRef(doc, v, documentPath, visited); err != nil { return err } @@ -801,26 +959,29 @@ func (loader *Loader) resolveSchemaRef(doc *T, component *SchemaRef, documentPat } func (loader *Loader) resolveSecuritySchemeRef(doc *T, component *SecuritySchemeRef, documentPath *url.URL) (err error) { - if component != nil && component.Value != nil { - if loader.visitedSecurityScheme == nil { - loader.visitedSecurityScheme = make(map[*SecurityScheme]struct{}) - } - if _, ok := loader.visitedSecurityScheme[component.Value]; ok { - return nil - } - loader.visitedSecurityScheme[component.Value] = struct{}{} + if component.isEmpty() { + return errMUSTSecurityScheme } - if component == nil { - return errors.New("invalid securityScheme: value MUST be an object") - } if ref := component.Ref; ref != "" { + if component.Value != nil { + return nil + } + if !loader.shouldVisitRef(ref, func(value any) { + component.Value = value.(*SecurityScheme) + refPath, _ := loader.resolveRefPath(ref, documentPath) + component.setRefPath(refPath) + }) { + 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) @@ -828,35 +989,39 @@ func (loader *Loader) resolveSecuritySchemeRef(doc *T, component *SecurityScheme 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 component != nil && component.Value != nil { - if loader.visitedExample == nil { - loader.visitedExample = make(map[*Example]struct{}) + if ref := component.Ref; ref != "" { + if component.Value != nil { + return nil } - if _, ok := loader.visitedExample[component.Value]; ok { + if !loader.shouldVisitRef(ref, func(value any) { + component.Value = value.(*Example) + refPath, _ := loader.resolveRefPath(ref, documentPath) + component.setRefPath(refPath) + }) { return nil } - loader.visitedExample[component.Value] = struct{}{} - } - - if component == nil { - return errors.New("invalid example: value MUST be an object") - } - if ref := component.Ref; ref != "" { + 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) @@ -864,35 +1029,43 @@ func (loader *Loader) resolveExampleRef(doc *T, component *ExampleRef, documentP 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 != nil && component.Value != nil { - if loader.visitedCallback == nil { - loader.visitedCallback = make(map[*Callback]struct{}) - } - if _, ok := loader.visitedCallback[component.Value]; ok { - return nil - } - loader.visitedCallback[component.Value] = struct{}{} + if component.isEmpty() { + return errMUSTCallback } - if component == nil { - return errors.New("invalid callback: value MUST be an object") - } if ref := component.Ref; ref != "" { + if component.Value != nil { + return nil + } + if !loader.shouldVisitRef(ref, func(value any) { + component.Value = value.(*Callback) + refPath, _ := loader.resolveRefPath(ref, documentPath) + component.setRefPath(refPath) + }) { + 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) @@ -900,17 +1073,24 @@ func (loader *Loader) resolveCallbackRef(doc *T, component *CallbackRef, documen 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 } - for _, pathItem := range *value { + pathItems := value.Map() + for _, name := range componentNames(pathItems) { + pathItem := pathItems[name] if err = loader.resolvePathItemRef(doc, pathItem, documentPath); err != nil { return err } @@ -919,26 +1099,29 @@ func (loader *Loader) resolveCallbackRef(doc *T, component *CallbackRef, documen } func (loader *Loader) resolveLinkRef(doc *T, component *LinkRef, documentPath *url.URL) (err error) { - if component != nil && component.Value != nil { - if loader.visitedLink == nil { - loader.visitedLink = make(map[*Link]struct{}) - } - if _, ok := loader.visitedLink[component.Value]; ok { - return nil - } - loader.visitedLink[component.Value] = struct{}{} + if component.isEmpty() { + return errMUSTLink } - if component == nil { - return errors.New("invalid link: value MUST be an object") - } if ref := component.Ref; ref != "" { + if component.Value != nil { + return nil + } + if !loader.shouldVisitRef(ref, func(value any) { + component.Value = value.(*Link) + refPath, _ := loader.resolveRefPath(ref, documentPath) + component.setRefPath(refPath) + }) { + 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) @@ -946,49 +1129,53 @@ func (loader *Loader) resolveLinkRef(doc *T, component *LinkRef, documentPath *u 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 { - return errors.New("invalid path item: value MUST be an object") - } - ref := pathItem.Ref - if ref != "" { - if 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 { + 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 err + return } *pathItem = p } else { var resolved PathItem if doc, documentPath, err = loader.resolveComponent(doc, ref, documentPath, &resolved); err != nil { - return err + if err == errMUSTPathItem { + return nil + } + return } *pathItem = resolved } pathItem.Ref = ref + defer loader.unvisitRef(ref, pathItem) } for _, parameter := range pathItem.Parameters { @@ -996,7 +1183,9 @@ func (loader *Loader) resolvePathItemRef(doc *T, pathItem *PathItem, documentPat return } } - for _, operation := range pathItem.Operations() { + 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 @@ -1007,12 +1196,15 @@ func (loader *Loader) resolvePathItemRef(doc *T, pathItem *PathItem, documentPat return } } - for _, response := range operation.Responses { + responses := operation.Responses.Map() + for _, name := range componentNames(responses) { + response := responses[name] if err = loader.resolveResponseRef(doc, response, documentPath); err != nil { return } } - for _, callback := range operation.Callbacks { + for _, name := range componentNames(operation.Callbacks) { + callback := operation.Callbacks[name] if err = loader.resolveCallbackRef(doc, callback, documentPath); err != nil { return } @@ -1024,16 +1216,3 @@ func (loader *Loader) resolvePathItemRef(doc *T, pathItem *PathItem, documentPat func unescapeRefString(ref string) string { return strings.Replace(strings.Replace(ref, "~1", "/", -1), "~0", "~", -1) } - -func visitedLimit(visited []string, ref string) bool { - visitedCount := 0 - for _, v := range visited { - if v == ref { - visitedCount++ - if visitedCount >= CircularReferenceCounter { - return true - } - } - } - return false -} diff --git a/index/server/vendor/github.com/getkin/kin-openapi/openapi3/loader_uri_reader.go b/index/server/vendor/github.com/getkin/kin-openapi/openapi3/loader_uri_reader.go index 92ac043f9..b023dfb29 100644 --- a/index/server/vendor/github.com/getkin/kin-openapi/openapi3/loader_uri_reader.go +++ b/index/server/vendor/github.com/getkin/kin-openapi/openapi3/loader_uri_reader.go @@ -3,9 +3,10 @@ package openapi3 import ( "errors" "fmt" - "io/ioutil" + "io" "net/http" "net/url" + "os" "path/filepath" "sync" ) @@ -63,19 +64,22 @@ func ReadFromHTTP(cl *http.Client) ReadFromURIFunc { if resp.StatusCode > 399 { return nil, fmt.Errorf("error loading %q: request returned status code %d", location.String(), resp.StatusCode) } - return ioutil.ReadAll(resp.Body) + 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 location.Host != "" { - return nil, ErrURINotSupported - } - if location.Scheme != "" && location.Scheme != "file" { + if !is_file(location) { return nil, ErrURINotSupported } - return ioutil.ReadFile(location.Path) + return os.ReadFile(filepath.FromSlash(location.Path)) } // URIMapCache returns a ReadFromURIFunc that caches the contents read from URI diff --git a/index/server/vendor/github.com/getkin/kin-openapi/openapi3/maplike.go b/index/server/vendor/github.com/getkin/kin-openapi/openapi3/maplike.go new file mode 100644 index 000000000..35b336571 --- /dev/null +++ b/index/server/vendor/github.com/getkin/kin-openapi/openapi3/maplike.go @@ -0,0 +1,435 @@ +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 + } + + if k == originKey { + var data []byte + if data, err = json.Marshal(v); err != nil { + return + } + if err = json.Unmarshal(data, &x.Origin); err != nil { + return + } + 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 + } + + if k == originKey { + var data []byte + if data, err = json.Marshal(v); err != nil { + return + } + if err = json.Unmarshal(data, &x.Origin); err != nil { + return + } + 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 + } + + if k == originKey { + var data []byte + if data, err = json.Marshal(v); err != nil { + return + } + if err = json.Unmarshal(data, &x.Origin); err != nil { + return + } + 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/index/server/vendor/github.com/getkin/kin-openapi/openapi3/marsh.go b/index/server/vendor/github.com/getkin/kin-openapi/openapi3/marsh.go new file mode 100644 index 000000000..2f00828a6 --- /dev/null +++ b/index/server/vendor/github.com/getkin/kin-openapi/openapi3/marsh.go @@ -0,0 +1,34 @@ +package openapi3 + +import ( + "encoding/json" + "fmt" + "strings" + + "github.com/oasdiff/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, includeOrigin bool) 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.UnmarshalWithOrigin(data, v, includeOrigin); 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/index/server/vendor/github.com/getkin/kin-openapi/openapi3/media_type.go b/index/server/vendor/github.com/getkin/kin-openapi/openapi3/media_type.go index 2a9b4721c..576826f37 100644 --- a/index/server/vendor/github.com/getkin/kin-openapi/openapi3/media_type.go +++ b/index/server/vendor/github.com/getkin/kin-openapi/openapi3/media_type.go @@ -13,10 +13,11 @@ import ( // 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]interface{} `json:"-" yaml:"-"` + Extensions map[string]any `json:"-" yaml:"-"` + Origin *Origin `json:"__origin__,omitempty" yaml:"__origin__,omitempty"` Schema *SchemaRef `json:"schema,omitempty" yaml:"schema,omitempty"` - Example interface{} `json:"example,omitempty" yaml:"example,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"` } @@ -41,7 +42,7 @@ func (mediaType *MediaType) WithSchemaRef(schema *SchemaRef) *MediaType { return mediaType } -func (mediaType *MediaType) WithExample(name string, value interface{}) *MediaType { +func (mediaType *MediaType) WithExample(name string, value any) *MediaType { example := mediaType.Examples if example == nil { example = make(map[string]*ExampleRef) @@ -65,7 +66,16 @@ func (mediaType *MediaType) WithEncoding(name string, enc *Encoding) *MediaType // MarshalJSON returns the JSON encoding of MediaType. func (mediaType MediaType) MarshalJSON() ([]byte, error) { - m := make(map[string]interface{}, 4+len(mediaType.Extensions)) + 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 } @@ -81,7 +91,7 @@ func (mediaType MediaType) MarshalJSON() ([]byte, error) { if x := mediaType.Encoding; len(x) != 0 { m["encoding"] = x } - return json.Marshal(m) + return m, nil } // UnmarshalJSON sets MediaType to a copy of data. @@ -89,13 +99,17 @@ func (mediaType *MediaType) UnmarshalJSON(data []byte) error { type MediaTypeBis MediaType var x MediaTypeBis if err := json.Unmarshal(data, &x); err != nil { - return err + return unmarshalError(err) } _ = json.Unmarshal(data, &x.Extensions) + delete(x.Extensions, originKey) 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 } @@ -145,8 +159,8 @@ func (mediaType *MediaType) Validate(ctx context.Context, opts ...ValidationOpti return validateExtensions(ctx, mediaType.Extensions) } -// JSONLookup implements github.com/go-openapi/jsonpointer#JSONPointable -func (mediaType MediaType) JSONLookup(token string) (interface{}, error) { +// 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 { diff --git a/index/server/vendor/github.com/getkin/kin-openapi/openapi3/openapi3.go b/index/server/vendor/github.com/getkin/kin-openapi/openapi3/openapi3.go index 8b8f71bb7..ef1592e8c 100644 --- a/index/server/vendor/github.com/getkin/kin-openapi/openapi3/openapi3.go +++ b/index/server/vendor/github.com/getkin/kin-openapi/openapi3/openapi3.go @@ -5,28 +5,71 @@ import ( "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]interface{} `json:"-" yaml:"-"` + 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 + 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) { - m := make(map[string]interface{}, 4+len(doc.Extensions)) +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 } @@ -48,7 +91,7 @@ func (doc T) MarshalJSON() ([]byte, error) { if x := doc.ExternalDocs; x != nil { m["externalDocs"] = x } - return json.Marshal(m) + return m, nil } // UnmarshalJSON sets T to a copy of data. @@ -56,7 +99,7 @@ func (doc *T) UnmarshalJSON(data []byte) error { type TBis T var x TBis if err := json.Unmarshal(data, &x); err != nil { - return err + return unmarshalError(err) } _ = json.Unmarshal(data, &x.Extensions) delete(x.Extensions, "openapi") @@ -67,18 +110,21 @@ func (doc *T) UnmarshalJSON(data []byte) error { 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 = make(Paths) + doc.Paths = NewPaths() } - pathItem := doc.Paths[path] + pathItem := doc.Paths.Value(path) if pathItem == nil { pathItem = &PathItem{} - doc.Paths[path] = pathItem + doc.Paths.Set(path, pathItem) } pathItem.SetOperation(method, operation) } @@ -87,6 +133,10 @@ 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 { @@ -97,7 +147,6 @@ func (doc *T) Validate(ctx context.Context, opts ...ValidationOption) error { } var wrap func(error) error - // NOTE: only mention info/components/paths/... key in this func's errors. wrap = func(e error) error { return fmt.Errorf("invalid components: %w", e) } if v := doc.Components; v != nil { diff --git a/index/server/vendor/github.com/getkin/kin-openapi/openapi3/operation.go b/index/server/vendor/github.com/getkin/kin-openapi/openapi3/operation.go index 645c0805f..f32836e68 100644 --- a/index/server/vendor/github.com/getkin/kin-openapi/openapi3/operation.go +++ b/index/server/vendor/github.com/getkin/kin-openapi/openapi3/operation.go @@ -13,7 +13,8 @@ import ( // Operation represents "operation" specified by" OpenAPI/Swagger 3.0 standard. // See https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#operation-object type Operation struct { - Extensions map[string]interface{} `json:"-" yaml:"-"` + Extensions map[string]any `json:"-" yaml:"-"` + Origin *Origin `json:"__origin__,omitempty" yaml:"__origin__,omitempty"` // Optional tags for documentation. Tags []string `json:"tags,omitempty" yaml:"tags,omitempty"` @@ -34,7 +35,7 @@ type Operation struct { RequestBody *RequestBodyRef `json:"requestBody,omitempty" yaml:"requestBody,omitempty"` // Responses. - Responses Responses `json:"responses" yaml:"responses"` // Required + Responses *Responses `json:"responses" yaml:"responses"` // Required // Optional callbacks Callbacks Callbacks `json:"callbacks,omitempty" yaml:"callbacks,omitempty"` @@ -58,7 +59,16 @@ func NewOperation() *Operation { // MarshalJSON returns the JSON encoding of Operation. func (operation Operation) MarshalJSON() ([]byte, error) { - m := make(map[string]interface{}, 12+len(operation.Extensions)) + 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 } @@ -96,7 +106,7 @@ func (operation Operation) MarshalJSON() ([]byte, error) { if x := operation.ExternalDocs; x != nil { m["externalDocs"] = x } - return json.Marshal(m) + return m, nil } // UnmarshalJSON sets Operation to a copy of data. @@ -104,9 +114,10 @@ func (operation *Operation) UnmarshalJSON(data []byte) error { type OperationBis Operation var x OperationBis if err := json.Unmarshal(data, &x); err != nil { - return err + return unmarshalError(err) } _ = json.Unmarshal(data, &x.Extensions) + delete(x.Extensions, originKey) delete(x.Extensions, "tags") delete(x.Extensions, "summary") delete(x.Extensions, "description") @@ -119,12 +130,15 @@ func (operation *Operation) UnmarshalJSON(data []byte) error { 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 github.com/go-openapi/jsonpointer#JSONPointable -func (operation Operation) JSONLookup(token string) (interface{}, error) { +// 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 { @@ -162,24 +176,18 @@ func (operation Operation) JSONLookup(token string) (interface{}, error) { } func (operation *Operation) AddParameter(p *Parameter) { - operation.Parameters = append(operation.Parameters, &ParameterRef{ - Value: p, - }) + operation.Parameters = append(operation.Parameters, &ParameterRef{Value: p}) } func (operation *Operation) AddResponse(status int, response *Response) { - responses := operation.Responses - if responses == nil { - responses = NewResponses() - operation.Responses = responses - } code := "default" - if status != 0 { + if 0 < status && status < 1000 { code = strconv.FormatInt(int64(status), 10) } - responses[code] = &ResponseRef{ - Value: response, + 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. diff --git a/index/server/vendor/github.com/getkin/kin-openapi/openapi3/origin.go b/index/server/vendor/github.com/getkin/kin-openapi/openapi3/origin.go new file mode 100644 index 000000000..d2a51d946 --- /dev/null +++ b/index/server/vendor/github.com/getkin/kin-openapi/openapi3/origin.go @@ -0,0 +1,17 @@ +package openapi3 + +const originKey = "__origin__" + +// Origin contains the origin of a collection. +// Key is the location of the collection itself. +// Fields is a map of the location of each field in the collection. +type Origin struct { + Key *Location `json:"key,omitempty" yaml:"key,omitempty"` + Fields map[string]Location `json:"fields,omitempty" yaml:"fields,omitempty"` +} + +// Location is a struct that contains the location of a field. +type Location struct { + Line int `json:"line,omitempty" yaml:"line,omitempty"` + Column int `json:"column,omitempty" yaml:"column,omitempty"` +} diff --git a/index/server/vendor/github.com/getkin/kin-openapi/openapi3/parameter.go b/index/server/vendor/github.com/getkin/kin-openapi/openapi3/parameter.go index ec1893e9a..23582f7df 100644 --- a/index/server/vendor/github.com/getkin/kin-openapi/openapi3/parameter.go +++ b/index/server/vendor/github.com/getkin/kin-openapi/openapi3/parameter.go @@ -11,41 +11,22 @@ import ( "github.com/go-openapi/jsonpointer" ) -type ParametersMap map[string]*ParameterRef - -var _ jsonpointer.JSONPointable = (*ParametersMap)(nil) - -// JSONLookup implements github.com/go-openapi/jsonpointer#JSONPointable -func (p ParametersMap) JSONLookup(token string) (interface{}, error) { - ref, ok := p[token] - if ref == nil || ok == false { - return nil, fmt.Errorf("object has no field %q", token) - } - - if ref.Ref != "" { - return &Ref{Ref: ref.Ref}, nil - } - return ref.Value, nil -} - // Parameters is specified by OpenAPI/Swagger 3.0 standard. type Parameters []*ParameterRef var _ jsonpointer.JSONPointable = (*Parameters)(nil) -// JSONLookup implements github.com/go-openapi/jsonpointer#JSONPointable -func (p Parameters) JSONLookup(token string) (interface{}, error) { +// 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 } @@ -91,21 +72,22 @@ func (parameters Parameters) Validate(ctx context.Context, opts ...ValidationOpt // 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]interface{} `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 interface{} `json:"example,omitempty" yaml:"example,omitempty"` - Examples Examples `json:"examples,omitempty" yaml:"examples,omitempty"` - Content Content `json:"content,omitempty" yaml:"content,omitempty"` + Extensions map[string]any `json:"-" yaml:"-"` + Origin *Origin `json:"__origin__,omitempty" yaml:"__origin__,omitempty"` + + 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) @@ -169,7 +151,16 @@ func (parameter *Parameter) WithSchema(value *Schema) *Parameter { // MarshalJSON returns the JSON encoding of Parameter. func (parameter Parameter) MarshalJSON() ([]byte, error) { - m := make(map[string]interface{}, 13+len(parameter.Extensions)) + 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 } @@ -214,7 +205,7 @@ func (parameter Parameter) MarshalJSON() ([]byte, error) { m["content"] = x } - return json.Marshal(m) + return m, nil } // UnmarshalJSON sets Parameter to a copy of data. @@ -222,10 +213,11 @@ func (parameter *Parameter) UnmarshalJSON(data []byte) error { type ParameterBis Parameter var x ParameterBis if err := json.Unmarshal(data, &x); err != nil { - return err + return unmarshalError(err) } _ = json.Unmarshal(data, &x.Extensions) + delete(x.Extensions, originKey) delete(x.Extensions, "name") delete(x.Extensions, "in") delete(x.Extensions, "description") @@ -239,13 +231,16 @@ func (parameter *Parameter) UnmarshalJSON(data []byte) error { 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 github.com/go-openapi/jsonpointer#JSONPointable -func (parameter Parameter) JSONLookup(token string) (interface{}, error) { +// 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 { @@ -370,12 +365,17 @@ func (parameter *Parameter) Validate(ctx context.Context, opts ...ValidationOpti return fmt.Errorf("parameter %q schema is invalid: %w", parameter.Name, e) } - if (parameter.Schema == nil) == (parameter.Content == nil) { + 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) } @@ -416,3 +416,9 @@ func (parameter *Parameter) Validate(ctx context.Context, opts ...ValidationOpti return validateExtensions(ctx, parameter.Extensions) } + +// UnmarshalJSON sets ParametersMap to a copy of data. +func (parametersMap *ParametersMap) UnmarshalJSON(data []byte) (err error) { + *parametersMap, _, err = unmarshalStringMapP[ParameterRef](data) + return +} diff --git a/index/server/vendor/github.com/getkin/kin-openapi/openapi3/path_item.go b/index/server/vendor/github.com/getkin/kin-openapi/openapi3/path_item.go index fab75d93c..983978616 100644 --- a/index/server/vendor/github.com/getkin/kin-openapi/openapi3/path_item.go +++ b/index/server/vendor/github.com/getkin/kin-openapi/openapi3/path_item.go @@ -11,7 +11,8 @@ import ( // 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]interface{} `json:"-" yaml:"-"` + Extensions map[string]any `json:"-" yaml:"-"` + Origin *Origin `json:"__origin__,omitempty" yaml:"__origin__,omitempty"` Ref string `json:"$ref,omitempty" yaml:"$ref,omitempty"` Summary string `json:"summary,omitempty" yaml:"summary,omitempty"` @@ -31,11 +32,20 @@ type PathItem struct { // MarshalJSON returns the JSON encoding of PathItem. func (pathItem PathItem) MarshalJSON() ([]byte, error) { + x, err := pathItem.MarshalYAML() + if err != nil { + return nil, err + } + return json.Marshal(x) +} + +// MarshalYAML returns the YAML encoding of PathItem. +func (pathItem PathItem) MarshalYAML() (any, error) { if ref := pathItem.Ref; ref != "" { - return json.Marshal(Ref{Ref: ref}) + return Ref{Ref: ref}, nil } - m := make(map[string]interface{}, 13+len(pathItem.Extensions)) + m := make(map[string]any, 13+len(pathItem.Extensions)) for k, v := range pathItem.Extensions { m[k] = v } @@ -78,7 +88,7 @@ func (pathItem PathItem) MarshalJSON() ([]byte, error) { if x := pathItem.Parameters; len(x) != 0 { m["parameters"] = x } - return json.Marshal(m) + return m, nil } // UnmarshalJSON sets PathItem to a copy of data. @@ -86,9 +96,10 @@ func (pathItem *PathItem) UnmarshalJSON(data []byte) error { type PathItemBis PathItem var x PathItemBis if err := json.Unmarshal(data, &x); err != nil { - return err + return unmarshalError(err) } _ = json.Unmarshal(data, &x.Extensions) + delete(x.Extensions, originKey) delete(x.Extensions, "$ref") delete(x.Extensions, "summary") delete(x.Extensions, "description") @@ -103,6 +114,9 @@ func (pathItem *PathItem) UnmarshalJSON(data []byte) error { 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 } @@ -207,5 +221,30 @@ func (pathItem *PathItem) Validate(ctx context.Context, opts ...ValidationOption } } + 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/index/server/vendor/github.com/getkin/kin-openapi/openapi3/paths.go b/index/server/vendor/github.com/getkin/kin-openapi/openapi3/paths.go index 0986b0557..edd8c8787 100644 --- a/index/server/vendor/github.com/getkin/kin-openapi/openapi3/paths.go +++ b/index/server/vendor/github.com/getkin/kin-openapi/openapi3/paths.go @@ -9,28 +9,54 @@ import ( // Paths is specified by OpenAPI/Swagger standard version 3. // See https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#paths-object -type Paths map[string]*PathItem +type Paths struct { + Extensions map[string]any `json:"-" yaml:"-"` + Origin *Origin `json:"__origin__,omitempty" yaml:"__origin__,omitempty"` + + 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 { +func (paths *Paths) Validate(ctx context.Context, opts ...ValidationOption) error { ctx = WithValidationOptions(ctx, opts...) - normalizedPaths := make(map[string]string, len(paths)) + normalizedPaths := make(map[string]string, paths.Len()) - keys := make([]string, 0, len(paths)) - for key := range paths { + keys := make([]string, 0, paths.Len()) + for key := range paths.Map() { keys = append(keys, key) } sort.Strings(keys) for _, path := range keys { - pathItem := paths[path] + 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[path] = pathItem + paths.Set(path, pathItem) } normalizedPath, _, varsInPath := normalizeTemplatedPath(path) @@ -106,23 +132,23 @@ func (paths Paths) Validate(ctx context.Context, opts ...ValidationOption) error return err } - return nil + 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 { +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 == nil { + if paths.Len() == 0 { return nil } vars := make(map[int][]string) max := 0 - for path := range paths { + for path := range paths.Map() { count := strings.Count(path, "}") vars[count] = append(vars[count], path) if count > max { @@ -130,7 +156,7 @@ func (paths Paths) InMatchingOrder() []string { } } - ordered := make([]string, 0, len(paths)) + 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))) @@ -152,15 +178,15 @@ func (paths Paths) InMatchingOrder() []string { // pathItem := path.Find("/person/{name}") // // would return the correct path item. -func (paths Paths) Find(key string) *PathItem { +func (paths *Paths) Find(key string) *PathItem { // Try directly access the map - pathItem := paths[key] + pathItem := paths.Value(key) if pathItem != nil { return pathItem } normalizedPath, expected, _ := normalizeTemplatedPath(key) - for path, pathItem := range paths { + for path, pathItem := range paths.Map() { pathNormalized, got, _ := normalizeTemplatedPath(path) if got == expected && pathNormalized == normalizedPath { return pathItem @@ -169,9 +195,9 @@ func (paths Paths) Find(key string) *PathItem { return nil } -func (paths Paths) validateUniqueOperationIDs() error { +func (paths *Paths) validateUniqueOperationIDs() error { operationIDs := make(map[string]string) - for urlPath, pathItem := range paths { + for urlPath, pathItem := range paths.Map() { if pathItem == nil { continue } diff --git a/index/server/vendor/github.com/getkin/kin-openapi/openapi3/ref.go b/index/server/vendor/github.com/getkin/kin-openapi/openapi3/ref.go index a937de4a5..83893b69a 100644 --- a/index/server/vendor/github.com/getkin/kin-openapi/openapi3/ref.go +++ b/index/server/vendor/github.com/getkin/kin-openapi/openapi3/ref.go @@ -1,7 +1,10 @@ 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"` + Ref string `json:"$ref" yaml:"$ref"` + Origin *Origin `json:"__origin__,omitempty" yaml:"__origin__,omitempty"` } diff --git a/index/server/vendor/github.com/getkin/kin-openapi/openapi3/refs.go b/index/server/vendor/github.com/getkin/kin-openapi/openapi3/refs.go index 15f5179da..fc11164a5 100644 --- a/index/server/vendor/github.com/getkin/kin-openapi/openapi3/refs.go +++ b/index/server/vendor/github.com/getkin/kin-openapi/openapi3/refs.go @@ -1,10 +1,13 @@ +// 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" @@ -13,27 +16,56 @@ import ( // 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 + Origin *Origin + 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 copyURI(x.refPath) } + +func (x *CallbackRef) setRefPath(u *url.URL) { + // Once the refPath is set don't override. References can be loaded + // multiple times not all with access to the correct path info. + if x.refPath != nil { + return + } + + x.refPath = copyURI(u) +} + // MarshalYAML returns the YAML encoding of CallbackRef. -func (x CallbackRef) MarshalYAML() (interface{}, error) { +func (x CallbackRef) MarshalYAML() (any, error) { if ref := x.Ref; ref != "" { return &Ref{Ref: ref}, nil } - return x.Value, nil + return x.Value.MarshalYAML() } // MarshalJSON returns the JSON encoding of CallbackRef. func (x CallbackRef) MarshalJSON() ([]byte, error) { - if ref := x.Ref; ref != "" { - return json.Marshal(Ref{Ref: ref}) + y, err := x.MarshalYAML() + if err != nil { + return nil, err } - return json.Marshal(x.Value) + return json.Marshal(y) } // UnmarshalJSON sets CallbackRef to a copy of data. @@ -41,12 +73,21 @@ 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 + x.Origin = refOnly.Origin 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 } @@ -56,32 +97,56 @@ func (x *CallbackRef) UnmarshalJSON(data []byte) error { // 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 { - extras := make([]string, 0, len(extra)) allowed := getValidationOptions(ctx).extraSiblingFieldsAllowed - if allowed == nil { - allowed = make(map[string]struct{}, 0) - } for _, ex := range extra { - if _, ok := allowed[ex]; !ok { + 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 len(extras) != 0 { - return fmt.Errorf("extra sibling fields: %+v", extras) + } + + 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 github.com/go-openapi/jsonpointer#JSONPointable -func (x *CallbackRef) JSONLookup(token string) (interface{}, error) { +// 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 } @@ -89,27 +154,56 @@ func (x *CallbackRef) JSONLookup(token string) (interface{}, error) { // 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 + Origin *Origin + 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 copyURI(x.refPath) } + +func (x *ExampleRef) setRefPath(u *url.URL) { + // Once the refPath is set don't override. References can be loaded + // multiple times not all with access to the correct path info. + if x.refPath != nil { + return + } + + x.refPath = copyURI(u) +} + // MarshalYAML returns the YAML encoding of ExampleRef. -func (x ExampleRef) MarshalYAML() (interface{}, error) { +func (x ExampleRef) MarshalYAML() (any, error) { if ref := x.Ref; ref != "" { return &Ref{Ref: ref}, nil } - return x.Value, nil + return x.Value.MarshalYAML() } // MarshalJSON returns the JSON encoding of ExampleRef. func (x ExampleRef) MarshalJSON() ([]byte, error) { - if ref := x.Ref; ref != "" { - return json.Marshal(Ref{Ref: ref}) + y, err := x.MarshalYAML() + if err != nil { + return nil, err } - return x.Value.MarshalJSON() + return json.Marshal(y) } // UnmarshalJSON sets ExampleRef to a copy of data. @@ -117,12 +211,21 @@ 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 + x.Origin = refOnly.Origin 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 } @@ -132,32 +235,56 @@ func (x *ExampleRef) UnmarshalJSON(data []byte) error { // 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 { - extras := make([]string, 0, len(extra)) allowed := getValidationOptions(ctx).extraSiblingFieldsAllowed - if allowed == nil { - allowed = make(map[string]struct{}, 0) - } for _, ex := range extra { - if _, ok := allowed[ex]; !ok { + 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 len(extras) != 0 { - return fmt.Errorf("extra sibling fields: %+v", extras) + } + + 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 github.com/go-openapi/jsonpointer#JSONPointable -func (x *ExampleRef) JSONLookup(token string) (interface{}, error) { +// 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 } @@ -165,27 +292,56 @@ func (x *ExampleRef) JSONLookup(token string) (interface{}, error) { // 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 + Origin *Origin + 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 copyURI(x.refPath) } + +func (x *HeaderRef) setRefPath(u *url.URL) { + // Once the refPath is set don't override. References can be loaded + // multiple times not all with access to the correct path info. + if x.refPath != nil { + return + } + + x.refPath = copyURI(u) +} + // MarshalYAML returns the YAML encoding of HeaderRef. -func (x HeaderRef) MarshalYAML() (interface{}, error) { +func (x HeaderRef) MarshalYAML() (any, error) { if ref := x.Ref; ref != "" { return &Ref{Ref: ref}, nil } - return x.Value, nil + return x.Value.MarshalYAML() } // MarshalJSON returns the JSON encoding of HeaderRef. func (x HeaderRef) MarshalJSON() ([]byte, error) { - if ref := x.Ref; ref != "" { - return json.Marshal(Ref{Ref: ref}) + y, err := x.MarshalYAML() + if err != nil { + return nil, err } - return x.Value.MarshalJSON() + return json.Marshal(y) } // UnmarshalJSON sets HeaderRef to a copy of data. @@ -193,12 +349,21 @@ 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 + x.Origin = refOnly.Origin 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 } @@ -208,32 +373,56 @@ func (x *HeaderRef) UnmarshalJSON(data []byte) error { // 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 { - extras := make([]string, 0, len(extra)) allowed := getValidationOptions(ctx).extraSiblingFieldsAllowed - if allowed == nil { - allowed = make(map[string]struct{}, 0) - } for _, ex := range extra { - if _, ok := allowed[ex]; !ok { + 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 len(extras) != 0 { - return fmt.Errorf("extra sibling fields: %+v", extras) + } + + 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 github.com/go-openapi/jsonpointer#JSONPointable -func (x *HeaderRef) JSONLookup(token string) (interface{}, error) { +// 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 } @@ -241,27 +430,56 @@ func (x *HeaderRef) JSONLookup(token string) (interface{}, error) { // 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 + Origin *Origin + 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 copyURI(x.refPath) } + +func (x *LinkRef) setRefPath(u *url.URL) { + // Once the refPath is set don't override. References can be loaded + // multiple times not all with access to the correct path info. + if x.refPath != nil { + return + } + + x.refPath = copyURI(u) +} + // MarshalYAML returns the YAML encoding of LinkRef. -func (x LinkRef) MarshalYAML() (interface{}, error) { +func (x LinkRef) MarshalYAML() (any, error) { if ref := x.Ref; ref != "" { return &Ref{Ref: ref}, nil } - return x.Value, nil + return x.Value.MarshalYAML() } // MarshalJSON returns the JSON encoding of LinkRef. func (x LinkRef) MarshalJSON() ([]byte, error) { - if ref := x.Ref; ref != "" { - return json.Marshal(Ref{Ref: ref}) + y, err := x.MarshalYAML() + if err != nil { + return nil, err } - return x.Value.MarshalJSON() + return json.Marshal(y) } // UnmarshalJSON sets LinkRef to a copy of data. @@ -269,12 +487,21 @@ 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 + x.Origin = refOnly.Origin 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 } @@ -284,32 +511,56 @@ func (x *LinkRef) UnmarshalJSON(data []byte) error { // 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 { - extras := make([]string, 0, len(extra)) allowed := getValidationOptions(ctx).extraSiblingFieldsAllowed - if allowed == nil { - allowed = make(map[string]struct{}, 0) - } for _, ex := range extra { - if _, ok := allowed[ex]; !ok { + 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 len(extras) != 0 { - return fmt.Errorf("extra sibling fields: %+v", extras) + } + + 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 github.com/go-openapi/jsonpointer#JSONPointable -func (x *LinkRef) JSONLookup(token string) (interface{}, error) { +// 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 } @@ -317,27 +568,56 @@ func (x *LinkRef) JSONLookup(token string) (interface{}, error) { // 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 + Origin *Origin + 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 copyURI(x.refPath) } + +func (x *ParameterRef) setRefPath(u *url.URL) { + // Once the refPath is set don't override. References can be loaded + // multiple times not all with access to the correct path info. + if x.refPath != nil { + return + } + + x.refPath = copyURI(u) +} + // MarshalYAML returns the YAML encoding of ParameterRef. -func (x ParameterRef) MarshalYAML() (interface{}, error) { +func (x ParameterRef) MarshalYAML() (any, error) { if ref := x.Ref; ref != "" { return &Ref{Ref: ref}, nil } - return x.Value, nil + return x.Value.MarshalYAML() } // MarshalJSON returns the JSON encoding of ParameterRef. func (x ParameterRef) MarshalJSON() ([]byte, error) { - if ref := x.Ref; ref != "" { - return json.Marshal(Ref{Ref: ref}) + y, err := x.MarshalYAML() + if err != nil { + return nil, err } - return x.Value.MarshalJSON() + return json.Marshal(y) } // UnmarshalJSON sets ParameterRef to a copy of data. @@ -345,12 +625,21 @@ 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 + x.Origin = refOnly.Origin 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 } @@ -360,32 +649,56 @@ func (x *ParameterRef) UnmarshalJSON(data []byte) error { // 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 { - extras := make([]string, 0, len(extra)) allowed := getValidationOptions(ctx).extraSiblingFieldsAllowed - if allowed == nil { - allowed = make(map[string]struct{}, 0) - } for _, ex := range extra { - if _, ok := allowed[ex]; !ok { + 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 len(extras) != 0 { - return fmt.Errorf("extra sibling fields: %+v", extras) + } + + 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 github.com/go-openapi/jsonpointer#JSONPointable -func (x *ParameterRef) JSONLookup(token string) (interface{}, error) { +// 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 } @@ -393,27 +706,56 @@ func (x *ParameterRef) JSONLookup(token string) (interface{}, error) { // 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 + Origin *Origin + 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 copyURI(x.refPath) } + +func (x *RequestBodyRef) setRefPath(u *url.URL) { + // Once the refPath is set don't override. References can be loaded + // multiple times not all with access to the correct path info. + if x.refPath != nil { + return + } + + x.refPath = copyURI(u) +} + // MarshalYAML returns the YAML encoding of RequestBodyRef. -func (x RequestBodyRef) MarshalYAML() (interface{}, error) { +func (x RequestBodyRef) MarshalYAML() (any, error) { if ref := x.Ref; ref != "" { return &Ref{Ref: ref}, nil } - return x.Value, nil + return x.Value.MarshalYAML() } // MarshalJSON returns the JSON encoding of RequestBodyRef. func (x RequestBodyRef) MarshalJSON() ([]byte, error) { - if ref := x.Ref; ref != "" { - return json.Marshal(Ref{Ref: ref}) + y, err := x.MarshalYAML() + if err != nil { + return nil, err } - return x.Value.MarshalJSON() + return json.Marshal(y) } // UnmarshalJSON sets RequestBodyRef to a copy of data. @@ -421,12 +763,21 @@ 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 + x.Origin = refOnly.Origin 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 } @@ -436,32 +787,56 @@ func (x *RequestBodyRef) UnmarshalJSON(data []byte) error { // 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 { - extras := make([]string, 0, len(extra)) allowed := getValidationOptions(ctx).extraSiblingFieldsAllowed - if allowed == nil { - allowed = make(map[string]struct{}, 0) - } for _, ex := range extra { - if _, ok := allowed[ex]; !ok { + 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 len(extras) != 0 { - return fmt.Errorf("extra sibling fields: %+v", extras) + } + + 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 github.com/go-openapi/jsonpointer#JSONPointable -func (x *RequestBodyRef) JSONLookup(token string) (interface{}, error) { +// 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 } @@ -469,27 +844,56 @@ func (x *RequestBodyRef) JSONLookup(token string) (interface{}, error) { // 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 + Origin *Origin + 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 copyURI(x.refPath) } + +func (x *ResponseRef) setRefPath(u *url.URL) { + // Once the refPath is set don't override. References can be loaded + // multiple times not all with access to the correct path info. + if x.refPath != nil { + return + } + + x.refPath = copyURI(u) +} + // MarshalYAML returns the YAML encoding of ResponseRef. -func (x ResponseRef) MarshalYAML() (interface{}, error) { +func (x ResponseRef) MarshalYAML() (any, error) { if ref := x.Ref; ref != "" { return &Ref{Ref: ref}, nil } - return x.Value, nil + return x.Value.MarshalYAML() } // MarshalJSON returns the JSON encoding of ResponseRef. func (x ResponseRef) MarshalJSON() ([]byte, error) { - if ref := x.Ref; ref != "" { - return json.Marshal(Ref{Ref: ref}) + y, err := x.MarshalYAML() + if err != nil { + return nil, err } - return x.Value.MarshalJSON() + return json.Marshal(y) } // UnmarshalJSON sets ResponseRef to a copy of data. @@ -497,12 +901,21 @@ 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 + x.Origin = refOnly.Origin 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 } @@ -512,32 +925,56 @@ func (x *ResponseRef) UnmarshalJSON(data []byte) error { // 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 { - extras := make([]string, 0, len(extra)) allowed := getValidationOptions(ctx).extraSiblingFieldsAllowed - if allowed == nil { - allowed = make(map[string]struct{}, 0) - } for _, ex := range extra { - if _, ok := allowed[ex]; !ok { + 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 len(extras) != 0 { - return fmt.Errorf("extra sibling fields: %+v", extras) + } + + 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 github.com/go-openapi/jsonpointer#JSONPointable -func (x *ResponseRef) JSONLookup(token string) (interface{}, error) { +// 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 } @@ -545,27 +982,56 @@ func (x *ResponseRef) JSONLookup(token string) (interface{}, error) { // 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 + Origin *Origin + 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 copyURI(x.refPath) } + +func (x *SchemaRef) setRefPath(u *url.URL) { + // Once the refPath is set don't override. References can be loaded + // multiple times not all with access to the correct path info. + if x.refPath != nil { + return + } + + x.refPath = copyURI(u) +} + // MarshalYAML returns the YAML encoding of SchemaRef. -func (x SchemaRef) MarshalYAML() (interface{}, error) { +func (x SchemaRef) MarshalYAML() (any, error) { if ref := x.Ref; ref != "" { return &Ref{Ref: ref}, nil } - return x.Value, nil + return x.Value.MarshalYAML() } // MarshalJSON returns the JSON encoding of SchemaRef. func (x SchemaRef) MarshalJSON() ([]byte, error) { - if ref := x.Ref; ref != "" { - return json.Marshal(Ref{Ref: ref}) + y, err := x.MarshalYAML() + if err != nil { + return nil, err } - return x.Value.MarshalJSON() + return json.Marshal(y) } // UnmarshalJSON sets SchemaRef to a copy of data. @@ -573,12 +1039,21 @@ 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 + x.Origin = refOnly.Origin 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 } @@ -588,32 +1063,56 @@ func (x *SchemaRef) UnmarshalJSON(data []byte) error { // 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 { - extras := make([]string, 0, len(extra)) allowed := getValidationOptions(ctx).extraSiblingFieldsAllowed - if allowed == nil { - allowed = make(map[string]struct{}, 0) - } for _, ex := range extra { - if _, ok := allowed[ex]; !ok { + 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 len(extras) != 0 { - return fmt.Errorf("extra sibling fields: %+v", extras) + } + + 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 github.com/go-openapi/jsonpointer#JSONPointable -func (x *SchemaRef) JSONLookup(token string) (interface{}, error) { +// 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 } @@ -621,27 +1120,56 @@ func (x *SchemaRef) JSONLookup(token string) (interface{}, error) { // 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 + Origin *Origin + 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 copyURI(x.refPath) } + +func (x *SecuritySchemeRef) setRefPath(u *url.URL) { + // Once the refPath is set don't override. References can be loaded + // multiple times not all with access to the correct path info. + if x.refPath != nil { + return + } + + x.refPath = copyURI(u) +} + // MarshalYAML returns the YAML encoding of SecuritySchemeRef. -func (x SecuritySchemeRef) MarshalYAML() (interface{}, error) { +func (x SecuritySchemeRef) MarshalYAML() (any, error) { if ref := x.Ref; ref != "" { return &Ref{Ref: ref}, nil } - return x.Value, nil + return x.Value.MarshalYAML() } // MarshalJSON returns the JSON encoding of SecuritySchemeRef. func (x SecuritySchemeRef) MarshalJSON() ([]byte, error) { - if ref := x.Ref; ref != "" { - return json.Marshal(Ref{Ref: ref}) + y, err := x.MarshalYAML() + if err != nil { + return nil, err } - return x.Value.MarshalJSON() + return json.Marshal(y) } // UnmarshalJSON sets SecuritySchemeRef to a copy of data. @@ -649,12 +1177,21 @@ 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 + x.Origin = refOnly.Origin 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 } @@ -664,32 +1201,56 @@ func (x *SecuritySchemeRef) UnmarshalJSON(data []byte) error { // 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 { - extras := make([]string, 0, len(extra)) allowed := getValidationOptions(ctx).extraSiblingFieldsAllowed - if allowed == nil { - allowed = make(map[string]struct{}, 0) - } for _, ex := range extra { - if _, ok := allowed[ex]; !ok { + 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 len(extras) != 0 { - return fmt.Errorf("extra sibling fields: %+v", extras) + } + + 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 github.com/go-openapi/jsonpointer#JSONPointable -func (x *SecuritySchemeRef) JSONLookup(token string) (interface{}, error) { +// 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/index/server/vendor/github.com/getkin/kin-openapi/openapi3/refs.tmpl b/index/server/vendor/github.com/getkin/kin-openapi/openapi3/refs.tmpl new file mode 100644 index 000000000..028deba7d --- /dev/null +++ b/index/server/vendor/github.com/getkin/kin-openapi/openapi3/refs.tmpl @@ -0,0 +1,153 @@ +// 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 + Origin *Origin + + 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 copyURI(x.refPath) } + +func (x *{{ $type.Name }}Ref) setRefPath(u *url.URL) { + // Once the refPath is set don't override. References can be loaded + // multiple times not all with access to the correct path info. + if x.refPath != nil { + return + } + + x.refPath = copyURI(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 + x.Origin = refOnly.Origin + 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/index/server/vendor/github.com/getkin/kin-openapi/openapi3/refs_test.tmpl b/index/server/vendor/github.com/getkin/kin-openapi/openapi3/refs_test.tmpl new file mode 100644 index 000000000..634fccf6f --- /dev/null +++ b/index/server/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/index/server/vendor/github.com/getkin/kin-openapi/openapi3/request_body.go b/index/server/vendor/github.com/getkin/kin-openapi/openapi3/request_body.go index de8919f41..6c15ba051 100644 --- a/index/server/vendor/github.com/getkin/kin-openapi/openapi3/request_body.go +++ b/index/server/vendor/github.com/getkin/kin-openapi/openapi3/request_body.go @@ -4,32 +4,13 @@ import ( "context" "encoding/json" "errors" - "fmt" - - "github.com/go-openapi/jsonpointer" ) -type RequestBodies map[string]*RequestBodyRef - -var _ jsonpointer.JSONPointable = (*RequestBodyRef)(nil) - -// JSONLookup implements github.com/go-openapi/jsonpointer#JSONPointable -func (r RequestBodies) JSONLookup(token string) (interface{}, error) { - ref, ok := r[token] - if ok == false { - return nil, fmt.Errorf("object has no field %q", token) - } - - if ref != nil && ref.Ref != "" { - return &Ref{Ref: ref.Ref}, nil - } - return ref.Value, nil -} - // 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]interface{} `json:"-" yaml:"-"` + Extensions map[string]any `json:"-" yaml:"-"` + Origin *Origin `json:"__origin__,omitempty" yaml:"__origin__,omitempty"` Description string `json:"description,omitempty" yaml:"description,omitempty"` Required bool `json:"required,omitempty" yaml:"required,omitempty"` @@ -95,7 +76,16 @@ func (requestBody *RequestBody) GetMediaType(mediaType string) *MediaType { // MarshalJSON returns the JSON encoding of RequestBody. func (requestBody RequestBody) MarshalJSON() ([]byte, error) { - m := make(map[string]interface{}, 3+len(requestBody.Extensions)) + 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 } @@ -108,7 +98,7 @@ func (requestBody RequestBody) MarshalJSON() ([]byte, error) { if x := requestBody.Content; true { m["content"] = x } - return json.Marshal(m) + return m, nil } // UnmarshalJSON sets RequestBody to a copy of data. @@ -116,12 +106,16 @@ func (requestBody *RequestBody) UnmarshalJSON(data []byte) error { type RequestBodyBis RequestBody var x RequestBodyBis if err := json.Unmarshal(data, &x); err != nil { - return err + return unmarshalError(err) } _ = json.Unmarshal(data, &x.Extensions) + delete(x.Extensions, originKey) 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 } @@ -144,3 +138,9 @@ func (requestBody *RequestBody) Validate(ctx context.Context, opts ...Validation return validateExtensions(ctx, requestBody.Extensions) } + +// UnmarshalJSON sets RequestBodies to a copy of data. +func (requestBodies *RequestBodies) UnmarshalJSON(data []byte) (err error) { + *requestBodies, _, err = unmarshalStringMapP[RequestBodyRef](data) + return +} diff --git a/index/server/vendor/github.com/getkin/kin-openapi/openapi3/response.go b/index/server/vendor/github.com/getkin/kin-openapi/openapi3/response.go index b85c9145c..b87324a28 100644 --- a/index/server/vendor/github.com/getkin/kin-openapi/openapi3/response.go +++ b/index/server/vendor/github.com/getkin/kin-openapi/openapi3/response.go @@ -4,72 +4,106 @@ import ( "context" "encoding/json" "errors" - "fmt" "sort" "strconv" - - "github.com/go-openapi/jsonpointer" ) // 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 map[string]*ResponseRef +type Responses struct { + Extensions map[string]any `json:"-" yaml:"-"` + Origin *Origin `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 +} -var _ jsonpointer.JSONPointable = (*Responses)(nil) +// NewResponsesOption describes options to NewResponses func +type NewResponsesOption func(*Responses) -func NewResponses() Responses { - r := make(Responses) - r["default"] = &ResponseRef{Value: NewResponse().WithDescription("")} - return r +// 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) + } + } } -func (responses Responses) Default() *ResponseRef { - return responses["default"] +// 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}) + } + } } -func (responses Responses) Get(status int) *ResponseRef { - return responses[strconv.FormatInt(int64(status), 10)] +// 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 { +func (responses *Responses) Validate(ctx context.Context, opts ...ValidationOption) error { ctx = WithValidationOptions(ctx, opts...) - if len(responses) == 0 { + if responses.Len() == 0 { return errors.New("the responses object MUST contain at least one response code") } - keys := make([]string, 0, len(responses)) - for key := range responses { + keys := make([]string, 0, responses.Len()) + for key := range responses.Map() { keys = append(keys, key) } sort.Strings(keys) for _, key := range keys { - v := responses[key] + v := responses.Value(key) if err := v.Validate(ctx); err != nil { return err } } - return nil -} -// JSONLookup implements github.com/go-openapi/jsonpointer#JSONPointable -func (responses Responses) JSONLookup(token string) (interface{}, error) { - ref, ok := responses[token] - if ok == false { - return nil, fmt.Errorf("invalid token reference: %q", token) - } - - if ref != nil && ref.Ref != "" { - return &Ref{Ref: ref.Ref}, nil - } - return ref.Value, nil + 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]interface{} `json:"-" yaml:"-"` + Extensions map[string]any `json:"-" yaml:"-"` + Origin *Origin `json:"__origin__,omitempty" yaml:"__origin__,omitempty"` Description *string `json:"description,omitempty" yaml:"description,omitempty"` Headers Headers `json:"headers,omitempty" yaml:"headers,omitempty"` @@ -103,7 +137,16 @@ func (response *Response) WithJSONSchemaRef(schema *SchemaRef) *Response { // MarshalJSON returns the JSON encoding of Response. func (response Response) MarshalJSON() ([]byte, error) { - m := make(map[string]interface{}, 4+len(response.Extensions)) + 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 } @@ -119,7 +162,7 @@ func (response Response) MarshalJSON() ([]byte, error) { if x := response.Links; len(x) != 0 { m["links"] = x } - return json.Marshal(m) + return m, nil } // UnmarshalJSON sets Response to a copy of data. @@ -127,13 +170,17 @@ func (response *Response) UnmarshalJSON(data []byte) error { type ResponseBis Response var x ResponseBis if err := json.Unmarshal(data, &x); err != nil { - return err + return unmarshalError(err) } _ = json.Unmarshal(data, &x.Extensions) + delete(x.Extensions, originKey) 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 } @@ -181,3 +228,9 @@ func (response *Response) Validate(ctx context.Context, opts ...ValidationOption return validateExtensions(ctx, response.Extensions) } + +// UnmarshalJSON sets ResponseBodies to a copy of data. +func (responseBodies *ResponseBodies) UnmarshalJSON(data []byte) (err error) { + *responseBodies, _, err = unmarshalStringMapP[ResponseRef](data) + return +} diff --git a/index/server/vendor/github.com/getkin/kin-openapi/openapi3/schema.go b/index/server/vendor/github.com/getkin/kin-openapi/openapi3/schema.go index 4bfbca0bd..75bcf1268 100644 --- a/index/server/vendor/github.com/getkin/kin-openapi/openapi3/schema.go +++ b/index/server/vendor/github.com/getkin/kin-openapi/openapi3/schema.go @@ -9,10 +9,10 @@ import ( "math" "math/big" "reflect" - "regexp" "sort" "strconv" "strings" + "sync" "unicode/utf16" "github.com/go-openapi/jsonpointer" @@ -26,12 +26,7 @@ const ( TypeNumber = "number" TypeObject = "object" TypeString = "string" - - // constants for integer formats - formatMinInt32 = float64(math.MinInt32) - formatMaxInt32 = float64(math.MaxInt32) - formatMinInt64 = float64(math.MinInt64) - formatMaxInt64 = float64(math.MaxInt64) + TypeNull = "null" ) var ( @@ -47,27 +42,9 @@ var ( 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") -) - -// 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 -} + compiledPatterns sync.Map +) // NewSchemaRef simply builds a SchemaRef func NewSchemaRef(ref string, value *Schema) *SchemaRef { @@ -77,29 +54,12 @@ func NewSchemaRef(ref string, value *Schema) *SchemaRef { } } -type Schemas map[string]*SchemaRef - -var _ jsonpointer.JSONPointable = (*Schemas)(nil) - -// JSONLookup implements github.com/go-openapi/jsonpointer#JSONPointable -func (s Schemas) JSONLookup(token string) (interface{}, error) { - ref, ok := s[token] - if ref == nil || ok == false { - return nil, fmt.Errorf("object has no field %q", token) - } - - if ref.Ref != "" { - return &Ref{Ref: ref.Ref}, nil - } - return ref.Value, nil -} - type SchemaRefs []*SchemaRef var _ jsonpointer.JSONPointable = (*SchemaRefs)(nil) -// JSONLookup implements github.com/go-openapi/jsonpointer#JSONPointable -func (s SchemaRefs) JSONLookup(token string) (interface{}, error) { +// 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 @@ -120,19 +80,20 @@ func (s SchemaRefs) JSONLookup(token string) (interface{}, error) { // Schema is specified by OpenAPI/Swagger 3.0 standard. // See https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#schema-object type Schema struct { - Extensions map[string]interface{} `json:"-" yaml:"-"` + Extensions map[string]any `json:"-" yaml:"-"` + Origin *Origin `json:"__origin__,omitempty" yaml:"__origin__,omitempty"` 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 string `json:"type,omitempty" yaml:"type,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 []interface{} `json:"enum,omitempty" yaml:"enum,omitempty"` - Default interface{} `json:"default,omitempty" yaml:"default,omitempty"` - Example interface{} `json:"example,omitempty" yaml:"example,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 @@ -154,10 +115,9 @@ type Schema struct { 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"` - compiledPattern *regexp.Regexp + 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"` @@ -173,36 +133,114 @@ type Schema struct { 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 } -// MarshalJSON returns the JSON encoding of AdditionalProperties. -func (addProps AdditionalProperties) MarshalJSON() ([]byte, error) { +// MarshalYAML returns the YAML encoding of AdditionalProperties. +func (addProps AdditionalProperties) MarshalYAML() (any, error) { if x := addProps.Has; x != nil { if *x { - return []byte("true"), nil + return true, nil } - return []byte("false"), nil + return false, nil } if x := addProps.Schema; x != nil { - return json.Marshal(x) + 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 interface{} + var x any if err := json.Unmarshal(data, &x); err != nil { - return err + return unmarshalError(err) } switch y := x.(type) { case nil: case bool: addProps.Has = &y - case map[string]interface{}: + case map[string]any: if len(y) == 0 { addProps.Schema = &SchemaRef{Value: &Schema{}} } else { @@ -226,7 +264,17 @@ func NewSchema() *Schema { // MarshalJSON returns the JSON encoding of Schema. func (schema Schema) MarshalJSON() ([]byte, error) { - m := make(map[string]interface{}, 36+len(schema.Extensions)) + 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 } @@ -243,7 +291,7 @@ func (schema Schema) MarshalJSON() ([]byte, error) { if x := schema.Not; x != nil { m["not"] = x } - if x := schema.Type; len(x) != 0 { + if x := schema.Type; x != nil { m["type"] = x } if x := schema.Title; len(x) != 0 { @@ -352,7 +400,7 @@ func (schema Schema) MarshalJSON() ([]byte, error) { m["discriminator"] = x } - return json.Marshal(m) + return m, nil } // UnmarshalJSON sets Schema to a copy of data. @@ -360,10 +408,11 @@ func (schema *Schema) UnmarshalJSON(data []byte) error { type SchemaBis Schema var x SchemaBis if err := json.Unmarshal(data, &x); err != nil { - return err + return unmarshalError(err) } _ = json.Unmarshal(data, &x.Extensions) + delete(x.Extensions, originKey) delete(x.Extensions, "oneOf") delete(x.Extensions, "anyOf") delete(x.Extensions, "allOf") @@ -413,6 +462,10 @@ func (schema *Schema) UnmarshalJSON(data []byte) error { delete(x.Extensions, "additionalProperties") delete(x.Extensions, "discriminator") + if len(x.Extensions) == 0 { + x.Extensions = nil + } + *schema = Schema(x) if schema.Format == "date" { @@ -424,8 +477,8 @@ func (schema *Schema) UnmarshalJSON(data []byte) error { return nil } -// JSONLookup implements github.com/go-openapi/jsonpointer#JSONPointable -func (schema Schema) JSONLookup(token string) (interface{}, error) { +// 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 { @@ -561,72 +614,72 @@ func NewAllOfSchema(schemas ...*Schema) *Schema { func NewBoolSchema() *Schema { return &Schema{ - Type: TypeBoolean, + Type: &Types{TypeBoolean}, } } func NewFloat64Schema() *Schema { return &Schema{ - Type: TypeNumber, + Type: &Types{TypeNumber}, } } func NewIntegerSchema() *Schema { return &Schema{ - Type: TypeInteger, + Type: &Types{TypeInteger}, } } func NewInt32Schema() *Schema { return &Schema{ - Type: TypeInteger, + Type: &Types{TypeInteger}, Format: "int32", } } func NewInt64Schema() *Schema { return &Schema{ - Type: TypeInteger, + Type: &Types{TypeInteger}, Format: "int64", } } func NewStringSchema() *Schema { return &Schema{ - Type: TypeString, + Type: &Types{TypeString}, } } func NewDateTimeSchema() *Schema { return &Schema{ - Type: TypeString, + Type: &Types{TypeString}, Format: "date-time", } } func NewUUIDSchema() *Schema { return &Schema{ - Type: TypeString, + Type: &Types{TypeString}, Format: "uuid", } } func NewBytesSchema() *Schema { return &Schema{ - Type: TypeString, + Type: &Types{TypeString}, Format: "byte", } } func NewArraySchema() *Schema { return &Schema{ - Type: TypeArray, + Type: &Types{TypeArray}, } } func NewObjectSchema() *Schema { return &Schema{ - Type: TypeObject, + Type: &Types{TypeObject}, Properties: make(Schemas), } } @@ -656,12 +709,12 @@ func (schema *Schema) WithExclusiveMax(value bool) *Schema { return schema } -func (schema *Schema) WithEnum(values ...interface{}) *Schema { +func (schema *Schema) WithEnum(values ...any) *Schema { schema.Enum = values return schema } -func (schema *Schema) WithDefault(defaultValue interface{}) *Schema { +func (schema *Schema) WithDefault(defaultValue any) *Schema { schema.Default = defaultValue return schema } @@ -712,7 +765,6 @@ func (schema *Schema) WithMaxLengthDecodedBase64(i int64) *Schema { func (schema *Schema) WithPattern(pattern string) *Schema { schema.Pattern = pattern - schema.compiledPattern = nil return schema } @@ -767,6 +819,11 @@ func (schema *Schema) WithProperties(properties map[string]*Schema) *Schema { 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 @@ -797,9 +854,13 @@ func (schema *Schema) WithAdditionalProperties(v *Schema) *Schema { 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 != "" || schema.Format != "" || len(schema.Enum) != 0 || + 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 || @@ -809,35 +870,35 @@ func (schema *Schema) IsEmpty() bool { schema.MinProps != 0 || schema.MaxProps != nil { return false } - if n := schema.Not; n != nil && !n.Value.IsEmpty() { + if n := schema.Not; n != nil && n.Value != nil && !n.Value.IsEmpty() { return false } - if ap := schema.AdditionalProperties.Schema; ap != nil && !ap.Value.IsEmpty() { + 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.IsEmpty() { + if items := schema.Items; items != nil && items.Value != nil && !items.Value.IsEmpty() { return false } for _, s := range schema.Properties { - if !s.Value.IsEmpty() { + if ss := s.Value; ss != nil && !ss.IsEmpty() { return false } } for _, s := range schema.OneOf { - if !s.Value.IsEmpty() { + if ss := s.Value; ss != nil && !ss.IsEmpty() { return false } } for _, s := range schema.AnyOf { - if !s.Value.IsEmpty() { + if ss := s.Value; ss != nil && !ss.IsEmpty() { return false } } for _, s := range schema.AllOf { - if !s.Value.IsEmpty() { + if ss := s.Value; ss != nil && !ss.IsEmpty() { return false } } @@ -847,130 +908,141 @@ func (schema *Schema) IsEmpty() bool { // 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...) - return schema.validate(ctx, []*Schema{}) + _, err := schema.validate(ctx, []*Schema{}) + return err } -func (schema *Schema) validate(ctx context.Context, stack []*Schema) error { +// 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 nil + return stack, nil } } stack = append(stack, schema) if schema.ReadOnly && schema.WriteOnly { - return errors.New("a property MUST NOT be marked as both readOnly and writeOnly being true") + return 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 foundUnresolvedRef(item.Ref) + return stack, foundUnresolvedRef(item.Ref) } - if err := v.validate(ctx, stack); err != nil { - return err + + 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 foundUnresolvedRef(item.Ref) + return stack, foundUnresolvedRef(item.Ref) } - if err := v.validate(ctx, stack); err != nil { - return err + + 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 foundUnresolvedRef(item.Ref) + return stack, foundUnresolvedRef(item.Ref) } - if err := v.validate(ctx, stack); err != nil { - return err + + 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 foundUnresolvedRef(ref.Ref) + return stack, foundUnresolvedRef(ref.Ref) } - if err := v.validate(ctx, stack); err != nil { - return err + + var err error + if stack, err = v.validate(ctx, stack); err != nil { + return stack, err } } - schemaType := schema.Type - switch schemaType { - case "": - case TypeBoolean: - case TypeNumber: - if format := schema.Format; len(format) > 0 { - switch format { - case "float", "double": - default: - if validationOpts.schemaFormatValidationEnabled { - return unsupportedFormat(format) + 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 validationOpts.schemaFormatValidationEnabled { - return 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: - // Try to check for custom defined formats - if _, ok := SchemaStringFormats[format]; !ok && validationOpts.schemaFormatValidationEnabled { - return 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 schema.Pattern != "" && !validationOpts.schemaPatternValidationDisabled { - if err := schema.compilePattern(); err != nil { - return err + if !validationOpts.schemaPatternValidationDisabled && schema.Pattern != "" { + if _, err := schema.compilePattern(validationOpts.regexCompilerFunc); 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) } - case TypeArray: - if schema.Items == nil { - return errors.New("when schema type is 'array', schema 'items' must be non-null") - } - case TypeObject: - default: - return fmt.Errorf("unsupported 'type' value %q", schemaType) } if ref := schema.Items; ref != nil { v := ref.Value if v == nil { - return foundUnresolvedRef(ref.Ref) + return stack, foundUnresolvedRef(ref.Ref) } - if err := v.validate(ctx, stack); err != nil { - return err + + var err error + if stack, err = v.validate(ctx, stack); err != nil { + return stack, err } } @@ -983,48 +1055,52 @@ func (schema *Schema) validate(ctx context.Context, stack []*Schema) error { ref := schema.Properties[name] v := ref.Value if v == nil { - return foundUnresolvedRef(ref.Ref) + return stack, foundUnresolvedRef(ref.Ref) } - if err := v.validate(ctx, stack); err != nil { - return err + + 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 errors.New("additionalProperties are set to both boolean and schema") + 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 foundUnresolvedRef(ref.Ref) + return stack, foundUnresolvedRef(ref.Ref) } - if err := v.validate(ctx, stack); err != nil { - return err + + 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 fmt.Errorf("invalid external docs: %w", err) + 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 fmt.Errorf("invalid default: %w", err) + 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 fmt.Errorf("invalid example: %w", err) + return stack, fmt.Errorf("invalid example: %w", err) } } - return validateExtensions(ctx, schema.Extensions) + return stack, validateExtensions(ctx, schema.Extensions) } -func (schema *Schema) IsMatching(value interface{}) bool { +func (schema *Schema) IsMatching(value any) bool { settings := newSchemaValidationSettings(FailFast()) return schema.visitJSON(settings, value) == nil } @@ -1044,25 +1120,29 @@ func (schema *Schema) IsMatchingJSONString(value string) bool { return schema.visitJSON(settings, value) == nil } -func (schema *Schema) IsMatchingJSONArray(value []interface{}) bool { +func (schema *Schema) IsMatchingJSONArray(value []any) bool { settings := newSchemaValidationSettings(FailFast()) return schema.visitJSON(settings, value) == nil } -func (schema *Schema) IsMatchingJSONObject(value map[string]interface{}) bool { +func (schema *Schema) IsMatchingJSONObject(value map[string]any) bool { settings := newSchemaValidationSettings(FailFast()) return schema.visitJSON(settings, value) == nil } -func (schema *Schema) VisitJSON(value interface{}, opts ...SchemaValidationOption) error { +func (schema *Schema) VisitJSON(value any, opts ...SchemaValidationOption) error { settings := newSchemaValidationSettings(opts...) return schema.visitJSON(settings, value) } -func (schema *Schema) visitJSON(settings *schemaValidationSettings, value interface{}) (err error) { +func (schema *Schema) visitJSON(settings *schemaValidationSettings, value any) (err error) { switch value := value.(type) { case nil: - return schema.visitJSONNull(settings) + // 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 @@ -1073,13 +1153,28 @@ func (schema *Schema) visitJSON(settings *schemaValidationSettings, value interf } if schema.IsEmpty() { + switch value.(type) { + case nil: + return schema.visitJSONNull(settings) + default: + return + } + } + + if err = schema.visitNotOperation(settings, value); err != nil { return } - if err = schema.visitSetOperations(settings, value); err != nil { + 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: @@ -1105,12 +1200,12 @@ func (schema *Schema) visitJSON(settings *schemaValidationSettings, value interf return schema.visitJSONNumber(settings, value) case string: return schema.visitJSONString(settings, value) - case []interface{}: + case []any: return schema.visitJSONArray(settings, value) - case map[string]interface{}: + case map[string]any: return schema.visitJSONObject(settings, value) - case map[interface{}]interface{}: // for YAML cf. issue #444 - values := make(map[string]interface{}, len(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 @@ -1124,7 +1219,7 @@ func (schema *Schema) visitJSON(settings *schemaValidationSettings, value interf // Catch slice of non-empty interface type if reflect.TypeOf(value).Kind() == reflect.Slice { valueR := reflect.ValueOf(value) - newValue := make([]interface{}, 0, valueR.Len()) + newValue := make([]any, 0, valueR.Len()) for i := 0; i < valueR.Len(); i++ { newValue = append(newValue, valueR.Index(i).Interface()) } @@ -1140,7 +1235,7 @@ func (schema *Schema) visitJSON(settings *schemaValidationSettings, value interf } } -func (schema *Schema) visitSetOperations(settings *schemaValidationSettings, value interface{}) (err error) { +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) { @@ -1152,6 +1247,10 @@ func (schema *Schema) visitSetOperations(settings *schemaValidationSettings, val if v == f { return } + case int64: + if v == float64(c) { + return + } default: if reflect.DeepEqual(v, value) { return @@ -1170,7 +1269,10 @@ func (schema *Schema) visitSetOperations(settings *schemaValidationSettings, val 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 { @@ -1188,19 +1290,25 @@ func (schema *Schema) visitSetOperations(settings *schemaValidationSettings, val } } } + 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]interface{}); okcheck { + 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) @@ -1210,7 +1318,7 @@ func (schema *Schema) visitSetOperations(settings *schemaValidationSettings, val 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 { @@ -1219,7 +1327,7 @@ func (schema *Schema) visitSetOperations(settings *schemaValidationSettings, val Schema: schema, SchemaField: "discriminator", Reason: fmt.Sprintf("discriminator property %q has invalid value", pn), - } + }, false } } } @@ -1233,7 +1341,7 @@ func (schema *Schema) visitSetOperations(settings *schemaValidationSettings, val for idx, item := range v { v := item.Value if v == nil { - return foundUnresolvedRef(item.Ref) + return foundUnresolvedRef(item.Ref), false } if discriminatorRef != "" && discriminatorRef != item.Ref { @@ -1256,7 +1364,7 @@ func (schema *Schema) visitSetOperations(settings *schemaValidationSettings, val if ok != 1 { if settings.failfast { - return errSchema + return errSchema, false } e := &SchemaError{ Value: value, @@ -1272,13 +1380,14 @@ func (schema *Schema) visitSetOperations(settings *schemaValidationSettings, val e.Reason = `value doesn't match any schema from "oneOf"` } - return e + 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 { @@ -1290,7 +1399,7 @@ func (schema *Schema) visitSetOperations(settings *schemaValidationSettings, val for idx, item := range v { v := item.Value if v == nil { - return foundUnresolvedRef(item.Ref) + 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 { @@ -1304,7 +1413,7 @@ func (schema *Schema) visitSetOperations(settings *schemaValidationSettings, val } if !ok { if settings.failfast { - return errSchema + return errSchema, false } return &SchemaError{ Value: value, @@ -1312,20 +1421,21 @@ func (schema *Schema) visitSetOperations(settings *schemaValidationSettings, val 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) + return foundUnresolvedRef(item.Ref), false } if err := v.visitJSON(settings, value); err != nil { if settings.failfast { - return errSchema + return errSchema, false } return &SchemaError{ Value: value, @@ -1334,9 +1444,12 @@ func (schema *Schema) visitSetOperations(settings *schemaValidationSettings, val Reason: `doesn't match all schemas from "allOf"`, Origin: err, customizeMessageError: settings.customizeMessageError, - } + }, false } + visitedAllOf = true } + + run = !((visitedOneOf || visitedAnyOf || visitedAllOf) && value == nil) return } @@ -1345,7 +1458,7 @@ func (schema *Schema) visitSetOperations(settings *schemaValidationSettings, val // 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.Nullable { + if schema.PermitsNull() { return } if settings.failfast { @@ -1366,7 +1479,7 @@ func (schema *Schema) VisitJSONBoolean(value bool) error { } func (schema *Schema) visitJSONBoolean(settings *schemaValidationSettings, value bool) (err error) { - if schemaType := schema.Type; schemaType != "" && schemaType != TypeBoolean { + if !schema.Type.Permits(TypeBoolean) { return schema.expectedType(settings, value) } return @@ -1380,7 +1493,9 @@ func (schema *Schema) VisitJSONNumber(value float64) error { func (schema *Schema) visitJSONNumber(settings *schemaValidationSettings, value float64) error { var me MultiError schemaType := schema.Type - if schemaType == TypeInteger { + requireInteger := false + if schemaType.Permits(TypeInteger) && !schemaType.Permits(TypeNumber) { + requireInteger = true if bigFloat := big.NewFloat(value); !bigFloat.IsInt() { if settings.failfast { return errSchema @@ -1389,7 +1504,7 @@ func (schema *Schema) visitJSONNumber(settings *schemaValidationSettings, value Value: value, Schema: schema, SchemaField: "type", - Reason: fmt.Sprintf("value must be an integer"), + Reason: "value must be an integer", customizeMessageError: settings.customizeMessageError, } if !settings.multiError { @@ -1397,44 +1512,61 @@ func (schema *Schema) visitJSONNumber(settings *schemaValidationSettings, value } me = append(me, err) } - } else if schemaType != "" && schemaType != TypeNumber { + } else if !(schemaType.Permits(TypeInteger) || schemaType.Permits(TypeNumber)) { return schema.expectedType(settings, value) } // formats - if schemaType == TypeInteger && schema.Format != "" { - formatMin := float64(0) - formatMax := float64(0) - switch schema.Format { - case "int32": - formatMin = formatMinInt32 - formatMax = formatMaxInt32 - case "int64": - formatMin = formatMinInt64 - formatMax = formatMaxInt64 - default: - if settings.formatValidationEnabled { - return unsupportedFormat(schema.Format) - } - } - if formatMin != 0 && formatMax != 0 && !(formatMin <= value && value <= formatMax) { - if settings.failfast { - return errSchema - } - err := &SchemaError{ - Value: value, - Schema: schema, - SchemaField: "format", - Reason: fmt.Sprintf("number must be an %s", schema.Format), - customizeMessageError: settings.customizeMessageError, + 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) + } } - if !settings.multiError { - return 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) + } } - me = append(me, 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 { @@ -1542,7 +1674,7 @@ func (schema *Schema) VisitJSONString(value string) error { } func (schema *Schema) visitJSONString(settings *schemaValidationSettings, value string) error { - if schemaType := schema.Type; schemaType != "" && schemaType != TypeString { + if !schema.Type.Permits(TypeString) { return schema.expectedType(settings, value) } @@ -1596,51 +1728,48 @@ func (schema *Schema) visitJSONString(settings *schemaValidationSettings, value } // "pattern" - if schema.Pattern != "" && schema.compiledPattern == nil && !settings.patternValidationDisabled { - var err error - if err = schema.compilePattern(); err != nil { + if !settings.patternValidationDisabled && schema.Pattern != "" { + cpiface, _ := compiledPatterns.Load(schema.Pattern) + cp, _ := cpiface.(RegexMatcher) + if cp == nil { + var err error + if cp, err = schema.compilePattern(settings.regexCompiler); 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) } } - if cp := schema.compiledPattern; cp != nil && !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 { - switch { - case f.regexp != nil && f.callback == nil: - if cp := f.regexp; !cp.MatchString(value) { - formatStrErr = fmt.Sprintf(`string doesn't match the format %q (regular expression "%s")`, format, cp.String()) - } - case f.regexp == nil && f.callback != nil: - if err := f.callback(value); err != nil { - var schemaErr = &SchemaError{} - if errors.As(err, &schemaErr) { - formatStrErr = fmt.Sprintf(`string doesn't match the format %q (%s)`, format, schemaErr.Reason) - } else { - formatStrErr = fmt.Sprintf(`string doesn't match the format %q (%v)`, format, err) - } - formatErr = err + if err := f.Validate(value); err != nil { + var reason string + schemaErr := &SchemaError{} + if errors.As(err, &schemaErr) { + reason = schemaErr.Reason + } else { + reason = err.Error() } - default: - formatStrErr = fmt.Sprintf("corrupted entry %q in SchemaStringFormats", format) + 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) } } } @@ -1667,13 +1796,13 @@ func (schema *Schema) visitJSONString(settings *schemaValidationSettings, value return nil } -func (schema *Schema) VisitJSONArray(value []interface{}) error { +func (schema *Schema) VisitJSONArray(value []any) error { settings := newSchemaValidationSettings() return schema.visitJSONArray(settings, value) } -func (schema *Schema) visitJSONArray(settings *schemaValidationSettings, value []interface{}) error { - if schemaType := schema.Type; schemaType != "" && schemaType != TypeArray { +func (schema *Schema) visitJSONArray(settings *schemaValidationSettings, value []any) error { + if !schema.Type.Permits(TypeArray) { return schema.expectedType(settings, value) } @@ -1766,13 +1895,13 @@ func (schema *Schema) visitJSONArray(settings *schemaValidationSettings, value [ return nil } -func (schema *Schema) VisitJSONObject(value map[string]interface{}) error { +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]interface{}) error { - if schemaType := schema.Type; schemaType != "" && schemaType != TypeObject { +func (schema *Schema) visitJSONObject(settings *schemaValidationSettings, value map[string]any) error { + if !schema.Type.Permits(TypeObject) { return schema.expectedType(settings, value) } @@ -1950,41 +2079,37 @@ func (schema *Schema) visitJSONObject(settings *schemaValidationSettings, value return nil } -func (schema *Schema) expectedType(settings *schemaValidationSettings, value interface{}) error { +func (schema *Schema) expectedType(settings *schemaValidationSettings, value any) error { if settings.failfast { return errSchema } a := "a" - switch schema.Type { - case TypeArray, TypeObject, TypeInteger: - a = "an" + 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, schema.Type), + Reason: fmt.Sprintf("value must be %s %s", a, x), customizeMessageError: settings.customizeMessageError, } } -func (schema *Schema) compilePattern() (err error) { - if schema.compiledPattern, err = regexp.Compile(schema.Pattern); err != nil { - return &SchemaError{ - Schema: schema, - SchemaField: "pattern", - Origin: err, - Reason: fmt.Sprintf("cannot compile pattern %q: %v", schema.Pattern, err), - } - } - return nil -} - // SchemaError is an error that occurs during schema validation. type SchemaError struct { // Value is the value that failed validation. - Value interface{} + Value any // reversePath is the path to the value that failed validation. reversePath []string // Schema is the schema that failed validation. @@ -2003,14 +2128,16 @@ type SchemaError struct { var _ interface{ Unwrap() error } = SchemaError{} func markSchemaErrorKey(err error, key string) error { - var me multiErrorForOneOf - - if errors.As(err, &me) { - err = me.Unwrap() - } if v, ok := err.(*SchemaError); ok { v.reversePath = append(v.reversePath, key) + if v.Origin != nil { + if unwrapped := errors.Unwrap(v.Origin); unwrapped != nil { + if me, ok := unwrapped.(multiErrorForOneOf); ok { + _ = markSchemaErrorKey(MultiError(me), key) + } + } + } return v } if v, ok := err.(MultiError); ok { @@ -2089,12 +2216,12 @@ func (err SchemaError) Unwrap() error { return err.Origin } -func isSliceOfUniqueItems(xs []interface{}) bool { +func isSliceOfUniqueItems(xs []any) bool { s := len(xs) m := make(map[string]struct{}, s) for _, x := range xs { - // The input slice is coverted from a JSON string, there shall - // have no error when covert it back. + // 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{}{} } @@ -2103,7 +2230,7 @@ func isSliceOfUniqueItems(xs []interface{}) bool { // SliceUniqueItemsChecker is an function used to check if an given slice // have unique items. -type SliceUniqueItemsChecker func(items []interface{}) bool +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 @@ -2119,3 +2246,9 @@ func RegisterArrayUniqueItemsChecker(fn SliceUniqueItemsChecker) { func unsupportedFormat(format string) error { return fmt.Errorf("unsupported 'format' value %q", format) } + +// UnmarshalJSON sets Schemas to a copy of data. +func (schemas *Schemas) UnmarshalJSON(data []byte) (err error) { + *schemas, _, err = unmarshalStringMapP[SchemaRef](data) + return +} diff --git a/index/server/vendor/github.com/getkin/kin-openapi/openapi3/schema_formats.go b/index/server/vendor/github.com/getkin/kin-openapi/openapi3/schema_formats.go index ea38400c2..489105b48 100644 --- a/index/server/vendor/github.com/getkin/kin-openapi/openapi3/schema_formats.go +++ b/index/server/vendor/github.com/getkin/kin-openapi/openapi3/schema_formats.go @@ -2,9 +2,31 @@ package openapi3 import ( "fmt" - "net" + "math" + "net/netip" "regexp" - "strings" +) + +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 ( @@ -14,93 +36,136 @@ const ( // FormatOfStringForEmail pattern catches only some suspiciously wrong-looking email addresses. // Use DefineStringFormat(...) if you need something stricter. FormatOfStringForEmail = `^[^@]+@[^@<>",\s]+$` -) -// FormatCallback performs custom checks on exotic formats -type FormatCallback func(value string) error + // 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[1-9]|10|11|12)-(0[1-9]|[12][0-9]|3[01])$` -// Format represents a format validator registered by either DefineStringFormat or DefineStringFormatCallback -type Format struct { - regexp *regexp.Regexp - callback FormatCallback + // FormatOfStringDateTime is a RFC3339 date-time format regexp, for example "2017-07-21T17:32:28Z". + FormatOfStringDateTime = `^[0-9]{4}-(0[1-9]|10|11|12)-(0[1-9]|[12][0-9]|3[01])T([0-1][0-9]|2[0-3]):[0-5][0-9]:([0-5][0-9]|60)(\.[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))) } -// SchemaStringFormats allows for validating string formats -var SchemaStringFormats = make(map[string]Format, 4) +// DefineIPv4Format opts in ipv4 format validation on top of OAS 3 spec +func DefineIPv4Format() { + DefineStringFormatValidator("ipv4", NewIPValidator(true)) +} -// DefineStringFormat defines a new regexp pattern for a given format -func DefineStringFormat(name string, pattern string) { - re, err := regexp.Compile(pattern) - if err != nil { - err := fmt.Errorf("format %q has invalid pattern %q: %w", name, pattern, err) - panic(err) - } - SchemaStringFormats[name] = Format{regexp: re} +// DefineIPv6Format opts in ipv6 format validation on top of OAS 3 spec +func DefineIPv6Format() { + DefineStringFormatValidator("ipv6", NewIPValidator(false)) } -// DefineStringFormatCallback adds a validation function for a specific schema format entry -func DefineStringFormatCallback(name string, callback FormatCallback) { - SchemaStringFormats[name] = Format{callback: callback} +type stringRegexpFormatValidator struct { + re *regexp.Regexp } -func validateIP(ip string) error { - parsed := net.ParseIP(ip) - if parsed == nil { - return &SchemaError{ - Value: ip, - Reason: "Not an IP address", - } +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 } -func validateIPv4(ip string) error { - if err := validateIP(ip); err != nil { - return err - } +type callbackValidator[T any] struct { + fn func(T) error +} - if !(strings.Count(ip, ":") < 2) { - return &SchemaError{ - Value: ip, - Reason: "Not an IPv4 address (it's IPv6)", - } +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 } -func validateIPv6(ip string) error { - if err := validateIP(ip); err != nil { - return err - } +// 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} +} - if !(strings.Count(ip, ":") >= 2) { - return &SchemaError{ - Value: ip, - Reason: "Not an IPv6 address (it's IPv4)", - } +// 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 nil + return stringRegexpFormatValidator{re: re} } -func init() { - // Base64 - // The pattern supports base64 and b./ase64url. Padding ('=') is supported. - DefineStringFormat("byte", `(^$|^[a-zA-Z0-9+/\-_]*=*$)`) +// 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} +} - // date - DefineStringFormat("date", `^[0-9]{4}-(0[0-9]|10|11|12)-([0-2][0-9]|30|31)$`) +// DefineStringFormatValidator defines a custom format validator for a given string format. +func DefineStringFormatValidator(name string, validator StringFormatValidator) { + SchemaStringFormats[name] = validator +} - // date-time - DefineStringFormat("date-time", `^[0-9]{4}-(0[0-9]|10|11|12)-([0-2][0-9]|30|31)T[0-9]{2}:[0-9]{2}:[0-9]{2}(\.[0-9]+)?(Z|(\+|-)[0-9]{2}:[0-9]{2})?$`) +// 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 } -// DefineIPv4Format opts in ipv4 format validation on top of OAS 3 spec -func DefineIPv4Format() { - DefineStringFormatCallback("ipv4", validateIPv4) +// 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)) } -// DefineIPv6Format opts in ipv6 format validation on top of OAS 3 spec -func DefineIPv6Format() { - DefineStringFormatCallback("ipv6", validateIPv6) +// 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/index/server/vendor/github.com/getkin/kin-openapi/openapi3/schema_pattern.go b/index/server/vendor/github.com/getkin/kin-openapi/openapi3/schema_pattern.go new file mode 100644 index 000000000..581971378 --- /dev/null +++ b/index/server/vendor/github.com/getkin/kin-openapi/openapi3/schema_pattern.go @@ -0,0 +1,35 @@ +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(c RegexCompilerFunc) (cp RegexMatcher, err error) { + pattern := schema.Pattern + if c != nil { + cp, err = c(pattern) + } else { + cp, err = regexp.Compile(intoGoRegexp(pattern)) + } + if 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/index/server/vendor/github.com/getkin/kin-openapi/openapi3/schema_validation_settings.go b/index/server/vendor/github.com/getkin/kin-openapi/openapi3/schema_validation_settings.go index 17aad2fa7..e9c1422bd 100644 --- a/index/server/vendor/github.com/getkin/kin-openapi/openapi3/schema_validation_settings.go +++ b/index/server/vendor/github.com/getkin/kin-openapi/openapi3/schema_validation_settings.go @@ -7,6 +7,12 @@ import ( // SchemaValidationOption describes options a user has when validating request / response bodies. type SchemaValidationOption func(*schemaValidationSettings) +type RegexCompilerFunc func(expr string) (RegexMatcher, error) + +type RegexMatcher interface { + MatchString(s string) bool +} + type schemaValidationSettings struct { failfast bool multiError bool @@ -16,6 +22,8 @@ type schemaValidationSettings struct { readOnlyValidationDisabled bool writeOnlyValidationDisabled bool + regexCompiler RegexCompilerFunc + onceSettingDefaults sync.Once defaultsSet func() @@ -70,6 +78,11 @@ func SetSchemaErrorMessageCustomizer(f func(err *SchemaError) string) SchemaVali return func(s *schemaValidationSettings) { s.customizeMessageError = f } } +// SetSchemaRegexCompiler allows to override the regex implementation used to validate field "pattern". +func SetSchemaRegexCompiler(c RegexCompilerFunc) SchemaValidationOption { + return func(s *schemaValidationSettings) { s.regexCompiler = c } +} + func newSchemaValidationSettings(opts ...SchemaValidationOption) *schemaValidationSettings { settings := &schemaValidationSettings{} for _, opt := range opts { diff --git a/index/server/vendor/github.com/getkin/kin-openapi/openapi3/security_requirements.go b/index/server/vendor/github.com/getkin/kin-openapi/openapi3/security_requirements.go index 87891c954..6af80e8b3 100644 --- a/index/server/vendor/github.com/getkin/kin-openapi/openapi3/security_requirements.go +++ b/index/server/vendor/github.com/getkin/kin-openapi/openapi3/security_requirements.go @@ -49,3 +49,9 @@ func (security *SecurityRequirement) Validate(ctx context.Context, opts ...Valid return nil } + +// UnmarshalJSON sets SecurityRequirement to a copy of data. +func (security *SecurityRequirement) UnmarshalJSON(data []byte) (err error) { + *security, _, err = unmarshalStringMap[[]string](data) + return +} diff --git a/index/server/vendor/github.com/getkin/kin-openapi/openapi3/security_scheme.go b/index/server/vendor/github.com/getkin/kin-openapi/openapi3/security_scheme.go index 76cc21f37..676002aa5 100644 --- a/index/server/vendor/github.com/getkin/kin-openapi/openapi3/security_scheme.go +++ b/index/server/vendor/github.com/getkin/kin-openapi/openapi3/security_scheme.go @@ -6,31 +6,13 @@ import ( "errors" "fmt" "net/url" - - "github.com/go-openapi/jsonpointer" ) -type SecuritySchemes map[string]*SecuritySchemeRef - -// JSONLookup implements github.com/go-openapi/jsonpointer#JSONPointable -func (s SecuritySchemes) JSONLookup(token string) (interface{}, error) { - ref, ok := s[token] - if ref == nil || ok == false { - return nil, fmt.Errorf("object has no field %q", token) - } - - if ref.Ref != "" { - return &Ref{Ref: ref.Ref}, nil - } - return ref.Value, nil -} - -var _ jsonpointer.JSONPointable = (*SecuritySchemes)(nil) - // SecurityScheme is specified by OpenAPI/Swagger standard version 3. // See https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#security-scheme-object type SecurityScheme struct { - Extensions map[string]interface{} `json:"-" yaml:"-"` + Extensions map[string]any `json:"-" yaml:"-"` + Origin *Origin `json:"__origin__,omitempty" yaml:"__origin__,omitempty"` Type string `json:"type,omitempty" yaml:"type,omitempty"` Description string `json:"description,omitempty" yaml:"description,omitempty"` @@ -71,7 +53,16 @@ func NewJWTSecurityScheme() *SecurityScheme { // MarshalJSON returns the JSON encoding of SecurityScheme. func (ss SecurityScheme) MarshalJSON() ([]byte, error) { - m := make(map[string]interface{}, 8+len(ss.Extensions)) + 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 } @@ -99,7 +90,7 @@ func (ss SecurityScheme) MarshalJSON() ([]byte, error) { if x := ss.OpenIdConnectUrl; x != "" { m["openIdConnectUrl"] = x } - return json.Marshal(m) + return m, nil } // UnmarshalJSON sets SecurityScheme to a copy of data. @@ -107,9 +98,10 @@ func (ss *SecurityScheme) UnmarshalJSON(data []byte) error { type SecuritySchemeBis SecurityScheme var x SecuritySchemeBis if err := json.Unmarshal(data, &x); err != nil { - return err + return unmarshalError(err) } _ = json.Unmarshal(data, &x.Extensions) + delete(x.Extensions, originKey) delete(x.Extensions, "type") delete(x.Extensions, "description") delete(x.Extensions, "name") @@ -118,6 +110,9 @@ func (ss *SecurityScheme) UnmarshalJSON(data []byte) error { 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 } @@ -222,7 +217,8 @@ func (ss *SecurityScheme) Validate(ctx context.Context, opts ...ValidationOption // 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]interface{} `json:"-" yaml:"-"` + Extensions map[string]any `json:"-" yaml:"-"` + Origin *Origin `json:"__origin__,omitempty" yaml:"__origin__,omitempty"` Implicit *OAuthFlow `json:"implicit,omitempty" yaml:"implicit,omitempty"` Password *OAuthFlow `json:"password,omitempty" yaml:"password,omitempty"` @@ -241,7 +237,16 @@ const ( // MarshalJSON returns the JSON encoding of OAuthFlows. func (flows OAuthFlows) MarshalJSON() ([]byte, error) { - m := make(map[string]interface{}, 4+len(flows.Extensions)) + 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 } @@ -257,7 +262,7 @@ func (flows OAuthFlows) MarshalJSON() ([]byte, error) { if x := flows.AuthorizationCode; x != nil { m["authorizationCode"] = x } - return json.Marshal(m) + return m, nil } // UnmarshalJSON sets OAuthFlows to a copy of data. @@ -265,13 +270,17 @@ func (flows *OAuthFlows) UnmarshalJSON(data []byte) error { type OAuthFlowsBis OAuthFlows var x OAuthFlowsBis if err := json.Unmarshal(data, &x); err != nil { - return err + return unmarshalError(err) } _ = json.Unmarshal(data, &x.Extensions) + delete(x.Extensions, originKey) 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 } @@ -310,17 +319,27 @@ func (flows *OAuthFlows) Validate(ctx context.Context, opts ...ValidationOption) // 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]interface{} `json:"-" yaml:"-"` + Extensions map[string]any `json:"-" yaml:"-"` + Origin *Origin `json:"__origin__,omitempty" yaml:"__origin__,omitempty"` - 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 + 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 StringMap `json:"scopes" yaml:"scopes"` // required } // MarshalJSON returns the JSON encoding of OAuthFlow. func (flow OAuthFlow) MarshalJSON() ([]byte, error) { - m := make(map[string]interface{}, 4+len(flow.Extensions)) + 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 } @@ -334,7 +353,7 @@ func (flow OAuthFlow) MarshalJSON() ([]byte, error) { m["refreshUrl"] = x } m["scopes"] = flow.Scopes - return json.Marshal(m) + return m, nil } // UnmarshalJSON sets OAuthFlow to a copy of data. @@ -342,13 +361,18 @@ func (flow *OAuthFlow) UnmarshalJSON(data []byte) error { type OAuthFlowBis OAuthFlow var x OAuthFlowBis if err := json.Unmarshal(data, &x); err != nil { - return err + return unmarshalError(err) } _ = json.Unmarshal(data, &x.Extensions) + + delete(x.Extensions, originKey) 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 } @@ -410,3 +434,9 @@ func (flow *OAuthFlow) validate(ctx context.Context, typ oAuthFlowType, opts ... return flow.Validate(ctx, opts...) } + +// UnmarshalJSON sets SecuritySchemes to a copy of data. +func (securitySchemes *SecuritySchemes) UnmarshalJSON(data []byte) (err error) { + *securitySchemes, _, err = unmarshalStringMapP[SecuritySchemeRef](data) + return +} diff --git a/index/server/vendor/github.com/getkin/kin-openapi/openapi3/server.go b/index/server/vendor/github.com/getkin/kin-openapi/openapi3/server.go index 9fc99f90c..ea7751745 100644 --- a/index/server/vendor/github.com/getkin/kin-openapi/openapi3/server.go +++ b/index/server/vendor/github.com/getkin/kin-openapi/openapi3/server.go @@ -5,7 +5,6 @@ import ( "encoding/json" "errors" "fmt" - "math" "net/url" "sort" "strings" @@ -51,7 +50,8 @@ func (servers Servers) MatchURL(parsedURL *url.URL) (*Server, []string, string) // 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]interface{} `json:"-" yaml:"-"` + Extensions map[string]any `json:"-" yaml:"-"` + Origin *Origin `json:"__origin__,omitempty" yaml:"__origin__,omitempty"` URL string `json:"url" yaml:"url"` // Required Description string `json:"description,omitempty" yaml:"description,omitempty"` @@ -84,7 +84,16 @@ func (server *Server) BasePath() (string, error) { // MarshalJSON returns the JSON encoding of Server. func (server Server) MarshalJSON() ([]byte, error) { - m := make(map[string]interface{}, 3+len(server.Extensions)) + 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 } @@ -95,7 +104,7 @@ func (server Server) MarshalJSON() ([]byte, error) { if x := server.Variables; len(x) != 0 { m["variables"] = x } - return json.Marshal(m) + return m, nil } // UnmarshalJSON sets Server to a copy of data. @@ -103,12 +112,16 @@ func (server *Server) UnmarshalJSON(data []byte) error { type ServerBis Server var x ServerBis if err := json.Unmarshal(data, &x); err != nil { - return err + return unmarshalError(err) } _ = json.Unmarshal(data, &x.Extensions) + delete(x.Extensions, originKey) 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 } @@ -160,7 +173,7 @@ func (server Server) MatchRawURL(input string) ([]string, string, bool) { } else if ns < 0 { i = np } else { - i = int(math.Min(float64(np), float64(ns))) + i = min(np, ns) } if i < 0 { i = len(input) @@ -222,7 +235,8 @@ func (server *Server) Validate(ctx context.Context, opts ...ValidationOption) (e // 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]interface{} `json:"-" yaml:"-"` + Extensions map[string]any `json:"-" yaml:"-"` + Origin *Origin `json:"__origin__,omitempty" yaml:"__origin__,omitempty"` Enum []string `json:"enum,omitempty" yaml:"enum,omitempty"` Default string `json:"default,omitempty" yaml:"default,omitempty"` @@ -231,7 +245,16 @@ type ServerVariable struct { // MarshalJSON returns the JSON encoding of ServerVariable. func (serverVariable ServerVariable) MarshalJSON() ([]byte, error) { - m := make(map[string]interface{}, 4+len(serverVariable.Extensions)) + 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 } @@ -244,7 +267,7 @@ func (serverVariable ServerVariable) MarshalJSON() ([]byte, error) { if x := serverVariable.Description; x != "" { m["description"] = x } - return json.Marshal(m) + return m, nil } // UnmarshalJSON sets ServerVariable to a copy of data. @@ -252,12 +275,16 @@ func (serverVariable *ServerVariable) UnmarshalJSON(data []byte) error { type ServerVariableBis ServerVariable var x ServerVariableBis if err := json.Unmarshal(data, &x); err != nil { - return err + return unmarshalError(err) } _ = json.Unmarshal(data, &x.Extensions) + delete(x.Extensions, originKey) 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 } diff --git a/index/server/vendor/github.com/getkin/kin-openapi/openapi3/stringmap.go b/index/server/vendor/github.com/getkin/kin-openapi/openapi3/stringmap.go new file mode 100644 index 000000000..d1b91ac94 --- /dev/null +++ b/index/server/vendor/github.com/getkin/kin-openapi/openapi3/stringmap.go @@ -0,0 +1,88 @@ +package openapi3 + +import "encoding/json" + +// StringMap is a map[string]string that ignores the origin in the underlying json representation. +type StringMap map[string]string + +// UnmarshalJSON sets StringMap to a copy of data. +func (stringMap *StringMap) UnmarshalJSON(data []byte) (err error) { + *stringMap, _, err = unmarshalStringMap[string](data) + return +} + +// unmarshalStringMapP unmarshals given json into a map[string]*V +func unmarshalStringMapP[V any](data []byte) (map[string]*V, *Origin, error) { + var m map[string]any + if err := json.Unmarshal(data, &m); err != nil { + return nil, nil, err + } + + origin, err := popOrigin(m, originKey) + if err != nil { + return nil, nil, err + } + + result := make(map[string]*V, len(m)) + for k, v := range m { + value, err := deepCast[V](v) + if err != nil { + return nil, nil, err + } + result[k] = value + } + + return result, origin, nil +} + +// unmarshalStringMap unmarshals given json into a map[string]V +func unmarshalStringMap[V any](data []byte) (map[string]V, *Origin, error) { + var m map[string]any + if err := json.Unmarshal(data, &m); err != nil { + return nil, nil, err + } + + origin, err := popOrigin(m, originKey) + if err != nil { + return nil, nil, err + } + + result := make(map[string]V, len(m)) + for k, v := range m { + value, err := deepCast[V](v) + if err != nil { + return nil, nil, err + } + result[k] = *value + } + + return result, origin, nil +} + +// deepCast casts any value to a value of type V. +func deepCast[V any](value any) (*V, error) { + data, err := json.Marshal(value) + if err != nil { + return nil, err + } + + var result V + if err = json.Unmarshal(data, &result); err != nil { + return nil, err + } + return &result, nil +} + +// popOrigin removes the origin from the map and returns it. +func popOrigin(m map[string]any, key string) (*Origin, error) { + if !IncludeOrigin { + return nil, nil + } + + origin, err := deepCast[Origin](m[key]) + if err != nil { + return nil, err + } + delete(m, key) + return origin, nil +} diff --git a/index/server/vendor/github.com/getkin/kin-openapi/openapi3/tag.go b/index/server/vendor/github.com/getkin/kin-openapi/openapi3/tag.go index 93009a13c..841161ec0 100644 --- a/index/server/vendor/github.com/getkin/kin-openapi/openapi3/tag.go +++ b/index/server/vendor/github.com/getkin/kin-openapi/openapi3/tag.go @@ -33,7 +33,8 @@ func (tags Tags) Validate(ctx context.Context, opts ...ValidationOption) error { // 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]interface{} `json:"-" yaml:"-"` + Extensions map[string]any `json:"-" yaml:"-"` + Origin *Origin `json:"__origin__,omitempty" yaml:"__origin__,omitempty"` Name string `json:"name,omitempty" yaml:"name,omitempty"` Description string `json:"description,omitempty" yaml:"description,omitempty"` @@ -42,7 +43,16 @@ type Tag struct { // MarshalJSON returns the JSON encoding of Tag. func (t Tag) MarshalJSON() ([]byte, error) { - m := make(map[string]interface{}, 3+len(t.Extensions)) + 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 } @@ -55,7 +65,7 @@ func (t Tag) MarshalJSON() ([]byte, error) { if x := t.ExternalDocs; x != nil { m["externalDocs"] = x } - return json.Marshal(m) + return m, nil } // UnmarshalJSON sets Tag to a copy of data. @@ -63,12 +73,16 @@ func (t *Tag) UnmarshalJSON(data []byte) error { type TagBis Tag var x TagBis if err := json.Unmarshal(data, &x); err != nil { - return err + return unmarshalError(err) } _ = json.Unmarshal(data, &x.Extensions) + delete(x.Extensions, originKey) 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 } diff --git a/index/server/vendor/github.com/getkin/kin-openapi/openapi3/validation_options.go b/index/server/vendor/github.com/getkin/kin-openapi/openapi3/validation_options.go index 0ca12e5ab..1d141d40a 100644 --- a/index/server/vendor/github.com/getkin/kin-openapi/openapi3/validation_options.go +++ b/index/server/vendor/github.com/getkin/kin-openapi/openapi3/validation_options.go @@ -12,6 +12,8 @@ type ValidationOptions struct { schemaDefaultsValidationDisabled bool schemaFormatValidationEnabled bool schemaPatternValidationDisabled bool + schemaExtensionsInRefProhibited bool + regexCompilerFunc RegexCompilerFunc extraSiblingFieldsAllowed map[string]struct{} } @@ -20,10 +22,10 @@ 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 { - if options.extraSiblingFieldsAllowed == nil { - options.extraSiblingFieldsAllowed = make(map[string]struct{}, len(fields)) - } options.extraSiblingFieldsAllowed[field] = struct{}{} } } @@ -92,7 +94,35 @@ func DisableExamplesValidation() ValidationOption { } } -// WithValidationOptions allows adding validation options to a context object that can be used when validationg any OpenAPI type. +// 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 + } +} + +// SetRegexCompiler allows to override the regex implementation used to validate +// field "pattern". +func SetRegexCompiler(c RegexCompilerFunc) ValidationOption { + return func(options *ValidationOptions) { + options.regexCompilerFunc = c + } +} + +// 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 diff --git a/index/server/vendor/github.com/getkin/kin-openapi/openapi3/xml.go b/index/server/vendor/github.com/getkin/kin-openapi/openapi3/xml.go index 34ed3be32..e960249d3 100644 --- a/index/server/vendor/github.com/getkin/kin-openapi/openapi3/xml.go +++ b/index/server/vendor/github.com/getkin/kin-openapi/openapi3/xml.go @@ -8,7 +8,8 @@ import ( // 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]interface{} `json:"-" yaml:"-"` + Extensions map[string]any `json:"-" yaml:"-"` + Origin *Origin `json:"__origin__,omitempty" yaml:"__origin__,omitempty"` Name string `json:"name,omitempty" yaml:"name,omitempty"` Namespace string `json:"namespace,omitempty" yaml:"namespace,omitempty"` @@ -19,7 +20,16 @@ type XML struct { // MarshalJSON returns the JSON encoding of XML. func (xml XML) MarshalJSON() ([]byte, error) { - m := make(map[string]interface{}, 5+len(xml.Extensions)) + 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 } @@ -38,7 +48,7 @@ func (xml XML) MarshalJSON() ([]byte, error) { if x := xml.Wrapped; x { m["wrapped"] = x } - return json.Marshal(m) + return m, nil } // UnmarshalJSON sets XML to a copy of data. @@ -46,14 +56,18 @@ func (xml *XML) UnmarshalJSON(data []byte) error { type XMLBis XML var x XMLBis if err := json.Unmarshal(data, &x); err != nil { - return err + return unmarshalError(err) } _ = json.Unmarshal(data, &x.Extensions) + delete(x.Extensions, originKey) 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 } diff --git a/index/server/vendor/github.com/getkin/kin-openapi/openapi3filter/errors.go b/index/server/vendor/github.com/getkin/kin-openapi/openapi3filter/errors.go index b5454a75c..ea7c7c312 100644 --- a/index/server/vendor/github.com/getkin/kin-openapi/openapi3filter/errors.go +++ b/index/server/vendor/github.com/getkin/kin-openapi/openapi3filter/errors.go @@ -79,8 +79,7 @@ type SecurityRequirementsError struct { } func (err *SecurityRequirementsError) Error() string { - buff := &bytes.Buffer{} - buff.WriteString("security requirements failed: ") + buff := bytes.NewBufferString("security requirements failed: ") for i, e := range err.Errors { buff.WriteString(e.Error()) if i != len(err.Errors)-1 { @@ -90,3 +89,9 @@ func (err *SecurityRequirementsError) Error() string { return buff.String() } + +var _ interface{ Unwrap() []error } = SecurityRequirementsError{} + +func (err SecurityRequirementsError) Unwrap() []error { + return err.Errors +} diff --git a/index/server/vendor/github.com/getkin/kin-openapi/openapi3filter/internal.go b/index/server/vendor/github.com/getkin/kin-openapi/openapi3filter/internal.go index 5c6a8a6c6..f807e06f1 100644 --- a/index/server/vendor/github.com/getkin/kin-openapi/openapi3filter/internal.go +++ b/index/server/vendor/github.com/getkin/kin-openapi/openapi3filter/internal.go @@ -13,7 +13,7 @@ func parseMediaType(contentType string) string { return contentType[:i] } -func isNilValue(value interface{}) bool { +func isNilValue(value any) bool { if value == nil { return true } diff --git a/index/server/vendor/github.com/getkin/kin-openapi/openapi3filter/middleware.go b/index/server/vendor/github.com/getkin/kin-openapi/openapi3filter/middleware.go index 3bcb9db43..d20889ed9 100644 --- a/index/server/vendor/github.com/getkin/kin-openapi/openapi3filter/middleware.go +++ b/index/server/vendor/github.com/getkin/kin-openapi/openapi3filter/middleware.go @@ -2,8 +2,8 @@ package openapi3filter import ( "bytes" + "context" "io" - "io/ioutil" "log" "net/http" @@ -20,10 +20,10 @@ type Validator struct { } // ErrFunc handles errors that may occur during validation. -type ErrFunc func(w http.ResponseWriter, status int, code ErrCode, err error) +type ErrFunc func(ctx context.Context, w http.ResponseWriter, status int, code ErrCode, err error) // LogFunc handles log messages that may occur during validation. -type LogFunc func(message string, err error) +type LogFunc func(ctx context.Context, message string, err error) // ErrCode is used for classification of different types of errors that may // occur during validation. These may be used to write an appropriate response @@ -57,15 +57,15 @@ func (e ErrCode) responseText() string { } } -// NewValidator returns a new response validation middlware, using the given +// NewValidator returns a new response validation middleware, using the given // routes from an OpenAPI 3 specification. func NewValidator(router routers.Router, options ...ValidatorOption) *Validator { v := &Validator{ router: router, - errFunc: func(w http.ResponseWriter, status int, code ErrCode, _ error) { + errFunc: func(_ context.Context, w http.ResponseWriter, status int, code ErrCode, _ error) { http.Error(w, code.responseText(), status) }, - logFunc: func(message string, err error) { + logFunc: func(_ context.Context, message string, err error) { log.Printf("%s: %v", message, err) }, } @@ -118,10 +118,11 @@ func ValidationOptions(options Options) ValidatorOption { // request and response validation. func (v *Validator) Middleware(h http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + ctx := r.Context() route, pathParams, err := v.router.FindRoute(r) if err != nil { - v.logFunc("validation error: failed to find route for "+r.URL.String(), err) - v.errFunc(w, http.StatusNotFound, ErrCodeCannotFindRoute, err) + v.logFunc(ctx, "validation error: failed to find route for "+r.URL.String(), err) + v.errFunc(ctx, w, http.StatusNotFound, ErrCodeCannotFindRoute, err) return } requestValidationInput := &RequestValidationInput{ @@ -130,9 +131,9 @@ func (v *Validator) Middleware(h http.Handler) http.Handler { Route: route, Options: &v.options, } - if err = ValidateRequest(r.Context(), requestValidationInput); err != nil { - v.logFunc("invalid request", err) - v.errFunc(w, http.StatusBadRequest, ErrCodeRequestInvalid, err) + if err = ValidateRequest(ctx, requestValidationInput); err != nil { + v.logFunc(ctx, "invalid request", err) + v.errFunc(ctx, w, http.StatusBadRequest, ErrCodeRequestInvalid, err) return } @@ -145,22 +146,22 @@ func (v *Validator) Middleware(h http.Handler) http.Handler { h.ServeHTTP(wr, r) - if err = ValidateResponse(r.Context(), &ResponseValidationInput{ + if err = ValidateResponse(ctx, &ResponseValidationInput{ RequestValidationInput: requestValidationInput, Status: wr.statusCode(), Header: wr.Header(), - Body: ioutil.NopCloser(bytes.NewBuffer(wr.bodyContents())), + Body: io.NopCloser(bytes.NewBuffer(wr.bodyContents())), Options: &v.options, }); err != nil { - v.logFunc("invalid response", err) + v.logFunc(ctx, "invalid response", err) if v.strict { - v.errFunc(w, http.StatusInternalServerError, ErrCodeResponseInvalid, err) + v.errFunc(ctx, w, http.StatusInternalServerError, ErrCodeResponseInvalid, err) } return } if err = wr.flushBodyContents(); err != nil { - v.logFunc("failed to write response", err) + v.logFunc(ctx, "failed to write response", err) } }) } diff --git a/index/server/vendor/github.com/getkin/kin-openapi/openapi3filter/options.go b/index/server/vendor/github.com/getkin/kin-openapi/openapi3filter/options.go index 4bda60a26..e7fad8321 100644 --- a/index/server/vendor/github.com/getkin/kin-openapi/openapi3filter/options.go +++ b/index/server/vendor/github.com/getkin/kin-openapi/openapi3filter/options.go @@ -7,6 +7,9 @@ type Options struct { // Set ExcludeRequestBody so ValidateRequest skips request body validation ExcludeRequestBody bool + // Set ExcludeRequestQueryParams so ValidateRequest skips request query params validation + ExcludeRequestQueryParams bool + // Set ExcludeResponseBody so ValidateResponse skips response body validation ExcludeResponseBody bool @@ -22,6 +25,9 @@ type Options struct { MultiError bool + // Set RegexCompiler to override the regex implementation + RegexCompiler openapi3.RegexCompilerFunc + // A document with security schemes defined will not pass validation // unless an AuthenticationFunc is defined. // See NoopAuthenticationFunc diff --git a/index/server/vendor/github.com/getkin/kin-openapi/openapi3filter/req_resp_decoder.go b/index/server/vendor/github.com/getkin/kin-openapi/openapi3filter/req_resp_decoder.go index fd28c2f3e..7ed5221e5 100644 --- a/index/server/vendor/github.com/getkin/kin-openapi/openapi3filter/req_resp_decoder.go +++ b/index/server/vendor/github.com/getkin/kin-openapi/openapi3filter/req_resp_decoder.go @@ -8,16 +8,16 @@ import ( "errors" "fmt" "io" - "io/ioutil" "mime" "mime/multipart" "net/http" "net/url" + "reflect" "regexp" "strconv" "strings" - "gopkg.in/yaml.v3" + "github.com/oasdiff/yaml3" "github.com/getkin/kin-openapi/openapi3" ) @@ -39,11 +39,11 @@ const ( // ParseError describes errors which happens while parse operation's parameters, requestBody, or response. type ParseError struct { Kind ParseErrorKind - Value interface{} + Value any Reason string Cause error - path []interface{} + path []any } var _ interface{ Unwrap() error } = ParseError{} @@ -92,8 +92,8 @@ func (e ParseError) Unwrap() error { } // Path returns a path to the root cause. -func (e *ParseError) Path() []interface{} { - var path []interface{} +func (e *ParseError) Path() []any { + var path []any if v, ok := e.Cause.(*ParseError); ok { p := v.Path() if len(p) > 0 { @@ -113,7 +113,7 @@ func invalidSerializationMethodErr(sm *openapi3.SerializationMethod) error { // Decodes a parameter defined via the content property as an object. It uses // the user specified decoder, or our build-in decoder for application/json func decodeContentParameter(param *openapi3.Parameter, input *RequestValidationInput) ( - value interface{}, + value any, schema *openapi3.Schema, found bool, err error, @@ -164,7 +164,7 @@ func decodeContentParameter(param *openapi3.Parameter, input *RequestValidationI } func defaultContentParameterDecoder(param *openapi3.Parameter, values []string) ( - outValue interface{}, + outValue any, outSchema *openapi3.Schema, err error, ) { @@ -192,9 +192,9 @@ func defaultContentParameterDecoder(param *openapi3.Parameter, values []string) } outSchema = mt.Schema.Value - unmarshal := func(encoded string, paramSchema *openapi3.SchemaRef) (decoded interface{}, err error) { + unmarshal := func(encoded string, paramSchema *openapi3.SchemaRef) (decoded any, err error) { if err = json.Unmarshal([]byte(encoded), &decoded); err != nil { - if paramSchema != nil && paramSchema.Value.Type != "object" { + if paramSchema != nil && !paramSchema.Value.Type.Is("object") { decoded, err = encoded, nil } } @@ -207,9 +207,9 @@ func defaultContentParameterDecoder(param *openapi3.Parameter, values []string) return } } else { - outArray := make([]interface{}, 0, len(values)) + outArray := make([]any, 0, len(values)) for _, v := range values { - var item interface{} + var item any if item, err = unmarshal(v, outSchema.Items); err != nil { err = fmt.Errorf("error unmarshaling parameter %q", param.Name) return @@ -222,15 +222,15 @@ func defaultContentParameterDecoder(param *openapi3.Parameter, values []string) } type valueDecoder interface { - DecodePrimitive(param string, sm *openapi3.SerializationMethod, schema *openapi3.SchemaRef) (interface{}, bool, error) - DecodeArray(param string, sm *openapi3.SerializationMethod, schema *openapi3.SchemaRef) ([]interface{}, bool, error) - DecodeObject(param string, sm *openapi3.SerializationMethod, schema *openapi3.SchemaRef) (map[string]interface{}, bool, error) + DecodePrimitive(param string, sm *openapi3.SerializationMethod, schema *openapi3.SchemaRef) (any, bool, error) + DecodeArray(param string, sm *openapi3.SerializationMethod, schema *openapi3.SchemaRef) ([]any, bool, error) + DecodeObject(param string, sm *openapi3.SerializationMethod, schema *openapi3.SchemaRef) (map[string]any, bool, error) } // decodeStyledParameter returns a value of an operation's parameter from HTTP request for // parameters defined using the style format, and whether the parameter is supplied in the input. // The function returns ParseError when HTTP request contains an invalid value of a parameter. -func decodeStyledParameter(param *openapi3.Parameter, input *RequestValidationInput) (interface{}, bool, error) { +func decodeStyledParameter(param *openapi3.Parameter, input *RequestValidationInput) (any, bool, error) { sm, err := param.SerializationMethod() if err != nil { return nil, false, err @@ -259,11 +259,11 @@ func decodeStyledParameter(param *openapi3.Parameter, input *RequestValidationIn return decodeValue(dec, param.Name, sm, param.Schema, param.Required) } -func decodeValue(dec valueDecoder, param string, sm *openapi3.SerializationMethod, schema *openapi3.SchemaRef, required bool) (interface{}, bool, error) { +func decodeValue(dec valueDecoder, param string, sm *openapi3.SerializationMethod, schema *openapi3.SchemaRef, required bool) (any, bool, error) { var found bool if len(schema.Value.AllOf) > 0 { - var value interface{} + var value any var err error for _, sr := range schema.Value.AllOf { var f bool @@ -292,7 +292,7 @@ func decodeValue(dec valueDecoder, param string, sm *openapi3.SerializationMetho if len(schema.Value.OneOf) > 0 { isMatched := 0 - var value interface{} + var value any for _, sr := range schema.Value.OneOf { v, f, _ := decodeValue(dec, param, sm, sr, required) found = found || f @@ -315,15 +315,19 @@ func decodeValue(dec valueDecoder, param string, sm *openapi3.SerializationMetho return nil, found, errors.New("not implemented: decoding 'not'") } - if schema.Value.Type != "" { - var decodeFn func(param string, sm *openapi3.SerializationMethod, schema *openapi3.SchemaRef) (interface{}, bool, error) - switch schema.Value.Type { - case "array": - decodeFn = func(param string, sm *openapi3.SerializationMethod, schema *openapi3.SchemaRef) (interface{}, bool, error) { - return dec.DecodeArray(param, sm, schema) + if schema.Value.Type != nil { + var decodeFn func(param string, sm *openapi3.SerializationMethod, schema *openapi3.SchemaRef) (any, bool, error) + switch { + case schema.Value.Type.Is("array"): + decodeFn = func(param string, sm *openapi3.SerializationMethod, schema *openapi3.SchemaRef) (any, bool, error) { + res, b, e := dec.DecodeArray(param, sm, schema) + if len(res) == 0 { + return nil, b, e + } + return res, b, e } - case "object": - decodeFn = func(param string, sm *openapi3.SerializationMethod, schema *openapi3.SchemaRef) (interface{}, bool, error) { + case schema.Value.Type.Is("object"): + decodeFn = func(param string, sm *openapi3.SerializationMethod, schema *openapi3.SchemaRef) (any, bool, error) { return dec.DecodeObject(param, sm, schema) } default: @@ -355,7 +359,7 @@ type pathParamDecoder struct { pathParams map[string]string } -func (d *pathParamDecoder) DecodePrimitive(param string, sm *openapi3.SerializationMethod, schema *openapi3.SchemaRef) (interface{}, bool, error) { +func (d *pathParamDecoder) DecodePrimitive(param string, sm *openapi3.SerializationMethod, schema *openapi3.SchemaRef) (any, bool, error) { var prefix string switch sm.Style { case "simple": @@ -385,7 +389,7 @@ func (d *pathParamDecoder) DecodePrimitive(param string, sm *openapi3.Serializat return val, ok, err } -func (d *pathParamDecoder) DecodeArray(param string, sm *openapi3.SerializationMethod, schema *openapi3.SchemaRef) ([]interface{}, bool, error) { +func (d *pathParamDecoder) DecodeArray(param string, sm *openapi3.SerializationMethod, schema *openapi3.SchemaRef) ([]any, bool, error) { var prefix, delim string switch { case sm.Style == "simple": @@ -423,7 +427,7 @@ func (d *pathParamDecoder) DecodeArray(param string, sm *openapi3.SerializationM return val, ok, err } -func (d *pathParamDecoder) DecodeObject(param string, sm *openapi3.SerializationMethod, schema *openapi3.SchemaRef) (map[string]interface{}, bool, error) { +func (d *pathParamDecoder) DecodeObject(param string, sm *openapi3.SerializationMethod, schema *openapi3.SchemaRef) (map[string]any, bool, error) { var prefix, propsDelim, valueDelim string switch { case sm.Style == "simple" && !sm.Explode: @@ -469,6 +473,7 @@ func (d *pathParamDecoder) DecodeObject(param string, sm *openapi3.Serialization if err != nil { return nil, ok, err } + val, err := makeObject(props, schema) return val, ok, err } @@ -494,7 +499,7 @@ type urlValuesDecoder struct { values url.Values } -func (d *urlValuesDecoder) DecodePrimitive(param string, sm *openapi3.SerializationMethod, schema *openapi3.SchemaRef) (interface{}, bool, error) { +func (d *urlValuesDecoder) DecodePrimitive(param string, sm *openapi3.SerializationMethod, schema *openapi3.SchemaRef) (any, bool, error) { if sm.Style != "form" { return nil, false, invalidSerializationMethodErr(sm) } @@ -505,14 +510,14 @@ func (d *urlValuesDecoder) DecodePrimitive(param string, sm *openapi3.Serializat return nil, ok, nil } - if schema.Value.Type == "" && schema.Value.Pattern != "" { + if schema.Value.Type == nil && schema.Value.Pattern != "" { return values[0], ok, nil } val, err := parsePrimitive(values[0], schema) return val, ok, err } -func (d *urlValuesDecoder) DecodeArray(param string, sm *openapi3.SerializationMethod, schema *openapi3.SchemaRef) ([]interface{}, bool, error) { +func (d *urlValuesDecoder) DecodeArray(param string, sm *openapi3.SerializationMethod, schema *openapi3.SchemaRef) ([]any, bool, error) { if sm.Style == "deepObject" { return nil, false, invalidSerializationMethodErr(sm) } @@ -541,14 +546,14 @@ func (d *urlValuesDecoder) DecodeArray(param string, sm *openapi3.SerializationM // parseArray returns an array that contains items from a raw array. // Every item is parsed as a primitive value. // The function returns an error when an error happened while parse array's items. -func (d *urlValuesDecoder) parseArray(raw []string, sm *openapi3.SerializationMethod, schemaRef *openapi3.SchemaRef) ([]interface{}, error) { - var value []interface{} +func (d *urlValuesDecoder) parseArray(raw []string, sm *openapi3.SerializationMethod, schemaRef *openapi3.SchemaRef) ([]any, error) { + var value []any for i, v := range raw { item, err := d.parseValue(v, schemaRef.Value.Items) if err != nil { if v, ok := err.(*ParseError); ok { - return nil, &ParseError{path: []interface{}{i}, Cause: v} + return nil, &ParseError{path: []any{i}, Cause: v} } return nil, fmt.Errorf("item %d: %w", i, err) } @@ -563,9 +568,9 @@ func (d *urlValuesDecoder) parseArray(raw []string, sm *openapi3.SerializationMe return value, nil } -func (d *urlValuesDecoder) parseValue(v string, schema *openapi3.SchemaRef) (interface{}, error) { +func (d *urlValuesDecoder) parseValue(v string, schema *openapi3.SchemaRef) (any, error) { if len(schema.Value.AllOf) > 0 { - var value interface{} + var value any var err error for _, sr := range schema.Value.AllOf { value, err = d.parseValue(v, sr) @@ -577,7 +582,7 @@ func (d *urlValuesDecoder) parseValue(v string, schema *openapi3.SchemaRef) (int } if len(schema.Value.AnyOf) > 0 { - var value interface{} + var value any var err error for _, sr := range schema.Value.AnyOf { if value, err = d.parseValue(v, sr); err == nil { @@ -590,7 +595,7 @@ func (d *urlValuesDecoder) parseValue(v string, schema *openapi3.SchemaRef) (int if len(schema.Value.OneOf) > 0 { isMatched := 0 - var value interface{} + var value any var err error for _, sr := range schema.Value.OneOf { result, err := d.parseValue(v, sr) @@ -616,10 +621,13 @@ func (d *urlValuesDecoder) parseValue(v string, schema *openapi3.SchemaRef) (int } return parsePrimitive(v, schema) - } -func (d *urlValuesDecoder) DecodeObject(param string, sm *openapi3.SerializationMethod, schema *openapi3.SchemaRef) (map[string]interface{}, bool, error) { +const ( + urlDecoderDelimiter = "\x1F" // should not conflict with URL characters +) + +func (d *urlValuesDecoder) DecodeObject(param string, sm *openapi3.SerializationMethod, schema *openapi3.SchemaRef) (map[string]any, bool, error) { var propsFn func(url.Values) (map[string]string, error) switch sm.Style { case "form": @@ -646,12 +654,22 @@ func (d *urlValuesDecoder) DecodeObject(param string, sm *openapi3.Serialization propsFn = func(params url.Values) (map[string]string, error) { props := make(map[string]string) for key, values := range params { - groups := regexp.MustCompile(fmt.Sprintf("%s\\[(.+?)\\]", param)).FindAllStringSubmatch(key, -1) - if len(groups) == 0 { + if !regexp.MustCompile(fmt.Sprintf(`^%s\[`, regexp.QuoteMeta(param))).MatchString(key) { + continue + } + + matches := regexp.MustCompile(`\[(.*?)\]`).FindAllStringSubmatch(key, -1) + switch l := len(matches); { + case l == 0: // A query parameter's name does not match the required format, so skip it. continue + case l >= 1: + kk := []string{} + for _, m := range matches { + kk = append(kk, m[1]) + } + props[strings.Join(kk, urlDecoderDelimiter)] = strings.Join(values, urlDecoderDelimiter) } - props[groups[0][1]] = values[0] } if len(props) == 0 { // HTTP request does not contain query parameters encoded by rules of style "deepObject". @@ -662,7 +680,6 @@ func (d *urlValuesDecoder) DecodeObject(param string, sm *openapi3.Serialization default: return nil, false, invalidSerializationMethodErr(sm) } - props, err := propsFn(d.values) if err != nil { return nil, false, err @@ -670,17 +687,30 @@ func (d *urlValuesDecoder) DecodeObject(param string, sm *openapi3.Serialization if props == nil { return nil, false, nil } + val, err := makeObject(props, schema) + if err != nil { + return nil, false, err + } - // check the props found := false for propName := range schema.Value.Properties { if _, ok := props[propName]; ok { found = true break } + + if schema.Value.Type.Permits("array") || schema.Value.Type.Permits("object") { + for k := range props { + path := strings.Split(k, urlDecoderDelimiter) + if _, ok := deepGet(val, path...); ok { + found = true + break + } + } + } } - val, err := makeObject(props, schema) - return val, found, err + + return val, found, nil } // headerParamDecoder decodes values of header parameters. @@ -688,7 +718,7 @@ type headerParamDecoder struct { header http.Header } -func (d *headerParamDecoder) DecodePrimitive(param string, sm *openapi3.SerializationMethod, schema *openapi3.SchemaRef) (interface{}, bool, error) { +func (d *headerParamDecoder) DecodePrimitive(param string, sm *openapi3.SerializationMethod, schema *openapi3.SchemaRef) (any, bool, error) { if sm.Style != "simple" { return nil, false, invalidSerializationMethodErr(sm) } @@ -703,7 +733,7 @@ func (d *headerParamDecoder) DecodePrimitive(param string, sm *openapi3.Serializ return val, ok, err } -func (d *headerParamDecoder) DecodeArray(param string, sm *openapi3.SerializationMethod, schema *openapi3.SchemaRef) ([]interface{}, bool, error) { +func (d *headerParamDecoder) DecodeArray(param string, sm *openapi3.SerializationMethod, schema *openapi3.SchemaRef) ([]any, bool, error) { if sm.Style != "simple" { return nil, false, invalidSerializationMethodErr(sm) } @@ -718,7 +748,7 @@ func (d *headerParamDecoder) DecodeArray(param string, sm *openapi3.Serializatio return val, ok, err } -func (d *headerParamDecoder) DecodeObject(param string, sm *openapi3.SerializationMethod, schema *openapi3.SchemaRef) (map[string]interface{}, bool, error) { +func (d *headerParamDecoder) DecodeObject(param string, sm *openapi3.SerializationMethod, schema *openapi3.SchemaRef) (map[string]any, bool, error) { if sm.Style != "simple" { return nil, false, invalidSerializationMethodErr(sm) } @@ -745,7 +775,7 @@ type cookieParamDecoder struct { req *http.Request } -func (d *cookieParamDecoder) DecodePrimitive(param string, sm *openapi3.SerializationMethod, schema *openapi3.SchemaRef) (interface{}, bool, error) { +func (d *cookieParamDecoder) DecodePrimitive(param string, sm *openapi3.SerializationMethod, schema *openapi3.SchemaRef) (any, bool, error) { if sm.Style != "form" { return nil, false, invalidSerializationMethodErr(sm) } @@ -764,7 +794,7 @@ func (d *cookieParamDecoder) DecodePrimitive(param string, sm *openapi3.Serializ return val, found, err } -func (d *cookieParamDecoder) DecodeArray(param string, sm *openapi3.SerializationMethod, schema *openapi3.SchemaRef) ([]interface{}, bool, error) { +func (d *cookieParamDecoder) DecodeArray(param string, sm *openapi3.SerializationMethod, schema *openapi3.SchemaRef) ([]any, bool, error) { if sm.Style != "form" || sm.Explode { return nil, false, invalidSerializationMethodErr(sm) } @@ -782,7 +812,7 @@ func (d *cookieParamDecoder) DecodeArray(param string, sm *openapi3.Serializatio return val, found, err } -func (d *cookieParamDecoder) DecodeObject(param string, sm *openapi3.SerializationMethod, schema *openapi3.SchemaRef) (map[string]interface{}, bool, error) { +func (d *cookieParamDecoder) DecodeObject(param string, sm *openapi3.SerializationMethod, schema *openapi3.SchemaRef) (map[string]any, bool, error) { if sm.Style != "form" || sm.Explode { return nil, false, invalidSerializationMethodErr(sm) } @@ -812,7 +842,7 @@ func propsFromString(src, propDelim, valueDelim string) (map[string]string, erro pairs := strings.Split(src, propDelim) // When propDelim and valueDelim is equal the source string follow the next rule: - // every even item of pairs is a properies's name, and the subsequent odd item is a property's value. + // every even item of pairs is a properties's name, and the subsequent odd item is a property's value. if propDelim == valueDelim { // Taking into account the rule above, a valid source string must be splitted by propDelim // to an array with an even number of items. @@ -845,34 +875,252 @@ func propsFromString(src, propDelim, valueDelim string) (map[string]string, erro return props, nil } +func deepGet(m map[string]any, keys ...string) (any, bool) { + for _, key := range keys { + val, ok := m[key] + if !ok { + return nil, false + } + if m, ok = val.(map[string]any); !ok { + return val, true + } + } + return m, true +} + +func deepSet(m map[string]any, keys []string, value any) { + for i := 0; i < len(keys)-1; i++ { + key := keys[i] + if _, ok := m[key]; !ok { + m[key] = make(map[string]any) + } + m = m[key].(map[string]any) + } + m[keys[len(keys)-1]] = value +} + +func findNestedSchema(parentSchema *openapi3.SchemaRef, keys []string) (*openapi3.SchemaRef, error) { + currentSchema := parentSchema + for _, key := range keys { + if currentSchema.Value.Type.Includes(openapi3.TypeArray) { + currentSchema = currentSchema.Value.Items + } else { + propertySchema, ok := currentSchema.Value.Properties[key] + if !ok { + if currentSchema.Value.AdditionalProperties.Schema == nil { + return nil, fmt.Errorf("nested schema for key %q not found", key) + } + currentSchema = currentSchema.Value.AdditionalProperties.Schema + continue + } + currentSchema = propertySchema + } + } + return currentSchema, nil +} + // makeObject returns an object that contains properties from props. -// A value of every property is parsed as a primitive value. -// The function returns an error when an error happened while parse object's properties. -func makeObject(props map[string]string, schema *openapi3.SchemaRef) (map[string]interface{}, error) { - obj := make(map[string]interface{}) - for propName, propSchema := range schema.Value.Properties { - value, err := parsePrimitive(props[propName], propSchema) +func makeObject(props map[string]string, schema *openapi3.SchemaRef) (map[string]any, error) { + mobj := make(map[string]any) + + for kk, value := range props { + keys := strings.Split(kk, urlDecoderDelimiter) + if strings.Contains(value, urlDecoderDelimiter) { + // don't support implicit array indexes anymore + p := pathFromKeys(keys) + return nil, &ParseError{path: p, Kind: KindInvalidFormat, Reason: "array items must be set with indexes"} + } + deepSet(mobj, keys, value) + } + r, err := buildResObj(mobj, nil, "", schema) + if err != nil { + return nil, err + } + result, ok := r.(map[string]any) + if !ok { + return nil, &ParseError{Kind: KindOther, Reason: "invalid param object", Value: result} + } + + return result, nil +} + +// example: map[0:map[key:true] 1:map[key:false]] -> [map[key:true] map[key:false]] +func sliceMapToSlice(m map[string]any) ([]any, error) { + var result []any + + keys := make([]int, 0, len(m)) + for k := range m { + key, err := strconv.Atoi(k) if err != nil { - if v, ok := err.(*ParseError); ok { - return nil, &ParseError{path: []interface{}{propName}, Cause: v} + return nil, fmt.Errorf("array indexes must be integers: %w", err) + } + keys = append(keys, key) + } + max := -1 + for _, k := range keys { + if k > max { + max = k + } + } + for i := 0; i <= max; i++ { + val, ok := m[strconv.Itoa(i)] + if !ok { + result = append(result, nil) + continue + } + result = append(result, val) + } + return result, nil +} + +// buildResObj constructs an object based on a given schema and param values +func buildResObj(params map[string]any, parentKeys []string, key string, schema *openapi3.SchemaRef) (any, error) { + mapKeys := parentKeys + if key != "" { + mapKeys = append(mapKeys, key) + } + + switch { + case schema.Value.Type.Is("array"): + paramArr, ok := deepGet(params, mapKeys...) + if !ok { + return nil, nil + } + t, isMap := paramArr.(map[string]any) + if !isMap { + return nil, &ParseError{path: pathFromKeys(mapKeys), Kind: KindInvalidFormat, Reason: "array items must be set with indexes"} + } + // intermediate arrays have to be instantiated + arr, err := sliceMapToSlice(t) + if err != nil { + return nil, &ParseError{path: pathFromKeys(mapKeys), Kind: KindInvalidFormat, Reason: fmt.Sprintf("could not convert value map to array: %v", err)} + } + resultArr := make([]any /*not 0,*/, len(arr)) + for i := range arr { + r, err := buildResObj(params, mapKeys, strconv.Itoa(i), schema.Value.Items) + if err != nil { + return nil, err + } + if r != nil { + resultArr[i] = r } - return nil, fmt.Errorf("property %q: %w", propName, err) } - obj[propName] = value + return resultArr, nil + case schema.Value.Type.Is("object"): + resultMap := make(map[string]any) + additPropsSchema := schema.Value.AdditionalProperties.Schema + pp, _ := deepGet(params, mapKeys...) + objectParams, ok := pp.(map[string]any) + if !ok { + // not the expected type, but return it either way and leave validation up to ValidateParameter + return pp, nil + } + for k, propSchema := range schema.Value.Properties { + r, err := buildResObj(params, mapKeys, k, propSchema) + if err != nil { + return nil, err + } + if r != nil { + resultMap[k] = r + } + } + if additPropsSchema != nil { + // dynamic creation of possibly nested objects + for k := range objectParams { + r, err := buildResObj(params, mapKeys, k, additPropsSchema) + if err != nil { + return nil, err + } + if r != nil { + resultMap[k] = r + } + } + } + + return resultMap, nil + case len(schema.Value.AnyOf) > 0: + return buildFromSchemas(schema.Value.AnyOf, params, parentKeys, key) + case len(schema.Value.OneOf) > 0: + return buildFromSchemas(schema.Value.OneOf, params, parentKeys, key) + case len(schema.Value.AllOf) > 0: + return buildFromSchemas(schema.Value.AllOf, params, parentKeys, key) + default: + val, ok := deepGet(params, mapKeys...) + if !ok { + // leave validation up to ValidateParameter. here there really is not parameter set + return nil, nil + } + v, ok := val.(string) + if !ok { + return nil, &ParseError{path: pathFromKeys(mapKeys), Kind: KindInvalidFormat, Value: val, Reason: "path is not convertible to primitive"} + } + prim, err := parsePrimitive(v, schema) + if err != nil { + return nil, handlePropParseError(mapKeys, err) + } + + return prim, nil } - return obj, nil +} + +// buildFromSchemas decodes params with anyOf, oneOf, allOf schemas. +func buildFromSchemas(schemas openapi3.SchemaRefs, params map[string]any, mapKeys []string, key string) (any, error) { + resultMap := make(map[string]any) + for _, s := range schemas { + val, err := buildResObj(params, mapKeys, key, s) + if err == nil && val != nil { + + if m, ok := val.(map[string]any); ok { + for k, v := range m { + resultMap[k] = v + } + continue + } + + if a, ok := val.([]any); ok { + if len(a) > 0 { + return a, nil + } + continue + } + + // if its a primitive and not nil just return that and let it be validated + return val, nil + } + } + + if len(resultMap) > 0 { + return resultMap, nil + } + + return nil, nil +} + +func handlePropParseError(path []string, err error) error { + if v, ok := err.(*ParseError); ok { + return &ParseError{path: pathFromKeys(path), Cause: v} + } + return fmt.Errorf("property %q: %w", strings.Join(path, "."), err) +} + +func pathFromKeys(kk []string) []any { + path := make([]any, 0, len(kk)) + for _, v := range kk { + path = append(path, v) + } + return path } // parseArray returns an array that contains items from a raw array. // Every item is parsed as a primitive value. // The function returns an error when an error happened while parse array's items. -func parseArray(raw []string, schemaRef *openapi3.SchemaRef) ([]interface{}, error) { - var value []interface{} +func parseArray(raw []string, schemaRef *openapi3.SchemaRef) ([]any, error) { + var value []any for i, v := range raw { item, err := parsePrimitive(v, schemaRef.Value.Items) if err != nil { if v, ok := err.(*ParseError); ok { - return nil, &ParseError{path: []interface{}{i}, Cause: v} + return nil, &ParseError{path: []any{i}, Cause: v} } return nil, fmt.Errorf("item %d: %w", i, err) } @@ -890,40 +1138,49 @@ func parseArray(raw []string, schemaRef *openapi3.SchemaRef) ([]interface{}, err // parsePrimitive returns a value that is created by parsing a source string to a primitive type // that is specified by a schema. The function returns nil when the source string is empty. // The function panics when a schema has a non-primitive type. -func parsePrimitive(raw string, schema *openapi3.SchemaRef) (interface{}, error) { +func parsePrimitive(raw string, schema *openapi3.SchemaRef) (v any, err error) { if raw == "" { return nil, nil } - switch schema.Value.Type { + for _, typ := range schema.Value.Type.Slice() { + if v, err = parsePrimitiveCase(raw, schema, typ); err == nil { + return + } + } + return +} + +func parsePrimitiveCase(raw string, schema *openapi3.SchemaRef, typ string) (any, error) { + switch typ { case "integer": if schema.Value.Format == "int32" { v, err := strconv.ParseInt(raw, 0, 32) if err != nil { - return nil, &ParseError{Kind: KindInvalidFormat, Value: raw, Reason: "an invalid " + schema.Value.Type, Cause: err.(*strconv.NumError).Err} + return nil, &ParseError{Kind: KindInvalidFormat, Value: raw, Reason: "an invalid " + typ, Cause: err.(*strconv.NumError).Err} } return int32(v), nil } v, err := strconv.ParseInt(raw, 0, 64) if err != nil { - return nil, &ParseError{Kind: KindInvalidFormat, Value: raw, Reason: "an invalid " + schema.Value.Type, Cause: err.(*strconv.NumError).Err} + return nil, &ParseError{Kind: KindInvalidFormat, Value: raw, Reason: "an invalid " + typ, Cause: err.(*strconv.NumError).Err} } return v, nil case "number": v, err := strconv.ParseFloat(raw, 64) if err != nil { - return nil, &ParseError{Kind: KindInvalidFormat, Value: raw, Reason: "an invalid " + schema.Value.Type, Cause: err.(*strconv.NumError).Err} + return nil, &ParseError{Kind: KindInvalidFormat, Value: raw, Reason: "an invalid " + typ, Cause: err.(*strconv.NumError).Err} } return v, nil case "boolean": v, err := strconv.ParseBool(raw) if err != nil { - return nil, &ParseError{Kind: KindInvalidFormat, Value: raw, Reason: "an invalid " + schema.Value.Type, Cause: err.(*strconv.NumError).Err} + return nil, &ParseError{Kind: KindInvalidFormat, Value: raw, Reason: "an invalid " + typ, Cause: err.(*strconv.NumError).Err} } return v, nil case "string": return raw, nil default: - panic(fmt.Sprintf("schema has non primitive type %q", schema.Value.Type)) + return nil, &ParseError{Kind: KindOther, Value: raw, Reason: "schema has non primitive type " + typ} } } @@ -931,8 +1188,8 @@ func parsePrimitive(raw string, schema *openapi3.SchemaRef) (interface{}, error) type EncodingFn func(partName string) *openapi3.Encoding // BodyDecoder is an interface to decode a body of a request or response. -// An implementation must return a value that is a primitive, []interface{}, or map[string]interface{}. -type BodyDecoder func(io.Reader, http.Header, *openapi3.SchemaRef, EncodingFn) (interface{}, error) +// An implementation must return a value that is a primitive, []any, or map[string]any. +type BodyDecoder func(io.Reader, http.Header, *openapi3.SchemaRef, EncodingFn) (any, error) // bodyDecoders contains decoders for supported content types of a body. // By default, there is content type "application/json" is supported only. @@ -980,7 +1237,7 @@ const prefixUnsupportedCT = "unsupported content type" // The function returns ParseError when a body is invalid. func decodeBody(body io.Reader, header http.Header, schema *openapi3.SchemaRef, encFn EncodingFn) ( string, - interface{}, + any, error, ) { contentType := header.Get(headerCT) @@ -1005,29 +1262,33 @@ func decodeBody(body io.Reader, header http.Header, schema *openapi3.SchemaRef, } func init() { - RegisterBodyDecoder("application/json", jsonBodyDecoder) - RegisterBodyDecoder("application/json-patch+json", jsonBodyDecoder) + RegisterBodyDecoder("application/json", JSONBodyDecoder) + RegisterBodyDecoder("application/json-patch+json", JSONBodyDecoder) + RegisterBodyDecoder("application/ld+json", JSONBodyDecoder) + RegisterBodyDecoder("application/hal+json", JSONBodyDecoder) + RegisterBodyDecoder("application/vnd.api+json", JSONBodyDecoder) RegisterBodyDecoder("application/octet-stream", FileBodyDecoder) - RegisterBodyDecoder("application/problem+json", jsonBodyDecoder) - RegisterBodyDecoder("application/x-www-form-urlencoded", urlencodedBodyDecoder) - RegisterBodyDecoder("application/x-yaml", yamlBodyDecoder) - RegisterBodyDecoder("application/yaml", yamlBodyDecoder) - RegisterBodyDecoder("application/zip", zipFileBodyDecoder) - RegisterBodyDecoder("multipart/form-data", multipartBodyDecoder) - RegisterBodyDecoder("text/csv", csvBodyDecoder) - RegisterBodyDecoder("text/plain", plainBodyDecoder) + RegisterBodyDecoder("application/problem+json", JSONBodyDecoder) + RegisterBodyDecoder("application/x-www-form-urlencoded", UrlencodedBodyDecoder) + RegisterBodyDecoder("application/x-yaml", YamlBodyDecoder) + RegisterBodyDecoder("application/yaml", YamlBodyDecoder) + RegisterBodyDecoder("multipart/form-data", MultipartBodyDecoder) + RegisterBodyDecoder("text/csv", CsvBodyDecoder) + RegisterBodyDecoder("text/plain", PlainBodyDecoder) } -func plainBodyDecoder(body io.Reader, header http.Header, schema *openapi3.SchemaRef, encFn EncodingFn) (interface{}, error) { - data, err := ioutil.ReadAll(body) +func PlainBodyDecoder(body io.Reader, header http.Header, schema *openapi3.SchemaRef, encFn EncodingFn) (any, error) { + data, err := io.ReadAll(body) if err != nil { return nil, &ParseError{Kind: KindInvalidFormat, Cause: err} } return string(data), nil } -func jsonBodyDecoder(body io.Reader, header http.Header, schema *openapi3.SchemaRef, encFn EncodingFn) (interface{}, error) { - var value interface{} +// JSONBodyDecoder decodes a JSON formatted body. It is public so that is easy +// to register additional JSON based formats. +func JSONBodyDecoder(body io.Reader, header http.Header, schema *openapi3.SchemaRef, encFn EncodingFn) (any, error) { + var value any dec := json.NewDecoder(body) dec.UseNumber() if err := dec.Decode(&value); err != nil { @@ -1036,35 +1297,36 @@ func jsonBodyDecoder(body io.Reader, header http.Header, schema *openapi3.Schema return value, nil } -func yamlBodyDecoder(body io.Reader, header http.Header, schema *openapi3.SchemaRef, encFn EncodingFn) (interface{}, error) { - var value interface{} +func YamlBodyDecoder(body io.Reader, header http.Header, schema *openapi3.SchemaRef, encFn EncodingFn) (any, error) { + var value any if err := yaml.NewDecoder(body).Decode(&value); err != nil { return nil, &ParseError{Kind: KindInvalidFormat, Cause: err} } return value, nil } -func urlencodedBodyDecoder(body io.Reader, header http.Header, schema *openapi3.SchemaRef, encFn EncodingFn) (interface{}, error) { +func UrlencodedBodyDecoder(body io.Reader, header http.Header, schema *openapi3.SchemaRef, encFn EncodingFn) (any, error) { // Validate schema of request body. // By the OpenAPI 3 specification request body's schema must have type "object". // Properties of the schema describes individual parts of request body. - if schema.Value.Type != "object" { + if !schema.Value.Type.Is("object") { return nil, errors.New("unsupported schema of request body") } for propName, propSchema := range schema.Value.Properties { - switch propSchema.Value.Type { - case "object": + propType := propSchema.Value.Type + switch { + case propType.Is("object"): return nil, fmt.Errorf("unsupported schema of request body's property %q", propName) - case "array": + case propType.Is("array"): items := propSchema.Value.Items.Value - if items.Type != "string" && items.Type != "integer" && items.Type != "number" && items.Type != "boolean" { + if !(items.Type.Is("string") || items.Type.Is("integer") || items.Type.Is("number") || items.Type.Is("boolean")) { return nil, fmt.Errorf("unsupported schema of request body's property %q", propName) } } } // Parse form. - b, err := ioutil.ReadAll(body) + b, err := io.ReadAll(body) if err != nil { return nil, err } @@ -1074,34 +1336,67 @@ func urlencodedBodyDecoder(body io.Reader, header http.Header, schema *openapi3. } // Make an object value from form values. - obj := make(map[string]interface{}) + obj := make(map[string]any) dec := &urlValuesDecoder{values: values} - for name, prop := range schema.Value.Properties { - var ( - value interface{} - enc *openapi3.Encoding - ) - if encFn != nil { - enc = encFn(name) + + if err := decodeSchemaConstructs(dec, []*openapi3.SchemaRef{schema}, obj, encFn); err != nil { + return nil, err + } + + return obj, nil +} + +// decodeSchemaConstructs tries to decode properties based on provided schemas. +// This function is for decoding purposes only and not for validation. +func decodeSchemaConstructs(dec *urlValuesDecoder, schemas []*openapi3.SchemaRef, obj map[string]any, encFn EncodingFn) error { + for _, schemaRef := range schemas { + + // Decode schema constructs (allOf, anyOf, oneOf) + if err := decodeSchemaConstructs(dec, schemaRef.Value.AllOf, obj, encFn); err != nil { + return err + } + if err := decodeSchemaConstructs(dec, schemaRef.Value.AnyOf, obj, encFn); err != nil { + return err + } + if err := decodeSchemaConstructs(dec, schemaRef.Value.OneOf, obj, encFn); err != nil { + return err } - sm := enc.SerializationMethod() - if value, _, err = decodeValue(dec, name, sm, prop, false); err != nil { - return nil, err + for name, prop := range schemaRef.Value.Properties { + value, _, err := decodeProperty(dec, name, prop, encFn) + if err != nil { + continue + } + if existingValue, exists := obj[name]; exists && !isEqual(existingValue, value) { + return fmt.Errorf("conflicting values for property %q", name) + } + obj[name] = value } - obj[name] = value } - return obj, nil + return nil +} + +func isEqual(value1, value2 any) bool { + return reflect.DeepEqual(value1, value2) } -func multipartBodyDecoder(body io.Reader, header http.Header, schema *openapi3.SchemaRef, encFn EncodingFn) (interface{}, error) { - if schema.Value.Type != "object" { +func decodeProperty(dec valueDecoder, name string, prop *openapi3.SchemaRef, encFn EncodingFn) (any, bool, error) { + var enc *openapi3.Encoding + if encFn != nil { + enc = encFn(name) + } + sm := enc.SerializationMethod() + return decodeValue(dec, name, sm, prop, false) +} + +func MultipartBodyDecoder(body io.Reader, header http.Header, schema *openapi3.SchemaRef, encFn EncodingFn) (any, error) { + if !schema.Value.Type.Is("object") { return nil, errors.New("unsupported schema of request body") } // Parse form. - values := make(map[string][]interface{}) + values := make(map[string][]any) contentType := header.Get(headerCT) _, params, err := mime.ParseMediaType(contentType) if err != nil { @@ -1145,10 +1440,10 @@ func multipartBodyDecoder(body io.Reader, header http.Header, schema *openapi3.S if anyProperties := schema.Value.AdditionalProperties.Has; anyProperties != nil { switch *anyProperties { case true: - //additionalProperties: true + // additionalProperties: true continue default: - //additionalProperties: false + // additionalProperties: false return nil, &ParseError{Kind: KindOther, Cause: fmt.Errorf("part %s: undefined", name)} } } @@ -1159,15 +1454,15 @@ func multipartBodyDecoder(body io.Reader, header http.Header, schema *openapi3.S return nil, &ParseError{Kind: KindOther, Cause: fmt.Errorf("part %s: undefined", name)} } } - if valueSchema.Value.Type == "array" { + if valueSchema.Value.Type.Is("array") { valueSchema = valueSchema.Value.Items } } - var value interface{} + var value any if _, value, err = decodeBody(part, http.Header(part.Header), valueSchema, subEncFn); err != nil { if v, ok := err.(*ParseError); ok { - return nil, &ParseError{path: []interface{}{name}, Cause: v} + return nil, &ParseError{path: []any{name}, Cause: v} } return nil, fmt.Errorf("part %s: %w", name, err) } @@ -1198,13 +1493,13 @@ func multipartBodyDecoder(body io.Reader, header http.Header, schema *openapi3.S } // Make an object value from form values. - obj := make(map[string]interface{}) + obj := make(map[string]any) for name, prop := range allTheProperties { vv := values[name] if len(vv) == 0 { continue } - if prop.Value.Type == "array" { + if prop.Value.Type.Is("array") { obj[name] = vv } else { obj[name] = vv[0] @@ -1215,16 +1510,17 @@ func multipartBodyDecoder(body io.Reader, header http.Header, schema *openapi3.S } // FileBodyDecoder is a body decoder that decodes a file body to a string. -func FileBodyDecoder(body io.Reader, header http.Header, schema *openapi3.SchemaRef, encFn EncodingFn) (interface{}, error) { - data, err := ioutil.ReadAll(body) +func FileBodyDecoder(body io.Reader, header http.Header, schema *openapi3.SchemaRef, encFn EncodingFn) (any, error) { + data, err := io.ReadAll(body) if err != nil { return nil, err } return string(data), nil } -// zipFileBodyDecoder is a body decoder that decodes a zip file body to a string. -func zipFileBodyDecoder(body io.Reader, header http.Header, schema *openapi3.SchemaRef, encFn EncodingFn) (interface{}, error) { +// ZipFileBodyDecoder is a body decoder that decodes a zip file body to a string. +// Use with caution as this implementation may be susceptible to a zip bomb attack. +func ZipFileBodyDecoder(body io.Reader, header http.Header, schema *openapi3.SchemaRef, encFn EncodingFn) (any, error) { buff := bytes.NewBuffer([]byte{}) size, err := io.Copy(buff, body) if err != nil { @@ -1265,7 +1561,6 @@ func zipFileBodyDecoder(body io.Reader, header http.Header, schema *openapi3.Sch return nil }() - if err != nil { return nil, err } @@ -1274,11 +1569,11 @@ func zipFileBodyDecoder(body io.Reader, header http.Header, schema *openapi3.Sch return string(content), nil } -// csvBodyDecoder is a body decoder that decodes a csv body to a string. -func csvBodyDecoder(body io.Reader, header http.Header, schema *openapi3.SchemaRef, encFn EncodingFn) (interface{}, error) { +// CsvBodyDecoder is a body decoder that decodes a csv body to a string. +func CsvBodyDecoder(body io.Reader, header http.Header, schema *openapi3.SchemaRef, encFn EncodingFn) (any, error) { r := csv.NewReader(body) - var content string + var sb strings.Builder for { record, err := r.Read() if err == io.EOF { @@ -1288,8 +1583,9 @@ func csvBodyDecoder(body io.Reader, header http.Header, schema *openapi3.SchemaR return nil, err } - content += strings.Join(record, ",") + "\n" + sb.WriteString(strings.Join(record, ",")) + sb.WriteString("\n") } - return content, nil + return sb.String(), nil } diff --git a/index/server/vendor/github.com/getkin/kin-openapi/openapi3filter/req_resp_encoder.go b/index/server/vendor/github.com/getkin/kin-openapi/openapi3filter/req_resp_encoder.go index 36b7db6fd..5c328c756 100644 --- a/index/server/vendor/github.com/getkin/kin-openapi/openapi3filter/req_resp_encoder.go +++ b/index/server/vendor/github.com/getkin/kin-openapi/openapi3filter/req_resp_encoder.go @@ -3,25 +3,28 @@ package openapi3filter import ( "encoding/json" "fmt" + "sync" ) -func encodeBody(body interface{}, mediaType string) ([]byte, error) { - encoder, ok := bodyEncoders[mediaType] - if !ok { - return nil, &ParseError{ - Kind: KindUnsupportedFormat, - Reason: fmt.Sprintf("%s %q", prefixUnsupportedCT, mediaType), - } +func encodeBody(body any, mediaType string) ([]byte, error) { + if encoder := RegisteredBodyEncoder(mediaType); encoder != nil { + return encoder(body) + } + return nil, &ParseError{ + Kind: KindUnsupportedFormat, + Reason: fmt.Sprintf("%s %q", prefixUnsupportedCT, mediaType), } - return encoder(body) } -type BodyEncoder func(body interface{}) ([]byte, error) +// BodyEncoder really is an (encoding/json).Marshaler +type BodyEncoder func(body any) ([]byte, error) +var bodyEncodersM sync.RWMutex var bodyEncoders = map[string]BodyEncoder{ "application/json": json.Marshal, } +// RegisterBodyEncoder enables package-wide decoding of contentType values func RegisterBodyEncoder(contentType string, encoder BodyEncoder) { if contentType == "" { panic("contentType is empty") @@ -29,21 +32,27 @@ func RegisterBodyEncoder(contentType string, encoder BodyEncoder) { if encoder == nil { panic("encoder is not defined") } + bodyEncodersM.Lock() bodyEncoders[contentType] = encoder + bodyEncodersM.Unlock() } -// This call is not thread-safe: body encoders should not be created/destroyed by multiple goroutines. +// UnregisterBodyEncoder disables package-wide decoding of contentType values func UnregisterBodyEncoder(contentType string) { if contentType == "" { panic("contentType is empty") } + bodyEncodersM.Lock() delete(bodyEncoders, contentType) + bodyEncodersM.Unlock() } // RegisteredBodyEncoder returns the registered body encoder for the given content type. // // If no encoder was registered for the given content type, nil is returned. -// This call is not thread-safe: body encoders should not be created/destroyed by multiple goroutines. func RegisteredBodyEncoder(contentType string) BodyEncoder { - return bodyEncoders[contentType] + bodyEncodersM.RLock() + mayBE := bodyEncoders[contentType] + bodyEncodersM.RUnlock() + return mayBE } diff --git a/index/server/vendor/github.com/getkin/kin-openapi/openapi3filter/validate_request.go b/index/server/vendor/github.com/getkin/kin-openapi/openapi3filter/validate_request.go index f4debcda1..7456931a4 100644 --- a/index/server/vendor/github.com/getkin/kin-openapi/openapi3filter/validate_request.go +++ b/index/server/vendor/github.com/getkin/kin-openapi/openapi3filter/validate_request.go @@ -6,9 +6,10 @@ import ( "errors" "fmt" "io" - "io/ioutil" "net/http" + "net/url" "sort" + "strings" "github.com/getkin/kin-openapi/openapi3" ) @@ -29,7 +30,7 @@ var ErrInvalidEmptyValue = errors.New("empty value is not allowed") // // Note: One can tune the behavior of uniqueItems: true verification // by registering a custom function with openapi3.RegisterArrayUniqueItemsChecker -func ValidateRequest(ctx context.Context, input *RequestValidationInput) (err error) { +func ValidateRequest(ctx context.Context, input *RequestValidationInput) error { var me openapi3.MultiError options := input.Options @@ -49,10 +50,10 @@ func ValidateRequest(ctx context.Context, input *RequestValidationInput) (err er security = &route.Spec.Security } if security != nil { - if err = ValidateSecurityRequirements(ctx, input, *security); err != nil && !options.MultiError { - return - } - if err != nil { + if err := ValidateSecurityRequirements(ctx, input, *security); err != nil { + if !options.MultiError { + return err + } me = append(me, err) } } @@ -66,20 +67,23 @@ func ValidateRequest(ctx context.Context, input *RequestValidationInput) (err er } } - if err = ValidateParameter(ctx, input, parameter); err != nil && !options.MultiError { - return - } - if err != nil { + if err := ValidateParameter(ctx, input, parameter); err != nil { + if !options.MultiError { + return err + } me = append(me, err) } } // For each parameter of the Operation for _, parameter := range operationParameters { - if err = ValidateParameter(ctx, input, parameter.Value); err != nil && !options.MultiError { - return + if options.ExcludeRequestQueryParams && parameter.Value.In == openapi3.ParameterInQuery { + continue } - if err != nil { + if err := ValidateParameter(ctx, input, parameter.Value); err != nil { + if !options.MultiError { + return err + } me = append(me, err) } } @@ -87,10 +91,10 @@ func ValidateRequest(ctx context.Context, input *RequestValidationInput) (err er // RequestBody requestBody := operation.RequestBody if requestBody != nil && !options.ExcludeRequestBody { - if err = ValidateRequestBody(ctx, input, requestBody.Value); err != nil && !options.MultiError { - return - } - if err != nil { + if err := ValidateRequestBody(ctx, input, requestBody.Value); err != nil { + if !options.MultiError { + return err + } me = append(me, err) } } @@ -98,7 +102,36 @@ func ValidateRequest(ctx context.Context, input *RequestValidationInput) (err er if len(me) > 0 { return me } - return + return nil +} + +// appendToQueryValues adds to query parameters each value in the provided slice +func appendToQueryValues[T any](q url.Values, parameterName string, v []T) { + for _, i := range v { + q.Add(parameterName, fmt.Sprintf("%v", i)) + } +} + +func joinValues(values []any, sep string) string { + strValues := make([]string, 0, len(values)) + for _, v := range values { + strValues = append(strValues, fmt.Sprintf("%v", v)) + } + return strings.Join(strValues, sep) +} + +// populateDefaultQueryParameters populates default values inside query parameters, while ensuring types are respected +func populateDefaultQueryParameters(q url.Values, parameterName string, value any, explode bool) { + switch t := value.(type) { + case []any: + if explode { + appendToQueryValues(q, parameterName, t) + } else { + q.Add(parameterName, joinValues(t, ",")) + } + default: + q.Add(parameterName, fmt.Sprintf("%v", value)) + } } // ValidateParameter validates a parameter's value by JSON schema. @@ -119,7 +152,7 @@ func ValidateParameter(ctx context.Context, input *RequestValidationInput, param options = &Options{} } - var value interface{} + var value any var err error var found bool var schema *openapi3.Schema @@ -137,24 +170,34 @@ func ValidateParameter(ctx context.Context, input *RequestValidationInput, param } // Set default value if needed - if !options.SkipSettingDefaults && value == nil && schema != nil && schema.Default != nil { + if !options.SkipSettingDefaults && value == nil && schema != nil { value = schema.Default - req := input.Request - switch parameter.In { - case openapi3.ParameterInPath: - // Path parameters are required. - // Next check `parameter.Required && !found` will catch this. - case openapi3.ParameterInQuery: - q := req.URL.Query() - q.Add(parameter.Name, fmt.Sprintf("%v", value)) - req.URL.RawQuery = q.Encode() - case openapi3.ParameterInHeader: - req.Header.Add(parameter.Name, fmt.Sprintf("%v", value)) - case openapi3.ParameterInCookie: - req.AddCookie(&http.Cookie{ - Name: parameter.Name, - Value: fmt.Sprintf("%v", value), - }) + for _, subSchema := range schema.AllOf { + if subSchema.Value.Default != nil { + value = subSchema.Value.Default + break // This is not a validation of the schema itself, so use the first default value. + } + } + + if value != nil { + req := input.Request + switch parameter.In { + case openapi3.ParameterInPath: + // Path parameters are required. + // Next check `parameter.Required && !found` will catch this. + case openapi3.ParameterInQuery: + q := req.URL.Query() + explode := parameter.Explode != nil && *parameter.Explode + populateDefaultQueryParameters(q, parameter.Name, value, explode) + req.URL.RawQuery = q.Encode() + case openapi3.ParameterInHeader: + req.Header.Add(parameter.Name, fmt.Sprintf("%v", value)) + case openapi3.ParameterInCookie: + req.AddCookie(&http.Cookie{ + Name: parameter.Name, + Value: fmt.Sprintf("%v", value), + }) + } } } @@ -208,7 +251,7 @@ func ValidateRequestBody(ctx context.Context, input *RequestValidationInput, req if req.Body != http.NoBody && req.Body != nil { defer req.Body.Close() var err error - if data, err = ioutil.ReadAll(req.Body); err != nil { + if data, err = io.ReadAll(req.Body); err != nil { return &RequestError{ Input: input, RequestBody: requestBody, @@ -286,6 +329,9 @@ func ValidateRequestBody(ctx context.Context, input *RequestValidationInput, req if options.ExcludeReadOnlyValidations { opts = append(opts, openapi3.DisableReadOnlyValidation()) } + if options.RegexCompiler != nil { + opts = append(opts, openapi3.SetSchemaRegexCompiler(options.RegexCompiler)) + } // Validate JSON with the schema if err := contentType.Schema.Value.VisitJSON(value, opts...); err != nil { diff --git a/index/server/vendor/github.com/getkin/kin-openapi/openapi3filter/validate_request_input.go b/index/server/vendor/github.com/getkin/kin-openapi/openapi3filter/validate_request_input.go index 91dd102b6..c7565ebb7 100644 --- a/index/server/vendor/github.com/getkin/kin-openapi/openapi3filter/validate_request_input.go +++ b/index/server/vendor/github.com/getkin/kin-openapi/openapi3filter/validate_request_input.go @@ -17,7 +17,7 @@ import ( // If a query parameter appears multiple times, values[] will have more // than one value, but for all other parameter types it should have just // one. -type ContentParameterDecoder func(param *openapi3.Parameter, values []string) (interface{}, *openapi3.Schema, error) +type ContentParameterDecoder func(param *openapi3.Parameter, values []string) (any, *openapi3.Schema, error) type RequestValidationInput struct { Request *http.Request diff --git a/index/server/vendor/github.com/getkin/kin-openapi/openapi3filter/validate_response.go b/index/server/vendor/github.com/getkin/kin-openapi/openapi3filter/validate_response.go index dca13380a..a2dcc03a3 100644 --- a/index/server/vendor/github.com/getkin/kin-openapi/openapi3filter/validate_response.go +++ b/index/server/vendor/github.com/getkin/kin-openapi/openapi3filter/validate_response.go @@ -5,7 +5,7 @@ import ( "bytes" "context" "fmt" - "io/ioutil" + "io" "net/http" "sort" "strings" @@ -44,10 +44,10 @@ func ValidateResponse(ctx context.Context, input *ResponseValidationInput) error // Find input for the current status responses := route.Operation.Responses - if len(responses) == 0 { + if responses.Len() == 0 { return nil } - responseRef := responses.Get(status) // Response + responseRef := responses.Status(status) // Response if responseRef == nil { responseRef = responses.Default() // Default input } @@ -94,7 +94,7 @@ func ValidateResponse(ctx context.Context, input *ResponseValidationInput) error } content := response.Content - if len(content) == 0 || options.ExcludeResponseBody { + if len(content) == 0 { // An operation does not contains a validation schema for responses with this status code. return nil } @@ -104,7 +104,7 @@ func ValidateResponse(ctx context.Context, input *ResponseValidationInput) error if contentType == nil { return &ResponseError{ Input: input, - Reason: fmt.Sprintf("response header Content-Type has unexpected value: %q", inputMIME), + Reason: fmt.Sprintf("response %s: %q", prefixInvalidCT, inputMIME), } } @@ -125,7 +125,7 @@ func ValidateResponse(ctx context.Context, input *ResponseValidationInput) error defer body.Close() // Read all - data, err := ioutil.ReadAll(body) + data, err := io.ReadAll(body) if err != nil { return &ResponseError{ Input: input, @@ -162,7 +162,7 @@ func ValidateResponse(ctx context.Context, input *ResponseValidationInput) error func validateResponseHeader(headerName string, headerRef *openapi3.HeaderRef, input *ResponseValidationInput, opts []openapi3.SchemaValidationOption) error { var err error - var decodedValue interface{} + var decodedValue any var found bool var sm *openapi3.SerializationMethod dec := &headerParamDecoder{header: input.Header} diff --git a/index/server/vendor/github.com/getkin/kin-openapi/openapi3filter/validate_response_input.go b/index/server/vendor/github.com/getkin/kin-openapi/openapi3filter/validate_response_input.go index edf38730a..5592fe61d 100644 --- a/index/server/vendor/github.com/getkin/kin-openapi/openapi3filter/validate_response_input.go +++ b/index/server/vendor/github.com/getkin/kin-openapi/openapi3filter/validate_response_input.go @@ -3,7 +3,6 @@ package openapi3filter import ( "bytes" "io" - "io/ioutil" "net/http" ) @@ -16,7 +15,7 @@ type ResponseValidationInput struct { } func (input *ResponseValidationInput) SetBodyBytes(value []byte) *ResponseValidationInput { - input.Body = ioutil.NopCloser(bytes.NewReader(value)) + input.Body = io.NopCloser(bytes.NewReader(value)) return input } diff --git a/index/server/vendor/github.com/getkin/kin-openapi/openapi3filter/validation_error.go b/index/server/vendor/github.com/getkin/kin-openapi/openapi3filter/validation_error.go index 7e685cdef..4153eef7e 100644 --- a/index/server/vendor/github.com/getkin/kin-openapi/openapi3filter/validation_error.go +++ b/index/server/vendor/github.com/getkin/kin-openapi/openapi3filter/validation_error.go @@ -35,8 +35,7 @@ var _ error = &ValidationError{} // Error implements the error interface. func (e *ValidationError) Error() string { - b := new(bytes.Buffer) - b.WriteString("[") + b := bytes.NewBufferString("[") if e.Status != 0 { b.WriteString(strconv.Itoa(e.Status)) } diff --git a/index/server/vendor/github.com/getkin/kin-openapi/routers/gorillamux/router.go b/index/server/vendor/github.com/getkin/kin-openapi/routers/gorillamux/router.go index bbf81cea8..2d092426a 100644 --- a/index/server/vendor/github.com/getkin/kin-openapi/routers/gorillamux/router.go +++ b/index/server/vendor/github.com/getkin/kin-openapi/routers/gorillamux/router.go @@ -57,7 +57,7 @@ func NewRouter(doc *openapi3.T) (routers.Router, error) { muxRouter := mux.NewRouter().UseEncodedPath() r := &Router{} for _, path := range doc.Paths.InMatchingOrder() { - pathItem := doc.Paths[path] + pathItem := doc.Paths.Value(path) if len(pathItem.Servers) > 0 { if servers, err = makeServers(pathItem.Servers); err != nil { return nil, err @@ -113,7 +113,7 @@ func (r *Router) FindRoute(req *http.Request) (*routers.Route, map[string]string } route := *r.routes[i] route.Method = req.Method - route.Operation = route.Spec.Paths[route.Path].GetOperation(route.Method) + route.Operation = route.Spec.Paths.Value(route.Path).GetOperation(route.Method) return &route, vars, nil } switch match.MatchErr { diff --git a/index/server/vendor/github.com/getkin/kin-openapi/routers/legacy/pathpattern/node.go b/index/server/vendor/github.com/getkin/kin-openapi/routers/legacy/pathpattern/node.go index 011dda358..75932a26d 100644 --- a/index/server/vendor/github.com/getkin/kin-openapi/routers/legacy/pathpattern/node.go +++ b/index/server/vendor/github.com/getkin/kin-openapi/routers/legacy/pathpattern/node.go @@ -4,7 +4,7 @@ // - "/" // - "/abc"" // - "/abc/{variable}" (matches until next '/' or end-of-string) -// - "/abc/{variable*}" (matches everything, including "/abc" if "/abc" has noot) +// - "/abc/{variable*}" (matches everything, including "/abc" if "/abc" has root) // - "/abc/{ variable | prefix_(.*}_suffix }" (matches regular expressions) package pathpattern @@ -55,7 +55,7 @@ func PathFromHost(host string, specialDashes bool) string { type Node struct { VariableNames []string - Value interface{} + Value any Suffixes SuffixList } @@ -153,7 +153,7 @@ func (list SuffixList) Swap(i, j int) { list[i], list[j] = b, a } -func (currentNode *Node) MustAdd(path string, value interface{}, options *Options) { +func (currentNode *Node) MustAdd(path string, value any, options *Options) { node, err := currentNode.CreateNode(path, options) if err != nil { panic(err) @@ -161,7 +161,7 @@ func (currentNode *Node) MustAdd(path string, value interface{}, options *Option node.Value = value } -func (currentNode *Node) Add(path string, value interface{}, options *Options) error { +func (currentNode *Node) Add(path string, value any, options *Options) error { node, err := currentNode.CreateNode(path, options) if err != nil { return err diff --git a/index/server/vendor/github.com/getkin/kin-openapi/routers/legacy/router.go b/index/server/vendor/github.com/getkin/kin-openapi/routers/legacy/router.go index 911422b85..306449d3c 100644 --- a/index/server/vendor/github.com/getkin/kin-openapi/routers/legacy/router.go +++ b/index/server/vendor/github.com/getkin/kin-openapi/routers/legacy/router.go @@ -64,7 +64,7 @@ func NewRouter(doc *openapi3.T, opts ...openapi3.ValidationOption) (routers.Rout } router := &Router{doc: doc} root := router.node() - for path, pathItem := range doc.Paths { + for path, pathItem := range doc.Paths.Map() { for method, operation := range pathItem.Operations() { method = strings.ToUpper(method) if err := root.Add(method+" "+path, &routers.Route{ @@ -143,7 +143,7 @@ func (router *Router) FindRoute(req *http.Request) (*routers.Route, map[string]s route, _ = node.Value.(*routers.Route) } if route == nil { - pathItem := doc.Paths[remainingPath] + pathItem := doc.Paths.Value(remainingPath) if pathItem == nil { return nil, nil, &routers.RouteError{Reason: routers.ErrPathNotFound.Error()} } @@ -157,10 +157,7 @@ func (router *Router) FindRoute(req *http.Request) (*routers.Route, map[string]s } paramKeys := node.VariableNames for i, value := range paramValues { - key := paramKeys[i] - if strings.HasSuffix(key, "*") { - key = key[:len(key)-1] - } + key := strings.TrimSuffix(paramKeys[i], "*") pathParams[key] = value } return route, pathParams, nil diff --git a/index/server/vendor/github.com/go-openapi/jsonpointer/.golangci.yml b/index/server/vendor/github.com/go-openapi/jsonpointer/.golangci.yml new file mode 100644 index 000000000..22f8d21cc --- /dev/null +++ b/index/server/vendor/github.com/go-openapi/jsonpointer/.golangci.yml @@ -0,0 +1,61 @@ +linters-settings: + govet: + check-shadowing: true + golint: + min-confidence: 0 + gocyclo: + min-complexity: 45 + maligned: + suggest-new: true + dupl: + threshold: 200 + goconst: + min-len: 2 + min-occurrences: 3 + +linters: + enable-all: true + disable: + - maligned + - unparam + - lll + - gochecknoinits + - gochecknoglobals + - funlen + - godox + - gocognit + - whitespace + - wsl + - wrapcheck + - testpackage + - nlreturn + - gomnd + - exhaustivestruct + - goerr113 + - errorlint + - nestif + - godot + - gofumpt + - paralleltest + - tparallel + - thelper + - ifshort + - exhaustruct + - varnamelen + - gci + - depguard + - errchkjson + - inamedparam + - nonamedreturns + - musttag + - ireturn + - forcetypeassert + - cyclop + # deprecated linters + - deadcode + - interfacer + - scopelint + - varcheck + - structcheck + - golint + - nosnakecase diff --git a/index/server/vendor/github.com/go-openapi/jsonpointer/README.md b/index/server/vendor/github.com/go-openapi/jsonpointer/README.md index 813788aff..0108f1d57 100644 --- a/index/server/vendor/github.com/go-openapi/jsonpointer/README.md +++ b/index/server/vendor/github.com/go-openapi/jsonpointer/README.md @@ -1,6 +1,10 @@ -# gojsonpointer [![Build Status](https://travis-ci.org/go-openapi/jsonpointer.svg?branch=master)](https://travis-ci.org/go-openapi/jsonpointer) [![codecov](https://codecov.io/gh/go-openapi/jsonpointer/branch/master/graph/badge.svg)](https://codecov.io/gh/go-openapi/jsonpointer) [![Slack Status](https://slackin.goswagger.io/badge.svg)](https://slackin.goswagger.io) +# gojsonpointer [![Build Status](https://github.com/go-openapi/jsonpointer/actions/workflows/go-test.yml/badge.svg)](https://github.com/go-openapi/jsonpointer/actions?query=workflow%3A"go+test") [![codecov](https://codecov.io/gh/go-openapi/jsonpointer/branch/master/graph/badge.svg)](https://codecov.io/gh/go-openapi/jsonpointer) + +[![Slack Status](https://slackin.goswagger.io/badge.svg)](https://slackin.goswagger.io) +[![license](http://img.shields.io/badge/license-Apache%20v2-orange.svg)](https://raw.githubusercontent.com/go-openapi/jsonpointer/master/LICENSE) +[![Go Reference](https://pkg.go.dev/badge/github.com/go-openapi/jsonpointer.svg)](https://pkg.go.dev/github.com/go-openapi/jsonpointer) +[![Go Report Card](https://goreportcard.com/badge/github.com/go-openapi/jsonpointer)](https://goreportcard.com/report/github.com/go-openapi/jsonpointer) -[![license](http://img.shields.io/badge/license-Apache%20v2-orange.svg)](https://raw.githubusercontent.com/go-openapi/jsonpointer/master/LICENSE) [![GoDoc](https://godoc.org/github.com/go-openapi/jsonpointer?status.svg)](http://godoc.org/github.com/go-openapi/jsonpointer) An implementation of JSON Pointer - Go language ## Status diff --git a/index/server/vendor/github.com/go-openapi/jsonpointer/pointer.go b/index/server/vendor/github.com/go-openapi/jsonpointer/pointer.go index 7df9853de..d970c7cf4 100644 --- a/index/server/vendor/github.com/go-openapi/jsonpointer/pointer.go +++ b/index/server/vendor/github.com/go-openapi/jsonpointer/pointer.go @@ -26,6 +26,7 @@ package jsonpointer import ( + "encoding/json" "errors" "fmt" "reflect" @@ -40,6 +41,7 @@ const ( pointerSeparator = `/` invalidStart = `JSON pointer must be empty or start with a "` + pointerSeparator + notFound = `Can't find the pointer in the document` ) var jsonPointableType = reflect.TypeOf(new(JSONPointable)).Elem() @@ -48,13 +50,13 @@ var jsonSetableType = reflect.TypeOf(new(JSONSetable)).Elem() // JSONPointable is an interface for structs to implement when they need to customize the // json pointer process type JSONPointable interface { - JSONLookup(string) (interface{}, error) + JSONLookup(string) (any, error) } // JSONSetable is an interface for structs to implement when they need to customize the // json pointer process type JSONSetable interface { - JSONSet(string, interface{}) error + JSONSet(string, any) error } // New creates a new json pointer for the given string @@ -81,9 +83,7 @@ func (p *Pointer) parse(jsonPointerString string) error { err = errors.New(invalidStart) } else { referenceTokens := strings.Split(jsonPointerString, pointerSeparator) - for _, referenceToken := range referenceTokens[1:] { - p.referenceTokens = append(p.referenceTokens, referenceToken) - } + p.referenceTokens = append(p.referenceTokens, referenceTokens[1:]...) } } @@ -91,38 +91,58 @@ func (p *Pointer) parse(jsonPointerString string) error { } // Get uses the pointer to retrieve a value from a JSON document -func (p *Pointer) Get(document interface{}) (interface{}, reflect.Kind, error) { +func (p *Pointer) Get(document any) (any, reflect.Kind, error) { return p.get(document, swag.DefaultJSONNameProvider) } // Set uses the pointer to set a value from a JSON document -func (p *Pointer) Set(document interface{}, value interface{}) (interface{}, error) { +func (p *Pointer) Set(document any, value any) (any, error) { return document, p.set(document, value, swag.DefaultJSONNameProvider) } // GetForToken gets a value for a json pointer token 1 level deep -func GetForToken(document interface{}, decodedToken string) (interface{}, reflect.Kind, error) { +func GetForToken(document any, decodedToken string) (any, reflect.Kind, error) { return getSingleImpl(document, decodedToken, swag.DefaultJSONNameProvider) } // SetForToken gets a value for a json pointer token 1 level deep -func SetForToken(document interface{}, decodedToken string, value interface{}) (interface{}, error) { +func SetForToken(document any, decodedToken string, value any) (any, error) { return document, setSingleImpl(document, value, decodedToken, swag.DefaultJSONNameProvider) } -func getSingleImpl(node interface{}, decodedToken string, nameProvider *swag.NameProvider) (interface{}, reflect.Kind, error) { +func isNil(input any) bool { + if input == nil { + return true + } + + kind := reflect.TypeOf(input).Kind() + switch kind { //nolint:exhaustive + case reflect.Ptr, reflect.Map, reflect.Slice, reflect.Chan: + return reflect.ValueOf(input).IsNil() + default: + return false + } +} + +func getSingleImpl(node any, decodedToken string, nameProvider *swag.NameProvider) (any, reflect.Kind, error) { rValue := reflect.Indirect(reflect.ValueOf(node)) kind := rValue.Kind() + if isNil(node) { + return nil, kind, fmt.Errorf("nil value has not field %q", decodedToken) + } - if rValue.Type().Implements(jsonPointableType) { - r, err := node.(JSONPointable).JSONLookup(decodedToken) + switch typed := node.(type) { + case JSONPointable: + r, err := typed.JSONLookup(decodedToken) if err != nil { return nil, kind, err } return r, kind, nil + case *any: // case of a pointer to interface, that is not resolved by reflect.Indirect + return getSingleImpl(*typed, decodedToken, nameProvider) } - switch kind { + switch kind { //nolint:exhaustive case reflect.Struct: nm, ok := nameProvider.GetGoNameForType(rValue.Type(), decodedToken) if !ok { @@ -159,7 +179,7 @@ func getSingleImpl(node interface{}, decodedToken string, nameProvider *swag.Nam } -func setSingleImpl(node, data interface{}, decodedToken string, nameProvider *swag.NameProvider) error { +func setSingleImpl(node, data any, decodedToken string, nameProvider *swag.NameProvider) error { rValue := reflect.Indirect(reflect.ValueOf(node)) if ns, ok := node.(JSONSetable); ok { // pointer impl @@ -170,7 +190,7 @@ func setSingleImpl(node, data interface{}, decodedToken string, nameProvider *sw return node.(JSONSetable).JSONSet(decodedToken, data) } - switch rValue.Kind() { + switch rValue.Kind() { //nolint:exhaustive case reflect.Struct: nm, ok := nameProvider.GetGoNameForType(rValue.Type(), decodedToken) if !ok { @@ -210,7 +230,7 @@ func setSingleImpl(node, data interface{}, decodedToken string, nameProvider *sw } -func (p *Pointer) get(node interface{}, nameProvider *swag.NameProvider) (interface{}, reflect.Kind, error) { +func (p *Pointer) get(node any, nameProvider *swag.NameProvider) (any, reflect.Kind, error) { if nameProvider == nil { nameProvider = swag.DefaultJSONNameProvider @@ -231,8 +251,7 @@ func (p *Pointer) get(node interface{}, nameProvider *swag.NameProvider) (interf if err != nil { return nil, knd, err } - node, kind = r, knd - + node = r } rValue := reflect.ValueOf(node) @@ -241,11 +260,11 @@ func (p *Pointer) get(node interface{}, nameProvider *swag.NameProvider) (interf return node, kind, nil } -func (p *Pointer) set(node, data interface{}, nameProvider *swag.NameProvider) error { +func (p *Pointer) set(node, data any, nameProvider *swag.NameProvider) error { knd := reflect.ValueOf(node).Kind() if knd != reflect.Ptr && knd != reflect.Struct && knd != reflect.Map && knd != reflect.Slice && knd != reflect.Array { - return fmt.Errorf("only structs, pointers, maps and slices are supported for setting values") + return errors.New("only structs, pointers, maps and slices are supported for setting values") } if nameProvider == nil { @@ -284,7 +303,7 @@ func (p *Pointer) set(node, data interface{}, nameProvider *swag.NameProvider) e continue } - switch kind { + switch kind { //nolint:exhaustive case reflect.Struct: nm, ok := nameProvider.GetGoNameForType(rValue.Type(), decodedToken) if !ok { @@ -363,6 +382,128 @@ func (p *Pointer) String() string { return pointerString } +func (p *Pointer) Offset(document string) (int64, error) { + dec := json.NewDecoder(strings.NewReader(document)) + var offset int64 + for _, ttk := range p.DecodedTokens() { + tk, err := dec.Token() + if err != nil { + return 0, err + } + switch tk := tk.(type) { + case json.Delim: + switch tk { + case '{': + offset, err = offsetSingleObject(dec, ttk) + if err != nil { + return 0, err + } + case '[': + offset, err = offsetSingleArray(dec, ttk) + if err != nil { + return 0, err + } + default: + return 0, fmt.Errorf("invalid token %#v", tk) + } + default: + return 0, fmt.Errorf("invalid token %#v", tk) + } + } + return offset, nil +} + +func offsetSingleObject(dec *json.Decoder, decodedToken string) (int64, error) { + for dec.More() { + offset := dec.InputOffset() + tk, err := dec.Token() + if err != nil { + return 0, err + } + switch tk := tk.(type) { + case json.Delim: + switch tk { + case '{': + if err = drainSingle(dec); err != nil { + return 0, err + } + case '[': + if err = drainSingle(dec); err != nil { + return 0, err + } + } + case string: + if tk == decodedToken { + return offset, nil + } + default: + return 0, fmt.Errorf("invalid token %#v", tk) + } + } + return 0, fmt.Errorf("token reference %q not found", decodedToken) +} + +func offsetSingleArray(dec *json.Decoder, decodedToken string) (int64, error) { + idx, err := strconv.Atoi(decodedToken) + if err != nil { + return 0, fmt.Errorf("token reference %q is not a number: %v", decodedToken, err) + } + var i int + for i = 0; i < idx && dec.More(); i++ { + tk, err := dec.Token() + if err != nil { + return 0, err + } + + if delim, isDelim := tk.(json.Delim); isDelim { + switch delim { + case '{': + if err = drainSingle(dec); err != nil { + return 0, err + } + case '[': + if err = drainSingle(dec); err != nil { + return 0, err + } + } + } + } + + if !dec.More() { + return 0, fmt.Errorf("token reference %q not found", decodedToken) + } + return dec.InputOffset(), nil +} + +// drainSingle drains a single level of object or array. +// The decoder has to guarantee the beginning delim (i.e. '{' or '[') has been consumed. +func drainSingle(dec *json.Decoder) error { + for dec.More() { + tk, err := dec.Token() + if err != nil { + return err + } + if delim, isDelim := tk.(json.Delim); isDelim { + switch delim { + case '{': + if err = drainSingle(dec); err != nil { + return err + } + case '[': + if err = drainSingle(dec); err != nil { + return err + } + } + } + } + + // Consumes the ending delim + if _, err := dec.Token(); err != nil { + return err + } + return nil +} + // Specific JSON pointer encoding here // ~0 => ~ // ~1 => / @@ -377,14 +518,14 @@ const ( // Unescape unescapes a json pointer reference token string to the original representation func Unescape(token string) string { - step1 := strings.Replace(token, encRefTok1, decRefTok1, -1) - step2 := strings.Replace(step1, encRefTok0, decRefTok0, -1) + step1 := strings.ReplaceAll(token, encRefTok1, decRefTok1) + step2 := strings.ReplaceAll(step1, encRefTok0, decRefTok0) return step2 } // Escape escapes a pointer reference token string func Escape(token string) string { - step1 := strings.Replace(token, decRefTok0, encRefTok0, -1) - step2 := strings.Replace(step1, decRefTok1, encRefTok1, -1) + step1 := strings.ReplaceAll(token, decRefTok0, encRefTok0) + step2 := strings.ReplaceAll(step1, decRefTok1, encRefTok1) return step2 } diff --git a/index/server/vendor/github.com/go-openapi/swag/.gitignore b/index/server/vendor/github.com/go-openapi/swag/.gitignore index d69b53acc..c4b1b64f0 100644 --- a/index/server/vendor/github.com/go-openapi/swag/.gitignore +++ b/index/server/vendor/github.com/go-openapi/swag/.gitignore @@ -2,3 +2,4 @@ secrets.yml vendor Godeps .idea +*.out diff --git a/index/server/vendor/github.com/go-openapi/swag/.golangci.yml b/index/server/vendor/github.com/go-openapi/swag/.golangci.yml index bf503e400..80e2be004 100644 --- a/index/server/vendor/github.com/go-openapi/swag/.golangci.yml +++ b/index/server/vendor/github.com/go-openapi/swag/.golangci.yml @@ -4,14 +4,14 @@ linters-settings: golint: min-confidence: 0 gocyclo: - min-complexity: 25 + min-complexity: 45 maligned: suggest-new: true dupl: - threshold: 100 + threshold: 200 goconst: min-len: 3 - min-occurrences: 2 + min-occurrences: 3 linters: enable-all: true @@ -20,35 +20,41 @@ linters: - lll - gochecknoinits - gochecknoglobals - - nlreturn - - testpackage + - funlen + - godox + - gocognit + - whitespace + - wsl - wrapcheck + - testpackage + - nlreturn - gomnd - - exhaustive - exhaustivestruct - goerr113 - - wsl - - whitespace - - gofumpt - - godot + - errorlint - nestif - - godox - - funlen - - gci - - gocognit + - godot + - gofumpt - paralleltest + - tparallel - thelper - ifshort - - gomoddirectives - - cyclop - - forcetypeassert - - ireturn - - tagliatelle - - varnamelen - - goimports - - tenv - - golint - exhaustruct - - nilnil + - varnamelen + - gci + - depguard + - errchkjson + - inamedparam - nonamedreturns + - musttag + - ireturn + - forcetypeassert + - cyclop + # deprecated linters + - deadcode + - interfacer + - scopelint + - varcheck + - structcheck + - golint - nosnakecase diff --git a/index/server/vendor/github.com/go-openapi/swag/BENCHMARK.md b/index/server/vendor/github.com/go-openapi/swag/BENCHMARK.md new file mode 100644 index 000000000..e7f28ed6b --- /dev/null +++ b/index/server/vendor/github.com/go-openapi/swag/BENCHMARK.md @@ -0,0 +1,52 @@ +# Benchmarks + +## Name mangling utilities + +```bash +go test -bench XXX -run XXX -benchtime 30s +``` + +### Benchmarks at b3e7a5386f996177e4808f11acb2aa93a0f660df + +``` +goos: linux +goarch: amd64 +pkg: github.com/go-openapi/swag +cpu: Intel(R) Core(TM) i5-6200U CPU @ 2.30GHz +BenchmarkToXXXName/ToGoName-4 862623 44101 ns/op 10450 B/op 732 allocs/op +BenchmarkToXXXName/ToVarName-4 853656 40728 ns/op 10468 B/op 734 allocs/op +BenchmarkToXXXName/ToFileName-4 1268312 27813 ns/op 9785 B/op 617 allocs/op +BenchmarkToXXXName/ToCommandName-4 1276322 27903 ns/op 9785 B/op 617 allocs/op +BenchmarkToXXXName/ToHumanNameLower-4 895334 40354 ns/op 10472 B/op 731 allocs/op +BenchmarkToXXXName/ToHumanNameTitle-4 882441 40678 ns/op 10566 B/op 749 allocs/op +``` + +### Benchmarks after PR #79 + +~ x10 performance improvement and ~ /100 memory allocations. + +``` +goos: linux +goarch: amd64 +pkg: github.com/go-openapi/swag +cpu: Intel(R) Core(TM) i5-6200U CPU @ 2.30GHz +BenchmarkToXXXName/ToGoName-4 9595830 3991 ns/op 42 B/op 5 allocs/op +BenchmarkToXXXName/ToVarName-4 9194276 3984 ns/op 62 B/op 7 allocs/op +BenchmarkToXXXName/ToFileName-4 17002711 2123 ns/op 147 B/op 7 allocs/op +BenchmarkToXXXName/ToCommandName-4 16772926 2111 ns/op 147 B/op 7 allocs/op +BenchmarkToXXXName/ToHumanNameLower-4 9788331 3749 ns/op 92 B/op 6 allocs/op +BenchmarkToXXXName/ToHumanNameTitle-4 9188260 3941 ns/op 104 B/op 6 allocs/op +``` + +``` +goos: linux +goarch: amd64 +pkg: github.com/go-openapi/swag +cpu: AMD Ryzen 7 5800X 8-Core Processor +BenchmarkToXXXName/ToGoName-16 18527378 1972 ns/op 42 B/op 5 allocs/op +BenchmarkToXXXName/ToVarName-16 15552692 2093 ns/op 62 B/op 7 allocs/op +BenchmarkToXXXName/ToFileName-16 32161176 1117 ns/op 147 B/op 7 allocs/op +BenchmarkToXXXName/ToCommandName-16 32256634 1137 ns/op 147 B/op 7 allocs/op +BenchmarkToXXXName/ToHumanNameLower-16 18599661 1946 ns/op 92 B/op 6 allocs/op +BenchmarkToXXXName/ToHumanNameTitle-16 17581353 2054 ns/op 105 B/op 6 allocs/op +``` diff --git a/index/server/vendor/github.com/go-openapi/swag/README.md b/index/server/vendor/github.com/go-openapi/swag/README.md index 217f6fa50..a72922299 100644 --- a/index/server/vendor/github.com/go-openapi/swag/README.md +++ b/index/server/vendor/github.com/go-openapi/swag/README.md @@ -1,7 +1,8 @@ -# Swag [![Build Status](https://travis-ci.org/go-openapi/swag.svg?branch=master)](https://travis-ci.org/go-openapi/swag) [![codecov](https://codecov.io/gh/go-openapi/swag/branch/master/graph/badge.svg)](https://codecov.io/gh/go-openapi/swag) [![Slack Status](https://slackin.goswagger.io/badge.svg)](https://slackin.goswagger.io) +# Swag [![Build Status](https://github.com/go-openapi/swag/actions/workflows/go-test.yml/badge.svg)](https://github.com/go-openapi/swag/actions?query=workflow%3A"go+test") [![codecov](https://codecov.io/gh/go-openapi/swag/branch/master/graph/badge.svg)](https://codecov.io/gh/go-openapi/swag) +[![Slack Status](https://slackin.goswagger.io/badge.svg)](https://slackin.goswagger.io) [![license](http://img.shields.io/badge/license-Apache%20v2-orange.svg)](https://raw.githubusercontent.com/go-openapi/swag/master/LICENSE) -[![GoDoc](https://godoc.org/github.com/go-openapi/swag?status.svg)](http://godoc.org/github.com/go-openapi/swag) +[![Go Reference](https://pkg.go.dev/badge/github.com/go-openapi/swag.svg)](https://pkg.go.dev/github.com/go-openapi/swag) [![Go Report Card](https://goreportcard.com/badge/github.com/go-openapi/swag)](https://goreportcard.com/report/github.com/go-openapi/swag) Contains a bunch of helper functions for go-openapi and go-swagger projects. @@ -18,4 +19,5 @@ You may also use it standalone for your projects. This repo has only few dependencies outside of the standard library: -* YAML utilities depend on gopkg.in/yaml.v2 +* YAML utilities depend on `gopkg.in/yaml.v3` +* `github.com/mailru/easyjson v0.7.7` diff --git a/index/server/vendor/github.com/go-openapi/swag/initialism_index.go b/index/server/vendor/github.com/go-openapi/swag/initialism_index.go new file mode 100644 index 000000000..20a359bb6 --- /dev/null +++ b/index/server/vendor/github.com/go-openapi/swag/initialism_index.go @@ -0,0 +1,202 @@ +// Copyright 2015 go-swagger maintainers +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package swag + +import ( + "sort" + "strings" + "sync" +) + +var ( + // commonInitialisms are common acronyms that are kept as whole uppercased words. + commonInitialisms *indexOfInitialisms + + // initialisms is a slice of sorted initialisms + initialisms []string + + // a copy of initialisms pre-baked as []rune + initialismsRunes [][]rune + initialismsUpperCased [][]rune + + isInitialism func(string) bool + + maxAllocMatches int +) + +func init() { + // Taken from https://github.com/golang/lint/blob/3390df4df2787994aea98de825b964ac7944b817/lint.go#L732-L769 + configuredInitialisms := map[string]bool{ + "ACL": true, + "API": true, + "ASCII": true, + "CPU": true, + "CSS": true, + "DNS": true, + "EOF": true, + "GUID": true, + "HTML": true, + "HTTPS": true, + "HTTP": true, + "ID": true, + "IP": true, + "IPv4": true, + "IPv6": true, + "JSON": true, + "LHS": true, + "OAI": true, + "QPS": true, + "RAM": true, + "RHS": true, + "RPC": true, + "SLA": true, + "SMTP": true, + "SQL": true, + "SSH": true, + "TCP": true, + "TLS": true, + "TTL": true, + "UDP": true, + "UI": true, + "UID": true, + "UUID": true, + "URI": true, + "URL": true, + "UTF8": true, + "VM": true, + "XML": true, + "XMPP": true, + "XSRF": true, + "XSS": true, + } + + // a thread-safe index of initialisms + commonInitialisms = newIndexOfInitialisms().load(configuredInitialisms) + initialisms = commonInitialisms.sorted() + initialismsRunes = asRunes(initialisms) + initialismsUpperCased = asUpperCased(initialisms) + maxAllocMatches = maxAllocHeuristic(initialismsRunes) + + // a test function + isInitialism = commonInitialisms.isInitialism +} + +func asRunes(in []string) [][]rune { + out := make([][]rune, len(in)) + for i, initialism := range in { + out[i] = []rune(initialism) + } + + return out +} + +func asUpperCased(in []string) [][]rune { + out := make([][]rune, len(in)) + + for i, initialism := range in { + out[i] = []rune(upper(trim(initialism))) + } + + return out +} + +func maxAllocHeuristic(in [][]rune) int { + heuristic := make(map[rune]int) + for _, initialism := range in { + heuristic[initialism[0]]++ + } + + var maxAlloc int + for _, val := range heuristic { + if val > maxAlloc { + maxAlloc = val + } + } + + return maxAlloc +} + +// AddInitialisms add additional initialisms +func AddInitialisms(words ...string) { + for _, word := range words { + // commonInitialisms[upper(word)] = true + commonInitialisms.add(upper(word)) + } + // sort again + initialisms = commonInitialisms.sorted() + initialismsRunes = asRunes(initialisms) + initialismsUpperCased = asUpperCased(initialisms) +} + +// indexOfInitialisms is a thread-safe implementation of the sorted index of initialisms. +// Since go1.9, this may be implemented with sync.Map. +type indexOfInitialisms struct { + sortMutex *sync.Mutex + index *sync.Map +} + +func newIndexOfInitialisms() *indexOfInitialisms { + return &indexOfInitialisms{ + sortMutex: new(sync.Mutex), + index: new(sync.Map), + } +} + +func (m *indexOfInitialisms) load(initial map[string]bool) *indexOfInitialisms { + m.sortMutex.Lock() + defer m.sortMutex.Unlock() + for k, v := range initial { + m.index.Store(k, v) + } + return m +} + +func (m *indexOfInitialisms) isInitialism(key string) bool { + _, ok := m.index.Load(key) + return ok +} + +func (m *indexOfInitialisms) add(key string) *indexOfInitialisms { + m.index.Store(key, true) + return m +} + +func (m *indexOfInitialisms) sorted() (result []string) { + m.sortMutex.Lock() + defer m.sortMutex.Unlock() + m.index.Range(func(key, _ interface{}) bool { + k := key.(string) + result = append(result, k) + return true + }) + sort.Sort(sort.Reverse(byInitialism(result))) + return +} + +type byInitialism []string + +func (s byInitialism) Len() int { + return len(s) +} +func (s byInitialism) Swap(i, j int) { + s[i], s[j] = s[j], s[i] +} +func (s byInitialism) Less(i, j int) bool { + if len(s[i]) != len(s[j]) { + return len(s[i]) < len(s[j]) + } + + return strings.Compare(s[i], s[j]) > 0 +} diff --git a/index/server/vendor/github.com/go-openapi/swag/loading.go b/index/server/vendor/github.com/go-openapi/swag/loading.go index 00038c377..783442fdd 100644 --- a/index/server/vendor/github.com/go-openapi/swag/loading.go +++ b/index/server/vendor/github.com/go-openapi/swag/loading.go @@ -21,6 +21,7 @@ import ( "net/http" "net/url" "os" + "path" "path/filepath" "runtime" "strings" @@ -40,43 +41,97 @@ var LoadHTTPBasicAuthPassword = "" var LoadHTTPCustomHeaders = map[string]string{} // LoadFromFileOrHTTP loads the bytes from a file or a remote http server based on the path passed in -func LoadFromFileOrHTTP(path string) ([]byte, error) { - return LoadStrategy(path, os.ReadFile, loadHTTPBytes(LoadHTTPTimeout))(path) +func LoadFromFileOrHTTP(pth string) ([]byte, error) { + return LoadStrategy(pth, os.ReadFile, loadHTTPBytes(LoadHTTPTimeout))(pth) } // LoadFromFileOrHTTPWithTimeout loads the bytes from a file or a remote http server based on the path passed in // timeout arg allows for per request overriding of the request timeout -func LoadFromFileOrHTTPWithTimeout(path string, timeout time.Duration) ([]byte, error) { - return LoadStrategy(path, os.ReadFile, loadHTTPBytes(timeout))(path) +func LoadFromFileOrHTTPWithTimeout(pth string, timeout time.Duration) ([]byte, error) { + return LoadStrategy(pth, os.ReadFile, loadHTTPBytes(timeout))(pth) } -// LoadStrategy returns a loader function for a given path or uri -func LoadStrategy(path string, local, remote func(string) ([]byte, error)) func(string) ([]byte, error) { - if strings.HasPrefix(path, "http") { +// LoadStrategy returns a loader function for a given path or URI. +// +// The load strategy returns the remote load for any path starting with `http`. +// So this works for any URI with a scheme `http` or `https`. +// +// The fallback strategy is to call the local loader. +// +// The local loader takes a local file system path (absolute or relative) as argument, +// or alternatively a `file://...` URI, **without host** (see also below for windows). +// +// There are a few liberalities, initially intended to be tolerant regarding the URI syntax, +// especially on windows. +// +// Before the local loader is called, the given path is transformed: +// - percent-encoded characters are unescaped +// - simple paths (e.g. `./folder/file`) are passed as-is +// - on windows, occurrences of `/` are replaced by `\`, so providing a relative path such a `folder/file` works too. +// +// For paths provided as URIs with the "file" scheme, please note that: +// - `file://` is simply stripped. +// This means that the host part of the URI is not parsed at all. +// For example, `file:///folder/file" becomes "/folder/file`, +// but `file://localhost/folder/file` becomes `localhost/folder/file` on unix systems. +// Similarly, `file://./folder/file` yields `./folder/file`. +// - on windows, `file://...` can take a host so as to specify an UNC share location. +// +// Reminder about windows-specifics: +// - `file://host/folder/file` becomes an UNC path like `\\host\folder\file` (no port specification is supported) +// - `file:///c:/folder/file` becomes `C:\folder\file` +// - `file://c:/folder/file` is tolerated (without leading `/`) and becomes `c:\folder\file` +func LoadStrategy(pth string, local, remote func(string) ([]byte, error)) func(string) ([]byte, error) { + if strings.HasPrefix(pth, "http") { return remote } - return func(pth string) ([]byte, error) { - upth, err := pathUnescape(pth) + + return func(p string) ([]byte, error) { + upth, err := url.PathUnescape(p) if err != nil { return nil, err } - if strings.HasPrefix(pth, `file://`) { - if runtime.GOOS == "windows" { - // support for canonical file URIs on windows. - // Zero tolerance here for dodgy URIs. - u, _ := url.Parse(upth) - if u.Host != "" { - // assume UNC name (volume share) - // file://host/share/folder\... ==> \\host\share\path\folder - // NOTE: UNC port not yet supported - upth = strings.Join([]string{`\`, u.Host, u.Path}, `\`) - } else { - // file:///c:/folder/... ==> just remove the leading slash - upth = strings.TrimPrefix(upth, `file:///`) - } - } else { - upth = strings.TrimPrefix(upth, `file://`) + if !strings.HasPrefix(p, `file://`) { + // regular file path provided: just normalize slashes + return local(filepath.FromSlash(upth)) + } + + if runtime.GOOS != "windows" { + // crude processing: this leaves full URIs with a host with a (mostly) unexpected result + upth = strings.TrimPrefix(upth, `file://`) + + return local(filepath.FromSlash(upth)) + } + + // windows-only pre-processing of file://... URIs + + // support for canonical file URIs on windows. + u, err := url.Parse(filepath.ToSlash(upth)) + if err != nil { + return nil, err + } + + if u.Host != "" { + // assume UNC name (volume share) + // NOTE: UNC port not yet supported + + // when the "host" segment is a drive letter: + // file://C:/folder/... => C:\folder + upth = path.Clean(strings.Join([]string{u.Host, u.Path}, `/`)) + if !strings.HasSuffix(u.Host, ":") && u.Host[0] != '.' { + // tolerance: if we have a leading dot, this can't be a host + // file://host/share/folder\... ==> \\host\share\path\folder + upth = "//" + upth + } + } else { + // no host, let's figure out if this is a drive letter + upth = strings.TrimPrefix(upth, `file://`) + first, _, _ := strings.Cut(strings.TrimPrefix(u.Path, "/"), "/") + if strings.HasSuffix(first, ":") { + // drive letter in the first segment: + // file:///c:/folder/... ==> strip the leading slash + upth = strings.TrimPrefix(upth, `/`) } } diff --git a/index/server/vendor/github.com/go-openapi/swag/name_lexem.go b/index/server/vendor/github.com/go-openapi/swag/name_lexem.go index aa7f6a9bb..8bb64ac32 100644 --- a/index/server/vendor/github.com/go-openapi/swag/name_lexem.go +++ b/index/server/vendor/github.com/go-openapi/swag/name_lexem.go @@ -14,74 +14,80 @@ package swag -import "unicode" +import ( + "unicode" + "unicode/utf8" +) type ( - nameLexem interface { - GetUnsafeGoName() string - GetOriginal() string - IsInitialism() bool - } + lexemKind uint8 - initialismNameLexem struct { + nameLexem struct { original string matchedInitialism string + kind lexemKind } +) - casualNameLexem struct { - original string - } +const ( + lexemKindCasualName lexemKind = iota + lexemKindInitialismName ) -func newInitialismNameLexem(original, matchedInitialism string) *initialismNameLexem { - return &initialismNameLexem{ +func newInitialismNameLexem(original, matchedInitialism string) nameLexem { + return nameLexem{ + kind: lexemKindInitialismName, original: original, matchedInitialism: matchedInitialism, } } -func newCasualNameLexem(original string) *casualNameLexem { - return &casualNameLexem{ +func newCasualNameLexem(original string) nameLexem { + return nameLexem{ + kind: lexemKindCasualName, original: original, } } -func (l *initialismNameLexem) GetUnsafeGoName() string { - return l.matchedInitialism -} +func (l nameLexem) GetUnsafeGoName() string { + if l.kind == lexemKindInitialismName { + return l.matchedInitialism + } + + var ( + first rune + rest string + ) -func (l *casualNameLexem) GetUnsafeGoName() string { - var first rune - var rest string for i, orig := range l.original { if i == 0 { first = orig continue } + if i > 0 { rest = l.original[i:] break } } + if len(l.original) > 1 { - return string(unicode.ToUpper(first)) + lower(rest) + b := poolOfBuffers.BorrowBuffer(utf8.UTFMax + len(rest)) + defer func() { + poolOfBuffers.RedeemBuffer(b) + }() + b.WriteRune(unicode.ToUpper(first)) + b.WriteString(lower(rest)) + return b.String() } return l.original } -func (l *initialismNameLexem) GetOriginal() string { +func (l nameLexem) GetOriginal() string { return l.original } -func (l *casualNameLexem) GetOriginal() string { - return l.original -} - -func (l *initialismNameLexem) IsInitialism() bool { - return true -} - -func (l *casualNameLexem) IsInitialism() bool { - return false +func (l nameLexem) IsInitialism() bool { + return l.kind == lexemKindInitialismName } diff --git a/index/server/vendor/github.com/go-openapi/swag/post_go18.go b/index/server/vendor/github.com/go-openapi/swag/post_go18.go deleted file mode 100644 index f5228b82c..000000000 --- a/index/server/vendor/github.com/go-openapi/swag/post_go18.go +++ /dev/null @@ -1,24 +0,0 @@ -// Copyright 2015 go-swagger maintainers -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -//go:build go1.8 -// +build go1.8 - -package swag - -import "net/url" - -func pathUnescape(path string) (string, error) { - return url.PathUnescape(path) -} diff --git a/index/server/vendor/github.com/go-openapi/swag/post_go19.go b/index/server/vendor/github.com/go-openapi/swag/post_go19.go deleted file mode 100644 index 7c7da9c08..000000000 --- a/index/server/vendor/github.com/go-openapi/swag/post_go19.go +++ /dev/null @@ -1,68 +0,0 @@ -// Copyright 2015 go-swagger maintainers -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -//go:build go1.9 -// +build go1.9 - -package swag - -import ( - "sort" - "sync" -) - -// indexOfInitialisms is a thread-safe implementation of the sorted index of initialisms. -// Since go1.9, this may be implemented with sync.Map. -type indexOfInitialisms struct { - sortMutex *sync.Mutex - index *sync.Map -} - -func newIndexOfInitialisms() *indexOfInitialisms { - return &indexOfInitialisms{ - sortMutex: new(sync.Mutex), - index: new(sync.Map), - } -} - -func (m *indexOfInitialisms) load(initial map[string]bool) *indexOfInitialisms { - m.sortMutex.Lock() - defer m.sortMutex.Unlock() - for k, v := range initial { - m.index.Store(k, v) - } - return m -} - -func (m *indexOfInitialisms) isInitialism(key string) bool { - _, ok := m.index.Load(key) - return ok -} - -func (m *indexOfInitialisms) add(key string) *indexOfInitialisms { - m.index.Store(key, true) - return m -} - -func (m *indexOfInitialisms) sorted() (result []string) { - m.sortMutex.Lock() - defer m.sortMutex.Unlock() - m.index.Range(func(key, value interface{}) bool { - k := key.(string) - result = append(result, k) - return true - }) - sort.Sort(sort.Reverse(byInitialism(result))) - return -} diff --git a/index/server/vendor/github.com/go-openapi/swag/pre_go18.go b/index/server/vendor/github.com/go-openapi/swag/pre_go18.go deleted file mode 100644 index 2757d9b95..000000000 --- a/index/server/vendor/github.com/go-openapi/swag/pre_go18.go +++ /dev/null @@ -1,24 +0,0 @@ -// Copyright 2015 go-swagger maintainers -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -//go:build !go1.8 -// +build !go1.8 - -package swag - -import "net/url" - -func pathUnescape(path string) (string, error) { - return url.QueryUnescape(path) -} diff --git a/index/server/vendor/github.com/go-openapi/swag/pre_go19.go b/index/server/vendor/github.com/go-openapi/swag/pre_go19.go deleted file mode 100644 index 0565db377..000000000 --- a/index/server/vendor/github.com/go-openapi/swag/pre_go19.go +++ /dev/null @@ -1,70 +0,0 @@ -// Copyright 2015 go-swagger maintainers -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -//go:build !go1.9 -// +build !go1.9 - -package swag - -import ( - "sort" - "sync" -) - -// indexOfInitialisms is a thread-safe implementation of the sorted index of initialisms. -// Before go1.9, this may be implemented with a mutex on the map. -type indexOfInitialisms struct { - getMutex *sync.Mutex - index map[string]bool -} - -func newIndexOfInitialisms() *indexOfInitialisms { - return &indexOfInitialisms{ - getMutex: new(sync.Mutex), - index: make(map[string]bool, 50), - } -} - -func (m *indexOfInitialisms) load(initial map[string]bool) *indexOfInitialisms { - m.getMutex.Lock() - defer m.getMutex.Unlock() - for k, v := range initial { - m.index[k] = v - } - return m -} - -func (m *indexOfInitialisms) isInitialism(key string) bool { - m.getMutex.Lock() - defer m.getMutex.Unlock() - _, ok := m.index[key] - return ok -} - -func (m *indexOfInitialisms) add(key string) *indexOfInitialisms { - m.getMutex.Lock() - defer m.getMutex.Unlock() - m.index[key] = true - return m -} - -func (m *indexOfInitialisms) sorted() (result []string) { - m.getMutex.Lock() - defer m.getMutex.Unlock() - for k := range m.index { - result = append(result, k) - } - sort.Sort(sort.Reverse(byInitialism(result))) - return -} diff --git a/index/server/vendor/github.com/go-openapi/swag/split.go b/index/server/vendor/github.com/go-openapi/swag/split.go index a1825fb7d..274727a86 100644 --- a/index/server/vendor/github.com/go-openapi/swag/split.go +++ b/index/server/vendor/github.com/go-openapi/swag/split.go @@ -15,124 +15,269 @@ package swag import ( + "bytes" + "sync" "unicode" + "unicode/utf8" ) -var nameReplaceTable = map[rune]string{ - '@': "At ", - '&': "And ", - '|': "Pipe ", - '$': "Dollar ", - '!': "Bang ", - '-': "", - '_': "", -} - type ( splitter struct { - postSplitInitialismCheck bool initialisms []string + initialismsRunes [][]rune + initialismsUpperCased [][]rune // initialisms cached in their trimmed, upper-cased version + postSplitInitialismCheck bool + } + + splitterOption func(*splitter) + + initialismMatch struct { + body []rune + start, end int + complete bool + } + initialismMatches []initialismMatch +) + +type ( + // memory pools of temporary objects. + // + // These are used to recycle temporarily allocated objects + // and relieve the GC from undue pressure. + + matchesPool struct { + *sync.Pool } - splitterOption func(*splitter) *splitter + buffersPool struct { + *sync.Pool + } + + lexemsPool struct { + *sync.Pool + } + + splittersPool struct { + *sync.Pool + } ) -// split calls the splitter; splitter provides more control and post options +var ( + // poolOfMatches holds temporary slices for recycling during the initialism match process + poolOfMatches = matchesPool{ + Pool: &sync.Pool{ + New: func() any { + s := make(initialismMatches, 0, maxAllocMatches) + + return &s + }, + }, + } + + poolOfBuffers = buffersPool{ + Pool: &sync.Pool{ + New: func() any { + return new(bytes.Buffer) + }, + }, + } + + poolOfLexems = lexemsPool{ + Pool: &sync.Pool{ + New: func() any { + s := make([]nameLexem, 0, maxAllocMatches) + + return &s + }, + }, + } + + poolOfSplitters = splittersPool{ + Pool: &sync.Pool{ + New: func() any { + s := newSplitter() + + return &s + }, + }, + } +) + +// nameReplaceTable finds a word representation for special characters. +func nameReplaceTable(r rune) (string, bool) { + switch r { + case '@': + return "At ", true + case '&': + return "And ", true + case '|': + return "Pipe ", true + case '$': + return "Dollar ", true + case '!': + return "Bang ", true + case '-': + return "", true + case '_': + return "", true + default: + return "", false + } +} + +// split calls the splitter. +// +// Use newSplitter for more control and options func split(str string) []string { - lexems := newSplitter().split(str) - result := make([]string, 0, len(lexems)) + s := poolOfSplitters.BorrowSplitter() + lexems := s.split(str) + result := make([]string, 0, len(*lexems)) - for _, lexem := range lexems { + for _, lexem := range *lexems { result = append(result, lexem.GetOriginal()) } + poolOfLexems.RedeemLexems(lexems) + poolOfSplitters.RedeemSplitter(s) return result } -func (s *splitter) split(str string) []nameLexem { - return s.toNameLexems(str) -} - -func newSplitter(options ...splitterOption) *splitter { - splitter := &splitter{ +func newSplitter(options ...splitterOption) splitter { + s := splitter{ postSplitInitialismCheck: false, initialisms: initialisms, + initialismsRunes: initialismsRunes, + initialismsUpperCased: initialismsUpperCased, } for _, option := range options { - splitter = option(splitter) + option(&s) } - return splitter + return s } // withPostSplitInitialismCheck allows to catch initialisms after main split process -func withPostSplitInitialismCheck(s *splitter) *splitter { +func withPostSplitInitialismCheck(s *splitter) { s.postSplitInitialismCheck = true +} + +func (p matchesPool) BorrowMatches() *initialismMatches { + s := p.Get().(*initialismMatches) + *s = (*s)[:0] // reset slice, keep allocated capacity + return s } -type ( - initialismMatch struct { - start, end int - body []rune - complete bool +func (p buffersPool) BorrowBuffer(size int) *bytes.Buffer { + s := p.Get().(*bytes.Buffer) + s.Reset() + + if s.Cap() < size { + s.Grow(size) } - initialismMatches []*initialismMatch -) -func (s *splitter) toNameLexems(name string) []nameLexem { + return s +} + +func (p lexemsPool) BorrowLexems() *[]nameLexem { + s := p.Get().(*[]nameLexem) + *s = (*s)[:0] // reset slice, keep allocated capacity + + return s +} + +func (p splittersPool) BorrowSplitter(options ...splitterOption) *splitter { + s := p.Get().(*splitter) + s.postSplitInitialismCheck = false // reset options + for _, apply := range options { + apply(s) + } + + return s +} + +func (p matchesPool) RedeemMatches(s *initialismMatches) { + p.Put(s) +} + +func (p buffersPool) RedeemBuffer(s *bytes.Buffer) { + p.Put(s) +} + +func (p lexemsPool) RedeemLexems(s *[]nameLexem) { + p.Put(s) +} + +func (p splittersPool) RedeemSplitter(s *splitter) { + p.Put(s) +} + +func (m initialismMatch) isZero() bool { + return m.start == 0 && m.end == 0 +} + +func (s splitter) split(name string) *[]nameLexem { nameRunes := []rune(name) matches := s.gatherInitialismMatches(nameRunes) + if matches == nil { + return poolOfLexems.BorrowLexems() + } + return s.mapMatchesToNameLexems(nameRunes, matches) } -func (s *splitter) gatherInitialismMatches(nameRunes []rune) initialismMatches { - matches := make(initialismMatches, 0) +func (s splitter) gatherInitialismMatches(nameRunes []rune) *initialismMatches { + var matches *initialismMatches for currentRunePosition, currentRune := range nameRunes { - newMatches := make(initialismMatches, 0, len(matches)) + // recycle these allocations as we loop over runes + // with such recycling, only 2 slices should be allocated per call + // instead of o(n). + newMatches := poolOfMatches.BorrowMatches() // check current initialism matches - for _, match := range matches { - if keepCompleteMatch := match.complete; keepCompleteMatch { - newMatches = append(newMatches, match) - continue - } + if matches != nil { // skip first iteration + for _, match := range *matches { + if keepCompleteMatch := match.complete; keepCompleteMatch { + *newMatches = append(*newMatches, match) + continue + } - // drop failed match - currentMatchRune := match.body[currentRunePosition-match.start] - if !s.initialismRuneEqual(currentMatchRune, currentRune) { - continue - } + // drop failed match + currentMatchRune := match.body[currentRunePosition-match.start] + if currentMatchRune != currentRune { + continue + } - // try to complete ongoing match - if currentRunePosition-match.start == len(match.body)-1 { - // we are close; the next step is to check the symbol ahead - // if it is a small letter, then it is not the end of match - // but beginning of the next word - - if currentRunePosition < len(nameRunes)-1 { - nextRune := nameRunes[currentRunePosition+1] - if newWord := unicode.IsLower(nextRune); newWord { - // oh ok, it was the start of a new word - continue + // try to complete ongoing match + if currentRunePosition-match.start == len(match.body)-1 { + // we are close; the next step is to check the symbol ahead + // if it is a small letter, then it is not the end of match + // but beginning of the next word + + if currentRunePosition < len(nameRunes)-1 { + nextRune := nameRunes[currentRunePosition+1] + if newWord := unicode.IsLower(nextRune); newWord { + // oh ok, it was the start of a new word + continue + } } + + match.complete = true + match.end = currentRunePosition } - match.complete = true - match.end = currentRunePosition + *newMatches = append(*newMatches, match) } - - newMatches = append(newMatches, match) } // check for new initialism matches - for _, initialism := range s.initialisms { - initialismRunes := []rune(initialism) - if s.initialismRuneEqual(initialismRunes[0], currentRune) { - newMatches = append(newMatches, &initialismMatch{ + for i := range s.initialisms { + initialismRunes := s.initialismsRunes[i] + if initialismRunes[0] == currentRune { + *newMatches = append(*newMatches, initialismMatch{ start: currentRunePosition, body: initialismRunes, complete: false, @@ -140,24 +285,28 @@ func (s *splitter) gatherInitialismMatches(nameRunes []rune) initialismMatches { } } + if matches != nil { + poolOfMatches.RedeemMatches(matches) + } matches = newMatches } + // up to the caller to redeem this last slice return matches } -func (s *splitter) mapMatchesToNameLexems(nameRunes []rune, matches initialismMatches) []nameLexem { - nameLexems := make([]nameLexem, 0) +func (s splitter) mapMatchesToNameLexems(nameRunes []rune, matches *initialismMatches) *[]nameLexem { + nameLexems := poolOfLexems.BorrowLexems() - var lastAcceptedMatch *initialismMatch - for _, match := range matches { + var lastAcceptedMatch initialismMatch + for _, match := range *matches { if !match.complete { continue } - if firstMatch := lastAcceptedMatch == nil; firstMatch { - nameLexems = append(nameLexems, s.breakCasualString(nameRunes[:match.start])...) - nameLexems = append(nameLexems, s.breakInitialism(string(match.body))) + if firstMatch := lastAcceptedMatch.isZero(); firstMatch { + s.appendBrokenDownCasualString(nameLexems, nameRunes[:match.start]) + *nameLexems = append(*nameLexems, s.breakInitialism(string(match.body))) lastAcceptedMatch = match @@ -169,63 +318,66 @@ func (s *splitter) mapMatchesToNameLexems(nameRunes []rune, matches initialismMa } middle := nameRunes[lastAcceptedMatch.end+1 : match.start] - nameLexems = append(nameLexems, s.breakCasualString(middle)...) - nameLexems = append(nameLexems, s.breakInitialism(string(match.body))) + s.appendBrokenDownCasualString(nameLexems, middle) + *nameLexems = append(*nameLexems, s.breakInitialism(string(match.body))) lastAcceptedMatch = match } // we have not found any accepted matches - if lastAcceptedMatch == nil { - return s.breakCasualString(nameRunes) - } - - if lastAcceptedMatch.end+1 != len(nameRunes) { + if lastAcceptedMatch.isZero() { + *nameLexems = (*nameLexems)[:0] + s.appendBrokenDownCasualString(nameLexems, nameRunes) + } else if lastAcceptedMatch.end+1 != len(nameRunes) { rest := nameRunes[lastAcceptedMatch.end+1:] - nameLexems = append(nameLexems, s.breakCasualString(rest)...) + s.appendBrokenDownCasualString(nameLexems, rest) } - return nameLexems -} + poolOfMatches.RedeemMatches(matches) -func (s *splitter) initialismRuneEqual(a, b rune) bool { - return a == b + return nameLexems } -func (s *splitter) breakInitialism(original string) nameLexem { +func (s splitter) breakInitialism(original string) nameLexem { return newInitialismNameLexem(original, original) } -func (s *splitter) breakCasualString(str []rune) []nameLexem { - segments := make([]nameLexem, 0) - currentSegment := "" +func (s splitter) appendBrokenDownCasualString(segments *[]nameLexem, str []rune) { + currentSegment := poolOfBuffers.BorrowBuffer(len(str)) // unlike strings.Builder, bytes.Buffer initial storage can reused + defer func() { + poolOfBuffers.RedeemBuffer(currentSegment) + }() addCasualNameLexem := func(original string) { - segments = append(segments, newCasualNameLexem(original)) + *segments = append(*segments, newCasualNameLexem(original)) } addInitialismNameLexem := func(original, match string) { - segments = append(segments, newInitialismNameLexem(original, match)) + *segments = append(*segments, newInitialismNameLexem(original, match)) } - addNameLexem := func(original string) { - if s.postSplitInitialismCheck { - for _, initialism := range s.initialisms { - if upper(initialism) == upper(original) { - addInitialismNameLexem(original, initialism) + var addNameLexem func(string) + if s.postSplitInitialismCheck { + addNameLexem = func(original string) { + for i := range s.initialisms { + if isEqualFoldIgnoreSpace(s.initialismsUpperCased[i], original) { + addInitialismNameLexem(original, s.initialisms[i]) + return } } - } - addCasualNameLexem(original) + addCasualNameLexem(original) + } + } else { + addNameLexem = addCasualNameLexem } - for _, rn := range string(str) { - if replace, found := nameReplaceTable[rn]; found { - if currentSegment != "" { - addNameLexem(currentSegment) - currentSegment = "" + for _, rn := range str { + if replace, found := nameReplaceTable(rn); found { + if currentSegment.Len() > 0 { + addNameLexem(currentSegment.String()) + currentSegment.Reset() } if replace != "" { @@ -236,27 +388,121 @@ func (s *splitter) breakCasualString(str []rune) []nameLexem { } if !unicode.In(rn, unicode.L, unicode.M, unicode.N, unicode.Pc) { - if currentSegment != "" { - addNameLexem(currentSegment) - currentSegment = "" + if currentSegment.Len() > 0 { + addNameLexem(currentSegment.String()) + currentSegment.Reset() } continue } if unicode.IsUpper(rn) { - if currentSegment != "" { - addNameLexem(currentSegment) + if currentSegment.Len() > 0 { + addNameLexem(currentSegment.String()) } - currentSegment = "" + currentSegment.Reset() } - currentSegment += string(rn) + currentSegment.WriteRune(rn) + } + + if currentSegment.Len() > 0 { + addNameLexem(currentSegment.String()) } +} + +// isEqualFoldIgnoreSpace is the same as strings.EqualFold, but +// it ignores leading and trailing blank spaces in the compared +// string. +// +// base is assumed to be composed of upper-cased runes, and be already +// trimmed. +// +// This code is heavily inspired from strings.EqualFold. +func isEqualFoldIgnoreSpace(base []rune, str string) bool { + var i, baseIndex int + // equivalent to b := []byte(str), but without data copy + b := hackStringBytes(str) + + for i < len(b) { + if c := b[i]; c < utf8.RuneSelf { + // fast path for ASCII + if c != ' ' && c != '\t' { + break + } + i++ + + continue + } + + // unicode case + r, size := utf8.DecodeRune(b[i:]) + if !unicode.IsSpace(r) { + break + } + i += size + } + + if i >= len(b) { + return len(base) == 0 + } + + for _, baseRune := range base { + if i >= len(b) { + break + } + + if c := b[i]; c < utf8.RuneSelf { + // single byte rune case (ASCII) + if baseRune >= utf8.RuneSelf { + return false + } + + baseChar := byte(baseRune) + if c != baseChar && + !('a' <= c && c <= 'z' && c-'a'+'A' == baseChar) { + return false + } + + baseIndex++ + i++ + + continue + } + + // unicode case + r, size := utf8.DecodeRune(b[i:]) + if unicode.ToUpper(r) != baseRune { + return false + } + baseIndex++ + i += size + } + + if baseIndex != len(base) { + return false + } + + // all passed: now we should only have blanks + for i < len(b) { + if c := b[i]; c < utf8.RuneSelf { + // fast path for ASCII + if c != ' ' && c != '\t' { + return false + } + i++ + + continue + } + + // unicode case + r, size := utf8.DecodeRune(b[i:]) + if !unicode.IsSpace(r) { + return false + } - if currentSegment != "" { - addNameLexem(currentSegment) + i += size } - return segments + return true } diff --git a/index/server/vendor/github.com/go-openapi/swag/string_bytes.go b/index/server/vendor/github.com/go-openapi/swag/string_bytes.go new file mode 100644 index 000000000..90745d5ca --- /dev/null +++ b/index/server/vendor/github.com/go-openapi/swag/string_bytes.go @@ -0,0 +1,8 @@ +package swag + +import "unsafe" + +// hackStringBytes returns the (unsafe) underlying bytes slice of a string. +func hackStringBytes(str string) []byte { + return unsafe.Slice(unsafe.StringData(str), len(str)) +} diff --git a/index/server/vendor/github.com/go-openapi/swag/util.go b/index/server/vendor/github.com/go-openapi/swag/util.go index f78ab684a..5051401c4 100644 --- a/index/server/vendor/github.com/go-openapi/swag/util.go +++ b/index/server/vendor/github.com/go-openapi/swag/util.go @@ -18,76 +18,25 @@ import ( "reflect" "strings" "unicode" + "unicode/utf8" ) -// commonInitialisms are common acronyms that are kept as whole uppercased words. -var commonInitialisms *indexOfInitialisms - -// initialisms is a slice of sorted initialisms -var initialisms []string - -var isInitialism func(string) bool - // GoNamePrefixFunc sets an optional rule to prefix go names // which do not start with a letter. // +// The prefix function is assumed to return a string that starts with an upper case letter. +// // e.g. to help convert "123" into "{prefix}123" // // The default is to prefix with "X" var GoNamePrefixFunc func(string) string -func init() { - // Taken from https://github.com/golang/lint/blob/3390df4df2787994aea98de825b964ac7944b817/lint.go#L732-L769 - var configuredInitialisms = map[string]bool{ - "ACL": true, - "API": true, - "ASCII": true, - "CPU": true, - "CSS": true, - "DNS": true, - "EOF": true, - "GUID": true, - "HTML": true, - "HTTPS": true, - "HTTP": true, - "ID": true, - "IP": true, - "IPv4": true, - "IPv6": true, - "JSON": true, - "LHS": true, - "OAI": true, - "QPS": true, - "RAM": true, - "RHS": true, - "RPC": true, - "SLA": true, - "SMTP": true, - "SQL": true, - "SSH": true, - "TCP": true, - "TLS": true, - "TTL": true, - "UDP": true, - "UI": true, - "UID": true, - "UUID": true, - "URI": true, - "URL": true, - "UTF8": true, - "VM": true, - "XML": true, - "XMPP": true, - "XSRF": true, - "XSS": true, +func prefixFunc(name, in string) string { + if GoNamePrefixFunc == nil { + return "X" + in } - // a thread-safe index of initialisms - commonInitialisms = newIndexOfInitialisms().load(configuredInitialisms) - initialisms = commonInitialisms.sorted() - - // a test function - isInitialism = commonInitialisms.isInitialism + return GoNamePrefixFunc(name) + in } const ( @@ -156,25 +105,9 @@ func SplitByFormat(data, format string) []string { return result } -type byInitialism []string - -func (s byInitialism) Len() int { - return len(s) -} -func (s byInitialism) Swap(i, j int) { - s[i], s[j] = s[j], s[i] -} -func (s byInitialism) Less(i, j int) bool { - if len(s[i]) != len(s[j]) { - return len(s[i]) < len(s[j]) - } - - return strings.Compare(s[i], s[j]) > 0 -} - // Removes leading whitespaces func trim(str string) string { - return strings.Trim(str, " ") + return strings.TrimSpace(str) } // Shortcut to strings.ToUpper() @@ -188,15 +121,20 @@ func lower(str string) string { } // Camelize an uppercased word -func Camelize(word string) (camelized string) { +func Camelize(word string) string { + camelized := poolOfBuffers.BorrowBuffer(len(word)) + defer func() { + poolOfBuffers.RedeemBuffer(camelized) + }() + for pos, ru := range []rune(word) { if pos > 0 { - camelized += string(unicode.ToLower(ru)) + camelized.WriteRune(unicode.ToLower(ru)) } else { - camelized += string(unicode.ToUpper(ru)) + camelized.WriteRune(unicode.ToUpper(ru)) } } - return + return camelized.String() } // ToFileName lowercases and underscores a go type name @@ -224,33 +162,40 @@ func ToCommandName(name string) string { // ToHumanNameLower represents a code name as a human series of words func ToHumanNameLower(name string) string { - in := newSplitter(withPostSplitInitialismCheck).split(name) - out := make([]string, 0, len(in)) + s := poolOfSplitters.BorrowSplitter(withPostSplitInitialismCheck) + in := s.split(name) + poolOfSplitters.RedeemSplitter(s) + out := make([]string, 0, len(*in)) - for _, w := range in { + for _, w := range *in { if !w.IsInitialism() { out = append(out, lower(w.GetOriginal())) } else { - out = append(out, w.GetOriginal()) + out = append(out, trim(w.GetOriginal())) } } + poolOfLexems.RedeemLexems(in) return strings.Join(out, " ") } // ToHumanNameTitle represents a code name as a human series of words with the first letters titleized func ToHumanNameTitle(name string) string { - in := newSplitter(withPostSplitInitialismCheck).split(name) + s := poolOfSplitters.BorrowSplitter(withPostSplitInitialismCheck) + in := s.split(name) + poolOfSplitters.RedeemSplitter(s) - out := make([]string, 0, len(in)) - for _, w := range in { - original := w.GetOriginal() + out := make([]string, 0, len(*in)) + for _, w := range *in { + original := trim(w.GetOriginal()) if !w.IsInitialism() { out = append(out, Camelize(original)) } else { out = append(out, original) } } + poolOfLexems.RedeemLexems(in) + return strings.Join(out, " ") } @@ -264,7 +209,7 @@ func ToJSONName(name string) string { out = append(out, lower(w)) continue } - out = append(out, Camelize(w)) + out = append(out, Camelize(trim(w))) } return strings.Join(out, "") } @@ -283,35 +228,70 @@ func ToVarName(name string) string { // ToGoName translates a swagger name which can be underscored or camel cased to a name that golint likes func ToGoName(name string) string { - lexems := newSplitter(withPostSplitInitialismCheck).split(name) + s := poolOfSplitters.BorrowSplitter(withPostSplitInitialismCheck) + lexems := s.split(name) + poolOfSplitters.RedeemSplitter(s) + defer func() { + poolOfLexems.RedeemLexems(lexems) + }() + lexemes := *lexems + + if len(lexemes) == 0 { + return "" + } + + result := poolOfBuffers.BorrowBuffer(len(name)) + defer func() { + poolOfBuffers.RedeemBuffer(result) + }() - result := "" - for _, lexem := range lexems { + // check if not starting with a letter, upper case + firstPart := lexemes[0].GetUnsafeGoName() + if lexemes[0].IsInitialism() { + firstPart = upper(firstPart) + } + + if c := firstPart[0]; c < utf8.RuneSelf { + // ASCII + switch { + case 'A' <= c && c <= 'Z': + result.WriteString(firstPart) + case 'a' <= c && c <= 'z': + result.WriteByte(c - 'a' + 'A') + result.WriteString(firstPart[1:]) + default: + result.WriteString(prefixFunc(name, firstPart)) + // NOTE: no longer check if prefixFunc returns a string that starts with uppercase: + // assume this is always the case + } + } else { + // unicode + firstRune, _ := utf8.DecodeRuneInString(firstPart) + switch { + case !unicode.IsLetter(firstRune): + result.WriteString(prefixFunc(name, firstPart)) + case !unicode.IsUpper(firstRune): + result.WriteString(prefixFunc(name, firstPart)) + /* + result.WriteRune(unicode.ToUpper(firstRune)) + result.WriteString(firstPart[offset:]) + */ + default: + result.WriteString(firstPart) + } + } + + for _, lexem := range lexemes[1:] { goName := lexem.GetUnsafeGoName() // to support old behavior if lexem.IsInitialism() { goName = upper(goName) } - result += goName + result.WriteString(goName) } - if len(result) > 0 { - // Only prefix with X when the first character isn't an ascii letter - first := []rune(result)[0] - if !unicode.IsLetter(first) || (first > unicode.MaxASCII && !unicode.IsUpper(first)) { - if GoNamePrefixFunc == nil { - return "X" + result - } - result = GoNamePrefixFunc(name) + result - } - first = []rune(result)[0] - if unicode.IsLetter(first) && !unicode.IsUpper(first) { - result = string(append([]rune{unicode.ToUpper(first)}, []rune(result)[1:]...)) - } - } - - return result + return result.String() } // ContainsStrings searches a slice of strings for a case-sensitive match @@ -341,13 +321,22 @@ type zeroable interface { // IsZero returns true when the value passed into the function is a zero value. // This allows for safer checking of interface values. func IsZero(data interface{}) bool { + v := reflect.ValueOf(data) + // check for nil data + switch v.Kind() { //nolint:exhaustive + case reflect.Interface, reflect.Map, reflect.Ptr, reflect.Slice: + if v.IsNil() { + return true + } + } + // check for things that have an IsZero method instead if vv, ok := data.(zeroable); ok { return vv.IsZero() } + // continue with slightly more complex reflection - v := reflect.ValueOf(data) - switch v.Kind() { + switch v.Kind() { //nolint:exhaustive case reflect.String: return v.Len() == 0 case reflect.Bool: @@ -358,24 +347,13 @@ func IsZero(data interface{}) bool { return v.Uint() == 0 case reflect.Float32, reflect.Float64: return v.Float() == 0 - case reflect.Interface, reflect.Map, reflect.Ptr, reflect.Slice: - return v.IsNil() case reflect.Struct, reflect.Array: return reflect.DeepEqual(data, reflect.Zero(v.Type()).Interface()) case reflect.Invalid: return true + default: + return false } - return false -} - -// AddInitialisms add additional initialisms -func AddInitialisms(words ...string) { - for _, word := range words { - // commonInitialisms[upper(word)] = true - commonInitialisms.add(upper(word)) - } - // sort again - initialisms = commonInitialisms.sorted() } // CommandLineOptionsGroup represents a group of user-defined command line options diff --git a/index/server/vendor/github.com/go-openapi/swag/yaml.go b/index/server/vendor/github.com/go-openapi/swag/yaml.go index f09ee609f..f59e02593 100644 --- a/index/server/vendor/github.com/go-openapi/swag/yaml.go +++ b/index/server/vendor/github.com/go-openapi/swag/yaml.go @@ -16,8 +16,11 @@ package swag import ( "encoding/json" + "errors" "fmt" "path/filepath" + "reflect" + "sort" "strconv" "github.com/mailru/easyjson/jlexer" @@ -48,7 +51,7 @@ func BytesToYAMLDoc(data []byte) (interface{}, error) { return nil, err } if document.Kind != yaml.DocumentNode || len(document.Content) != 1 || document.Content[0].Kind != yaml.MappingNode { - return nil, fmt.Errorf("only YAML documents that are objects are supported") + return nil, errors.New("only YAML documents that are objects are supported") } return &document, nil } @@ -147,7 +150,7 @@ func yamlScalar(node *yaml.Node) (interface{}, error) { case yamlTimestamp: return node.Value, nil case yamlNull: - return nil, nil + return nil, nil //nolint:nilnil default: return nil, fmt.Errorf("YAML tag %q is not supported", node.LongTag()) } @@ -245,7 +248,27 @@ func (s JSONMapSlice) MarshalYAML() (interface{}, error) { return yaml.Marshal(&n) } +func isNil(input interface{}) bool { + if input == nil { + return true + } + kind := reflect.TypeOf(input).Kind() + switch kind { //nolint:exhaustive + case reflect.Ptr, reflect.Map, reflect.Slice, reflect.Chan: + return reflect.ValueOf(input).IsNil() + default: + return false + } +} + func json2yaml(item interface{}) (*yaml.Node, error) { + if isNil(item) { + return &yaml.Node{ + Kind: yaml.ScalarNode, + Value: "null", + }, nil + } + switch val := item.(type) { case JSONMapSlice: var n yaml.Node @@ -265,7 +288,14 @@ func json2yaml(item interface{}) (*yaml.Node, error) { case map[string]interface{}: var n yaml.Node n.Kind = yaml.MappingNode - for k, v := range val { + keys := make([]string, 0, len(val)) + for k := range val { + keys = append(keys, k) + } + sort.Strings(keys) + + for _, k := range keys { + v := val[k] childNode, err := json2yaml(v) if err != nil { return nil, err @@ -318,8 +348,9 @@ func json2yaml(item interface{}) (*yaml.Node, error) { Tag: yamlBoolScalar, Value: strconv.FormatBool(val), }, nil + default: + return nil, fmt.Errorf("unhandled type: %T", val) } - return nil, nil } // JSONMapItem represents the value of a key in a JSON object held by JSONMapSlice diff --git a/index/server/vendor/github.com/invopop/yaml/.golangci.toml b/index/server/vendor/github.com/invopop/yaml/.golangci.toml deleted file mode 100644 index 4a438ca2c..000000000 --- a/index/server/vendor/github.com/invopop/yaml/.golangci.toml +++ /dev/null @@ -1,15 +0,0 @@ -[run] -timeout = "120s" - -[output] -format = "colored-line-number" - -[linters] -enable = [ - "gocyclo", "unconvert", "goimports", "unused", "varcheck", - "vetshadow", "misspell", "nakedret", "errcheck", "revive", "ineffassign", - "deadcode", "goconst", "vet", "unparam", "gofmt" -] - -[issues] -exclude-use-default = false diff --git a/index/server/vendor/github.com/invopop/yaml/.gitignore b/index/server/vendor/github.com/oasdiff/yaml/.gitignore similarity index 100% rename from index/server/vendor/github.com/invopop/yaml/.gitignore rename to index/server/vendor/github.com/oasdiff/yaml/.gitignore diff --git a/index/server/vendor/github.com/oasdiff/yaml/.golangci.toml b/index/server/vendor/github.com/oasdiff/yaml/.golangci.toml new file mode 100644 index 000000000..61b2b79a2 --- /dev/null +++ b/index/server/vendor/github.com/oasdiff/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/index/server/vendor/github.com/invopop/yaml/LICENSE b/index/server/vendor/github.com/oasdiff/yaml/LICENSE similarity index 100% rename from index/server/vendor/github.com/invopop/yaml/LICENSE rename to index/server/vendor/github.com/oasdiff/yaml/LICENSE diff --git a/index/server/vendor/github.com/invopop/yaml/README.md b/index/server/vendor/github.com/oasdiff/yaml/README.md similarity index 91% rename from index/server/vendor/github.com/invopop/yaml/README.md rename to index/server/vendor/github.com/oasdiff/yaml/README.md index 2c33dfe59..bdb01877b 100644 --- a/index/server/vendor/github.com/invopop/yaml/README.md +++ b/index/server/vendor/github.com/oasdiff/yaml/README.md @@ -5,6 +5,11 @@ [![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) +## Fork +This fork is an improved version of the invopop/yaml package, designed to include line and column location information for YAML elements during unmarshalling. +To include location information use ```UnmarshalWithOrigin``` instead of ```Unmarshal```. +The heavy lifting is done by the underlying [oasdiff/yaml3](https://github.com/oasdiff/yaml3) package. + ## 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. diff --git a/index/server/vendor/github.com/invopop/yaml/fields.go b/index/server/vendor/github.com/oasdiff/yaml/fields.go similarity index 99% rename from index/server/vendor/github.com/invopop/yaml/fields.go rename to index/server/vendor/github.com/oasdiff/yaml/fields.go index 52b30c6bd..3fe5f12fd 100644 --- a/index/server/vendor/github.com/invopop/yaml/fields.go +++ b/index/server/vendor/github.com/oasdiff/yaml/fields.go @@ -347,8 +347,9 @@ const ( // 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 +// - 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 diff --git a/index/server/vendor/github.com/invopop/yaml/yaml.go b/index/server/vendor/github.com/oasdiff/yaml/yaml.go similarity index 89% rename from index/server/vendor/github.com/invopop/yaml/yaml.go rename to index/server/vendor/github.com/oasdiff/yaml/yaml.go index 66c5c668e..c49678ee1 100644 --- a/index/server/vendor/github.com/invopop/yaml/yaml.go +++ b/index/server/vendor/github.com/oasdiff/yaml/yaml.go @@ -5,18 +5,18 @@ // 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" + "github.com/oasdiff/yaml3" ) // Marshal the object into JSON then converts JSON to YAML and returns the @@ -38,10 +38,20 @@ func Marshal(o interface{}) ([]byte, error) { // JSONOpt is a decoding option for decoding from JSON format. type JSONOpt func(*json.Decoder) *json.Decoder +// YAMLOpt is a decoding option for decoding from YAML format. +type YAMLOpt func(*yaml.Decoder) *yaml.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 { + return UnmarshalWithOrigin(y, o, false, opts...) +} + +// UnmarshalWithOrigin is like Unmarshal but if withOrigin is true, it will +// include the origin information in the output. +func UnmarshalWithOrigin(y []byte, o interface{}, withOrigin bool, opts ...JSONOpt) error { dec := yaml.NewDecoder(bytes.NewReader(y)) + dec.Origin(withOrigin) return unmarshal(dec, o, opts) } @@ -97,13 +107,12 @@ func JSONToYAML(j []byte) ([]byte, error) { // 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. -// +// - 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) @@ -113,7 +122,11 @@ 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 { - return nil, err + // 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 diff --git a/index/server/vendor/github.com/oasdiff/yaml3/LICENSE b/index/server/vendor/github.com/oasdiff/yaml3/LICENSE new file mode 100644 index 000000000..2683e4bb1 --- /dev/null +++ b/index/server/vendor/github.com/oasdiff/yaml3/LICENSE @@ -0,0 +1,50 @@ + +This project is covered by two different licenses: MIT and Apache. + +#### MIT License #### + +The following files were ported to Go from C files of libyaml, and thus +are still covered by their original MIT license, with the additional +copyright staring in 2011 when the project was ported over: + + apic.go emitterc.go parserc.go readerc.go scannerc.go + writerc.go yamlh.go yamlprivateh.go + +Copyright (c) 2006-2010 Kirill Simonov +Copyright (c) 2006-2011 Kirill Simonov + +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. + +### Apache License ### + +All the remaining project files are covered by the Apache license: + +Copyright (c) 2011-2019 Canonical Ltd + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. diff --git a/index/server/vendor/github.com/oasdiff/yaml3/NOTICE b/index/server/vendor/github.com/oasdiff/yaml3/NOTICE new file mode 100644 index 000000000..866d74a7a --- /dev/null +++ b/index/server/vendor/github.com/oasdiff/yaml3/NOTICE @@ -0,0 +1,13 @@ +Copyright 2011-2016 Canonical Ltd. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. diff --git a/index/server/vendor/github.com/oasdiff/yaml3/README.md b/index/server/vendor/github.com/oasdiff/yaml3/README.md new file mode 100644 index 000000000..f08ebf4aa --- /dev/null +++ b/index/server/vendor/github.com/oasdiff/yaml3/README.md @@ -0,0 +1,153 @@ +# YAML support for the Go language + +Fork +---- +This fork is an improved version of the go-yaml/yaml package, designed to include line and column location information for YAML elements during unmarshalling. + +Introduction +------------ + +The yaml package enables Go programs to comfortably encode and decode YAML +values. It was developed within [Canonical](https://www.canonical.com) as +part of the [juju](https://juju.ubuntu.com) project, and is based on a +pure Go port of the well-known [libyaml](http://pyyaml.org/wiki/LibYAML) +C library to parse and generate YAML data quickly and reliably. + +Compatibility +------------- + +The yaml package supports most of YAML 1.2, but preserves some behavior +from 1.1 for backwards compatibility. + +Specifically, as of v3 of the yaml package: + + - YAML 1.1 bools (_yes/no, on/off_) are supported as long as they are being + decoded into a typed bool value. Otherwise they behave as a string. Booleans + in YAML 1.2 are _true/false_ only. + - Octals encode and decode as _0777_ per YAML 1.1, rather than _0o777_ + as specified in YAML 1.2, because most parsers still use the old format. + Octals in the _0o777_ format are supported though, so new files work. + - Does not support base-60 floats. These are gone from YAML 1.2, and were + actually never supported by this package as it's clearly a poor choice. + +and offers backwards +compatibility with YAML 1.1 in some cases. +1.2, including support for +anchors, tags, map merging, etc. Multi-document unmarshalling is not yet +implemented, and base-60 floats from YAML 1.1 are purposefully not +supported since they're a poor design and are gone in YAML 1.2. + +Installation and usage +---------------------- + +The import path for the package is *gopkg.in/yaml.v3*. + +To install it, run: + + go get gopkg.in/yaml.v3 + +API documentation +----------------- + +If opened in a browser, the import path itself leads to the API documentation: + + - [https://gopkg.in/yaml.v3](https://gopkg.in/yaml.v3) + +API stability +------------- + +The package API for yaml v3 will remain stable as described in [gopkg.in](https://gopkg.in). + + +License +------- + +The yaml package is licensed under the MIT and Apache License 2.0 licenses. +Please see the LICENSE file for details. + + +Example +------- + +```Go +package main + +import ( + "fmt" + "log" + + "oasdiff/yaml" +) + +var data = ` +a: Easy! +b: + c: 2 + d: [3, 4] +` + +// Note: struct fields must be public in order for unmarshal to +// correctly populate the data. +type T struct { + A string + B struct { + RenamedC int `yaml:"c"` + D []int `yaml:",flow"` + } +} + +func main() { + t := T{} + + err := yaml.Unmarshal([]byte(data), &t) + if err != nil { + log.Fatalf("error: %v", err) + } + fmt.Printf("--- t:\n%v\n\n", t) + + d, err := yaml.Marshal(&t) + if err != nil { + log.Fatalf("error: %v", err) + } + fmt.Printf("--- t dump:\n%s\n\n", string(d)) + + m := make(map[interface{}]interface{}) + + err = yaml.Unmarshal([]byte(data), &m) + if err != nil { + log.Fatalf("error: %v", err) + } + fmt.Printf("--- m:\n%v\n\n", m) + + d, err = yaml.Marshal(&m) + if err != nil { + log.Fatalf("error: %v", err) + } + fmt.Printf("--- m dump:\n%s\n\n", string(d)) +} +``` + +This example will generate the following output: + +``` +--- t: +{Easy! {2 [3 4]}} + +--- t dump: +a: Easy! +b: + c: 2 + d: [3, 4] + + +--- m: +map[a:Easy! b:map[c:2 d:[3 4]]] + +--- m dump: +a: Easy! +b: + c: 2 + d: + - 3 + - 4 +``` diff --git a/index/server/vendor/github.com/oasdiff/yaml3/apic.go b/index/server/vendor/github.com/oasdiff/yaml3/apic.go new file mode 100644 index 000000000..ae7d049f1 --- /dev/null +++ b/index/server/vendor/github.com/oasdiff/yaml3/apic.go @@ -0,0 +1,747 @@ +// +// Copyright (c) 2011-2019 Canonical Ltd +// Copyright (c) 2006-2010 Kirill Simonov +// +// 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. + +package yaml + +import ( + "io" +) + +func yaml_insert_token(parser *yaml_parser_t, pos int, token *yaml_token_t) { + //fmt.Println("yaml_insert_token", "pos:", pos, "typ:", token.typ, "head:", parser.tokens_head, "len:", len(parser.tokens)) + + // Check if we can move the queue at the beginning of the buffer. + if parser.tokens_head > 0 && len(parser.tokens) == cap(parser.tokens) { + if parser.tokens_head != len(parser.tokens) { + copy(parser.tokens, parser.tokens[parser.tokens_head:]) + } + parser.tokens = parser.tokens[:len(parser.tokens)-parser.tokens_head] + parser.tokens_head = 0 + } + parser.tokens = append(parser.tokens, *token) + if pos < 0 { + return + } + copy(parser.tokens[parser.tokens_head+pos+1:], parser.tokens[parser.tokens_head+pos:]) + parser.tokens[parser.tokens_head+pos] = *token +} + +// Create a new parser object. +func yaml_parser_initialize(parser *yaml_parser_t) bool { + *parser = yaml_parser_t{ + raw_buffer: make([]byte, 0, input_raw_buffer_size), + buffer: make([]byte, 0, input_buffer_size), + } + return true +} + +// Destroy a parser object. +func yaml_parser_delete(parser *yaml_parser_t) { + *parser = yaml_parser_t{} +} + +// String read handler. +func yaml_string_read_handler(parser *yaml_parser_t, buffer []byte) (n int, err error) { + if parser.input_pos == len(parser.input) { + return 0, io.EOF + } + n = copy(buffer, parser.input[parser.input_pos:]) + parser.input_pos += n + return n, nil +} + +// Reader read handler. +func yaml_reader_read_handler(parser *yaml_parser_t, buffer []byte) (n int, err error) { + return parser.input_reader.Read(buffer) +} + +// Set a string input. +func yaml_parser_set_input_string(parser *yaml_parser_t, input []byte) { + if parser.read_handler != nil { + panic("must set the input source only once") + } + parser.read_handler = yaml_string_read_handler + parser.input = input + parser.input_pos = 0 +} + +// Set a file input. +func yaml_parser_set_input_reader(parser *yaml_parser_t, r io.Reader) { + if parser.read_handler != nil { + panic("must set the input source only once") + } + parser.read_handler = yaml_reader_read_handler + parser.input_reader = r +} + +// Set the source encoding. +func yaml_parser_set_encoding(parser *yaml_parser_t, encoding yaml_encoding_t) { + if parser.encoding != yaml_ANY_ENCODING { + panic("must set the encoding only once") + } + parser.encoding = encoding +} + +// Create a new emitter object. +func yaml_emitter_initialize(emitter *yaml_emitter_t) { + *emitter = yaml_emitter_t{ + buffer: make([]byte, output_buffer_size), + raw_buffer: make([]byte, 0, output_raw_buffer_size), + states: make([]yaml_emitter_state_t, 0, initial_stack_size), + events: make([]yaml_event_t, 0, initial_queue_size), + best_width: -1, + } +} + +// Destroy an emitter object. +func yaml_emitter_delete(emitter *yaml_emitter_t) { + *emitter = yaml_emitter_t{} +} + +// String write handler. +func yaml_string_write_handler(emitter *yaml_emitter_t, buffer []byte) error { + *emitter.output_buffer = append(*emitter.output_buffer, buffer...) + return nil +} + +// yaml_writer_write_handler uses emitter.output_writer to write the +// emitted text. +func yaml_writer_write_handler(emitter *yaml_emitter_t, buffer []byte) error { + _, err := emitter.output_writer.Write(buffer) + return err +} + +// Set a string output. +func yaml_emitter_set_output_string(emitter *yaml_emitter_t, output_buffer *[]byte) { + if emitter.write_handler != nil { + panic("must set the output target only once") + } + emitter.write_handler = yaml_string_write_handler + emitter.output_buffer = output_buffer +} + +// Set a file output. +func yaml_emitter_set_output_writer(emitter *yaml_emitter_t, w io.Writer) { + if emitter.write_handler != nil { + panic("must set the output target only once") + } + emitter.write_handler = yaml_writer_write_handler + emitter.output_writer = w +} + +// Set the output encoding. +func yaml_emitter_set_encoding(emitter *yaml_emitter_t, encoding yaml_encoding_t) { + if emitter.encoding != yaml_ANY_ENCODING { + panic("must set the output encoding only once") + } + emitter.encoding = encoding +} + +// Set the canonical output style. +func yaml_emitter_set_canonical(emitter *yaml_emitter_t, canonical bool) { + emitter.canonical = canonical +} + +// Set the indentation increment. +func yaml_emitter_set_indent(emitter *yaml_emitter_t, indent int) { + if indent < 2 || indent > 9 { + indent = 2 + } + emitter.best_indent = indent +} + +// Set the preferred line width. +func yaml_emitter_set_width(emitter *yaml_emitter_t, width int) { + if width < 0 { + width = -1 + } + emitter.best_width = width +} + +// Set if unescaped non-ASCII characters are allowed. +func yaml_emitter_set_unicode(emitter *yaml_emitter_t, unicode bool) { + emitter.unicode = unicode +} + +// Set the preferred line break character. +func yaml_emitter_set_break(emitter *yaml_emitter_t, line_break yaml_break_t) { + emitter.line_break = line_break +} + +///* +// * Destroy a token object. +// */ +// +//YAML_DECLARE(void) +//yaml_token_delete(yaml_token_t *token) +//{ +// assert(token); // Non-NULL token object expected. +// +// switch (token.type) +// { +// case YAML_TAG_DIRECTIVE_TOKEN: +// yaml_free(token.data.tag_directive.handle); +// yaml_free(token.data.tag_directive.prefix); +// break; +// +// case YAML_ALIAS_TOKEN: +// yaml_free(token.data.alias.value); +// break; +// +// case YAML_ANCHOR_TOKEN: +// yaml_free(token.data.anchor.value); +// break; +// +// case YAML_TAG_TOKEN: +// yaml_free(token.data.tag.handle); +// yaml_free(token.data.tag.suffix); +// break; +// +// case YAML_SCALAR_TOKEN: +// yaml_free(token.data.scalar.value); +// break; +// +// default: +// break; +// } +// +// memset(token, 0, sizeof(yaml_token_t)); +//} +// +///* +// * Check if a string is a valid UTF-8 sequence. +// * +// * Check 'reader.c' for more details on UTF-8 encoding. +// */ +// +//static int +//yaml_check_utf8(yaml_char_t *start, size_t length) +//{ +// yaml_char_t *end = start+length; +// yaml_char_t *pointer = start; +// +// while (pointer < end) { +// unsigned char octet; +// unsigned int width; +// unsigned int value; +// size_t k; +// +// octet = pointer[0]; +// width = (octet & 0x80) == 0x00 ? 1 : +// (octet & 0xE0) == 0xC0 ? 2 : +// (octet & 0xF0) == 0xE0 ? 3 : +// (octet & 0xF8) == 0xF0 ? 4 : 0; +// value = (octet & 0x80) == 0x00 ? octet & 0x7F : +// (octet & 0xE0) == 0xC0 ? octet & 0x1F : +// (octet & 0xF0) == 0xE0 ? octet & 0x0F : +// (octet & 0xF8) == 0xF0 ? octet & 0x07 : 0; +// if (!width) return 0; +// if (pointer+width > end) return 0; +// for (k = 1; k < width; k ++) { +// octet = pointer[k]; +// if ((octet & 0xC0) != 0x80) return 0; +// value = (value << 6) + (octet & 0x3F); +// } +// if (!((width == 1) || +// (width == 2 && value >= 0x80) || +// (width == 3 && value >= 0x800) || +// (width == 4 && value >= 0x10000))) return 0; +// +// pointer += width; +// } +// +// return 1; +//} +// + +// Create STREAM-START. +func yaml_stream_start_event_initialize(event *yaml_event_t, encoding yaml_encoding_t) { + *event = yaml_event_t{ + typ: yaml_STREAM_START_EVENT, + encoding: encoding, + } +} + +// Create STREAM-END. +func yaml_stream_end_event_initialize(event *yaml_event_t) { + *event = yaml_event_t{ + typ: yaml_STREAM_END_EVENT, + } +} + +// Create DOCUMENT-START. +func yaml_document_start_event_initialize( + event *yaml_event_t, + version_directive *yaml_version_directive_t, + tag_directives []yaml_tag_directive_t, + implicit bool, +) { + *event = yaml_event_t{ + typ: yaml_DOCUMENT_START_EVENT, + version_directive: version_directive, + tag_directives: tag_directives, + implicit: implicit, + } +} + +// Create DOCUMENT-END. +func yaml_document_end_event_initialize(event *yaml_event_t, implicit bool) { + *event = yaml_event_t{ + typ: yaml_DOCUMENT_END_EVENT, + implicit: implicit, + } +} + +// Create ALIAS. +func yaml_alias_event_initialize(event *yaml_event_t, anchor []byte) bool { + *event = yaml_event_t{ + typ: yaml_ALIAS_EVENT, + anchor: anchor, + } + return true +} + +// Create SCALAR. +func yaml_scalar_event_initialize(event *yaml_event_t, anchor, tag, value []byte, plain_implicit, quoted_implicit bool, style yaml_scalar_style_t) bool { + *event = yaml_event_t{ + typ: yaml_SCALAR_EVENT, + anchor: anchor, + tag: tag, + value: value, + implicit: plain_implicit, + quoted_implicit: quoted_implicit, + style: yaml_style_t(style), + } + return true +} + +// Create SEQUENCE-START. +func yaml_sequence_start_event_initialize(event *yaml_event_t, anchor, tag []byte, implicit bool, style yaml_sequence_style_t) bool { + *event = yaml_event_t{ + typ: yaml_SEQUENCE_START_EVENT, + anchor: anchor, + tag: tag, + implicit: implicit, + style: yaml_style_t(style), + } + return true +} + +// Create SEQUENCE-END. +func yaml_sequence_end_event_initialize(event *yaml_event_t) bool { + *event = yaml_event_t{ + typ: yaml_SEQUENCE_END_EVENT, + } + return true +} + +// Create MAPPING-START. +func yaml_mapping_start_event_initialize(event *yaml_event_t, anchor, tag []byte, implicit bool, style yaml_mapping_style_t) { + *event = yaml_event_t{ + typ: yaml_MAPPING_START_EVENT, + anchor: anchor, + tag: tag, + implicit: implicit, + style: yaml_style_t(style), + } +} + +// Create MAPPING-END. +func yaml_mapping_end_event_initialize(event *yaml_event_t) { + *event = yaml_event_t{ + typ: yaml_MAPPING_END_EVENT, + } +} + +// Destroy an event object. +func yaml_event_delete(event *yaml_event_t) { + *event = yaml_event_t{} +} + +///* +// * Create a document object. +// */ +// +//YAML_DECLARE(int) +//yaml_document_initialize(document *yaml_document_t, +// version_directive *yaml_version_directive_t, +// tag_directives_start *yaml_tag_directive_t, +// tag_directives_end *yaml_tag_directive_t, +// start_implicit int, end_implicit int) +//{ +// struct { +// error yaml_error_type_t +// } context +// struct { +// start *yaml_node_t +// end *yaml_node_t +// top *yaml_node_t +// } nodes = { NULL, NULL, NULL } +// version_directive_copy *yaml_version_directive_t = NULL +// struct { +// start *yaml_tag_directive_t +// end *yaml_tag_directive_t +// top *yaml_tag_directive_t +// } tag_directives_copy = { NULL, NULL, NULL } +// value yaml_tag_directive_t = { NULL, NULL } +// mark yaml_mark_t = { 0, 0, 0 } +// +// assert(document) // Non-NULL document object is expected. +// assert((tag_directives_start && tag_directives_end) || +// (tag_directives_start == tag_directives_end)) +// // Valid tag directives are expected. +// +// if (!STACK_INIT(&context, nodes, INITIAL_STACK_SIZE)) goto error +// +// if (version_directive) { +// version_directive_copy = yaml_malloc(sizeof(yaml_version_directive_t)) +// if (!version_directive_copy) goto error +// version_directive_copy.major = version_directive.major +// version_directive_copy.minor = version_directive.minor +// } +// +// if (tag_directives_start != tag_directives_end) { +// tag_directive *yaml_tag_directive_t +// if (!STACK_INIT(&context, tag_directives_copy, INITIAL_STACK_SIZE)) +// goto error +// for (tag_directive = tag_directives_start +// tag_directive != tag_directives_end; tag_directive ++) { +// assert(tag_directive.handle) +// assert(tag_directive.prefix) +// if (!yaml_check_utf8(tag_directive.handle, +// strlen((char *)tag_directive.handle))) +// goto error +// if (!yaml_check_utf8(tag_directive.prefix, +// strlen((char *)tag_directive.prefix))) +// goto error +// value.handle = yaml_strdup(tag_directive.handle) +// value.prefix = yaml_strdup(tag_directive.prefix) +// if (!value.handle || !value.prefix) goto error +// if (!PUSH(&context, tag_directives_copy, value)) +// goto error +// value.handle = NULL +// value.prefix = NULL +// } +// } +// +// DOCUMENT_INIT(*document, nodes.start, nodes.end, version_directive_copy, +// tag_directives_copy.start, tag_directives_copy.top, +// start_implicit, end_implicit, mark, mark) +// +// return 1 +// +//error: +// STACK_DEL(&context, nodes) +// yaml_free(version_directive_copy) +// while (!STACK_EMPTY(&context, tag_directives_copy)) { +// value yaml_tag_directive_t = POP(&context, tag_directives_copy) +// yaml_free(value.handle) +// yaml_free(value.prefix) +// } +// STACK_DEL(&context, tag_directives_copy) +// yaml_free(value.handle) +// yaml_free(value.prefix) +// +// return 0 +//} +// +///* +// * Destroy a document object. +// */ +// +//YAML_DECLARE(void) +//yaml_document_delete(document *yaml_document_t) +//{ +// struct { +// error yaml_error_type_t +// } context +// tag_directive *yaml_tag_directive_t +// +// context.error = YAML_NO_ERROR // Eliminate a compiler warning. +// +// assert(document) // Non-NULL document object is expected. +// +// while (!STACK_EMPTY(&context, document.nodes)) { +// node yaml_node_t = POP(&context, document.nodes) +// yaml_free(node.tag) +// switch (node.type) { +// case YAML_SCALAR_NODE: +// yaml_free(node.data.scalar.value) +// break +// case YAML_SEQUENCE_NODE: +// STACK_DEL(&context, node.data.sequence.items) +// break +// case YAML_MAPPING_NODE: +// STACK_DEL(&context, node.data.mapping.pairs) +// break +// default: +// assert(0) // Should not happen. +// } +// } +// STACK_DEL(&context, document.nodes) +// +// yaml_free(document.version_directive) +// for (tag_directive = document.tag_directives.start +// tag_directive != document.tag_directives.end +// tag_directive++) { +// yaml_free(tag_directive.handle) +// yaml_free(tag_directive.prefix) +// } +// yaml_free(document.tag_directives.start) +// +// memset(document, 0, sizeof(yaml_document_t)) +//} +// +///** +// * Get a document node. +// */ +// +//YAML_DECLARE(yaml_node_t *) +//yaml_document_get_node(document *yaml_document_t, index int) +//{ +// assert(document) // Non-NULL document object is expected. +// +// if (index > 0 && document.nodes.start + index <= document.nodes.top) { +// return document.nodes.start + index - 1 +// } +// return NULL +//} +// +///** +// * Get the root object. +// */ +// +//YAML_DECLARE(yaml_node_t *) +//yaml_document_get_root_node(document *yaml_document_t) +//{ +// assert(document) // Non-NULL document object is expected. +// +// if (document.nodes.top != document.nodes.start) { +// return document.nodes.start +// } +// return NULL +//} +// +///* +// * Add a scalar node to a document. +// */ +// +//YAML_DECLARE(int) +//yaml_document_add_scalar(document *yaml_document_t, +// tag *yaml_char_t, value *yaml_char_t, length int, +// style yaml_scalar_style_t) +//{ +// struct { +// error yaml_error_type_t +// } context +// mark yaml_mark_t = { 0, 0, 0 } +// tag_copy *yaml_char_t = NULL +// value_copy *yaml_char_t = NULL +// node yaml_node_t +// +// assert(document) // Non-NULL document object is expected. +// assert(value) // Non-NULL value is expected. +// +// if (!tag) { +// tag = (yaml_char_t *)YAML_DEFAULT_SCALAR_TAG +// } +// +// if (!yaml_check_utf8(tag, strlen((char *)tag))) goto error +// tag_copy = yaml_strdup(tag) +// if (!tag_copy) goto error +// +// if (length < 0) { +// length = strlen((char *)value) +// } +// +// if (!yaml_check_utf8(value, length)) goto error +// value_copy = yaml_malloc(length+1) +// if (!value_copy) goto error +// memcpy(value_copy, value, length) +// value_copy[length] = '\0' +// +// SCALAR_NODE_INIT(node, tag_copy, value_copy, length, style, mark, mark) +// if (!PUSH(&context, document.nodes, node)) goto error +// +// return document.nodes.top - document.nodes.start +// +//error: +// yaml_free(tag_copy) +// yaml_free(value_copy) +// +// return 0 +//} +// +///* +// * Add a sequence node to a document. +// */ +// +//YAML_DECLARE(int) +//yaml_document_add_sequence(document *yaml_document_t, +// tag *yaml_char_t, style yaml_sequence_style_t) +//{ +// struct { +// error yaml_error_type_t +// } context +// mark yaml_mark_t = { 0, 0, 0 } +// tag_copy *yaml_char_t = NULL +// struct { +// start *yaml_node_item_t +// end *yaml_node_item_t +// top *yaml_node_item_t +// } items = { NULL, NULL, NULL } +// node yaml_node_t +// +// assert(document) // Non-NULL document object is expected. +// +// if (!tag) { +// tag = (yaml_char_t *)YAML_DEFAULT_SEQUENCE_TAG +// } +// +// if (!yaml_check_utf8(tag, strlen((char *)tag))) goto error +// tag_copy = yaml_strdup(tag) +// if (!tag_copy) goto error +// +// if (!STACK_INIT(&context, items, INITIAL_STACK_SIZE)) goto error +// +// SEQUENCE_NODE_INIT(node, tag_copy, items.start, items.end, +// style, mark, mark) +// if (!PUSH(&context, document.nodes, node)) goto error +// +// return document.nodes.top - document.nodes.start +// +//error: +// STACK_DEL(&context, items) +// yaml_free(tag_copy) +// +// return 0 +//} +// +///* +// * Add a mapping node to a document. +// */ +// +//YAML_DECLARE(int) +//yaml_document_add_mapping(document *yaml_document_t, +// tag *yaml_char_t, style yaml_mapping_style_t) +//{ +// struct { +// error yaml_error_type_t +// } context +// mark yaml_mark_t = { 0, 0, 0 } +// tag_copy *yaml_char_t = NULL +// struct { +// start *yaml_node_pair_t +// end *yaml_node_pair_t +// top *yaml_node_pair_t +// } pairs = { NULL, NULL, NULL } +// node yaml_node_t +// +// assert(document) // Non-NULL document object is expected. +// +// if (!tag) { +// tag = (yaml_char_t *)YAML_DEFAULT_MAPPING_TAG +// } +// +// if (!yaml_check_utf8(tag, strlen((char *)tag))) goto error +// tag_copy = yaml_strdup(tag) +// if (!tag_copy) goto error +// +// if (!STACK_INIT(&context, pairs, INITIAL_STACK_SIZE)) goto error +// +// MAPPING_NODE_INIT(node, tag_copy, pairs.start, pairs.end, +// style, mark, mark) +// if (!PUSH(&context, document.nodes, node)) goto error +// +// return document.nodes.top - document.nodes.start +// +//error: +// STACK_DEL(&context, pairs) +// yaml_free(tag_copy) +// +// return 0 +//} +// +///* +// * Append an item to a sequence node. +// */ +// +//YAML_DECLARE(int) +//yaml_document_append_sequence_item(document *yaml_document_t, +// sequence int, item int) +//{ +// struct { +// error yaml_error_type_t +// } context +// +// assert(document) // Non-NULL document is required. +// assert(sequence > 0 +// && document.nodes.start + sequence <= document.nodes.top) +// // Valid sequence id is required. +// assert(document.nodes.start[sequence-1].type == YAML_SEQUENCE_NODE) +// // A sequence node is required. +// assert(item > 0 && document.nodes.start + item <= document.nodes.top) +// // Valid item id is required. +// +// if (!PUSH(&context, +// document.nodes.start[sequence-1].data.sequence.items, item)) +// return 0 +// +// return 1 +//} +// +///* +// * Append a pair of a key and a value to a mapping node. +// */ +// +//YAML_DECLARE(int) +//yaml_document_append_mapping_pair(document *yaml_document_t, +// mapping int, key int, value int) +//{ +// struct { +// error yaml_error_type_t +// } context +// +// pair yaml_node_pair_t +// +// assert(document) // Non-NULL document is required. +// assert(mapping > 0 +// && document.nodes.start + mapping <= document.nodes.top) +// // Valid mapping id is required. +// assert(document.nodes.start[mapping-1].type == YAML_MAPPING_NODE) +// // A mapping node is required. +// assert(key > 0 && document.nodes.start + key <= document.nodes.top) +// // Valid key id is required. +// assert(value > 0 && document.nodes.start + value <= document.nodes.top) +// // Valid value id is required. +// +// pair.key = key +// pair.value = value +// +// if (!PUSH(&context, +// document.nodes.start[mapping-1].data.mapping.pairs, pair)) +// return 0 +// +// return 1 +//} +// +// diff --git a/index/server/vendor/github.com/oasdiff/yaml3/decode.go b/index/server/vendor/github.com/oasdiff/yaml3/decode.go new file mode 100644 index 000000000..0f2b9f997 --- /dev/null +++ b/index/server/vendor/github.com/oasdiff/yaml3/decode.go @@ -0,0 +1,1008 @@ +// +// Copyright (c) 2011-2019 Canonical Ltd +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package yaml + +import ( + "encoding" + "encoding/base64" + "fmt" + "io" + "math" + "reflect" + "strconv" + "time" +) + +// ---------------------------------------------------------------------------- +// Parser, produces a node tree out of a libyaml event stream. + +type parser struct { + parser yaml_parser_t + event yaml_event_t + doc *Node + anchors map[string]*Node + doneInit bool + textless bool +} + +func newParser(b []byte) *parser { + p := parser{} + if !yaml_parser_initialize(&p.parser) { + panic("failed to initialize YAML emitter") + } + if len(b) == 0 { + b = []byte{'\n'} + } + yaml_parser_set_input_string(&p.parser, b) + return &p +} + +func newParserFromReader(r io.Reader) *parser { + p := parser{} + if !yaml_parser_initialize(&p.parser) { + panic("failed to initialize YAML emitter") + } + yaml_parser_set_input_reader(&p.parser, r) + return &p +} + +func (p *parser) init() { + if p.doneInit { + return + } + p.anchors = make(map[string]*Node) + p.expect(yaml_STREAM_START_EVENT) + p.doneInit = true +} + +func (p *parser) destroy() { + if p.event.typ != yaml_NO_EVENT { + yaml_event_delete(&p.event) + } + yaml_parser_delete(&p.parser) +} + +// expect consumes an event from the event stream and +// checks that it's of the expected type. +func (p *parser) expect(e yaml_event_type_t) { + if p.event.typ == yaml_NO_EVENT { + if !yaml_parser_parse(&p.parser, &p.event) { + p.fail() + } + } + if p.event.typ == yaml_STREAM_END_EVENT { + failf("attempted to go past the end of stream; corrupted value?") + } + if p.event.typ != e { + p.parser.problem = fmt.Sprintf("expected %s event but got %s", e, p.event.typ) + p.fail() + } + yaml_event_delete(&p.event) + p.event.typ = yaml_NO_EVENT +} + +// peek peeks at the next event in the event stream, +// puts the results into p.event and returns the event type. +func (p *parser) peek() yaml_event_type_t { + if p.event.typ != yaml_NO_EVENT { + return p.event.typ + } + // It's curious choice from the underlying API to generally return a + // positive result on success, but on this case return true in an error + // scenario. This was the source of bugs in the past (issue #666). + if !yaml_parser_parse(&p.parser, &p.event) || p.parser.error != yaml_NO_ERROR { + p.fail() + } + return p.event.typ +} + +func (p *parser) fail() { + var where string + var line int + if p.parser.context_mark.line != 0 { + line = p.parser.context_mark.line + // Scanner errors don't iterate line before returning error + if p.parser.error == yaml_SCANNER_ERROR { + line++ + } + } else if p.parser.problem_mark.line != 0 { + line = p.parser.problem_mark.line + // Scanner errors don't iterate line before returning error + if p.parser.error == yaml_SCANNER_ERROR { + line++ + } + } + if line != 0 { + where = "line " + strconv.Itoa(line) + ": " + } + var msg string + if len(p.parser.problem) > 0 { + msg = p.parser.problem + } else { + msg = "unknown problem parsing YAML content" + } + failf("%s%s", where, msg) +} + +func (p *parser) anchor(n *Node, anchor []byte) { + if anchor != nil { + n.Anchor = string(anchor) + p.anchors[n.Anchor] = n + } +} + +func (p *parser) parse() *Node { + p.init() + switch p.peek() { + case yaml_SCALAR_EVENT: + return p.scalar() + case yaml_ALIAS_EVENT: + return p.alias() + case yaml_MAPPING_START_EVENT: + return p.mapping() + case yaml_SEQUENCE_START_EVENT: + return p.sequence() + case yaml_DOCUMENT_START_EVENT: + return p.document() + case yaml_STREAM_END_EVENT: + // Happens when attempting to decode an empty buffer. + return nil + case yaml_TAIL_COMMENT_EVENT: + panic("internal error: unexpected tail comment event (please report)") + default: + panic("internal error: attempted to parse unknown event (please report): " + p.event.typ.String()) + } +} + +func (p *parser) node(kind Kind, defaultTag, tag, value string) *Node { + var style Style + if tag != "" && tag != "!" { + tag = shortTag(tag) + style = TaggedStyle + } else if defaultTag != "" { + tag = defaultTag + } else if kind == ScalarNode { + tag, _ = resolve("", value) + } + n := &Node{ + Kind: kind, + Tag: tag, + Value: value, + Style: style, + } + if !p.textless { + n.Line = p.event.start_mark.line + 1 + n.Column = p.event.start_mark.column + 1 + n.HeadComment = string(p.event.head_comment) + n.LineComment = string(p.event.line_comment) + n.FootComment = string(p.event.foot_comment) + } + return n +} + +func (p *parser) parseChild(parent *Node) *Node { + child := p.parse() + parent.Content = append(parent.Content, child) + return child +} + +func (p *parser) document() *Node { + n := p.node(DocumentNode, "", "", "") + p.doc = n + p.expect(yaml_DOCUMENT_START_EVENT) + p.parseChild(n) + if p.peek() == yaml_DOCUMENT_END_EVENT { + n.FootComment = string(p.event.foot_comment) + } + p.expect(yaml_DOCUMENT_END_EVENT) + return n +} + +func (p *parser) alias() *Node { + n := p.node(AliasNode, "", "", string(p.event.anchor)) + n.Alias = p.anchors[n.Value] + if n.Alias == nil { + failf("unknown anchor '%s' referenced", n.Value) + } + p.expect(yaml_ALIAS_EVENT) + return n +} + +func (p *parser) scalar() *Node { + var parsedStyle = p.event.scalar_style() + var nodeStyle Style + switch { + case parsedStyle&yaml_DOUBLE_QUOTED_SCALAR_STYLE != 0: + nodeStyle = DoubleQuotedStyle + case parsedStyle&yaml_SINGLE_QUOTED_SCALAR_STYLE != 0: + nodeStyle = SingleQuotedStyle + case parsedStyle&yaml_LITERAL_SCALAR_STYLE != 0: + nodeStyle = LiteralStyle + case parsedStyle&yaml_FOLDED_SCALAR_STYLE != 0: + nodeStyle = FoldedStyle + } + var nodeValue = string(p.event.value) + var nodeTag = string(p.event.tag) + var defaultTag string + if nodeStyle == 0 { + if nodeValue == "<<" { + defaultTag = mergeTag + } + } else { + defaultTag = strTag + } + n := p.node(ScalarNode, defaultTag, nodeTag, nodeValue) + n.Style |= nodeStyle + p.anchor(n, p.event.anchor) + p.expect(yaml_SCALAR_EVENT) + return n +} + +func (p *parser) sequence() *Node { + n := p.node(SequenceNode, seqTag, string(p.event.tag), "") + if p.event.sequence_style()&yaml_FLOW_SEQUENCE_STYLE != 0 { + n.Style |= FlowStyle + } + p.anchor(n, p.event.anchor) + p.expect(yaml_SEQUENCE_START_EVENT) + for p.peek() != yaml_SEQUENCE_END_EVENT { + p.parseChild(n) + } + n.LineComment = string(p.event.line_comment) + n.FootComment = string(p.event.foot_comment) + p.expect(yaml_SEQUENCE_END_EVENT) + return n +} + +func (p *parser) mapping() *Node { + n := p.node(MappingNode, mapTag, string(p.event.tag), "") + block := true + if p.event.mapping_style()&yaml_FLOW_MAPPING_STYLE != 0 { + block = false + n.Style |= FlowStyle + } + p.anchor(n, p.event.anchor) + p.expect(yaml_MAPPING_START_EVENT) + for p.peek() != yaml_MAPPING_END_EVENT { + k := p.parseChild(n) + if block && k.FootComment != "" { + // Must be a foot comment for the prior value when being dedented. + if len(n.Content) > 2 { + n.Content[len(n.Content)-3].FootComment = k.FootComment + k.FootComment = "" + } + } + v := p.parseChild(n) + if k.FootComment == "" && v.FootComment != "" { + k.FootComment = v.FootComment + v.FootComment = "" + } + if p.peek() == yaml_TAIL_COMMENT_EVENT { + if k.FootComment == "" { + k.FootComment = string(p.event.foot_comment) + } + p.expect(yaml_TAIL_COMMENT_EVENT) + } + } + n.LineComment = string(p.event.line_comment) + n.FootComment = string(p.event.foot_comment) + if n.Style&FlowStyle == 0 && n.FootComment != "" && len(n.Content) > 1 { + n.Content[len(n.Content)-2].FootComment = n.FootComment + n.FootComment = "" + } + p.expect(yaml_MAPPING_END_EVENT) + return n +} + +// ---------------------------------------------------------------------------- +// Decoder, unmarshals a node into a provided value. + +type decoder struct { + doc *Node + aliases map[*Node]bool + terrors []string + + stringMapType reflect.Type + generalMapType reflect.Type + + knownFields bool + origin bool + uniqueKeys bool + decodeCount int + aliasCount int + aliasDepth int + + mergedFields map[interface{}]bool +} + +var ( + nodeType = reflect.TypeOf(Node{}) + durationType = reflect.TypeOf(time.Duration(0)) + stringMapType = reflect.TypeOf(map[string]interface{}{}) + generalMapType = reflect.TypeOf(map[interface{}]interface{}{}) + ifaceType = generalMapType.Elem() + timeType = reflect.TypeOf(time.Time{}) + ptrTimeType = reflect.TypeOf(&time.Time{}) +) + +func newDecoder() *decoder { + d := &decoder{ + stringMapType: stringMapType, + generalMapType: generalMapType, + uniqueKeys: true, + } + d.aliases = make(map[*Node]bool) + return d +} + +func (d *decoder) terror(n *Node, tag string, out reflect.Value) { + if n.Tag != "" { + tag = n.Tag + } + value := n.Value + if tag != seqTag && tag != mapTag { + if len(value) > 10 { + value = " `" + value[:7] + "...`" + } else { + value = " `" + value + "`" + } + } + d.terrors = append(d.terrors, fmt.Sprintf("line %d: cannot unmarshal %s%s into %s", n.Line, shortTag(tag), value, out.Type())) +} + +func (d *decoder) callUnmarshaler(n *Node, u Unmarshaler) (good bool) { + err := u.UnmarshalYAML(n) + if e, ok := err.(*TypeError); ok { + d.terrors = append(d.terrors, e.Errors...) + return false + } + if err != nil { + fail(err) + } + return true +} + +func (d *decoder) callObsoleteUnmarshaler(n *Node, u obsoleteUnmarshaler) (good bool) { + terrlen := len(d.terrors) + err := u.UnmarshalYAML(func(v interface{}) (err error) { + defer handleErr(&err) + d.unmarshal(n, reflect.ValueOf(v)) + if len(d.terrors) > terrlen { + issues := d.terrors[terrlen:] + d.terrors = d.terrors[:terrlen] + return &TypeError{issues} + } + return nil + }) + if e, ok := err.(*TypeError); ok { + d.terrors = append(d.terrors, e.Errors...) + return false + } + if err != nil { + fail(err) + } + return true +} + +// d.prepare initializes and dereferences pointers and calls UnmarshalYAML +// if a value is found to implement it. +// It returns the initialized and dereferenced out value, whether +// unmarshalling was already done by UnmarshalYAML, and if so whether +// its types unmarshalled appropriately. +// +// If n holds a null value, prepare returns before doing anything. +func (d *decoder) prepare(n *Node, out reflect.Value) (newout reflect.Value, unmarshaled, good bool) { + if n.ShortTag() == nullTag { + return out, false, false + } + again := true + for again { + again = false + if out.Kind() == reflect.Ptr { + if out.IsNil() { + out.Set(reflect.New(out.Type().Elem())) + } + out = out.Elem() + again = true + } + if out.CanAddr() { + outi := out.Addr().Interface() + if u, ok := outi.(Unmarshaler); ok { + good = d.callUnmarshaler(n, u) + return out, true, good + } + if u, ok := outi.(obsoleteUnmarshaler); ok { + good = d.callObsoleteUnmarshaler(n, u) + return out, true, good + } + } + } + return out, false, false +} + +func (d *decoder) fieldByIndex(n *Node, v reflect.Value, index []int) (field reflect.Value) { + if n.ShortTag() == nullTag { + return reflect.Value{} + } + for _, num := range index { + for { + if v.Kind() == reflect.Ptr { + if v.IsNil() { + v.Set(reflect.New(v.Type().Elem())) + } + v = v.Elem() + continue + } + break + } + v = v.Field(num) + } + return v +} + +const ( + // 400,000 decode operations is ~500kb of dense object declarations, or + // ~5kb of dense object declarations with 10000% alias expansion + alias_ratio_range_low = 400000 + + // 4,000,000 decode operations is ~5MB of dense object declarations, or + // ~4.5MB of dense object declarations with 10% alias expansion + alias_ratio_range_high = 4000000 + + // alias_ratio_range is the range over which we scale allowed alias ratios + alias_ratio_range = float64(alias_ratio_range_high - alias_ratio_range_low) +) + +func allowedAliasRatio(decodeCount int) float64 { + switch { + case decodeCount <= alias_ratio_range_low: + // allow 99% to come from alias expansion for small-to-medium documents + return 0.99 + case decodeCount >= alias_ratio_range_high: + // allow 10% to come from alias expansion for very large documents + return 0.10 + default: + // scale smoothly from 99% down to 10% over the range. + // this maps to 396,000 - 400,000 allowed alias-driven decodes over the range. + // 400,000 decode operations is ~100MB of allocations in worst-case scenarios (single-item maps). + return 0.99 - 0.89*(float64(decodeCount-alias_ratio_range_low)/alias_ratio_range) + } +} + +func (d *decoder) unmarshal(n *Node, out reflect.Value) (good bool) { + d.decodeCount++ + if d.aliasDepth > 0 { + d.aliasCount++ + } + if d.aliasCount > 100 && d.decodeCount > 1000 && float64(d.aliasCount)/float64(d.decodeCount) > allowedAliasRatio(d.decodeCount) { + failf("document contains excessive aliasing") + } + if out.Type() == nodeType { + out.Set(reflect.ValueOf(n).Elem()) + return true + } + switch n.Kind { + case DocumentNode: + return d.document(n, out) + case AliasNode: + return d.alias(n, out) + } + out, unmarshaled, good := d.prepare(n, out) + if unmarshaled { + return good + } + switch n.Kind { + case ScalarNode: + good = d.scalar(n, out) + case MappingNode: + good = d.mapping(n, out) + case SequenceNode: + good = d.sequence(n, out) + case 0: + if n.IsZero() { + return d.null(out) + } + fallthrough + default: + failf("cannot decode node with unknown kind %d", n.Kind) + } + return good +} + +func (d *decoder) document(n *Node, out reflect.Value) (good bool) { + if len(n.Content) == 1 { + d.doc = n + d.unmarshal(n.Content[0], out) + return true + } + return false +} + +func (d *decoder) alias(n *Node, out reflect.Value) (good bool) { + if d.aliases[n] { + // TODO this could actually be allowed in some circumstances. + failf("anchor '%s' value contains itself", n.Value) + } + d.aliases[n] = true + d.aliasDepth++ + good = d.unmarshal(n.Alias, out) + d.aliasDepth-- + delete(d.aliases, n) + return good +} + +var zeroValue reflect.Value + +func resetMap(out reflect.Value) { + for _, k := range out.MapKeys() { + out.SetMapIndex(k, zeroValue) + } +} + +func (d *decoder) null(out reflect.Value) bool { + if out.CanAddr() { + switch out.Kind() { + case reflect.Interface, reflect.Ptr, reflect.Map, reflect.Slice: + out.Set(reflect.Zero(out.Type())) + return true + } + } + return false +} + +func (d *decoder) scalar(n *Node, out reflect.Value) bool { + var tag string + var resolved interface{} + if n.indicatedString() { + tag = strTag + resolved = n.Value + } else { + tag, resolved = resolve(n.Tag, n.Value) + if tag == binaryTag { + data, err := base64.StdEncoding.DecodeString(resolved.(string)) + if err != nil { + failf("!!binary value contains invalid base64 data") + } + resolved = string(data) + } + } + if resolved == nil { + return d.null(out) + } + if resolvedv := reflect.ValueOf(resolved); out.Type() == resolvedv.Type() { + // We've resolved to exactly the type we want, so use that. + out.Set(resolvedv) + return true + } + // Perhaps we can use the value as a TextUnmarshaler to + // set its value. + if out.CanAddr() { + u, ok := out.Addr().Interface().(encoding.TextUnmarshaler) + if ok { + var text []byte + if tag == binaryTag { + text = []byte(resolved.(string)) + } else { + // We let any value be unmarshaled into TextUnmarshaler. + // That might be more lax than we'd like, but the + // TextUnmarshaler itself should bowl out any dubious values. + text = []byte(n.Value) + } + err := u.UnmarshalText(text) + if err != nil { + fail(err) + } + return true + } + } + switch out.Kind() { + case reflect.String: + if tag == binaryTag { + out.SetString(resolved.(string)) + return true + } + out.SetString(n.Value) + return true + case reflect.Interface: + out.Set(reflect.ValueOf(resolved)) + return true + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + // This used to work in v2, but it's very unfriendly. + isDuration := out.Type() == durationType + + switch resolved := resolved.(type) { + case int: + if !isDuration && !out.OverflowInt(int64(resolved)) { + out.SetInt(int64(resolved)) + return true + } + case int64: + if !isDuration && !out.OverflowInt(resolved) { + out.SetInt(resolved) + return true + } + case uint64: + if !isDuration && resolved <= math.MaxInt64 && !out.OverflowInt(int64(resolved)) { + out.SetInt(int64(resolved)) + return true + } + case float64: + if !isDuration && resolved <= math.MaxInt64 && !out.OverflowInt(int64(resolved)) { + out.SetInt(int64(resolved)) + return true + } + case string: + if out.Type() == durationType { + d, err := time.ParseDuration(resolved) + if err == nil { + out.SetInt(int64(d)) + return true + } + } + } + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: + switch resolved := resolved.(type) { + case int: + if resolved >= 0 && !out.OverflowUint(uint64(resolved)) { + out.SetUint(uint64(resolved)) + return true + } + case int64: + if resolved >= 0 && !out.OverflowUint(uint64(resolved)) { + out.SetUint(uint64(resolved)) + return true + } + case uint64: + if !out.OverflowUint(uint64(resolved)) { + out.SetUint(uint64(resolved)) + return true + } + case float64: + if resolved <= math.MaxUint64 && !out.OverflowUint(uint64(resolved)) { + out.SetUint(uint64(resolved)) + return true + } + } + case reflect.Bool: + switch resolved := resolved.(type) { + case bool: + out.SetBool(resolved) + return true + case string: + // This offers some compatibility with the 1.1 spec (https://yaml.org/type/bool.html). + // It only works if explicitly attempting to unmarshal into a typed bool value. + switch resolved { + case "y", "Y", "yes", "Yes", "YES", "on", "On", "ON": + out.SetBool(true) + return true + case "n", "N", "no", "No", "NO", "off", "Off", "OFF": + out.SetBool(false) + return true + } + } + case reflect.Float32, reflect.Float64: + switch resolved := resolved.(type) { + case int: + out.SetFloat(float64(resolved)) + return true + case int64: + out.SetFloat(float64(resolved)) + return true + case uint64: + out.SetFloat(float64(resolved)) + return true + case float64: + out.SetFloat(resolved) + return true + } + case reflect.Struct: + if resolvedv := reflect.ValueOf(resolved); out.Type() == resolvedv.Type() { + out.Set(resolvedv) + return true + } + case reflect.Ptr: + panic("yaml internal error: please report the issue") + } + d.terror(n, tag, out) + return false +} + +func settableValueOf(i interface{}) reflect.Value { + v := reflect.ValueOf(i) + sv := reflect.New(v.Type()).Elem() + sv.Set(v) + return sv +} + +func (d *decoder) sequence(n *Node, out reflect.Value) (good bool) { + l := len(n.Content) + + var iface reflect.Value + switch out.Kind() { + case reflect.Slice: + out.Set(reflect.MakeSlice(out.Type(), l, l)) + case reflect.Array: + if l != out.Len() { + failf("invalid array: want %d elements but got %d", out.Len(), l) + } + case reflect.Interface: + // No type hints. Will have to use a generic sequence. + iface = out + out = settableValueOf(make([]interface{}, l)) + default: + d.terror(n, seqTag, out) + return false + } + et := out.Type().Elem() + + j := 0 + for i := 0; i < l; i++ { + e := reflect.New(et).Elem() + if d.origin { + addOriginInSeq(n.Content[i]) + } + if ok := d.unmarshal(n.Content[i], e); ok { + out.Index(j).Set(e) + j++ + } + } + if out.Kind() != reflect.Array { + out.Set(out.Slice(0, j)) + } + if iface.IsValid() { + iface.Set(out) + } + return true +} + +func (d *decoder) mapping(n *Node, out reflect.Value) (good bool) { + l := len(n.Content) + if d.uniqueKeys { + nerrs := len(d.terrors) + for i := 0; i < l; i += 2 { + ni := n.Content[i] + for j := i + 2; j < l; j += 2 { + nj := n.Content[j] + if ni.Kind == nj.Kind && ni.Value == nj.Value { + d.terrors = append(d.terrors, fmt.Sprintf("line %d: mapping key %#v already defined at line %d", nj.Line, nj.Value, ni.Line)) + } + } + } + if len(d.terrors) > nerrs { + return false + } + } + switch out.Kind() { + case reflect.Struct: + return d.mappingStruct(n, out) + case reflect.Map: + // okay + case reflect.Interface: + iface := out + if isStringMap(n) { + out = reflect.MakeMap(d.stringMapType) + } else { + out = reflect.MakeMap(d.generalMapType) + } + iface.Set(out) + default: + d.terror(n, mapTag, out) + return false + } + + outt := out.Type() + kt := outt.Key() + et := outt.Elem() + + stringMapType := d.stringMapType + generalMapType := d.generalMapType + if outt.Elem() == ifaceType { + if outt.Key().Kind() == reflect.String { + d.stringMapType = outt + } else if outt.Key() == ifaceType { + d.generalMapType = outt + } + } + + mergedFields := d.mergedFields + d.mergedFields = nil + + var mergeNode *Node + + mapIsNew := false + if out.IsNil() { + out.Set(reflect.MakeMap(outt)) + mapIsNew = true + } + for i := 0; i < l; i += 2 { + if isMerge(n.Content[i]) { + mergeNode = n.Content[i+1] + continue + } + k := reflect.New(kt).Elem() + if d.unmarshal(n.Content[i], k) { + if mergedFields != nil { + ki := k.Interface() + if mergedFields[ki] { + continue + } + mergedFields[ki] = true + } + kkind := k.Kind() + if kkind == reflect.Interface { + kkind = k.Elem().Kind() + } + if kkind == reflect.Map || kkind == reflect.Slice { + failf("invalid map key: %#v", k.Interface()) + } + e := reflect.New(et).Elem() + + if d.origin { + addOriginInMap(n.Content[i], n.Content[i+1]) + } + if d.unmarshal(n.Content[i+1], e) || n.Content[i+1].ShortTag() == nullTag && (mapIsNew || !out.MapIndex(k).IsValid()) { + out.SetMapIndex(k, e) + } + } + } + + d.mergedFields = mergedFields + if mergeNode != nil { + d.merge(n, mergeNode, out) + } + + d.stringMapType = stringMapType + d.generalMapType = generalMapType + return true +} + +func isStringMap(n *Node) bool { + if n.Kind != MappingNode { + return false + } + l := len(n.Content) + for i := 0; i < l; i += 2 { + shortTag := n.Content[i].ShortTag() + if shortTag != strTag && shortTag != mergeTag { + return false + } + } + return true +} + +func (d *decoder) mappingStruct(n *Node, out reflect.Value) (good bool) { + sinfo, err := getStructInfo(out.Type()) + if err != nil { + panic(err) + } + + var inlineMap reflect.Value + var elemType reflect.Type + if sinfo.InlineMap != -1 { + inlineMap = out.Field(sinfo.InlineMap) + elemType = inlineMap.Type().Elem() + } + + for _, index := range sinfo.InlineUnmarshalers { + field := d.fieldByIndex(n, out, index) + d.prepare(n, field) + } + + mergedFields := d.mergedFields + d.mergedFields = nil + var mergeNode *Node + var doneFields []bool + if d.uniqueKeys { + doneFields = make([]bool, len(sinfo.FieldsList)) + } + name := settableValueOf("") + l := len(n.Content) + for i := 0; i < l; i += 2 { + ni := n.Content[i] + if isMerge(ni) { + mergeNode = n.Content[i+1] + continue + } + if !d.unmarshal(ni, name) { + continue + } + sname := name.String() + if mergedFields != nil { + if mergedFields[sname] { + continue + } + mergedFields[sname] = true + } + if info, ok := sinfo.FieldsMap[sname]; ok { + if d.uniqueKeys { + if doneFields[info.Id] { + d.terrors = append(d.terrors, fmt.Sprintf("line %d: field %s already set in type %s", ni.Line, name.String(), out.Type())) + continue + } + doneFields[info.Id] = true + } + var field reflect.Value + if info.Inline == nil { + field = out.Field(info.Num) + } else { + field = d.fieldByIndex(n, out, info.Inline) + } + d.unmarshal(n.Content[i+1], field) + } else if sinfo.InlineMap != -1 { + if inlineMap.IsNil() { + inlineMap.Set(reflect.MakeMap(inlineMap.Type())) + } + value := reflect.New(elemType).Elem() + d.unmarshal(n.Content[i+1], value) + inlineMap.SetMapIndex(name, value) + } else if d.knownFields { + d.terrors = append(d.terrors, fmt.Sprintf("line %d: field %s not found in type %s", ni.Line, name.String(), out.Type())) + } + } + + d.mergedFields = mergedFields + if mergeNode != nil { + d.merge(n, mergeNode, out) + } + return true +} + +func failWantMap() { + failf("map merge requires map or sequence of maps as the value") +} + +func (d *decoder) merge(parent *Node, merge *Node, out reflect.Value) { + mergedFields := d.mergedFields + if mergedFields == nil { + d.mergedFields = make(map[interface{}]bool) + for i := 0; i < len(parent.Content); i += 2 { + k := reflect.New(ifaceType).Elem() + if d.unmarshal(parent.Content[i], k) { + d.mergedFields[k.Interface()] = true + } + } + } + + switch merge.Kind { + case MappingNode: + d.unmarshal(merge, out) + case AliasNode: + if merge.Alias != nil && merge.Alias.Kind != MappingNode { + failWantMap() + } + d.unmarshal(merge, out) + case SequenceNode: + for i := 0; i < len(merge.Content); i++ { + ni := merge.Content[i] + if ni.Kind == AliasNode { + if ni.Alias != nil && ni.Alias.Kind != MappingNode { + failWantMap() + } + } else if ni.Kind != MappingNode { + failWantMap() + } + d.unmarshal(ni, out) + } + default: + failWantMap() + } + + d.mergedFields = mergedFields +} + +func isMerge(n *Node) bool { + return n.Kind == ScalarNode && n.Value == "<<" && (n.Tag == "" || n.Tag == "!" || shortTag(n.Tag) == mergeTag) +} diff --git a/index/server/vendor/github.com/oasdiff/yaml3/emitterc.go b/index/server/vendor/github.com/oasdiff/yaml3/emitterc.go new file mode 100644 index 000000000..0f47c9ca8 --- /dev/null +++ b/index/server/vendor/github.com/oasdiff/yaml3/emitterc.go @@ -0,0 +1,2020 @@ +// +// Copyright (c) 2011-2019 Canonical Ltd +// Copyright (c) 2006-2010 Kirill Simonov +// +// 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. + +package yaml + +import ( + "bytes" + "fmt" +) + +// Flush the buffer if needed. +func flush(emitter *yaml_emitter_t) bool { + if emitter.buffer_pos+5 >= len(emitter.buffer) { + return yaml_emitter_flush(emitter) + } + return true +} + +// Put a character to the output buffer. +func put(emitter *yaml_emitter_t, value byte) bool { + if emitter.buffer_pos+5 >= len(emitter.buffer) && !yaml_emitter_flush(emitter) { + return false + } + emitter.buffer[emitter.buffer_pos] = value + emitter.buffer_pos++ + emitter.column++ + return true +} + +// Put a line break to the output buffer. +func put_break(emitter *yaml_emitter_t) bool { + if emitter.buffer_pos+5 >= len(emitter.buffer) && !yaml_emitter_flush(emitter) { + return false + } + switch emitter.line_break { + case yaml_CR_BREAK: + emitter.buffer[emitter.buffer_pos] = '\r' + emitter.buffer_pos += 1 + case yaml_LN_BREAK: + emitter.buffer[emitter.buffer_pos] = '\n' + emitter.buffer_pos += 1 + case yaml_CRLN_BREAK: + emitter.buffer[emitter.buffer_pos+0] = '\r' + emitter.buffer[emitter.buffer_pos+1] = '\n' + emitter.buffer_pos += 2 + default: + panic("unknown line break setting") + } + if emitter.column == 0 { + emitter.space_above = true + } + emitter.column = 0 + emitter.line++ + // [Go] Do this here and below and drop from everywhere else (see commented lines). + emitter.indention = true + return true +} + +// Copy a character from a string into buffer. +func write(emitter *yaml_emitter_t, s []byte, i *int) bool { + if emitter.buffer_pos+5 >= len(emitter.buffer) && !yaml_emitter_flush(emitter) { + return false + } + p := emitter.buffer_pos + w := width(s[*i]) + switch w { + case 4: + emitter.buffer[p+3] = s[*i+3] + fallthrough + case 3: + emitter.buffer[p+2] = s[*i+2] + fallthrough + case 2: + emitter.buffer[p+1] = s[*i+1] + fallthrough + case 1: + emitter.buffer[p+0] = s[*i+0] + default: + panic("unknown character width") + } + emitter.column++ + emitter.buffer_pos += w + *i += w + return true +} + +// Write a whole string into buffer. +func write_all(emitter *yaml_emitter_t, s []byte) bool { + for i := 0; i < len(s); { + if !write(emitter, s, &i) { + return false + } + } + return true +} + +// Copy a line break character from a string into buffer. +func write_break(emitter *yaml_emitter_t, s []byte, i *int) bool { + if s[*i] == '\n' { + if !put_break(emitter) { + return false + } + *i++ + } else { + if !write(emitter, s, i) { + return false + } + if emitter.column == 0 { + emitter.space_above = true + } + emitter.column = 0 + emitter.line++ + // [Go] Do this here and above and drop from everywhere else (see commented lines). + emitter.indention = true + } + return true +} + +// Set an emitter error and return false. +func yaml_emitter_set_emitter_error(emitter *yaml_emitter_t, problem string) bool { + emitter.error = yaml_EMITTER_ERROR + emitter.problem = problem + return false +} + +// Emit an event. +func yaml_emitter_emit(emitter *yaml_emitter_t, event *yaml_event_t) bool { + emitter.events = append(emitter.events, *event) + for !yaml_emitter_need_more_events(emitter) { + event := &emitter.events[emitter.events_head] + if !yaml_emitter_analyze_event(emitter, event) { + return false + } + if !yaml_emitter_state_machine(emitter, event) { + return false + } + yaml_event_delete(event) + emitter.events_head++ + } + return true +} + +// Check if we need to accumulate more events before emitting. +// +// We accumulate extra +// - 1 event for DOCUMENT-START +// - 2 events for SEQUENCE-START +// - 3 events for MAPPING-START +// +func yaml_emitter_need_more_events(emitter *yaml_emitter_t) bool { + if emitter.events_head == len(emitter.events) { + return true + } + var accumulate int + switch emitter.events[emitter.events_head].typ { + case yaml_DOCUMENT_START_EVENT: + accumulate = 1 + break + case yaml_SEQUENCE_START_EVENT: + accumulate = 2 + break + case yaml_MAPPING_START_EVENT: + accumulate = 3 + break + default: + return false + } + if len(emitter.events)-emitter.events_head > accumulate { + return false + } + var level int + for i := emitter.events_head; i < len(emitter.events); i++ { + switch emitter.events[i].typ { + case yaml_STREAM_START_EVENT, yaml_DOCUMENT_START_EVENT, yaml_SEQUENCE_START_EVENT, yaml_MAPPING_START_EVENT: + level++ + case yaml_STREAM_END_EVENT, yaml_DOCUMENT_END_EVENT, yaml_SEQUENCE_END_EVENT, yaml_MAPPING_END_EVENT: + level-- + } + if level == 0 { + return false + } + } + return true +} + +// Append a directive to the directives stack. +func yaml_emitter_append_tag_directive(emitter *yaml_emitter_t, value *yaml_tag_directive_t, allow_duplicates bool) bool { + for i := 0; i < len(emitter.tag_directives); i++ { + if bytes.Equal(value.handle, emitter.tag_directives[i].handle) { + if allow_duplicates { + return true + } + return yaml_emitter_set_emitter_error(emitter, "duplicate %TAG directive") + } + } + + // [Go] Do we actually need to copy this given garbage collection + // and the lack of deallocating destructors? + tag_copy := yaml_tag_directive_t{ + handle: make([]byte, len(value.handle)), + prefix: make([]byte, len(value.prefix)), + } + copy(tag_copy.handle, value.handle) + copy(tag_copy.prefix, value.prefix) + emitter.tag_directives = append(emitter.tag_directives, tag_copy) + return true +} + +// Increase the indentation level. +func yaml_emitter_increase_indent(emitter *yaml_emitter_t, flow, indentless bool) bool { + emitter.indents = append(emitter.indents, emitter.indent) + if emitter.indent < 0 { + if flow { + emitter.indent = emitter.best_indent + } else { + emitter.indent = 0 + } + } else if !indentless { + // [Go] This was changed so that indentations are more regular. + if emitter.states[len(emitter.states)-1] == yaml_EMIT_BLOCK_SEQUENCE_ITEM_STATE { + // The first indent inside a sequence will just skip the "- " indicator. + emitter.indent += 2 + } else { + // Everything else aligns to the chosen indentation. + emitter.indent = emitter.best_indent*((emitter.indent+emitter.best_indent)/emitter.best_indent) + } + } + return true +} + +// State dispatcher. +func yaml_emitter_state_machine(emitter *yaml_emitter_t, event *yaml_event_t) bool { + switch emitter.state { + default: + case yaml_EMIT_STREAM_START_STATE: + return yaml_emitter_emit_stream_start(emitter, event) + + case yaml_EMIT_FIRST_DOCUMENT_START_STATE: + return yaml_emitter_emit_document_start(emitter, event, true) + + case yaml_EMIT_DOCUMENT_START_STATE: + return yaml_emitter_emit_document_start(emitter, event, false) + + case yaml_EMIT_DOCUMENT_CONTENT_STATE: + return yaml_emitter_emit_document_content(emitter, event) + + case yaml_EMIT_DOCUMENT_END_STATE: + return yaml_emitter_emit_document_end(emitter, event) + + case yaml_EMIT_FLOW_SEQUENCE_FIRST_ITEM_STATE: + return yaml_emitter_emit_flow_sequence_item(emitter, event, true, false) + + case yaml_EMIT_FLOW_SEQUENCE_TRAIL_ITEM_STATE: + return yaml_emitter_emit_flow_sequence_item(emitter, event, false, true) + + case yaml_EMIT_FLOW_SEQUENCE_ITEM_STATE: + return yaml_emitter_emit_flow_sequence_item(emitter, event, false, false) + + case yaml_EMIT_FLOW_MAPPING_FIRST_KEY_STATE: + return yaml_emitter_emit_flow_mapping_key(emitter, event, true, false) + + case yaml_EMIT_FLOW_MAPPING_TRAIL_KEY_STATE: + return yaml_emitter_emit_flow_mapping_key(emitter, event, false, true) + + case yaml_EMIT_FLOW_MAPPING_KEY_STATE: + return yaml_emitter_emit_flow_mapping_key(emitter, event, false, false) + + case yaml_EMIT_FLOW_MAPPING_SIMPLE_VALUE_STATE: + return yaml_emitter_emit_flow_mapping_value(emitter, event, true) + + case yaml_EMIT_FLOW_MAPPING_VALUE_STATE: + return yaml_emitter_emit_flow_mapping_value(emitter, event, false) + + case yaml_EMIT_BLOCK_SEQUENCE_FIRST_ITEM_STATE: + return yaml_emitter_emit_block_sequence_item(emitter, event, true) + + case yaml_EMIT_BLOCK_SEQUENCE_ITEM_STATE: + return yaml_emitter_emit_block_sequence_item(emitter, event, false) + + case yaml_EMIT_BLOCK_MAPPING_FIRST_KEY_STATE: + return yaml_emitter_emit_block_mapping_key(emitter, event, true) + + case yaml_EMIT_BLOCK_MAPPING_KEY_STATE: + return yaml_emitter_emit_block_mapping_key(emitter, event, false) + + case yaml_EMIT_BLOCK_MAPPING_SIMPLE_VALUE_STATE: + return yaml_emitter_emit_block_mapping_value(emitter, event, true) + + case yaml_EMIT_BLOCK_MAPPING_VALUE_STATE: + return yaml_emitter_emit_block_mapping_value(emitter, event, false) + + case yaml_EMIT_END_STATE: + return yaml_emitter_set_emitter_error(emitter, "expected nothing after STREAM-END") + } + panic("invalid emitter state") +} + +// Expect STREAM-START. +func yaml_emitter_emit_stream_start(emitter *yaml_emitter_t, event *yaml_event_t) bool { + if event.typ != yaml_STREAM_START_EVENT { + return yaml_emitter_set_emitter_error(emitter, "expected STREAM-START") + } + if emitter.encoding == yaml_ANY_ENCODING { + emitter.encoding = event.encoding + if emitter.encoding == yaml_ANY_ENCODING { + emitter.encoding = yaml_UTF8_ENCODING + } + } + if emitter.best_indent < 2 || emitter.best_indent > 9 { + emitter.best_indent = 2 + } + if emitter.best_width >= 0 && emitter.best_width <= emitter.best_indent*2 { + emitter.best_width = 80 + } + if emitter.best_width < 0 { + emitter.best_width = 1<<31 - 1 + } + if emitter.line_break == yaml_ANY_BREAK { + emitter.line_break = yaml_LN_BREAK + } + + emitter.indent = -1 + emitter.line = 0 + emitter.column = 0 + emitter.whitespace = true + emitter.indention = true + emitter.space_above = true + emitter.foot_indent = -1 + + if emitter.encoding != yaml_UTF8_ENCODING { + if !yaml_emitter_write_bom(emitter) { + return false + } + } + emitter.state = yaml_EMIT_FIRST_DOCUMENT_START_STATE + return true +} + +// Expect DOCUMENT-START or STREAM-END. +func yaml_emitter_emit_document_start(emitter *yaml_emitter_t, event *yaml_event_t, first bool) bool { + + if event.typ == yaml_DOCUMENT_START_EVENT { + + if event.version_directive != nil { + if !yaml_emitter_analyze_version_directive(emitter, event.version_directive) { + return false + } + } + + for i := 0; i < len(event.tag_directives); i++ { + tag_directive := &event.tag_directives[i] + if !yaml_emitter_analyze_tag_directive(emitter, tag_directive) { + return false + } + if !yaml_emitter_append_tag_directive(emitter, tag_directive, false) { + return false + } + } + + for i := 0; i < len(default_tag_directives); i++ { + tag_directive := &default_tag_directives[i] + if !yaml_emitter_append_tag_directive(emitter, tag_directive, true) { + return false + } + } + + implicit := event.implicit + if !first || emitter.canonical { + implicit = false + } + + if emitter.open_ended && (event.version_directive != nil || len(event.tag_directives) > 0) { + if !yaml_emitter_write_indicator(emitter, []byte("..."), true, false, false) { + return false + } + if !yaml_emitter_write_indent(emitter) { + return false + } + } + + if event.version_directive != nil { + implicit = false + if !yaml_emitter_write_indicator(emitter, []byte("%YAML"), true, false, false) { + return false + } + if !yaml_emitter_write_indicator(emitter, []byte("1.1"), true, false, false) { + return false + } + if !yaml_emitter_write_indent(emitter) { + return false + } + } + + if len(event.tag_directives) > 0 { + implicit = false + for i := 0; i < len(event.tag_directives); i++ { + tag_directive := &event.tag_directives[i] + if !yaml_emitter_write_indicator(emitter, []byte("%TAG"), true, false, false) { + return false + } + if !yaml_emitter_write_tag_handle(emitter, tag_directive.handle) { + return false + } + if !yaml_emitter_write_tag_content(emitter, tag_directive.prefix, true) { + return false + } + if !yaml_emitter_write_indent(emitter) { + return false + } + } + } + + if yaml_emitter_check_empty_document(emitter) { + implicit = false + } + if !implicit { + if !yaml_emitter_write_indent(emitter) { + return false + } + if !yaml_emitter_write_indicator(emitter, []byte("---"), true, false, false) { + return false + } + if emitter.canonical || true { + if !yaml_emitter_write_indent(emitter) { + return false + } + } + } + + if len(emitter.head_comment) > 0 { + if !yaml_emitter_process_head_comment(emitter) { + return false + } + if !put_break(emitter) { + return false + } + } + + emitter.state = yaml_EMIT_DOCUMENT_CONTENT_STATE + return true + } + + if event.typ == yaml_STREAM_END_EVENT { + if emitter.open_ended { + if !yaml_emitter_write_indicator(emitter, []byte("..."), true, false, false) { + return false + } + if !yaml_emitter_write_indent(emitter) { + return false + } + } + if !yaml_emitter_flush(emitter) { + return false + } + emitter.state = yaml_EMIT_END_STATE + return true + } + + return yaml_emitter_set_emitter_error(emitter, "expected DOCUMENT-START or STREAM-END") +} + +// Expect the root node. +func yaml_emitter_emit_document_content(emitter *yaml_emitter_t, event *yaml_event_t) bool { + emitter.states = append(emitter.states, yaml_EMIT_DOCUMENT_END_STATE) + + if !yaml_emitter_process_head_comment(emitter) { + return false + } + if !yaml_emitter_emit_node(emitter, event, true, false, false, false) { + return false + } + if !yaml_emitter_process_line_comment(emitter) { + return false + } + if !yaml_emitter_process_foot_comment(emitter) { + return false + } + return true +} + +// Expect DOCUMENT-END. +func yaml_emitter_emit_document_end(emitter *yaml_emitter_t, event *yaml_event_t) bool { + if event.typ != yaml_DOCUMENT_END_EVENT { + return yaml_emitter_set_emitter_error(emitter, "expected DOCUMENT-END") + } + // [Go] Force document foot separation. + emitter.foot_indent = 0 + if !yaml_emitter_process_foot_comment(emitter) { + return false + } + emitter.foot_indent = -1 + if !yaml_emitter_write_indent(emitter) { + return false + } + if !event.implicit { + // [Go] Allocate the slice elsewhere. + if !yaml_emitter_write_indicator(emitter, []byte("..."), true, false, false) { + return false + } + if !yaml_emitter_write_indent(emitter) { + return false + } + } + if !yaml_emitter_flush(emitter) { + return false + } + emitter.state = yaml_EMIT_DOCUMENT_START_STATE + emitter.tag_directives = emitter.tag_directives[:0] + return true +} + +// Expect a flow item node. +func yaml_emitter_emit_flow_sequence_item(emitter *yaml_emitter_t, event *yaml_event_t, first, trail bool) bool { + if first { + if !yaml_emitter_write_indicator(emitter, []byte{'['}, true, true, false) { + return false + } + if !yaml_emitter_increase_indent(emitter, true, false) { + return false + } + emitter.flow_level++ + } + + if event.typ == yaml_SEQUENCE_END_EVENT { + if emitter.canonical && !first && !trail { + if !yaml_emitter_write_indicator(emitter, []byte{','}, false, false, false) { + return false + } + } + emitter.flow_level-- + emitter.indent = emitter.indents[len(emitter.indents)-1] + emitter.indents = emitter.indents[:len(emitter.indents)-1] + if emitter.column == 0 || emitter.canonical && !first { + if !yaml_emitter_write_indent(emitter) { + return false + } + } + if !yaml_emitter_write_indicator(emitter, []byte{']'}, false, false, false) { + return false + } + if !yaml_emitter_process_line_comment(emitter) { + return false + } + if !yaml_emitter_process_foot_comment(emitter) { + return false + } + emitter.state = emitter.states[len(emitter.states)-1] + emitter.states = emitter.states[:len(emitter.states)-1] + + return true + } + + if !first && !trail { + if !yaml_emitter_write_indicator(emitter, []byte{','}, false, false, false) { + return false + } + } + + if !yaml_emitter_process_head_comment(emitter) { + return false + } + if emitter.column == 0 { + if !yaml_emitter_write_indent(emitter) { + return false + } + } + + if emitter.canonical || emitter.column > emitter.best_width { + if !yaml_emitter_write_indent(emitter) { + return false + } + } + if len(emitter.line_comment)+len(emitter.foot_comment)+len(emitter.tail_comment) > 0 { + emitter.states = append(emitter.states, yaml_EMIT_FLOW_SEQUENCE_TRAIL_ITEM_STATE) + } else { + emitter.states = append(emitter.states, yaml_EMIT_FLOW_SEQUENCE_ITEM_STATE) + } + if !yaml_emitter_emit_node(emitter, event, false, true, false, false) { + return false + } + if len(emitter.line_comment)+len(emitter.foot_comment)+len(emitter.tail_comment) > 0 { + if !yaml_emitter_write_indicator(emitter, []byte{','}, false, false, false) { + return false + } + } + if !yaml_emitter_process_line_comment(emitter) { + return false + } + if !yaml_emitter_process_foot_comment(emitter) { + return false + } + return true +} + +// Expect a flow key node. +func yaml_emitter_emit_flow_mapping_key(emitter *yaml_emitter_t, event *yaml_event_t, first, trail bool) bool { + if first { + if !yaml_emitter_write_indicator(emitter, []byte{'{'}, true, true, false) { + return false + } + if !yaml_emitter_increase_indent(emitter, true, false) { + return false + } + emitter.flow_level++ + } + + if event.typ == yaml_MAPPING_END_EVENT { + if (emitter.canonical || len(emitter.head_comment)+len(emitter.foot_comment)+len(emitter.tail_comment) > 0) && !first && !trail { + if !yaml_emitter_write_indicator(emitter, []byte{','}, false, false, false) { + return false + } + } + if !yaml_emitter_process_head_comment(emitter) { + return false + } + emitter.flow_level-- + emitter.indent = emitter.indents[len(emitter.indents)-1] + emitter.indents = emitter.indents[:len(emitter.indents)-1] + if emitter.canonical && !first { + if !yaml_emitter_write_indent(emitter) { + return false + } + } + if !yaml_emitter_write_indicator(emitter, []byte{'}'}, false, false, false) { + return false + } + if !yaml_emitter_process_line_comment(emitter) { + return false + } + if !yaml_emitter_process_foot_comment(emitter) { + return false + } + emitter.state = emitter.states[len(emitter.states)-1] + emitter.states = emitter.states[:len(emitter.states)-1] + return true + } + + if !first && !trail { + if !yaml_emitter_write_indicator(emitter, []byte{','}, false, false, false) { + return false + } + } + + if !yaml_emitter_process_head_comment(emitter) { + return false + } + + if emitter.column == 0 { + if !yaml_emitter_write_indent(emitter) { + return false + } + } + + if emitter.canonical || emitter.column > emitter.best_width { + if !yaml_emitter_write_indent(emitter) { + return false + } + } + + if !emitter.canonical && yaml_emitter_check_simple_key(emitter) { + emitter.states = append(emitter.states, yaml_EMIT_FLOW_MAPPING_SIMPLE_VALUE_STATE) + return yaml_emitter_emit_node(emitter, event, false, false, true, true) + } + if !yaml_emitter_write_indicator(emitter, []byte{'?'}, true, false, false) { + return false + } + emitter.states = append(emitter.states, yaml_EMIT_FLOW_MAPPING_VALUE_STATE) + return yaml_emitter_emit_node(emitter, event, false, false, true, false) +} + +// Expect a flow value node. +func yaml_emitter_emit_flow_mapping_value(emitter *yaml_emitter_t, event *yaml_event_t, simple bool) bool { + if simple { + if !yaml_emitter_write_indicator(emitter, []byte{':'}, false, false, false) { + return false + } + } else { + if emitter.canonical || emitter.column > emitter.best_width { + if !yaml_emitter_write_indent(emitter) { + return false + } + } + if !yaml_emitter_write_indicator(emitter, []byte{':'}, true, false, false) { + return false + } + } + if len(emitter.line_comment)+len(emitter.foot_comment)+len(emitter.tail_comment) > 0 { + emitter.states = append(emitter.states, yaml_EMIT_FLOW_MAPPING_TRAIL_KEY_STATE) + } else { + emitter.states = append(emitter.states, yaml_EMIT_FLOW_MAPPING_KEY_STATE) + } + if !yaml_emitter_emit_node(emitter, event, false, false, true, false) { + return false + } + if len(emitter.line_comment)+len(emitter.foot_comment)+len(emitter.tail_comment) > 0 { + if !yaml_emitter_write_indicator(emitter, []byte{','}, false, false, false) { + return false + } + } + if !yaml_emitter_process_line_comment(emitter) { + return false + } + if !yaml_emitter_process_foot_comment(emitter) { + return false + } + return true +} + +// Expect a block item node. +func yaml_emitter_emit_block_sequence_item(emitter *yaml_emitter_t, event *yaml_event_t, first bool) bool { + if first { + if !yaml_emitter_increase_indent(emitter, false, false) { + return false + } + } + if event.typ == yaml_SEQUENCE_END_EVENT { + emitter.indent = emitter.indents[len(emitter.indents)-1] + emitter.indents = emitter.indents[:len(emitter.indents)-1] + emitter.state = emitter.states[len(emitter.states)-1] + emitter.states = emitter.states[:len(emitter.states)-1] + return true + } + if !yaml_emitter_process_head_comment(emitter) { + return false + } + if !yaml_emitter_write_indent(emitter) { + return false + } + if !yaml_emitter_write_indicator(emitter, []byte{'-'}, true, false, true) { + return false + } + emitter.states = append(emitter.states, yaml_EMIT_BLOCK_SEQUENCE_ITEM_STATE) + if !yaml_emitter_emit_node(emitter, event, false, true, false, false) { + return false + } + if !yaml_emitter_process_line_comment(emitter) { + return false + } + if !yaml_emitter_process_foot_comment(emitter) { + return false + } + return true +} + +// Expect a block key node. +func yaml_emitter_emit_block_mapping_key(emitter *yaml_emitter_t, event *yaml_event_t, first bool) bool { + if first { + if !yaml_emitter_increase_indent(emitter, false, false) { + return false + } + } + if !yaml_emitter_process_head_comment(emitter) { + return false + } + if event.typ == yaml_MAPPING_END_EVENT { + emitter.indent = emitter.indents[len(emitter.indents)-1] + emitter.indents = emitter.indents[:len(emitter.indents)-1] + emitter.state = emitter.states[len(emitter.states)-1] + emitter.states = emitter.states[:len(emitter.states)-1] + return true + } + if !yaml_emitter_write_indent(emitter) { + return false + } + if len(emitter.line_comment) > 0 { + // [Go] A line comment was provided for the key. That's unusual as the + // scanner associates line comments with the value. Either way, + // save the line comment and render it appropriately later. + emitter.key_line_comment = emitter.line_comment + emitter.line_comment = nil + } + if yaml_emitter_check_simple_key(emitter) { + emitter.states = append(emitter.states, yaml_EMIT_BLOCK_MAPPING_SIMPLE_VALUE_STATE) + return yaml_emitter_emit_node(emitter, event, false, false, true, true) + } + if !yaml_emitter_write_indicator(emitter, []byte{'?'}, true, false, true) { + return false + } + emitter.states = append(emitter.states, yaml_EMIT_BLOCK_MAPPING_VALUE_STATE) + return yaml_emitter_emit_node(emitter, event, false, false, true, false) +} + +// Expect a block value node. +func yaml_emitter_emit_block_mapping_value(emitter *yaml_emitter_t, event *yaml_event_t, simple bool) bool { + if simple { + if !yaml_emitter_write_indicator(emitter, []byte{':'}, false, false, false) { + return false + } + } else { + if !yaml_emitter_write_indent(emitter) { + return false + } + if !yaml_emitter_write_indicator(emitter, []byte{':'}, true, false, true) { + return false + } + } + if len(emitter.key_line_comment) > 0 { + // [Go] Line comments are generally associated with the value, but when there's + // no value on the same line as a mapping key they end up attached to the + // key itself. + if event.typ == yaml_SCALAR_EVENT { + if len(emitter.line_comment) == 0 { + // A scalar is coming and it has no line comments by itself yet, + // so just let it handle the line comment as usual. If it has a + // line comment, we can't have both so the one from the key is lost. + emitter.line_comment = emitter.key_line_comment + emitter.key_line_comment = nil + } + } else if event.sequence_style() != yaml_FLOW_SEQUENCE_STYLE && (event.typ == yaml_MAPPING_START_EVENT || event.typ == yaml_SEQUENCE_START_EVENT) { + // An indented block follows, so write the comment right now. + emitter.line_comment, emitter.key_line_comment = emitter.key_line_comment, emitter.line_comment + if !yaml_emitter_process_line_comment(emitter) { + return false + } + emitter.line_comment, emitter.key_line_comment = emitter.key_line_comment, emitter.line_comment + } + } + emitter.states = append(emitter.states, yaml_EMIT_BLOCK_MAPPING_KEY_STATE) + if !yaml_emitter_emit_node(emitter, event, false, false, true, false) { + return false + } + if !yaml_emitter_process_line_comment(emitter) { + return false + } + if !yaml_emitter_process_foot_comment(emitter) { + return false + } + return true +} + +func yaml_emitter_silent_nil_event(emitter *yaml_emitter_t, event *yaml_event_t) bool { + return event.typ == yaml_SCALAR_EVENT && event.implicit && !emitter.canonical && len(emitter.scalar_data.value) == 0 +} + +// Expect a node. +func yaml_emitter_emit_node(emitter *yaml_emitter_t, event *yaml_event_t, + root bool, sequence bool, mapping bool, simple_key bool) bool { + + emitter.root_context = root + emitter.sequence_context = sequence + emitter.mapping_context = mapping + emitter.simple_key_context = simple_key + + switch event.typ { + case yaml_ALIAS_EVENT: + return yaml_emitter_emit_alias(emitter, event) + case yaml_SCALAR_EVENT: + return yaml_emitter_emit_scalar(emitter, event) + case yaml_SEQUENCE_START_EVENT: + return yaml_emitter_emit_sequence_start(emitter, event) + case yaml_MAPPING_START_EVENT: + return yaml_emitter_emit_mapping_start(emitter, event) + default: + return yaml_emitter_set_emitter_error(emitter, + fmt.Sprintf("expected SCALAR, SEQUENCE-START, MAPPING-START, or ALIAS, but got %v", event.typ)) + } +} + +// Expect ALIAS. +func yaml_emitter_emit_alias(emitter *yaml_emitter_t, event *yaml_event_t) bool { + if !yaml_emitter_process_anchor(emitter) { + return false + } + emitter.state = emitter.states[len(emitter.states)-1] + emitter.states = emitter.states[:len(emitter.states)-1] + return true +} + +// Expect SCALAR. +func yaml_emitter_emit_scalar(emitter *yaml_emitter_t, event *yaml_event_t) bool { + if !yaml_emitter_select_scalar_style(emitter, event) { + return false + } + if !yaml_emitter_process_anchor(emitter) { + return false + } + if !yaml_emitter_process_tag(emitter) { + return false + } + if !yaml_emitter_increase_indent(emitter, true, false) { + return false + } + if !yaml_emitter_process_scalar(emitter) { + return false + } + emitter.indent = emitter.indents[len(emitter.indents)-1] + emitter.indents = emitter.indents[:len(emitter.indents)-1] + emitter.state = emitter.states[len(emitter.states)-1] + emitter.states = emitter.states[:len(emitter.states)-1] + return true +} + +// Expect SEQUENCE-START. +func yaml_emitter_emit_sequence_start(emitter *yaml_emitter_t, event *yaml_event_t) bool { + if !yaml_emitter_process_anchor(emitter) { + return false + } + if !yaml_emitter_process_tag(emitter) { + return false + } + if emitter.flow_level > 0 || emitter.canonical || event.sequence_style() == yaml_FLOW_SEQUENCE_STYLE || + yaml_emitter_check_empty_sequence(emitter) { + emitter.state = yaml_EMIT_FLOW_SEQUENCE_FIRST_ITEM_STATE + } else { + emitter.state = yaml_EMIT_BLOCK_SEQUENCE_FIRST_ITEM_STATE + } + return true +} + +// Expect MAPPING-START. +func yaml_emitter_emit_mapping_start(emitter *yaml_emitter_t, event *yaml_event_t) bool { + if !yaml_emitter_process_anchor(emitter) { + return false + } + if !yaml_emitter_process_tag(emitter) { + return false + } + if emitter.flow_level > 0 || emitter.canonical || event.mapping_style() == yaml_FLOW_MAPPING_STYLE || + yaml_emitter_check_empty_mapping(emitter) { + emitter.state = yaml_EMIT_FLOW_MAPPING_FIRST_KEY_STATE + } else { + emitter.state = yaml_EMIT_BLOCK_MAPPING_FIRST_KEY_STATE + } + return true +} + +// Check if the document content is an empty scalar. +func yaml_emitter_check_empty_document(emitter *yaml_emitter_t) bool { + return false // [Go] Huh? +} + +// Check if the next events represent an empty sequence. +func yaml_emitter_check_empty_sequence(emitter *yaml_emitter_t) bool { + if len(emitter.events)-emitter.events_head < 2 { + return false + } + return emitter.events[emitter.events_head].typ == yaml_SEQUENCE_START_EVENT && + emitter.events[emitter.events_head+1].typ == yaml_SEQUENCE_END_EVENT +} + +// Check if the next events represent an empty mapping. +func yaml_emitter_check_empty_mapping(emitter *yaml_emitter_t) bool { + if len(emitter.events)-emitter.events_head < 2 { + return false + } + return emitter.events[emitter.events_head].typ == yaml_MAPPING_START_EVENT && + emitter.events[emitter.events_head+1].typ == yaml_MAPPING_END_EVENT +} + +// Check if the next node can be expressed as a simple key. +func yaml_emitter_check_simple_key(emitter *yaml_emitter_t) bool { + length := 0 + switch emitter.events[emitter.events_head].typ { + case yaml_ALIAS_EVENT: + length += len(emitter.anchor_data.anchor) + case yaml_SCALAR_EVENT: + if emitter.scalar_data.multiline { + return false + } + length += len(emitter.anchor_data.anchor) + + len(emitter.tag_data.handle) + + len(emitter.tag_data.suffix) + + len(emitter.scalar_data.value) + case yaml_SEQUENCE_START_EVENT: + if !yaml_emitter_check_empty_sequence(emitter) { + return false + } + length += len(emitter.anchor_data.anchor) + + len(emitter.tag_data.handle) + + len(emitter.tag_data.suffix) + case yaml_MAPPING_START_EVENT: + if !yaml_emitter_check_empty_mapping(emitter) { + return false + } + length += len(emitter.anchor_data.anchor) + + len(emitter.tag_data.handle) + + len(emitter.tag_data.suffix) + default: + return false + } + return length <= 128 +} + +// Determine an acceptable scalar style. +func yaml_emitter_select_scalar_style(emitter *yaml_emitter_t, event *yaml_event_t) bool { + + no_tag := len(emitter.tag_data.handle) == 0 && len(emitter.tag_data.suffix) == 0 + if no_tag && !event.implicit && !event.quoted_implicit { + return yaml_emitter_set_emitter_error(emitter, "neither tag nor implicit flags are specified") + } + + style := event.scalar_style() + if style == yaml_ANY_SCALAR_STYLE { + style = yaml_PLAIN_SCALAR_STYLE + } + if emitter.canonical { + style = yaml_DOUBLE_QUOTED_SCALAR_STYLE + } + if emitter.simple_key_context && emitter.scalar_data.multiline { + style = yaml_DOUBLE_QUOTED_SCALAR_STYLE + } + + if style == yaml_PLAIN_SCALAR_STYLE { + if emitter.flow_level > 0 && !emitter.scalar_data.flow_plain_allowed || + emitter.flow_level == 0 && !emitter.scalar_data.block_plain_allowed { + style = yaml_SINGLE_QUOTED_SCALAR_STYLE + } + if len(emitter.scalar_data.value) == 0 && (emitter.flow_level > 0 || emitter.simple_key_context) { + style = yaml_SINGLE_QUOTED_SCALAR_STYLE + } + if no_tag && !event.implicit { + style = yaml_SINGLE_QUOTED_SCALAR_STYLE + } + } + if style == yaml_SINGLE_QUOTED_SCALAR_STYLE { + if !emitter.scalar_data.single_quoted_allowed { + style = yaml_DOUBLE_QUOTED_SCALAR_STYLE + } + } + if style == yaml_LITERAL_SCALAR_STYLE || style == yaml_FOLDED_SCALAR_STYLE { + if !emitter.scalar_data.block_allowed || emitter.flow_level > 0 || emitter.simple_key_context { + style = yaml_DOUBLE_QUOTED_SCALAR_STYLE + } + } + + if no_tag && !event.quoted_implicit && style != yaml_PLAIN_SCALAR_STYLE { + emitter.tag_data.handle = []byte{'!'} + } + emitter.scalar_data.style = style + return true +} + +// Write an anchor. +func yaml_emitter_process_anchor(emitter *yaml_emitter_t) bool { + if emitter.anchor_data.anchor == nil { + return true + } + c := []byte{'&'} + if emitter.anchor_data.alias { + c[0] = '*' + } + if !yaml_emitter_write_indicator(emitter, c, true, false, false) { + return false + } + return yaml_emitter_write_anchor(emitter, emitter.anchor_data.anchor) +} + +// Write a tag. +func yaml_emitter_process_tag(emitter *yaml_emitter_t) bool { + if len(emitter.tag_data.handle) == 0 && len(emitter.tag_data.suffix) == 0 { + return true + } + if len(emitter.tag_data.handle) > 0 { + if !yaml_emitter_write_tag_handle(emitter, emitter.tag_data.handle) { + return false + } + if len(emitter.tag_data.suffix) > 0 { + if !yaml_emitter_write_tag_content(emitter, emitter.tag_data.suffix, false) { + return false + } + } + } else { + // [Go] Allocate these slices elsewhere. + if !yaml_emitter_write_indicator(emitter, []byte("!<"), true, false, false) { + return false + } + if !yaml_emitter_write_tag_content(emitter, emitter.tag_data.suffix, false) { + return false + } + if !yaml_emitter_write_indicator(emitter, []byte{'>'}, false, false, false) { + return false + } + } + return true +} + +// Write a scalar. +func yaml_emitter_process_scalar(emitter *yaml_emitter_t) bool { + switch emitter.scalar_data.style { + case yaml_PLAIN_SCALAR_STYLE: + return yaml_emitter_write_plain_scalar(emitter, emitter.scalar_data.value, !emitter.simple_key_context) + + case yaml_SINGLE_QUOTED_SCALAR_STYLE: + return yaml_emitter_write_single_quoted_scalar(emitter, emitter.scalar_data.value, !emitter.simple_key_context) + + case yaml_DOUBLE_QUOTED_SCALAR_STYLE: + return yaml_emitter_write_double_quoted_scalar(emitter, emitter.scalar_data.value, !emitter.simple_key_context) + + case yaml_LITERAL_SCALAR_STYLE: + return yaml_emitter_write_literal_scalar(emitter, emitter.scalar_data.value) + + case yaml_FOLDED_SCALAR_STYLE: + return yaml_emitter_write_folded_scalar(emitter, emitter.scalar_data.value) + } + panic("unknown scalar style") +} + +// Write a head comment. +func yaml_emitter_process_head_comment(emitter *yaml_emitter_t) bool { + if len(emitter.tail_comment) > 0 { + if !yaml_emitter_write_indent(emitter) { + return false + } + if !yaml_emitter_write_comment(emitter, emitter.tail_comment) { + return false + } + emitter.tail_comment = emitter.tail_comment[:0] + emitter.foot_indent = emitter.indent + if emitter.foot_indent < 0 { + emitter.foot_indent = 0 + } + } + + if len(emitter.head_comment) == 0 { + return true + } + if !yaml_emitter_write_indent(emitter) { + return false + } + if !yaml_emitter_write_comment(emitter, emitter.head_comment) { + return false + } + emitter.head_comment = emitter.head_comment[:0] + return true +} + +// Write an line comment. +func yaml_emitter_process_line_comment(emitter *yaml_emitter_t) bool { + if len(emitter.line_comment) == 0 { + return true + } + if !emitter.whitespace { + if !put(emitter, ' ') { + return false + } + } + if !yaml_emitter_write_comment(emitter, emitter.line_comment) { + return false + } + emitter.line_comment = emitter.line_comment[:0] + return true +} + +// Write a foot comment. +func yaml_emitter_process_foot_comment(emitter *yaml_emitter_t) bool { + if len(emitter.foot_comment) == 0 { + return true + } + if !yaml_emitter_write_indent(emitter) { + return false + } + if !yaml_emitter_write_comment(emitter, emitter.foot_comment) { + return false + } + emitter.foot_comment = emitter.foot_comment[:0] + emitter.foot_indent = emitter.indent + if emitter.foot_indent < 0 { + emitter.foot_indent = 0 + } + return true +} + +// Check if a %YAML directive is valid. +func yaml_emitter_analyze_version_directive(emitter *yaml_emitter_t, version_directive *yaml_version_directive_t) bool { + if version_directive.major != 1 || version_directive.minor != 1 { + return yaml_emitter_set_emitter_error(emitter, "incompatible %YAML directive") + } + return true +} + +// Check if a %TAG directive is valid. +func yaml_emitter_analyze_tag_directive(emitter *yaml_emitter_t, tag_directive *yaml_tag_directive_t) bool { + handle := tag_directive.handle + prefix := tag_directive.prefix + if len(handle) == 0 { + return yaml_emitter_set_emitter_error(emitter, "tag handle must not be empty") + } + if handle[0] != '!' { + return yaml_emitter_set_emitter_error(emitter, "tag handle must start with '!'") + } + if handle[len(handle)-1] != '!' { + return yaml_emitter_set_emitter_error(emitter, "tag handle must end with '!'") + } + for i := 1; i < len(handle)-1; i += width(handle[i]) { + if !is_alpha(handle, i) { + return yaml_emitter_set_emitter_error(emitter, "tag handle must contain alphanumerical characters only") + } + } + if len(prefix) == 0 { + return yaml_emitter_set_emitter_error(emitter, "tag prefix must not be empty") + } + return true +} + +// Check if an anchor is valid. +func yaml_emitter_analyze_anchor(emitter *yaml_emitter_t, anchor []byte, alias bool) bool { + if len(anchor) == 0 { + problem := "anchor value must not be empty" + if alias { + problem = "alias value must not be empty" + } + return yaml_emitter_set_emitter_error(emitter, problem) + } + for i := 0; i < len(anchor); i += width(anchor[i]) { + if !is_alpha(anchor, i) { + problem := "anchor value must contain alphanumerical characters only" + if alias { + problem = "alias value must contain alphanumerical characters only" + } + return yaml_emitter_set_emitter_error(emitter, problem) + } + } + emitter.anchor_data.anchor = anchor + emitter.anchor_data.alias = alias + return true +} + +// Check if a tag is valid. +func yaml_emitter_analyze_tag(emitter *yaml_emitter_t, tag []byte) bool { + if len(tag) == 0 { + return yaml_emitter_set_emitter_error(emitter, "tag value must not be empty") + } + for i := 0; i < len(emitter.tag_directives); i++ { + tag_directive := &emitter.tag_directives[i] + if bytes.HasPrefix(tag, tag_directive.prefix) { + emitter.tag_data.handle = tag_directive.handle + emitter.tag_data.suffix = tag[len(tag_directive.prefix):] + return true + } + } + emitter.tag_data.suffix = tag + return true +} + +// Check if a scalar is valid. +func yaml_emitter_analyze_scalar(emitter *yaml_emitter_t, value []byte) bool { + var ( + block_indicators = false + flow_indicators = false + line_breaks = false + special_characters = false + tab_characters = false + + leading_space = false + leading_break = false + trailing_space = false + trailing_break = false + break_space = false + space_break = false + + preceded_by_whitespace = false + followed_by_whitespace = false + previous_space = false + previous_break = false + ) + + emitter.scalar_data.value = value + + if len(value) == 0 { + emitter.scalar_data.multiline = false + emitter.scalar_data.flow_plain_allowed = false + emitter.scalar_data.block_plain_allowed = true + emitter.scalar_data.single_quoted_allowed = true + emitter.scalar_data.block_allowed = false + return true + } + + if len(value) >= 3 && ((value[0] == '-' && value[1] == '-' && value[2] == '-') || (value[0] == '.' && value[1] == '.' && value[2] == '.')) { + block_indicators = true + flow_indicators = true + } + + preceded_by_whitespace = true + for i, w := 0, 0; i < len(value); i += w { + w = width(value[i]) + followed_by_whitespace = i+w >= len(value) || is_blank(value, i+w) + + if i == 0 { + switch value[i] { + case '#', ',', '[', ']', '{', '}', '&', '*', '!', '|', '>', '\'', '"', '%', '@', '`': + flow_indicators = true + block_indicators = true + case '?', ':': + flow_indicators = true + if followed_by_whitespace { + block_indicators = true + } + case '-': + if followed_by_whitespace { + flow_indicators = true + block_indicators = true + } + } + } else { + switch value[i] { + case ',', '?', '[', ']', '{', '}': + flow_indicators = true + case ':': + flow_indicators = true + if followed_by_whitespace { + block_indicators = true + } + case '#': + if preceded_by_whitespace { + flow_indicators = true + block_indicators = true + } + } + } + + if value[i] == '\t' { + tab_characters = true + } else if !is_printable(value, i) || !is_ascii(value, i) && !emitter.unicode { + special_characters = true + } + if is_space(value, i) { + if i == 0 { + leading_space = true + } + if i+width(value[i]) == len(value) { + trailing_space = true + } + if previous_break { + break_space = true + } + previous_space = true + previous_break = false + } else if is_break(value, i) { + line_breaks = true + if i == 0 { + leading_break = true + } + if i+width(value[i]) == len(value) { + trailing_break = true + } + if previous_space { + space_break = true + } + previous_space = false + previous_break = true + } else { + previous_space = false + previous_break = false + } + + // [Go]: Why 'z'? Couldn't be the end of the string as that's the loop condition. + preceded_by_whitespace = is_blankz(value, i) + } + + emitter.scalar_data.multiline = line_breaks + emitter.scalar_data.flow_plain_allowed = true + emitter.scalar_data.block_plain_allowed = true + emitter.scalar_data.single_quoted_allowed = true + emitter.scalar_data.block_allowed = true + + if leading_space || leading_break || trailing_space || trailing_break { + emitter.scalar_data.flow_plain_allowed = false + emitter.scalar_data.block_plain_allowed = false + } + if trailing_space { + emitter.scalar_data.block_allowed = false + } + if break_space { + emitter.scalar_data.flow_plain_allowed = false + emitter.scalar_data.block_plain_allowed = false + emitter.scalar_data.single_quoted_allowed = false + } + if space_break || tab_characters || special_characters { + emitter.scalar_data.flow_plain_allowed = false + emitter.scalar_data.block_plain_allowed = false + emitter.scalar_data.single_quoted_allowed = false + } + if space_break || special_characters { + emitter.scalar_data.block_allowed = false + } + if line_breaks { + emitter.scalar_data.flow_plain_allowed = false + emitter.scalar_data.block_plain_allowed = false + } + if flow_indicators { + emitter.scalar_data.flow_plain_allowed = false + } + if block_indicators { + emitter.scalar_data.block_plain_allowed = false + } + return true +} + +// Check if the event data is valid. +func yaml_emitter_analyze_event(emitter *yaml_emitter_t, event *yaml_event_t) bool { + + emitter.anchor_data.anchor = nil + emitter.tag_data.handle = nil + emitter.tag_data.suffix = nil + emitter.scalar_data.value = nil + + if len(event.head_comment) > 0 { + emitter.head_comment = event.head_comment + } + if len(event.line_comment) > 0 { + emitter.line_comment = event.line_comment + } + if len(event.foot_comment) > 0 { + emitter.foot_comment = event.foot_comment + } + if len(event.tail_comment) > 0 { + emitter.tail_comment = event.tail_comment + } + + switch event.typ { + case yaml_ALIAS_EVENT: + if !yaml_emitter_analyze_anchor(emitter, event.anchor, true) { + return false + } + + case yaml_SCALAR_EVENT: + if len(event.anchor) > 0 { + if !yaml_emitter_analyze_anchor(emitter, event.anchor, false) { + return false + } + } + if len(event.tag) > 0 && (emitter.canonical || (!event.implicit && !event.quoted_implicit)) { + if !yaml_emitter_analyze_tag(emitter, event.tag) { + return false + } + } + if !yaml_emitter_analyze_scalar(emitter, event.value) { + return false + } + + case yaml_SEQUENCE_START_EVENT: + if len(event.anchor) > 0 { + if !yaml_emitter_analyze_anchor(emitter, event.anchor, false) { + return false + } + } + if len(event.tag) > 0 && (emitter.canonical || !event.implicit) { + if !yaml_emitter_analyze_tag(emitter, event.tag) { + return false + } + } + + case yaml_MAPPING_START_EVENT: + if len(event.anchor) > 0 { + if !yaml_emitter_analyze_anchor(emitter, event.anchor, false) { + return false + } + } + if len(event.tag) > 0 && (emitter.canonical || !event.implicit) { + if !yaml_emitter_analyze_tag(emitter, event.tag) { + return false + } + } + } + return true +} + +// Write the BOM character. +func yaml_emitter_write_bom(emitter *yaml_emitter_t) bool { + if !flush(emitter) { + return false + } + pos := emitter.buffer_pos + emitter.buffer[pos+0] = '\xEF' + emitter.buffer[pos+1] = '\xBB' + emitter.buffer[pos+2] = '\xBF' + emitter.buffer_pos += 3 + return true +} + +func yaml_emitter_write_indent(emitter *yaml_emitter_t) bool { + indent := emitter.indent + if indent < 0 { + indent = 0 + } + if !emitter.indention || emitter.column > indent || (emitter.column == indent && !emitter.whitespace) { + if !put_break(emitter) { + return false + } + } + if emitter.foot_indent == indent { + if !put_break(emitter) { + return false + } + } + for emitter.column < indent { + if !put(emitter, ' ') { + return false + } + } + emitter.whitespace = true + //emitter.indention = true + emitter.space_above = false + emitter.foot_indent = -1 + return true +} + +func yaml_emitter_write_indicator(emitter *yaml_emitter_t, indicator []byte, need_whitespace, is_whitespace, is_indention bool) bool { + if need_whitespace && !emitter.whitespace { + if !put(emitter, ' ') { + return false + } + } + if !write_all(emitter, indicator) { + return false + } + emitter.whitespace = is_whitespace + emitter.indention = (emitter.indention && is_indention) + emitter.open_ended = false + return true +} + +func yaml_emitter_write_anchor(emitter *yaml_emitter_t, value []byte) bool { + if !write_all(emitter, value) { + return false + } + emitter.whitespace = false + emitter.indention = false + return true +} + +func yaml_emitter_write_tag_handle(emitter *yaml_emitter_t, value []byte) bool { + if !emitter.whitespace { + if !put(emitter, ' ') { + return false + } + } + if !write_all(emitter, value) { + return false + } + emitter.whitespace = false + emitter.indention = false + return true +} + +func yaml_emitter_write_tag_content(emitter *yaml_emitter_t, value []byte, need_whitespace bool) bool { + if need_whitespace && !emitter.whitespace { + if !put(emitter, ' ') { + return false + } + } + for i := 0; i < len(value); { + var must_write bool + switch value[i] { + case ';', '/', '?', ':', '@', '&', '=', '+', '$', ',', '_', '.', '~', '*', '\'', '(', ')', '[', ']': + must_write = true + default: + must_write = is_alpha(value, i) + } + if must_write { + if !write(emitter, value, &i) { + return false + } + } else { + w := width(value[i]) + for k := 0; k < w; k++ { + octet := value[i] + i++ + if !put(emitter, '%') { + return false + } + + c := octet >> 4 + if c < 10 { + c += '0' + } else { + c += 'A' - 10 + } + if !put(emitter, c) { + return false + } + + c = octet & 0x0f + if c < 10 { + c += '0' + } else { + c += 'A' - 10 + } + if !put(emitter, c) { + return false + } + } + } + } + emitter.whitespace = false + emitter.indention = false + return true +} + +func yaml_emitter_write_plain_scalar(emitter *yaml_emitter_t, value []byte, allow_breaks bool) bool { + if len(value) > 0 && !emitter.whitespace { + if !put(emitter, ' ') { + return false + } + } + + spaces := false + breaks := false + for i := 0; i < len(value); { + if is_space(value, i) { + if allow_breaks && !spaces && emitter.column > emitter.best_width && !is_space(value, i+1) { + if !yaml_emitter_write_indent(emitter) { + return false + } + i += width(value[i]) + } else { + if !write(emitter, value, &i) { + return false + } + } + spaces = true + } else if is_break(value, i) { + if !breaks && value[i] == '\n' { + if !put_break(emitter) { + return false + } + } + if !write_break(emitter, value, &i) { + return false + } + //emitter.indention = true + breaks = true + } else { + if breaks { + if !yaml_emitter_write_indent(emitter) { + return false + } + } + if !write(emitter, value, &i) { + return false + } + emitter.indention = false + spaces = false + breaks = false + } + } + + if len(value) > 0 { + emitter.whitespace = false + } + emitter.indention = false + if emitter.root_context { + emitter.open_ended = true + } + + return true +} + +func yaml_emitter_write_single_quoted_scalar(emitter *yaml_emitter_t, value []byte, allow_breaks bool) bool { + + if !yaml_emitter_write_indicator(emitter, []byte{'\''}, true, false, false) { + return false + } + + spaces := false + breaks := false + for i := 0; i < len(value); { + if is_space(value, i) { + if allow_breaks && !spaces && emitter.column > emitter.best_width && i > 0 && i < len(value)-1 && !is_space(value, i+1) { + if !yaml_emitter_write_indent(emitter) { + return false + } + i += width(value[i]) + } else { + if !write(emitter, value, &i) { + return false + } + } + spaces = true + } else if is_break(value, i) { + if !breaks && value[i] == '\n' { + if !put_break(emitter) { + return false + } + } + if !write_break(emitter, value, &i) { + return false + } + //emitter.indention = true + breaks = true + } else { + if breaks { + if !yaml_emitter_write_indent(emitter) { + return false + } + } + if value[i] == '\'' { + if !put(emitter, '\'') { + return false + } + } + if !write(emitter, value, &i) { + return false + } + emitter.indention = false + spaces = false + breaks = false + } + } + if !yaml_emitter_write_indicator(emitter, []byte{'\''}, false, false, false) { + return false + } + emitter.whitespace = false + emitter.indention = false + return true +} + +func yaml_emitter_write_double_quoted_scalar(emitter *yaml_emitter_t, value []byte, allow_breaks bool) bool { + spaces := false + if !yaml_emitter_write_indicator(emitter, []byte{'"'}, true, false, false) { + return false + } + + for i := 0; i < len(value); { + if !is_printable(value, i) || (!emitter.unicode && !is_ascii(value, i)) || + is_bom(value, i) || is_break(value, i) || + value[i] == '"' || value[i] == '\\' { + + octet := value[i] + + var w int + var v rune + switch { + case octet&0x80 == 0x00: + w, v = 1, rune(octet&0x7F) + case octet&0xE0 == 0xC0: + w, v = 2, rune(octet&0x1F) + case octet&0xF0 == 0xE0: + w, v = 3, rune(octet&0x0F) + case octet&0xF8 == 0xF0: + w, v = 4, rune(octet&0x07) + } + for k := 1; k < w; k++ { + octet = value[i+k] + v = (v << 6) + (rune(octet) & 0x3F) + } + i += w + + if !put(emitter, '\\') { + return false + } + + var ok bool + switch v { + case 0x00: + ok = put(emitter, '0') + case 0x07: + ok = put(emitter, 'a') + case 0x08: + ok = put(emitter, 'b') + case 0x09: + ok = put(emitter, 't') + case 0x0A: + ok = put(emitter, 'n') + case 0x0b: + ok = put(emitter, 'v') + case 0x0c: + ok = put(emitter, 'f') + case 0x0d: + ok = put(emitter, 'r') + case 0x1b: + ok = put(emitter, 'e') + case 0x22: + ok = put(emitter, '"') + case 0x5c: + ok = put(emitter, '\\') + case 0x85: + ok = put(emitter, 'N') + case 0xA0: + ok = put(emitter, '_') + case 0x2028: + ok = put(emitter, 'L') + case 0x2029: + ok = put(emitter, 'P') + default: + if v <= 0xFF { + ok = put(emitter, 'x') + w = 2 + } else if v <= 0xFFFF { + ok = put(emitter, 'u') + w = 4 + } else { + ok = put(emitter, 'U') + w = 8 + } + for k := (w - 1) * 4; ok && k >= 0; k -= 4 { + digit := byte((v >> uint(k)) & 0x0F) + if digit < 10 { + ok = put(emitter, digit+'0') + } else { + ok = put(emitter, digit+'A'-10) + } + } + } + if !ok { + return false + } + spaces = false + } else if is_space(value, i) { + if allow_breaks && !spaces && emitter.column > emitter.best_width && i > 0 && i < len(value)-1 { + if !yaml_emitter_write_indent(emitter) { + return false + } + if is_space(value, i+1) { + if !put(emitter, '\\') { + return false + } + } + i += width(value[i]) + } else if !write(emitter, value, &i) { + return false + } + spaces = true + } else { + if !write(emitter, value, &i) { + return false + } + spaces = false + } + } + if !yaml_emitter_write_indicator(emitter, []byte{'"'}, false, false, false) { + return false + } + emitter.whitespace = false + emitter.indention = false + return true +} + +func yaml_emitter_write_block_scalar_hints(emitter *yaml_emitter_t, value []byte) bool { + if is_space(value, 0) || is_break(value, 0) { + indent_hint := []byte{'0' + byte(emitter.best_indent)} + if !yaml_emitter_write_indicator(emitter, indent_hint, false, false, false) { + return false + } + } + + emitter.open_ended = false + + var chomp_hint [1]byte + if len(value) == 0 { + chomp_hint[0] = '-' + } else { + i := len(value) - 1 + for value[i]&0xC0 == 0x80 { + i-- + } + if !is_break(value, i) { + chomp_hint[0] = '-' + } else if i == 0 { + chomp_hint[0] = '+' + emitter.open_ended = true + } else { + i-- + for value[i]&0xC0 == 0x80 { + i-- + } + if is_break(value, i) { + chomp_hint[0] = '+' + emitter.open_ended = true + } + } + } + if chomp_hint[0] != 0 { + if !yaml_emitter_write_indicator(emitter, chomp_hint[:], false, false, false) { + return false + } + } + return true +} + +func yaml_emitter_write_literal_scalar(emitter *yaml_emitter_t, value []byte) bool { + if !yaml_emitter_write_indicator(emitter, []byte{'|'}, true, false, false) { + return false + } + if !yaml_emitter_write_block_scalar_hints(emitter, value) { + return false + } + if !yaml_emitter_process_line_comment(emitter) { + return false + } + //emitter.indention = true + emitter.whitespace = true + breaks := true + for i := 0; i < len(value); { + if is_break(value, i) { + if !write_break(emitter, value, &i) { + return false + } + //emitter.indention = true + breaks = true + } else { + if breaks { + if !yaml_emitter_write_indent(emitter) { + return false + } + } + if !write(emitter, value, &i) { + return false + } + emitter.indention = false + breaks = false + } + } + + return true +} + +func yaml_emitter_write_folded_scalar(emitter *yaml_emitter_t, value []byte) bool { + if !yaml_emitter_write_indicator(emitter, []byte{'>'}, true, false, false) { + return false + } + if !yaml_emitter_write_block_scalar_hints(emitter, value) { + return false + } + if !yaml_emitter_process_line_comment(emitter) { + return false + } + + //emitter.indention = true + emitter.whitespace = true + + breaks := true + leading_spaces := true + for i := 0; i < len(value); { + if is_break(value, i) { + if !breaks && !leading_spaces && value[i] == '\n' { + k := 0 + for is_break(value, k) { + k += width(value[k]) + } + if !is_blankz(value, k) { + if !put_break(emitter) { + return false + } + } + } + if !write_break(emitter, value, &i) { + return false + } + //emitter.indention = true + breaks = true + } else { + if breaks { + if !yaml_emitter_write_indent(emitter) { + return false + } + leading_spaces = is_blank(value, i) + } + if !breaks && is_space(value, i) && !is_space(value, i+1) && emitter.column > emitter.best_width { + if !yaml_emitter_write_indent(emitter) { + return false + } + i += width(value[i]) + } else { + if !write(emitter, value, &i) { + return false + } + } + emitter.indention = false + breaks = false + } + } + return true +} + +func yaml_emitter_write_comment(emitter *yaml_emitter_t, comment []byte) bool { + breaks := false + pound := false + for i := 0; i < len(comment); { + if is_break(comment, i) { + if !write_break(emitter, comment, &i) { + return false + } + //emitter.indention = true + breaks = true + pound = false + } else { + if breaks && !yaml_emitter_write_indent(emitter) { + return false + } + if !pound { + if comment[i] != '#' && (!put(emitter, '#') || !put(emitter, ' ')) { + return false + } + pound = true + } + if !write(emitter, comment, &i) { + return false + } + emitter.indention = false + breaks = false + } + } + if !breaks && !put_break(emitter) { + return false + } + + emitter.whitespace = true + //emitter.indention = true + return true +} diff --git a/index/server/vendor/github.com/oasdiff/yaml3/encode.go b/index/server/vendor/github.com/oasdiff/yaml3/encode.go new file mode 100644 index 000000000..de9e72a3e --- /dev/null +++ b/index/server/vendor/github.com/oasdiff/yaml3/encode.go @@ -0,0 +1,577 @@ +// +// Copyright (c) 2011-2019 Canonical Ltd +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package yaml + +import ( + "encoding" + "fmt" + "io" + "reflect" + "regexp" + "sort" + "strconv" + "strings" + "time" + "unicode/utf8" +) + +type encoder struct { + emitter yaml_emitter_t + event yaml_event_t + out []byte + flow bool + indent int + doneInit bool +} + +func newEncoder() *encoder { + e := &encoder{} + yaml_emitter_initialize(&e.emitter) + yaml_emitter_set_output_string(&e.emitter, &e.out) + yaml_emitter_set_unicode(&e.emitter, true) + return e +} + +func newEncoderWithWriter(w io.Writer) *encoder { + e := &encoder{} + yaml_emitter_initialize(&e.emitter) + yaml_emitter_set_output_writer(&e.emitter, w) + yaml_emitter_set_unicode(&e.emitter, true) + return e +} + +func (e *encoder) init() { + if e.doneInit { + return + } + if e.indent == 0 { + e.indent = 4 + } + e.emitter.best_indent = e.indent + yaml_stream_start_event_initialize(&e.event, yaml_UTF8_ENCODING) + e.emit() + e.doneInit = true +} + +func (e *encoder) finish() { + e.emitter.open_ended = false + yaml_stream_end_event_initialize(&e.event) + e.emit() +} + +func (e *encoder) destroy() { + yaml_emitter_delete(&e.emitter) +} + +func (e *encoder) emit() { + // This will internally delete the e.event value. + e.must(yaml_emitter_emit(&e.emitter, &e.event)) +} + +func (e *encoder) must(ok bool) { + if !ok { + msg := e.emitter.problem + if msg == "" { + msg = "unknown problem generating YAML content" + } + failf("%s", msg) + } +} + +func (e *encoder) marshalDoc(tag string, in reflect.Value) { + e.init() + var node *Node + if in.IsValid() { + node, _ = in.Interface().(*Node) + } + if node != nil && node.Kind == DocumentNode { + e.nodev(in) + } else { + yaml_document_start_event_initialize(&e.event, nil, nil, true) + e.emit() + e.marshal(tag, in) + yaml_document_end_event_initialize(&e.event, true) + e.emit() + } +} + +func (e *encoder) marshal(tag string, in reflect.Value) { + tag = shortTag(tag) + if !in.IsValid() || in.Kind() == reflect.Ptr && in.IsNil() { + e.nilv() + return + } + iface := in.Interface() + switch value := iface.(type) { + case *Node: + e.nodev(in) + return + case Node: + if !in.CanAddr() { + var n = reflect.New(in.Type()).Elem() + n.Set(in) + in = n + } + e.nodev(in.Addr()) + return + case time.Time: + e.timev(tag, in) + return + case *time.Time: + e.timev(tag, in.Elem()) + return + case time.Duration: + e.stringv(tag, reflect.ValueOf(value.String())) + return + case Marshaler: + v, err := value.MarshalYAML() + if err != nil { + fail(err) + } + if v == nil { + e.nilv() + return + } + e.marshal(tag, reflect.ValueOf(v)) + return + case encoding.TextMarshaler: + text, err := value.MarshalText() + if err != nil { + fail(err) + } + in = reflect.ValueOf(string(text)) + case nil: + e.nilv() + return + } + switch in.Kind() { + case reflect.Interface: + e.marshal(tag, in.Elem()) + case reflect.Map: + e.mapv(tag, in) + case reflect.Ptr: + e.marshal(tag, in.Elem()) + case reflect.Struct: + e.structv(tag, in) + case reflect.Slice, reflect.Array: + e.slicev(tag, in) + case reflect.String: + e.stringv(tag, in) + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + e.intv(tag, in) + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: + e.uintv(tag, in) + case reflect.Float32, reflect.Float64: + e.floatv(tag, in) + case reflect.Bool: + e.boolv(tag, in) + default: + panic("cannot marshal type: " + in.Type().String()) + } +} + +func (e *encoder) mapv(tag string, in reflect.Value) { + e.mappingv(tag, func() { + keys := keyList(in.MapKeys()) + sort.Sort(keys) + for _, k := range keys { + e.marshal("", k) + e.marshal("", in.MapIndex(k)) + } + }) +} + +func (e *encoder) fieldByIndex(v reflect.Value, index []int) (field reflect.Value) { + for _, num := range index { + for { + if v.Kind() == reflect.Ptr { + if v.IsNil() { + return reflect.Value{} + } + v = v.Elem() + continue + } + break + } + v = v.Field(num) + } + return v +} + +func (e *encoder) structv(tag string, in reflect.Value) { + sinfo, err := getStructInfo(in.Type()) + if err != nil { + panic(err) + } + e.mappingv(tag, func() { + for _, info := range sinfo.FieldsList { + var value reflect.Value + if info.Inline == nil { + value = in.Field(info.Num) + } else { + value = e.fieldByIndex(in, info.Inline) + if !value.IsValid() { + continue + } + } + if info.OmitEmpty && isZero(value) { + continue + } + e.marshal("", reflect.ValueOf(info.Key)) + e.flow = info.Flow + e.marshal("", value) + } + if sinfo.InlineMap >= 0 { + m := in.Field(sinfo.InlineMap) + if m.Len() > 0 { + e.flow = false + keys := keyList(m.MapKeys()) + sort.Sort(keys) + for _, k := range keys { + if _, found := sinfo.FieldsMap[k.String()]; found { + panic(fmt.Sprintf("cannot have key %q in inlined map: conflicts with struct field", k.String())) + } + e.marshal("", k) + e.flow = false + e.marshal("", m.MapIndex(k)) + } + } + } + }) +} + +func (e *encoder) mappingv(tag string, f func()) { + implicit := tag == "" + style := yaml_BLOCK_MAPPING_STYLE + if e.flow { + e.flow = false + style = yaml_FLOW_MAPPING_STYLE + } + yaml_mapping_start_event_initialize(&e.event, nil, []byte(tag), implicit, style) + e.emit() + f() + yaml_mapping_end_event_initialize(&e.event) + e.emit() +} + +func (e *encoder) slicev(tag string, in reflect.Value) { + implicit := tag == "" + style := yaml_BLOCK_SEQUENCE_STYLE + if e.flow { + e.flow = false + style = yaml_FLOW_SEQUENCE_STYLE + } + e.must(yaml_sequence_start_event_initialize(&e.event, nil, []byte(tag), implicit, style)) + e.emit() + n := in.Len() + for i := 0; i < n; i++ { + e.marshal("", in.Index(i)) + } + e.must(yaml_sequence_end_event_initialize(&e.event)) + e.emit() +} + +// isBase60 returns whether s is in base 60 notation as defined in YAML 1.1. +// +// The base 60 float notation in YAML 1.1 is a terrible idea and is unsupported +// in YAML 1.2 and by this package, but these should be marshalled quoted for +// the time being for compatibility with other parsers. +func isBase60Float(s string) (result bool) { + // Fast path. + if s == "" { + return false + } + c := s[0] + if !(c == '+' || c == '-' || c >= '0' && c <= '9') || strings.IndexByte(s, ':') < 0 { + return false + } + // Do the full match. + return base60float.MatchString(s) +} + +// From http://yaml.org/type/float.html, except the regular expression there +// is bogus. In practice parsers do not enforce the "\.[0-9_]*" suffix. +var base60float = regexp.MustCompile(`^[-+]?[0-9][0-9_]*(?::[0-5]?[0-9])+(?:\.[0-9_]*)?$`) + +// isOldBool returns whether s is bool notation as defined in YAML 1.1. +// +// We continue to force strings that YAML 1.1 would interpret as booleans to be +// rendered as quotes strings so that the marshalled output valid for YAML 1.1 +// parsing. +func isOldBool(s string) (result bool) { + switch s { + case "y", "Y", "yes", "Yes", "YES", "on", "On", "ON", + "n", "N", "no", "No", "NO", "off", "Off", "OFF": + return true + default: + return false + } +} + +func (e *encoder) stringv(tag string, in reflect.Value) { + var style yaml_scalar_style_t + s := in.String() + canUsePlain := true + switch { + case !utf8.ValidString(s): + if tag == binaryTag { + failf("explicitly tagged !!binary data must be base64-encoded") + } + if tag != "" { + failf("cannot marshal invalid UTF-8 data as %s", shortTag(tag)) + } + // It can't be encoded directly as YAML so use a binary tag + // and encode it as base64. + tag = binaryTag + s = encodeBase64(s) + case tag == "": + // Check to see if it would resolve to a specific + // tag when encoded unquoted. If it doesn't, + // there's no need to quote it. + rtag, _ := resolve("", s) + canUsePlain = rtag == strTag && !(isBase60Float(s) || isOldBool(s)) + } + // Note: it's possible for user code to emit invalid YAML + // if they explicitly specify a tag and a string containing + // text that's incompatible with that tag. + switch { + case strings.Contains(s, "\n"): + if e.flow { + style = yaml_DOUBLE_QUOTED_SCALAR_STYLE + } else { + style = yaml_LITERAL_SCALAR_STYLE + } + case canUsePlain: + style = yaml_PLAIN_SCALAR_STYLE + default: + style = yaml_DOUBLE_QUOTED_SCALAR_STYLE + } + e.emitScalar(s, "", tag, style, nil, nil, nil, nil) +} + +func (e *encoder) boolv(tag string, in reflect.Value) { + var s string + if in.Bool() { + s = "true" + } else { + s = "false" + } + e.emitScalar(s, "", tag, yaml_PLAIN_SCALAR_STYLE, nil, nil, nil, nil) +} + +func (e *encoder) intv(tag string, in reflect.Value) { + s := strconv.FormatInt(in.Int(), 10) + e.emitScalar(s, "", tag, yaml_PLAIN_SCALAR_STYLE, nil, nil, nil, nil) +} + +func (e *encoder) uintv(tag string, in reflect.Value) { + s := strconv.FormatUint(in.Uint(), 10) + e.emitScalar(s, "", tag, yaml_PLAIN_SCALAR_STYLE, nil, nil, nil, nil) +} + +func (e *encoder) timev(tag string, in reflect.Value) { + t := in.Interface().(time.Time) + s := t.Format(time.RFC3339Nano) + e.emitScalar(s, "", tag, yaml_PLAIN_SCALAR_STYLE, nil, nil, nil, nil) +} + +func (e *encoder) floatv(tag string, in reflect.Value) { + // Issue #352: When formatting, use the precision of the underlying value + precision := 64 + if in.Kind() == reflect.Float32 { + precision = 32 + } + + s := strconv.FormatFloat(in.Float(), 'g', -1, precision) + switch s { + case "+Inf": + s = ".inf" + case "-Inf": + s = "-.inf" + case "NaN": + s = ".nan" + } + e.emitScalar(s, "", tag, yaml_PLAIN_SCALAR_STYLE, nil, nil, nil, nil) +} + +func (e *encoder) nilv() { + e.emitScalar("null", "", "", yaml_PLAIN_SCALAR_STYLE, nil, nil, nil, nil) +} + +func (e *encoder) emitScalar(value, anchor, tag string, style yaml_scalar_style_t, head, line, foot, tail []byte) { + // TODO Kill this function. Replace all initialize calls by their underlining Go literals. + implicit := tag == "" + if !implicit { + tag = longTag(tag) + } + e.must(yaml_scalar_event_initialize(&e.event, []byte(anchor), []byte(tag), []byte(value), implicit, implicit, style)) + e.event.head_comment = head + e.event.line_comment = line + e.event.foot_comment = foot + e.event.tail_comment = tail + e.emit() +} + +func (e *encoder) nodev(in reflect.Value) { + e.node(in.Interface().(*Node), "") +} + +func (e *encoder) node(node *Node, tail string) { + // Zero nodes behave as nil. + if node.Kind == 0 && node.IsZero() { + e.nilv() + return + } + + // If the tag was not explicitly requested, and dropping it won't change the + // implicit tag of the value, don't include it in the presentation. + var tag = node.Tag + var stag = shortTag(tag) + var forceQuoting bool + if tag != "" && node.Style&TaggedStyle == 0 { + if node.Kind == ScalarNode { + if stag == strTag && node.Style&(SingleQuotedStyle|DoubleQuotedStyle|LiteralStyle|FoldedStyle) != 0 { + tag = "" + } else { + rtag, _ := resolve("", node.Value) + if rtag == stag { + tag = "" + } else if stag == strTag { + tag = "" + forceQuoting = true + } + } + } else { + var rtag string + switch node.Kind { + case MappingNode: + rtag = mapTag + case SequenceNode: + rtag = seqTag + } + if rtag == stag { + tag = "" + } + } + } + + switch node.Kind { + case DocumentNode: + yaml_document_start_event_initialize(&e.event, nil, nil, true) + e.event.head_comment = []byte(node.HeadComment) + e.emit() + for _, node := range node.Content { + e.node(node, "") + } + yaml_document_end_event_initialize(&e.event, true) + e.event.foot_comment = []byte(node.FootComment) + e.emit() + + case SequenceNode: + style := yaml_BLOCK_SEQUENCE_STYLE + if node.Style&FlowStyle != 0 { + style = yaml_FLOW_SEQUENCE_STYLE + } + e.must(yaml_sequence_start_event_initialize(&e.event, []byte(node.Anchor), []byte(longTag(tag)), tag == "", style)) + e.event.head_comment = []byte(node.HeadComment) + e.emit() + for _, node := range node.Content { + e.node(node, "") + } + e.must(yaml_sequence_end_event_initialize(&e.event)) + e.event.line_comment = []byte(node.LineComment) + e.event.foot_comment = []byte(node.FootComment) + e.emit() + + case MappingNode: + style := yaml_BLOCK_MAPPING_STYLE + if node.Style&FlowStyle != 0 { + style = yaml_FLOW_MAPPING_STYLE + } + yaml_mapping_start_event_initialize(&e.event, []byte(node.Anchor), []byte(longTag(tag)), tag == "", style) + e.event.tail_comment = []byte(tail) + e.event.head_comment = []byte(node.HeadComment) + e.emit() + + // The tail logic below moves the foot comment of prior keys to the following key, + // since the value for each key may be a nested structure and the foot needs to be + // processed only the entirety of the value is streamed. The last tail is processed + // with the mapping end event. + var tail string + for i := 0; i+1 < len(node.Content); i += 2 { + k := node.Content[i] + foot := k.FootComment + if foot != "" { + kopy := *k + kopy.FootComment = "" + k = &kopy + } + e.node(k, tail) + tail = foot + + v := node.Content[i+1] + e.node(v, "") + } + + yaml_mapping_end_event_initialize(&e.event) + e.event.tail_comment = []byte(tail) + e.event.line_comment = []byte(node.LineComment) + e.event.foot_comment = []byte(node.FootComment) + e.emit() + + case AliasNode: + yaml_alias_event_initialize(&e.event, []byte(node.Value)) + e.event.head_comment = []byte(node.HeadComment) + e.event.line_comment = []byte(node.LineComment) + e.event.foot_comment = []byte(node.FootComment) + e.emit() + + case ScalarNode: + value := node.Value + if !utf8.ValidString(value) { + if stag == binaryTag { + failf("explicitly tagged !!binary data must be base64-encoded") + } + if stag != "" { + failf("cannot marshal invalid UTF-8 data as %s", stag) + } + // It can't be encoded directly as YAML so use a binary tag + // and encode it as base64. + tag = binaryTag + value = encodeBase64(value) + } + + style := yaml_PLAIN_SCALAR_STYLE + switch { + case node.Style&DoubleQuotedStyle != 0: + style = yaml_DOUBLE_QUOTED_SCALAR_STYLE + case node.Style&SingleQuotedStyle != 0: + style = yaml_SINGLE_QUOTED_SCALAR_STYLE + case node.Style&LiteralStyle != 0: + style = yaml_LITERAL_SCALAR_STYLE + case node.Style&FoldedStyle != 0: + style = yaml_FOLDED_SCALAR_STYLE + case strings.Contains(value, "\n"): + style = yaml_LITERAL_SCALAR_STYLE + case forceQuoting: + style = yaml_DOUBLE_QUOTED_SCALAR_STYLE + } + + e.emitScalar(value, node.Anchor, tag, style, []byte(node.HeadComment), []byte(node.LineComment), []byte(node.FootComment), []byte(tail)) + default: + failf("cannot encode node with unknown kind %d", node.Kind) + } +} diff --git a/index/server/vendor/github.com/oasdiff/yaml3/origin.go b/index/server/vendor/github.com/oasdiff/yaml3/origin.go new file mode 100644 index 000000000..b8ffa2ca0 --- /dev/null +++ b/index/server/vendor/github.com/oasdiff/yaml3/origin.go @@ -0,0 +1,129 @@ +package yaml + +import "fmt" + +const originTag = "__origin__" + +func isScalar(n *Node) bool { + return n.Kind == ScalarNode +} + +func addOriginInSeq(n *Node) *Node { + + if n.Kind != MappingNode { + return n + } + + // in case of a sequence, we use the first element as the key + return addOrigin(n.Content[0], n) +} + +func addOriginInMap(key, n *Node) *Node { + + if n.Kind != MappingNode { + return n + } + + return addOrigin(key, n) +} + +func addOrigin(key, n *Node) *Node { + if isOrigin(key) { + return n + } + + n.Content = append(n.Content, getNamedMap(originTag, append(getKeyLocation(key), getNamedMap("fields", getFieldLocations(n))...))...) + return n +} + +func getFieldLocations(n *Node) []*Node { + + l := len(n.Content) + size := 0 + for i := 0; i < l; i += 2 { + if isScalar(n.Content[i+1]) { + size += 2 + } + } + + nodes := make([]*Node, 0, size) + for i := 0; i < l; i += 2 { + if isScalar(n.Content[i+1]) { + nodes = append(nodes, getNodeLocation(n.Content[i])...) + } + } + return nodes +} + +// isOrigin returns true if the key is an "origin" element +// the current implementation is not optimal, as it relies on the key's line number +// a better design would be to use a dedicated field in the Node struct +func isOrigin(key *Node) bool { + return key.Line == 0 +} + +func getNodeLocation(n *Node) []*Node { + return getNamedMap(n.Value, getLocationObject(n)) +} + +func getKeyLocation(n *Node) []*Node { + return getNamedMap("key", getLocationObject(n)) +} + +func getNamedMap(title string, content []*Node) []*Node { + if len(content) == 0 { + return nil + } + + return []*Node{ + { + Kind: ScalarNode, + Tag: "!!str", + Value: title, + }, + getMap(content), + } +} + +func getMap(content []*Node) *Node { + return &Node{ + Kind: MappingNode, + Tag: "!!map", + Content: content, + } +} + +func getLocationObject(key *Node) []*Node { + return []*Node{ + { + Kind: ScalarNode, + Tag: "!!str", + Value: "line", + }, + { + Kind: ScalarNode, + Tag: "!!int", + Value: fmt.Sprintf("%d", key.Line), + }, + { + Kind: ScalarNode, + Tag: "!!str", + Value: "column", + }, + { + Kind: ScalarNode, + Tag: "!!int", + Value: fmt.Sprintf("%d", key.Column), + }, + { + Kind: ScalarNode, + Tag: "!!str", + Value: "name", + }, + { + Kind: ScalarNode, + Tag: "!!string", + Value: key.Value, + }, + } +} diff --git a/index/server/vendor/github.com/oasdiff/yaml3/parserc.go b/index/server/vendor/github.com/oasdiff/yaml3/parserc.go new file mode 100644 index 000000000..268558a0d --- /dev/null +++ b/index/server/vendor/github.com/oasdiff/yaml3/parserc.go @@ -0,0 +1,1258 @@ +// +// Copyright (c) 2011-2019 Canonical Ltd +// Copyright (c) 2006-2010 Kirill Simonov +// +// 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. + +package yaml + +import ( + "bytes" +) + +// The parser implements the following grammar: +// +// stream ::= STREAM-START implicit_document? explicit_document* STREAM-END +// implicit_document ::= block_node DOCUMENT-END* +// explicit_document ::= DIRECTIVE* DOCUMENT-START block_node? DOCUMENT-END* +// block_node_or_indentless_sequence ::= +// ALIAS +// | properties (block_content | indentless_block_sequence)? +// | block_content +// | indentless_block_sequence +// block_node ::= ALIAS +// | properties block_content? +// | block_content +// flow_node ::= ALIAS +// | properties flow_content? +// | flow_content +// properties ::= TAG ANCHOR? | ANCHOR TAG? +// block_content ::= block_collection | flow_collection | SCALAR +// flow_content ::= flow_collection | SCALAR +// block_collection ::= block_sequence | block_mapping +// flow_collection ::= flow_sequence | flow_mapping +// block_sequence ::= BLOCK-SEQUENCE-START (BLOCK-ENTRY block_node?)* BLOCK-END +// indentless_sequence ::= (BLOCK-ENTRY block_node?)+ +// block_mapping ::= BLOCK-MAPPING_START +// ((KEY block_node_or_indentless_sequence?)? +// (VALUE block_node_or_indentless_sequence?)?)* +// BLOCK-END +// flow_sequence ::= FLOW-SEQUENCE-START +// (flow_sequence_entry FLOW-ENTRY)* +// flow_sequence_entry? +// FLOW-SEQUENCE-END +// flow_sequence_entry ::= flow_node | KEY flow_node? (VALUE flow_node?)? +// flow_mapping ::= FLOW-MAPPING-START +// (flow_mapping_entry FLOW-ENTRY)* +// flow_mapping_entry? +// FLOW-MAPPING-END +// flow_mapping_entry ::= flow_node | KEY flow_node? (VALUE flow_node?)? + +// Peek the next token in the token queue. +func peek_token(parser *yaml_parser_t) *yaml_token_t { + if parser.token_available || yaml_parser_fetch_more_tokens(parser) { + token := &parser.tokens[parser.tokens_head] + yaml_parser_unfold_comments(parser, token) + return token + } + return nil +} + +// yaml_parser_unfold_comments walks through the comments queue and joins all +// comments behind the position of the provided token into the respective +// top-level comment slices in the parser. +func yaml_parser_unfold_comments(parser *yaml_parser_t, token *yaml_token_t) { + for parser.comments_head < len(parser.comments) && token.start_mark.index >= parser.comments[parser.comments_head].token_mark.index { + comment := &parser.comments[parser.comments_head] + if len(comment.head) > 0 { + if token.typ == yaml_BLOCK_END_TOKEN { + // No heads on ends, so keep comment.head for a follow up token. + break + } + if len(parser.head_comment) > 0 { + parser.head_comment = append(parser.head_comment, '\n') + } + parser.head_comment = append(parser.head_comment, comment.head...) + } + if len(comment.foot) > 0 { + if len(parser.foot_comment) > 0 { + parser.foot_comment = append(parser.foot_comment, '\n') + } + parser.foot_comment = append(parser.foot_comment, comment.foot...) + } + if len(comment.line) > 0 { + if len(parser.line_comment) > 0 { + parser.line_comment = append(parser.line_comment, '\n') + } + parser.line_comment = append(parser.line_comment, comment.line...) + } + *comment = yaml_comment_t{} + parser.comments_head++ + } +} + +// Remove the next token from the queue (must be called after peek_token). +func skip_token(parser *yaml_parser_t) { + parser.token_available = false + parser.tokens_parsed++ + parser.stream_end_produced = parser.tokens[parser.tokens_head].typ == yaml_STREAM_END_TOKEN + parser.tokens_head++ +} + +// Get the next event. +func yaml_parser_parse(parser *yaml_parser_t, event *yaml_event_t) bool { + // Erase the event object. + *event = yaml_event_t{} + + // No events after the end of the stream or error. + if parser.stream_end_produced || parser.error != yaml_NO_ERROR || parser.state == yaml_PARSE_END_STATE { + return true + } + + // Generate the next event. + return yaml_parser_state_machine(parser, event) +} + +// Set parser error. +func yaml_parser_set_parser_error(parser *yaml_parser_t, problem string, problem_mark yaml_mark_t) bool { + parser.error = yaml_PARSER_ERROR + parser.problem = problem + parser.problem_mark = problem_mark + return false +} + +func yaml_parser_set_parser_error_context(parser *yaml_parser_t, context string, context_mark yaml_mark_t, problem string, problem_mark yaml_mark_t) bool { + parser.error = yaml_PARSER_ERROR + parser.context = context + parser.context_mark = context_mark + parser.problem = problem + parser.problem_mark = problem_mark + return false +} + +// State dispatcher. +func yaml_parser_state_machine(parser *yaml_parser_t, event *yaml_event_t) bool { + //trace("yaml_parser_state_machine", "state:", parser.state.String()) + + switch parser.state { + case yaml_PARSE_STREAM_START_STATE: + return yaml_parser_parse_stream_start(parser, event) + + case yaml_PARSE_IMPLICIT_DOCUMENT_START_STATE: + return yaml_parser_parse_document_start(parser, event, true) + + case yaml_PARSE_DOCUMENT_START_STATE: + return yaml_parser_parse_document_start(parser, event, false) + + case yaml_PARSE_DOCUMENT_CONTENT_STATE: + return yaml_parser_parse_document_content(parser, event) + + case yaml_PARSE_DOCUMENT_END_STATE: + return yaml_parser_parse_document_end(parser, event) + + case yaml_PARSE_BLOCK_NODE_STATE: + return yaml_parser_parse_node(parser, event, true, false) + + case yaml_PARSE_BLOCK_NODE_OR_INDENTLESS_SEQUENCE_STATE: + return yaml_parser_parse_node(parser, event, true, true) + + case yaml_PARSE_FLOW_NODE_STATE: + return yaml_parser_parse_node(parser, event, false, false) + + case yaml_PARSE_BLOCK_SEQUENCE_FIRST_ENTRY_STATE: + return yaml_parser_parse_block_sequence_entry(parser, event, true) + + case yaml_PARSE_BLOCK_SEQUENCE_ENTRY_STATE: + return yaml_parser_parse_block_sequence_entry(parser, event, false) + + case yaml_PARSE_INDENTLESS_SEQUENCE_ENTRY_STATE: + return yaml_parser_parse_indentless_sequence_entry(parser, event) + + case yaml_PARSE_BLOCK_MAPPING_FIRST_KEY_STATE: + return yaml_parser_parse_block_mapping_key(parser, event, true) + + case yaml_PARSE_BLOCK_MAPPING_KEY_STATE: + return yaml_parser_parse_block_mapping_key(parser, event, false) + + case yaml_PARSE_BLOCK_MAPPING_VALUE_STATE: + return yaml_parser_parse_block_mapping_value(parser, event) + + case yaml_PARSE_FLOW_SEQUENCE_FIRST_ENTRY_STATE: + return yaml_parser_parse_flow_sequence_entry(parser, event, true) + + case yaml_PARSE_FLOW_SEQUENCE_ENTRY_STATE: + return yaml_parser_parse_flow_sequence_entry(parser, event, false) + + case yaml_PARSE_FLOW_SEQUENCE_ENTRY_MAPPING_KEY_STATE: + return yaml_parser_parse_flow_sequence_entry_mapping_key(parser, event) + + case yaml_PARSE_FLOW_SEQUENCE_ENTRY_MAPPING_VALUE_STATE: + return yaml_parser_parse_flow_sequence_entry_mapping_value(parser, event) + + case yaml_PARSE_FLOW_SEQUENCE_ENTRY_MAPPING_END_STATE: + return yaml_parser_parse_flow_sequence_entry_mapping_end(parser, event) + + case yaml_PARSE_FLOW_MAPPING_FIRST_KEY_STATE: + return yaml_parser_parse_flow_mapping_key(parser, event, true) + + case yaml_PARSE_FLOW_MAPPING_KEY_STATE: + return yaml_parser_parse_flow_mapping_key(parser, event, false) + + case yaml_PARSE_FLOW_MAPPING_VALUE_STATE: + return yaml_parser_parse_flow_mapping_value(parser, event, false) + + case yaml_PARSE_FLOW_MAPPING_EMPTY_VALUE_STATE: + return yaml_parser_parse_flow_mapping_value(parser, event, true) + + default: + panic("invalid parser state") + } +} + +// Parse the production: +// stream ::= STREAM-START implicit_document? explicit_document* STREAM-END +// ************ +func yaml_parser_parse_stream_start(parser *yaml_parser_t, event *yaml_event_t) bool { + token := peek_token(parser) + if token == nil { + return false + } + if token.typ != yaml_STREAM_START_TOKEN { + return yaml_parser_set_parser_error(parser, "did not find expected ", token.start_mark) + } + parser.state = yaml_PARSE_IMPLICIT_DOCUMENT_START_STATE + *event = yaml_event_t{ + typ: yaml_STREAM_START_EVENT, + start_mark: token.start_mark, + end_mark: token.end_mark, + encoding: token.encoding, + } + skip_token(parser) + return true +} + +// Parse the productions: +// implicit_document ::= block_node DOCUMENT-END* +// * +// explicit_document ::= DIRECTIVE* DOCUMENT-START block_node? DOCUMENT-END* +// ************************* +func yaml_parser_parse_document_start(parser *yaml_parser_t, event *yaml_event_t, implicit bool) bool { + + token := peek_token(parser) + if token == nil { + return false + } + + // Parse extra document end indicators. + if !implicit { + for token.typ == yaml_DOCUMENT_END_TOKEN { + skip_token(parser) + token = peek_token(parser) + if token == nil { + return false + } + } + } + + if implicit && token.typ != yaml_VERSION_DIRECTIVE_TOKEN && + token.typ != yaml_TAG_DIRECTIVE_TOKEN && + token.typ != yaml_DOCUMENT_START_TOKEN && + token.typ != yaml_STREAM_END_TOKEN { + // Parse an implicit document. + if !yaml_parser_process_directives(parser, nil, nil) { + return false + } + parser.states = append(parser.states, yaml_PARSE_DOCUMENT_END_STATE) + parser.state = yaml_PARSE_BLOCK_NODE_STATE + + var head_comment []byte + if len(parser.head_comment) > 0 { + // [Go] Scan the header comment backwards, and if an empty line is found, break + // the header so the part before the last empty line goes into the + // document header, while the bottom of it goes into a follow up event. + for i := len(parser.head_comment) - 1; i > 0; i-- { + if parser.head_comment[i] == '\n' { + if i == len(parser.head_comment)-1 { + head_comment = parser.head_comment[:i] + parser.head_comment = parser.head_comment[i+1:] + break + } else if parser.head_comment[i-1] == '\n' { + head_comment = parser.head_comment[:i-1] + parser.head_comment = parser.head_comment[i+1:] + break + } + } + } + } + + *event = yaml_event_t{ + typ: yaml_DOCUMENT_START_EVENT, + start_mark: token.start_mark, + end_mark: token.end_mark, + + head_comment: head_comment, + } + + } else if token.typ != yaml_STREAM_END_TOKEN { + // Parse an explicit document. + var version_directive *yaml_version_directive_t + var tag_directives []yaml_tag_directive_t + start_mark := token.start_mark + if !yaml_parser_process_directives(parser, &version_directive, &tag_directives) { + return false + } + token = peek_token(parser) + if token == nil { + return false + } + if token.typ != yaml_DOCUMENT_START_TOKEN { + yaml_parser_set_parser_error(parser, + "did not find expected ", token.start_mark) + return false + } + parser.states = append(parser.states, yaml_PARSE_DOCUMENT_END_STATE) + parser.state = yaml_PARSE_DOCUMENT_CONTENT_STATE + end_mark := token.end_mark + + *event = yaml_event_t{ + typ: yaml_DOCUMENT_START_EVENT, + start_mark: start_mark, + end_mark: end_mark, + version_directive: version_directive, + tag_directives: tag_directives, + implicit: false, + } + skip_token(parser) + + } else { + // Parse the stream end. + parser.state = yaml_PARSE_END_STATE + *event = yaml_event_t{ + typ: yaml_STREAM_END_EVENT, + start_mark: token.start_mark, + end_mark: token.end_mark, + } + skip_token(parser) + } + + return true +} + +// Parse the productions: +// explicit_document ::= DIRECTIVE* DOCUMENT-START block_node? DOCUMENT-END* +// *********** +// +func yaml_parser_parse_document_content(parser *yaml_parser_t, event *yaml_event_t) bool { + token := peek_token(parser) + if token == nil { + return false + } + + if token.typ == yaml_VERSION_DIRECTIVE_TOKEN || + token.typ == yaml_TAG_DIRECTIVE_TOKEN || + token.typ == yaml_DOCUMENT_START_TOKEN || + token.typ == yaml_DOCUMENT_END_TOKEN || + token.typ == yaml_STREAM_END_TOKEN { + parser.state = parser.states[len(parser.states)-1] + parser.states = parser.states[:len(parser.states)-1] + return yaml_parser_process_empty_scalar(parser, event, + token.start_mark) + } + return yaml_parser_parse_node(parser, event, true, false) +} + +// Parse the productions: +// implicit_document ::= block_node DOCUMENT-END* +// ************* +// explicit_document ::= DIRECTIVE* DOCUMENT-START block_node? DOCUMENT-END* +// +func yaml_parser_parse_document_end(parser *yaml_parser_t, event *yaml_event_t) bool { + token := peek_token(parser) + if token == nil { + return false + } + + start_mark := token.start_mark + end_mark := token.start_mark + + implicit := true + if token.typ == yaml_DOCUMENT_END_TOKEN { + end_mark = token.end_mark + skip_token(parser) + implicit = false + } + + parser.tag_directives = parser.tag_directives[:0] + + parser.state = yaml_PARSE_DOCUMENT_START_STATE + *event = yaml_event_t{ + typ: yaml_DOCUMENT_END_EVENT, + start_mark: start_mark, + end_mark: end_mark, + implicit: implicit, + } + yaml_parser_set_event_comments(parser, event) + if len(event.head_comment) > 0 && len(event.foot_comment) == 0 { + event.foot_comment = event.head_comment + event.head_comment = nil + } + return true +} + +func yaml_parser_set_event_comments(parser *yaml_parser_t, event *yaml_event_t) { + event.head_comment = parser.head_comment + event.line_comment = parser.line_comment + event.foot_comment = parser.foot_comment + parser.head_comment = nil + parser.line_comment = nil + parser.foot_comment = nil + parser.tail_comment = nil + parser.stem_comment = nil +} + +// Parse the productions: +// block_node_or_indentless_sequence ::= +// ALIAS +// ***** +// | properties (block_content | indentless_block_sequence)? +// ********** * +// | block_content | indentless_block_sequence +// * +// block_node ::= ALIAS +// ***** +// | properties block_content? +// ********** * +// | block_content +// * +// flow_node ::= ALIAS +// ***** +// | properties flow_content? +// ********** * +// | flow_content +// * +// properties ::= TAG ANCHOR? | ANCHOR TAG? +// ************************* +// block_content ::= block_collection | flow_collection | SCALAR +// ****** +// flow_content ::= flow_collection | SCALAR +// ****** +func yaml_parser_parse_node(parser *yaml_parser_t, event *yaml_event_t, block, indentless_sequence bool) bool { + //defer trace("yaml_parser_parse_node", "block:", block, "indentless_sequence:", indentless_sequence)() + + token := peek_token(parser) + if token == nil { + return false + } + + if token.typ == yaml_ALIAS_TOKEN { + parser.state = parser.states[len(parser.states)-1] + parser.states = parser.states[:len(parser.states)-1] + *event = yaml_event_t{ + typ: yaml_ALIAS_EVENT, + start_mark: token.start_mark, + end_mark: token.end_mark, + anchor: token.value, + } + yaml_parser_set_event_comments(parser, event) + skip_token(parser) + return true + } + + start_mark := token.start_mark + end_mark := token.start_mark + + var tag_token bool + var tag_handle, tag_suffix, anchor []byte + var tag_mark yaml_mark_t + if token.typ == yaml_ANCHOR_TOKEN { + anchor = token.value + start_mark = token.start_mark + end_mark = token.end_mark + skip_token(parser) + token = peek_token(parser) + if token == nil { + return false + } + if token.typ == yaml_TAG_TOKEN { + tag_token = true + tag_handle = token.value + tag_suffix = token.suffix + tag_mark = token.start_mark + end_mark = token.end_mark + skip_token(parser) + token = peek_token(parser) + if token == nil { + return false + } + } + } else if token.typ == yaml_TAG_TOKEN { + tag_token = true + tag_handle = token.value + tag_suffix = token.suffix + start_mark = token.start_mark + tag_mark = token.start_mark + end_mark = token.end_mark + skip_token(parser) + token = peek_token(parser) + if token == nil { + return false + } + if token.typ == yaml_ANCHOR_TOKEN { + anchor = token.value + end_mark = token.end_mark + skip_token(parser) + token = peek_token(parser) + if token == nil { + return false + } + } + } + + var tag []byte + if tag_token { + if len(tag_handle) == 0 { + tag = tag_suffix + tag_suffix = nil + } else { + for i := range parser.tag_directives { + if bytes.Equal(parser.tag_directives[i].handle, tag_handle) { + tag = append([]byte(nil), parser.tag_directives[i].prefix...) + tag = append(tag, tag_suffix...) + break + } + } + if len(tag) == 0 { + yaml_parser_set_parser_error_context(parser, + "while parsing a node", start_mark, + "found undefined tag handle", tag_mark) + return false + } + } + } + + implicit := len(tag) == 0 + if indentless_sequence && token.typ == yaml_BLOCK_ENTRY_TOKEN { + end_mark = token.end_mark + parser.state = yaml_PARSE_INDENTLESS_SEQUENCE_ENTRY_STATE + *event = yaml_event_t{ + typ: yaml_SEQUENCE_START_EVENT, + start_mark: start_mark, + end_mark: end_mark, + anchor: anchor, + tag: tag, + implicit: implicit, + style: yaml_style_t(yaml_BLOCK_SEQUENCE_STYLE), + } + return true + } + if token.typ == yaml_SCALAR_TOKEN { + var plain_implicit, quoted_implicit bool + end_mark = token.end_mark + if (len(tag) == 0 && token.style == yaml_PLAIN_SCALAR_STYLE) || (len(tag) == 1 && tag[0] == '!') { + plain_implicit = true + } else if len(tag) == 0 { + quoted_implicit = true + } + parser.state = parser.states[len(parser.states)-1] + parser.states = parser.states[:len(parser.states)-1] + + *event = yaml_event_t{ + typ: yaml_SCALAR_EVENT, + start_mark: start_mark, + end_mark: end_mark, + anchor: anchor, + tag: tag, + value: token.value, + implicit: plain_implicit, + quoted_implicit: quoted_implicit, + style: yaml_style_t(token.style), + } + yaml_parser_set_event_comments(parser, event) + skip_token(parser) + return true + } + if token.typ == yaml_FLOW_SEQUENCE_START_TOKEN { + // [Go] Some of the events below can be merged as they differ only on style. + end_mark = token.end_mark + parser.state = yaml_PARSE_FLOW_SEQUENCE_FIRST_ENTRY_STATE + *event = yaml_event_t{ + typ: yaml_SEQUENCE_START_EVENT, + start_mark: start_mark, + end_mark: end_mark, + anchor: anchor, + tag: tag, + implicit: implicit, + style: yaml_style_t(yaml_FLOW_SEQUENCE_STYLE), + } + yaml_parser_set_event_comments(parser, event) + return true + } + if token.typ == yaml_FLOW_MAPPING_START_TOKEN { + end_mark = token.end_mark + parser.state = yaml_PARSE_FLOW_MAPPING_FIRST_KEY_STATE + *event = yaml_event_t{ + typ: yaml_MAPPING_START_EVENT, + start_mark: start_mark, + end_mark: end_mark, + anchor: anchor, + tag: tag, + implicit: implicit, + style: yaml_style_t(yaml_FLOW_MAPPING_STYLE), + } + yaml_parser_set_event_comments(parser, event) + return true + } + if block && token.typ == yaml_BLOCK_SEQUENCE_START_TOKEN { + end_mark = token.end_mark + parser.state = yaml_PARSE_BLOCK_SEQUENCE_FIRST_ENTRY_STATE + *event = yaml_event_t{ + typ: yaml_SEQUENCE_START_EVENT, + start_mark: start_mark, + end_mark: end_mark, + anchor: anchor, + tag: tag, + implicit: implicit, + style: yaml_style_t(yaml_BLOCK_SEQUENCE_STYLE), + } + if parser.stem_comment != nil { + event.head_comment = parser.stem_comment + parser.stem_comment = nil + } + return true + } + if block && token.typ == yaml_BLOCK_MAPPING_START_TOKEN { + end_mark = token.end_mark + parser.state = yaml_PARSE_BLOCK_MAPPING_FIRST_KEY_STATE + *event = yaml_event_t{ + typ: yaml_MAPPING_START_EVENT, + start_mark: start_mark, + end_mark: end_mark, + anchor: anchor, + tag: tag, + implicit: implicit, + style: yaml_style_t(yaml_BLOCK_MAPPING_STYLE), + } + if parser.stem_comment != nil { + event.head_comment = parser.stem_comment + parser.stem_comment = nil + } + return true + } + if len(anchor) > 0 || len(tag) > 0 { + parser.state = parser.states[len(parser.states)-1] + parser.states = parser.states[:len(parser.states)-1] + + *event = yaml_event_t{ + typ: yaml_SCALAR_EVENT, + start_mark: start_mark, + end_mark: end_mark, + anchor: anchor, + tag: tag, + implicit: implicit, + quoted_implicit: false, + style: yaml_style_t(yaml_PLAIN_SCALAR_STYLE), + } + return true + } + + context := "while parsing a flow node" + if block { + context = "while parsing a block node" + } + yaml_parser_set_parser_error_context(parser, context, start_mark, + "did not find expected node content", token.start_mark) + return false +} + +// Parse the productions: +// block_sequence ::= BLOCK-SEQUENCE-START (BLOCK-ENTRY block_node?)* BLOCK-END +// ******************** *********** * ********* +// +func yaml_parser_parse_block_sequence_entry(parser *yaml_parser_t, event *yaml_event_t, first bool) bool { + if first { + token := peek_token(parser) + if token == nil { + return false + } + parser.marks = append(parser.marks, token.start_mark) + skip_token(parser) + } + + token := peek_token(parser) + if token == nil { + return false + } + + if token.typ == yaml_BLOCK_ENTRY_TOKEN { + mark := token.end_mark + prior_head_len := len(parser.head_comment) + skip_token(parser) + yaml_parser_split_stem_comment(parser, prior_head_len) + token = peek_token(parser) + if token == nil { + return false + } + if token.typ != yaml_BLOCK_ENTRY_TOKEN && token.typ != yaml_BLOCK_END_TOKEN { + parser.states = append(parser.states, yaml_PARSE_BLOCK_SEQUENCE_ENTRY_STATE) + return yaml_parser_parse_node(parser, event, true, false) + } else { + parser.state = yaml_PARSE_BLOCK_SEQUENCE_ENTRY_STATE + return yaml_parser_process_empty_scalar(parser, event, mark) + } + } + if token.typ == yaml_BLOCK_END_TOKEN { + parser.state = parser.states[len(parser.states)-1] + parser.states = parser.states[:len(parser.states)-1] + parser.marks = parser.marks[:len(parser.marks)-1] + + *event = yaml_event_t{ + typ: yaml_SEQUENCE_END_EVENT, + start_mark: token.start_mark, + end_mark: token.end_mark, + } + + skip_token(parser) + return true + } + + context_mark := parser.marks[len(parser.marks)-1] + parser.marks = parser.marks[:len(parser.marks)-1] + return yaml_parser_set_parser_error_context(parser, + "while parsing a block collection", context_mark, + "did not find expected '-' indicator", token.start_mark) +} + +// Parse the productions: +// indentless_sequence ::= (BLOCK-ENTRY block_node?)+ +// *********** * +func yaml_parser_parse_indentless_sequence_entry(parser *yaml_parser_t, event *yaml_event_t) bool { + token := peek_token(parser) + if token == nil { + return false + } + + if token.typ == yaml_BLOCK_ENTRY_TOKEN { + mark := token.end_mark + prior_head_len := len(parser.head_comment) + skip_token(parser) + yaml_parser_split_stem_comment(parser, prior_head_len) + token = peek_token(parser) + if token == nil { + return false + } + if token.typ != yaml_BLOCK_ENTRY_TOKEN && + token.typ != yaml_KEY_TOKEN && + token.typ != yaml_VALUE_TOKEN && + token.typ != yaml_BLOCK_END_TOKEN { + parser.states = append(parser.states, yaml_PARSE_INDENTLESS_SEQUENCE_ENTRY_STATE) + return yaml_parser_parse_node(parser, event, true, false) + } + parser.state = yaml_PARSE_INDENTLESS_SEQUENCE_ENTRY_STATE + return yaml_parser_process_empty_scalar(parser, event, mark) + } + parser.state = parser.states[len(parser.states)-1] + parser.states = parser.states[:len(parser.states)-1] + + *event = yaml_event_t{ + typ: yaml_SEQUENCE_END_EVENT, + start_mark: token.start_mark, + end_mark: token.start_mark, // [Go] Shouldn't this be token.end_mark? + } + return true +} + +// Split stem comment from head comment. +// +// When a sequence or map is found under a sequence entry, the former head comment +// is assigned to the underlying sequence or map as a whole, not the individual +// sequence or map entry as would be expected otherwise. To handle this case the +// previous head comment is moved aside as the stem comment. +func yaml_parser_split_stem_comment(parser *yaml_parser_t, stem_len int) { + if stem_len == 0 { + return + } + + token := peek_token(parser) + if token == nil || token.typ != yaml_BLOCK_SEQUENCE_START_TOKEN && token.typ != yaml_BLOCK_MAPPING_START_TOKEN { + return + } + + parser.stem_comment = parser.head_comment[:stem_len] + if len(parser.head_comment) == stem_len { + parser.head_comment = nil + } else { + // Copy suffix to prevent very strange bugs if someone ever appends + // further bytes to the prefix in the stem_comment slice above. + parser.head_comment = append([]byte(nil), parser.head_comment[stem_len+1:]...) + } +} + +// Parse the productions: +// block_mapping ::= BLOCK-MAPPING_START +// ******************* +// ((KEY block_node_or_indentless_sequence?)? +// *** * +// (VALUE block_node_or_indentless_sequence?)?)* +// +// BLOCK-END +// ********* +// +func yaml_parser_parse_block_mapping_key(parser *yaml_parser_t, event *yaml_event_t, first bool) bool { + if first { + token := peek_token(parser) + if token == nil { + return false + } + parser.marks = append(parser.marks, token.start_mark) + skip_token(parser) + } + + token := peek_token(parser) + if token == nil { + return false + } + + // [Go] A tail comment was left from the prior mapping value processed. Emit an event + // as it needs to be processed with that value and not the following key. + if len(parser.tail_comment) > 0 { + *event = yaml_event_t{ + typ: yaml_TAIL_COMMENT_EVENT, + start_mark: token.start_mark, + end_mark: token.end_mark, + foot_comment: parser.tail_comment, + } + parser.tail_comment = nil + return true + } + + if token.typ == yaml_KEY_TOKEN { + mark := token.end_mark + skip_token(parser) + token = peek_token(parser) + if token == nil { + return false + } + if token.typ != yaml_KEY_TOKEN && + token.typ != yaml_VALUE_TOKEN && + token.typ != yaml_BLOCK_END_TOKEN { + parser.states = append(parser.states, yaml_PARSE_BLOCK_MAPPING_VALUE_STATE) + return yaml_parser_parse_node(parser, event, true, true) + } else { + parser.state = yaml_PARSE_BLOCK_MAPPING_VALUE_STATE + return yaml_parser_process_empty_scalar(parser, event, mark) + } + } else if token.typ == yaml_BLOCK_END_TOKEN { + parser.state = parser.states[len(parser.states)-1] + parser.states = parser.states[:len(parser.states)-1] + parser.marks = parser.marks[:len(parser.marks)-1] + *event = yaml_event_t{ + typ: yaml_MAPPING_END_EVENT, + start_mark: token.start_mark, + end_mark: token.end_mark, + } + yaml_parser_set_event_comments(parser, event) + skip_token(parser) + return true + } + + context_mark := parser.marks[len(parser.marks)-1] + parser.marks = parser.marks[:len(parser.marks)-1] + return yaml_parser_set_parser_error_context(parser, + "while parsing a block mapping", context_mark, + "did not find expected key", token.start_mark) +} + +// Parse the productions: +// block_mapping ::= BLOCK-MAPPING_START +// +// ((KEY block_node_or_indentless_sequence?)? +// +// (VALUE block_node_or_indentless_sequence?)?)* +// ***** * +// BLOCK-END +// +// +func yaml_parser_parse_block_mapping_value(parser *yaml_parser_t, event *yaml_event_t) bool { + token := peek_token(parser) + if token == nil { + return false + } + if token.typ == yaml_VALUE_TOKEN { + mark := token.end_mark + skip_token(parser) + token = peek_token(parser) + if token == nil { + return false + } + if token.typ != yaml_KEY_TOKEN && + token.typ != yaml_VALUE_TOKEN && + token.typ != yaml_BLOCK_END_TOKEN { + parser.states = append(parser.states, yaml_PARSE_BLOCK_MAPPING_KEY_STATE) + return yaml_parser_parse_node(parser, event, true, true) + } + parser.state = yaml_PARSE_BLOCK_MAPPING_KEY_STATE + return yaml_parser_process_empty_scalar(parser, event, mark) + } + parser.state = yaml_PARSE_BLOCK_MAPPING_KEY_STATE + return yaml_parser_process_empty_scalar(parser, event, token.start_mark) +} + +// Parse the productions: +// flow_sequence ::= FLOW-SEQUENCE-START +// ******************* +// (flow_sequence_entry FLOW-ENTRY)* +// * ********** +// flow_sequence_entry? +// * +// FLOW-SEQUENCE-END +// ***************** +// flow_sequence_entry ::= flow_node | KEY flow_node? (VALUE flow_node?)? +// * +// +func yaml_parser_parse_flow_sequence_entry(parser *yaml_parser_t, event *yaml_event_t, first bool) bool { + if first { + token := peek_token(parser) + if token == nil { + return false + } + parser.marks = append(parser.marks, token.start_mark) + skip_token(parser) + } + token := peek_token(parser) + if token == nil { + return false + } + if token.typ != yaml_FLOW_SEQUENCE_END_TOKEN { + if !first { + if token.typ == yaml_FLOW_ENTRY_TOKEN { + skip_token(parser) + token = peek_token(parser) + if token == nil { + return false + } + } else { + context_mark := parser.marks[len(parser.marks)-1] + parser.marks = parser.marks[:len(parser.marks)-1] + return yaml_parser_set_parser_error_context(parser, + "while parsing a flow sequence", context_mark, + "did not find expected ',' or ']'", token.start_mark) + } + } + + if token.typ == yaml_KEY_TOKEN { + parser.state = yaml_PARSE_FLOW_SEQUENCE_ENTRY_MAPPING_KEY_STATE + *event = yaml_event_t{ + typ: yaml_MAPPING_START_EVENT, + start_mark: token.start_mark, + end_mark: token.end_mark, + implicit: true, + style: yaml_style_t(yaml_FLOW_MAPPING_STYLE), + } + skip_token(parser) + return true + } else if token.typ != yaml_FLOW_SEQUENCE_END_TOKEN { + parser.states = append(parser.states, yaml_PARSE_FLOW_SEQUENCE_ENTRY_STATE) + return yaml_parser_parse_node(parser, event, false, false) + } + } + + parser.state = parser.states[len(parser.states)-1] + parser.states = parser.states[:len(parser.states)-1] + parser.marks = parser.marks[:len(parser.marks)-1] + + *event = yaml_event_t{ + typ: yaml_SEQUENCE_END_EVENT, + start_mark: token.start_mark, + end_mark: token.end_mark, + } + yaml_parser_set_event_comments(parser, event) + + skip_token(parser) + return true +} + +// +// Parse the productions: +// flow_sequence_entry ::= flow_node | KEY flow_node? (VALUE flow_node?)? +// *** * +// +func yaml_parser_parse_flow_sequence_entry_mapping_key(parser *yaml_parser_t, event *yaml_event_t) bool { + token := peek_token(parser) + if token == nil { + return false + } + if token.typ != yaml_VALUE_TOKEN && + token.typ != yaml_FLOW_ENTRY_TOKEN && + token.typ != yaml_FLOW_SEQUENCE_END_TOKEN { + parser.states = append(parser.states, yaml_PARSE_FLOW_SEQUENCE_ENTRY_MAPPING_VALUE_STATE) + return yaml_parser_parse_node(parser, event, false, false) + } + mark := token.end_mark + skip_token(parser) + parser.state = yaml_PARSE_FLOW_SEQUENCE_ENTRY_MAPPING_VALUE_STATE + return yaml_parser_process_empty_scalar(parser, event, mark) +} + +// Parse the productions: +// flow_sequence_entry ::= flow_node | KEY flow_node? (VALUE flow_node?)? +// ***** * +// +func yaml_parser_parse_flow_sequence_entry_mapping_value(parser *yaml_parser_t, event *yaml_event_t) bool { + token := peek_token(parser) + if token == nil { + return false + } + if token.typ == yaml_VALUE_TOKEN { + skip_token(parser) + token := peek_token(parser) + if token == nil { + return false + } + if token.typ != yaml_FLOW_ENTRY_TOKEN && token.typ != yaml_FLOW_SEQUENCE_END_TOKEN { + parser.states = append(parser.states, yaml_PARSE_FLOW_SEQUENCE_ENTRY_MAPPING_END_STATE) + return yaml_parser_parse_node(parser, event, false, false) + } + } + parser.state = yaml_PARSE_FLOW_SEQUENCE_ENTRY_MAPPING_END_STATE + return yaml_parser_process_empty_scalar(parser, event, token.start_mark) +} + +// Parse the productions: +// flow_sequence_entry ::= flow_node | KEY flow_node? (VALUE flow_node?)? +// * +// +func yaml_parser_parse_flow_sequence_entry_mapping_end(parser *yaml_parser_t, event *yaml_event_t) bool { + token := peek_token(parser) + if token == nil { + return false + } + parser.state = yaml_PARSE_FLOW_SEQUENCE_ENTRY_STATE + *event = yaml_event_t{ + typ: yaml_MAPPING_END_EVENT, + start_mark: token.start_mark, + end_mark: token.start_mark, // [Go] Shouldn't this be end_mark? + } + return true +} + +// Parse the productions: +// flow_mapping ::= FLOW-MAPPING-START +// ****************** +// (flow_mapping_entry FLOW-ENTRY)* +// * ********** +// flow_mapping_entry? +// ****************** +// FLOW-MAPPING-END +// **************** +// flow_mapping_entry ::= flow_node | KEY flow_node? (VALUE flow_node?)? +// * *** * +// +func yaml_parser_parse_flow_mapping_key(parser *yaml_parser_t, event *yaml_event_t, first bool) bool { + if first { + token := peek_token(parser) + parser.marks = append(parser.marks, token.start_mark) + skip_token(parser) + } + + token := peek_token(parser) + if token == nil { + return false + } + + if token.typ != yaml_FLOW_MAPPING_END_TOKEN { + if !first { + if token.typ == yaml_FLOW_ENTRY_TOKEN { + skip_token(parser) + token = peek_token(parser) + if token == nil { + return false + } + } else { + context_mark := parser.marks[len(parser.marks)-1] + parser.marks = parser.marks[:len(parser.marks)-1] + return yaml_parser_set_parser_error_context(parser, + "while parsing a flow mapping", context_mark, + "did not find expected ',' or '}'", token.start_mark) + } + } + + if token.typ == yaml_KEY_TOKEN { + skip_token(parser) + token = peek_token(parser) + if token == nil { + return false + } + if token.typ != yaml_VALUE_TOKEN && + token.typ != yaml_FLOW_ENTRY_TOKEN && + token.typ != yaml_FLOW_MAPPING_END_TOKEN { + parser.states = append(parser.states, yaml_PARSE_FLOW_MAPPING_VALUE_STATE) + return yaml_parser_parse_node(parser, event, false, false) + } else { + parser.state = yaml_PARSE_FLOW_MAPPING_VALUE_STATE + return yaml_parser_process_empty_scalar(parser, event, token.start_mark) + } + } else if token.typ != yaml_FLOW_MAPPING_END_TOKEN { + parser.states = append(parser.states, yaml_PARSE_FLOW_MAPPING_EMPTY_VALUE_STATE) + return yaml_parser_parse_node(parser, event, false, false) + } + } + + parser.state = parser.states[len(parser.states)-1] + parser.states = parser.states[:len(parser.states)-1] + parser.marks = parser.marks[:len(parser.marks)-1] + *event = yaml_event_t{ + typ: yaml_MAPPING_END_EVENT, + start_mark: token.start_mark, + end_mark: token.end_mark, + } + yaml_parser_set_event_comments(parser, event) + skip_token(parser) + return true +} + +// Parse the productions: +// flow_mapping_entry ::= flow_node | KEY flow_node? (VALUE flow_node?)? +// * ***** * +// +func yaml_parser_parse_flow_mapping_value(parser *yaml_parser_t, event *yaml_event_t, empty bool) bool { + token := peek_token(parser) + if token == nil { + return false + } + if empty { + parser.state = yaml_PARSE_FLOW_MAPPING_KEY_STATE + return yaml_parser_process_empty_scalar(parser, event, token.start_mark) + } + if token.typ == yaml_VALUE_TOKEN { + skip_token(parser) + token = peek_token(parser) + if token == nil { + return false + } + if token.typ != yaml_FLOW_ENTRY_TOKEN && token.typ != yaml_FLOW_MAPPING_END_TOKEN { + parser.states = append(parser.states, yaml_PARSE_FLOW_MAPPING_KEY_STATE) + return yaml_parser_parse_node(parser, event, false, false) + } + } + parser.state = yaml_PARSE_FLOW_MAPPING_KEY_STATE + return yaml_parser_process_empty_scalar(parser, event, token.start_mark) +} + +// Generate an empty scalar event. +func yaml_parser_process_empty_scalar(parser *yaml_parser_t, event *yaml_event_t, mark yaml_mark_t) bool { + *event = yaml_event_t{ + typ: yaml_SCALAR_EVENT, + start_mark: mark, + end_mark: mark, + value: nil, // Empty + implicit: true, + style: yaml_style_t(yaml_PLAIN_SCALAR_STYLE), + } + return true +} + +var default_tag_directives = []yaml_tag_directive_t{ + {[]byte("!"), []byte("!")}, + {[]byte("!!"), []byte("tag:yaml.org,2002:")}, +} + +// Parse directives. +func yaml_parser_process_directives(parser *yaml_parser_t, + version_directive_ref **yaml_version_directive_t, + tag_directives_ref *[]yaml_tag_directive_t) bool { + + var version_directive *yaml_version_directive_t + var tag_directives []yaml_tag_directive_t + + token := peek_token(parser) + if token == nil { + return false + } + + for token.typ == yaml_VERSION_DIRECTIVE_TOKEN || token.typ == yaml_TAG_DIRECTIVE_TOKEN { + if token.typ == yaml_VERSION_DIRECTIVE_TOKEN { + if version_directive != nil { + yaml_parser_set_parser_error(parser, + "found duplicate %YAML directive", token.start_mark) + return false + } + if token.major != 1 || token.minor != 1 { + yaml_parser_set_parser_error(parser, + "found incompatible YAML document", token.start_mark) + return false + } + version_directive = &yaml_version_directive_t{ + major: token.major, + minor: token.minor, + } + } else if token.typ == yaml_TAG_DIRECTIVE_TOKEN { + value := yaml_tag_directive_t{ + handle: token.value, + prefix: token.prefix, + } + if !yaml_parser_append_tag_directive(parser, value, false, token.start_mark) { + return false + } + tag_directives = append(tag_directives, value) + } + + skip_token(parser) + token = peek_token(parser) + if token == nil { + return false + } + } + + for i := range default_tag_directives { + if !yaml_parser_append_tag_directive(parser, default_tag_directives[i], true, token.start_mark) { + return false + } + } + + if version_directive_ref != nil { + *version_directive_ref = version_directive + } + if tag_directives_ref != nil { + *tag_directives_ref = tag_directives + } + return true +} + +// Append a tag directive to the directives stack. +func yaml_parser_append_tag_directive(parser *yaml_parser_t, value yaml_tag_directive_t, allow_duplicates bool, mark yaml_mark_t) bool { + for i := range parser.tag_directives { + if bytes.Equal(value.handle, parser.tag_directives[i].handle) { + if allow_duplicates { + return true + } + return yaml_parser_set_parser_error(parser, "found duplicate %TAG directive", mark) + } + } + + // [Go] I suspect the copy is unnecessary. This was likely done + // because there was no way to track ownership of the data. + value_copy := yaml_tag_directive_t{ + handle: make([]byte, len(value.handle)), + prefix: make([]byte, len(value.prefix)), + } + copy(value_copy.handle, value.handle) + copy(value_copy.prefix, value.prefix) + parser.tag_directives = append(parser.tag_directives, value_copy) + return true +} diff --git a/index/server/vendor/github.com/oasdiff/yaml3/readerc.go b/index/server/vendor/github.com/oasdiff/yaml3/readerc.go new file mode 100644 index 000000000..b7de0a89c --- /dev/null +++ b/index/server/vendor/github.com/oasdiff/yaml3/readerc.go @@ -0,0 +1,434 @@ +// +// Copyright (c) 2011-2019 Canonical Ltd +// Copyright (c) 2006-2010 Kirill Simonov +// +// 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. + +package yaml + +import ( + "io" +) + +// Set the reader error and return 0. +func yaml_parser_set_reader_error(parser *yaml_parser_t, problem string, offset int, value int) bool { + parser.error = yaml_READER_ERROR + parser.problem = problem + parser.problem_offset = offset + parser.problem_value = value + return false +} + +// Byte order marks. +const ( + bom_UTF8 = "\xef\xbb\xbf" + bom_UTF16LE = "\xff\xfe" + bom_UTF16BE = "\xfe\xff" +) + +// Determine the input stream encoding by checking the BOM symbol. If no BOM is +// found, the UTF-8 encoding is assumed. Return 1 on success, 0 on failure. +func yaml_parser_determine_encoding(parser *yaml_parser_t) bool { + // Ensure that we had enough bytes in the raw buffer. + for !parser.eof && len(parser.raw_buffer)-parser.raw_buffer_pos < 3 { + if !yaml_parser_update_raw_buffer(parser) { + return false + } + } + + // Determine the encoding. + buf := parser.raw_buffer + pos := parser.raw_buffer_pos + avail := len(buf) - pos + if avail >= 2 && buf[pos] == bom_UTF16LE[0] && buf[pos+1] == bom_UTF16LE[1] { + parser.encoding = yaml_UTF16LE_ENCODING + parser.raw_buffer_pos += 2 + parser.offset += 2 + } else if avail >= 2 && buf[pos] == bom_UTF16BE[0] && buf[pos+1] == bom_UTF16BE[1] { + parser.encoding = yaml_UTF16BE_ENCODING + parser.raw_buffer_pos += 2 + parser.offset += 2 + } else if avail >= 3 && buf[pos] == bom_UTF8[0] && buf[pos+1] == bom_UTF8[1] && buf[pos+2] == bom_UTF8[2] { + parser.encoding = yaml_UTF8_ENCODING + parser.raw_buffer_pos += 3 + parser.offset += 3 + } else { + parser.encoding = yaml_UTF8_ENCODING + } + return true +} + +// Update the raw buffer. +func yaml_parser_update_raw_buffer(parser *yaml_parser_t) bool { + size_read := 0 + + // Return if the raw buffer is full. + if parser.raw_buffer_pos == 0 && len(parser.raw_buffer) == cap(parser.raw_buffer) { + return true + } + + // Return on EOF. + if parser.eof { + return true + } + + // Move the remaining bytes in the raw buffer to the beginning. + if parser.raw_buffer_pos > 0 && parser.raw_buffer_pos < len(parser.raw_buffer) { + copy(parser.raw_buffer, parser.raw_buffer[parser.raw_buffer_pos:]) + } + parser.raw_buffer = parser.raw_buffer[:len(parser.raw_buffer)-parser.raw_buffer_pos] + parser.raw_buffer_pos = 0 + + // Call the read handler to fill the buffer. + size_read, err := parser.read_handler(parser, parser.raw_buffer[len(parser.raw_buffer):cap(parser.raw_buffer)]) + parser.raw_buffer = parser.raw_buffer[:len(parser.raw_buffer)+size_read] + if err == io.EOF { + parser.eof = true + } else if err != nil { + return yaml_parser_set_reader_error(parser, "input error: "+err.Error(), parser.offset, -1) + } + return true +} + +// Ensure that the buffer contains at least `length` characters. +// Return true on success, false on failure. +// +// The length is supposed to be significantly less that the buffer size. +func yaml_parser_update_buffer(parser *yaml_parser_t, length int) bool { + if parser.read_handler == nil { + panic("read handler must be set") + } + + // [Go] This function was changed to guarantee the requested length size at EOF. + // The fact we need to do this is pretty awful, but the description above implies + // for that to be the case, and there are tests + + // If the EOF flag is set and the raw buffer is empty, do nothing. + if parser.eof && parser.raw_buffer_pos == len(parser.raw_buffer) { + // [Go] ACTUALLY! Read the documentation of this function above. + // This is just broken. To return true, we need to have the + // given length in the buffer. Not doing that means every single + // check that calls this function to make sure the buffer has a + // given length is Go) panicking; or C) accessing invalid memory. + //return true + } + + // Return if the buffer contains enough characters. + if parser.unread >= length { + return true + } + + // Determine the input encoding if it is not known yet. + if parser.encoding == yaml_ANY_ENCODING { + if !yaml_parser_determine_encoding(parser) { + return false + } + } + + // Move the unread characters to the beginning of the buffer. + buffer_len := len(parser.buffer) + if parser.buffer_pos > 0 && parser.buffer_pos < buffer_len { + copy(parser.buffer, parser.buffer[parser.buffer_pos:]) + buffer_len -= parser.buffer_pos + parser.buffer_pos = 0 + } else if parser.buffer_pos == buffer_len { + buffer_len = 0 + parser.buffer_pos = 0 + } + + // Open the whole buffer for writing, and cut it before returning. + parser.buffer = parser.buffer[:cap(parser.buffer)] + + // Fill the buffer until it has enough characters. + first := true + for parser.unread < length { + + // Fill the raw buffer if necessary. + if !first || parser.raw_buffer_pos == len(parser.raw_buffer) { + if !yaml_parser_update_raw_buffer(parser) { + parser.buffer = parser.buffer[:buffer_len] + return false + } + } + first = false + + // Decode the raw buffer. + inner: + for parser.raw_buffer_pos != len(parser.raw_buffer) { + var value rune + var width int + + raw_unread := len(parser.raw_buffer) - parser.raw_buffer_pos + + // Decode the next character. + switch parser.encoding { + case yaml_UTF8_ENCODING: + // Decode a UTF-8 character. Check RFC 3629 + // (http://www.ietf.org/rfc/rfc3629.txt) for more details. + // + // The following table (taken from the RFC) is used for + // decoding. + // + // Char. number range | UTF-8 octet sequence + // (hexadecimal) | (binary) + // --------------------+------------------------------------ + // 0000 0000-0000 007F | 0xxxxxxx + // 0000 0080-0000 07FF | 110xxxxx 10xxxxxx + // 0000 0800-0000 FFFF | 1110xxxx 10xxxxxx 10xxxxxx + // 0001 0000-0010 FFFF | 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx + // + // Additionally, the characters in the range 0xD800-0xDFFF + // are prohibited as they are reserved for use with UTF-16 + // surrogate pairs. + + // Determine the length of the UTF-8 sequence. + octet := parser.raw_buffer[parser.raw_buffer_pos] + switch { + case octet&0x80 == 0x00: + width = 1 + case octet&0xE0 == 0xC0: + width = 2 + case octet&0xF0 == 0xE0: + width = 3 + case octet&0xF8 == 0xF0: + width = 4 + default: + // The leading octet is invalid. + return yaml_parser_set_reader_error(parser, + "invalid leading UTF-8 octet", + parser.offset, int(octet)) + } + + // Check if the raw buffer contains an incomplete character. + if width > raw_unread { + if parser.eof { + return yaml_parser_set_reader_error(parser, + "incomplete UTF-8 octet sequence", + parser.offset, -1) + } + break inner + } + + // Decode the leading octet. + switch { + case octet&0x80 == 0x00: + value = rune(octet & 0x7F) + case octet&0xE0 == 0xC0: + value = rune(octet & 0x1F) + case octet&0xF0 == 0xE0: + value = rune(octet & 0x0F) + case octet&0xF8 == 0xF0: + value = rune(octet & 0x07) + default: + value = 0 + } + + // Check and decode the trailing octets. + for k := 1; k < width; k++ { + octet = parser.raw_buffer[parser.raw_buffer_pos+k] + + // Check if the octet is valid. + if (octet & 0xC0) != 0x80 { + return yaml_parser_set_reader_error(parser, + "invalid trailing UTF-8 octet", + parser.offset+k, int(octet)) + } + + // Decode the octet. + value = (value << 6) + rune(octet&0x3F) + } + + // Check the length of the sequence against the value. + switch { + case width == 1: + case width == 2 && value >= 0x80: + case width == 3 && value >= 0x800: + case width == 4 && value >= 0x10000: + default: + return yaml_parser_set_reader_error(parser, + "invalid length of a UTF-8 sequence", + parser.offset, -1) + } + + // Check the range of the value. + if value >= 0xD800 && value <= 0xDFFF || value > 0x10FFFF { + return yaml_parser_set_reader_error(parser, + "invalid Unicode character", + parser.offset, int(value)) + } + + case yaml_UTF16LE_ENCODING, yaml_UTF16BE_ENCODING: + var low, high int + if parser.encoding == yaml_UTF16LE_ENCODING { + low, high = 0, 1 + } else { + low, high = 1, 0 + } + + // The UTF-16 encoding is not as simple as one might + // naively think. Check RFC 2781 + // (http://www.ietf.org/rfc/rfc2781.txt). + // + // Normally, two subsequent bytes describe a Unicode + // character. However a special technique (called a + // surrogate pair) is used for specifying character + // values larger than 0xFFFF. + // + // A surrogate pair consists of two pseudo-characters: + // high surrogate area (0xD800-0xDBFF) + // low surrogate area (0xDC00-0xDFFF) + // + // The following formulas are used for decoding + // and encoding characters using surrogate pairs: + // + // U = U' + 0x10000 (0x01 00 00 <= U <= 0x10 FF FF) + // U' = yyyyyyyyyyxxxxxxxxxx (0 <= U' <= 0x0F FF FF) + // W1 = 110110yyyyyyyyyy + // W2 = 110111xxxxxxxxxx + // + // where U is the character value, W1 is the high surrogate + // area, W2 is the low surrogate area. + + // Check for incomplete UTF-16 character. + if raw_unread < 2 { + if parser.eof { + return yaml_parser_set_reader_error(parser, + "incomplete UTF-16 character", + parser.offset, -1) + } + break inner + } + + // Get the character. + value = rune(parser.raw_buffer[parser.raw_buffer_pos+low]) + + (rune(parser.raw_buffer[parser.raw_buffer_pos+high]) << 8) + + // Check for unexpected low surrogate area. + if value&0xFC00 == 0xDC00 { + return yaml_parser_set_reader_error(parser, + "unexpected low surrogate area", + parser.offset, int(value)) + } + + // Check for a high surrogate area. + if value&0xFC00 == 0xD800 { + width = 4 + + // Check for incomplete surrogate pair. + if raw_unread < 4 { + if parser.eof { + return yaml_parser_set_reader_error(parser, + "incomplete UTF-16 surrogate pair", + parser.offset, -1) + } + break inner + } + + // Get the next character. + value2 := rune(parser.raw_buffer[parser.raw_buffer_pos+low+2]) + + (rune(parser.raw_buffer[parser.raw_buffer_pos+high+2]) << 8) + + // Check for a low surrogate area. + if value2&0xFC00 != 0xDC00 { + return yaml_parser_set_reader_error(parser, + "expected low surrogate area", + parser.offset+2, int(value2)) + } + + // Generate the value of the surrogate pair. + value = 0x10000 + ((value & 0x3FF) << 10) + (value2 & 0x3FF) + } else { + width = 2 + } + + default: + panic("impossible") + } + + // Check if the character is in the allowed range: + // #x9 | #xA | #xD | [#x20-#x7E] (8 bit) + // | #x85 | [#xA0-#xD7FF] | [#xE000-#xFFFD] (16 bit) + // | [#x10000-#x10FFFF] (32 bit) + switch { + case value == 0x09: + case value == 0x0A: + case value == 0x0D: + case value >= 0x20 && value <= 0x7E: + case value == 0x85: + case value >= 0xA0 && value <= 0xD7FF: + case value >= 0xE000 && value <= 0xFFFD: + case value >= 0x10000 && value <= 0x10FFFF: + default: + return yaml_parser_set_reader_error(parser, + "control characters are not allowed", + parser.offset, int(value)) + } + + // Move the raw pointers. + parser.raw_buffer_pos += width + parser.offset += width + + // Finally put the character into the buffer. + if value <= 0x7F { + // 0000 0000-0000 007F . 0xxxxxxx + parser.buffer[buffer_len+0] = byte(value) + buffer_len += 1 + } else if value <= 0x7FF { + // 0000 0080-0000 07FF . 110xxxxx 10xxxxxx + parser.buffer[buffer_len+0] = byte(0xC0 + (value >> 6)) + parser.buffer[buffer_len+1] = byte(0x80 + (value & 0x3F)) + buffer_len += 2 + } else if value <= 0xFFFF { + // 0000 0800-0000 FFFF . 1110xxxx 10xxxxxx 10xxxxxx + parser.buffer[buffer_len+0] = byte(0xE0 + (value >> 12)) + parser.buffer[buffer_len+1] = byte(0x80 + ((value >> 6) & 0x3F)) + parser.buffer[buffer_len+2] = byte(0x80 + (value & 0x3F)) + buffer_len += 3 + } else { + // 0001 0000-0010 FFFF . 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx + parser.buffer[buffer_len+0] = byte(0xF0 + (value >> 18)) + parser.buffer[buffer_len+1] = byte(0x80 + ((value >> 12) & 0x3F)) + parser.buffer[buffer_len+2] = byte(0x80 + ((value >> 6) & 0x3F)) + parser.buffer[buffer_len+3] = byte(0x80 + (value & 0x3F)) + buffer_len += 4 + } + + parser.unread++ + } + + // On EOF, put NUL into the buffer and return. + if parser.eof { + parser.buffer[buffer_len] = 0 + buffer_len++ + parser.unread++ + break + } + } + // [Go] Read the documentation of this function above. To return true, + // we need to have the given length in the buffer. Not doing that means + // every single check that calls this function to make sure the buffer + // has a given length is Go) panicking; or C) accessing invalid memory. + // This happens here due to the EOF above breaking early. + for buffer_len < length { + parser.buffer[buffer_len] = 0 + buffer_len++ + } + parser.buffer = parser.buffer[:buffer_len] + return true +} diff --git a/index/server/vendor/github.com/oasdiff/yaml3/resolve.go b/index/server/vendor/github.com/oasdiff/yaml3/resolve.go new file mode 100644 index 000000000..64ae88805 --- /dev/null +++ b/index/server/vendor/github.com/oasdiff/yaml3/resolve.go @@ -0,0 +1,326 @@ +// +// Copyright (c) 2011-2019 Canonical Ltd +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package yaml + +import ( + "encoding/base64" + "math" + "regexp" + "strconv" + "strings" + "time" +) + +type resolveMapItem struct { + value interface{} + tag string +} + +var resolveTable = make([]byte, 256) +var resolveMap = make(map[string]resolveMapItem) + +func init() { + t := resolveTable + t[int('+')] = 'S' // Sign + t[int('-')] = 'S' + for _, c := range "0123456789" { + t[int(c)] = 'D' // Digit + } + for _, c := range "yYnNtTfFoO~" { + t[int(c)] = 'M' // In map + } + t[int('.')] = '.' // Float (potentially in map) + + var resolveMapList = []struct { + v interface{} + tag string + l []string + }{ + {true, boolTag, []string{"true", "True", "TRUE"}}, + {false, boolTag, []string{"false", "False", "FALSE"}}, + {nil, nullTag, []string{"", "~", "null", "Null", "NULL"}}, + {math.NaN(), floatTag, []string{".nan", ".NaN", ".NAN"}}, + {math.Inf(+1), floatTag, []string{".inf", ".Inf", ".INF"}}, + {math.Inf(+1), floatTag, []string{"+.inf", "+.Inf", "+.INF"}}, + {math.Inf(-1), floatTag, []string{"-.inf", "-.Inf", "-.INF"}}, + {"<<", mergeTag, []string{"<<"}}, + } + + m := resolveMap + for _, item := range resolveMapList { + for _, s := range item.l { + m[s] = resolveMapItem{item.v, item.tag} + } + } +} + +const ( + nullTag = "!!null" + boolTag = "!!bool" + strTag = "!!str" + intTag = "!!int" + floatTag = "!!float" + timestampTag = "!!timestamp" + seqTag = "!!seq" + mapTag = "!!map" + binaryTag = "!!binary" + mergeTag = "!!merge" +) + +var longTags = make(map[string]string) +var shortTags = make(map[string]string) + +func init() { + for _, stag := range []string{nullTag, boolTag, strTag, intTag, floatTag, timestampTag, seqTag, mapTag, binaryTag, mergeTag} { + ltag := longTag(stag) + longTags[stag] = ltag + shortTags[ltag] = stag + } +} + +const longTagPrefix = "tag:yaml.org,2002:" + +func shortTag(tag string) string { + if strings.HasPrefix(tag, longTagPrefix) { + if stag, ok := shortTags[tag]; ok { + return stag + } + return "!!" + tag[len(longTagPrefix):] + } + return tag +} + +func longTag(tag string) string { + if strings.HasPrefix(tag, "!!") { + if ltag, ok := longTags[tag]; ok { + return ltag + } + return longTagPrefix + tag[2:] + } + return tag +} + +func resolvableTag(tag string) bool { + switch tag { + case "", strTag, boolTag, intTag, floatTag, nullTag, timestampTag: + return true + } + return false +} + +var yamlStyleFloat = regexp.MustCompile(`^[-+]?(\.[0-9]+|[0-9]+(\.[0-9]*)?)([eE][-+]?[0-9]+)?$`) + +func resolve(tag string, in string) (rtag string, out interface{}) { + tag = shortTag(tag) + if !resolvableTag(tag) { + return tag, in + } + + defer func() { + switch tag { + case "", rtag, strTag, binaryTag: + return + case floatTag: + if rtag == intTag { + switch v := out.(type) { + case int64: + rtag = floatTag + out = float64(v) + return + case int: + rtag = floatTag + out = float64(v) + return + } + } + } + failf("cannot decode %s `%s` as a %s", shortTag(rtag), in, shortTag(tag)) + }() + + // Any data is accepted as a !!str or !!binary. + // Otherwise, the prefix is enough of a hint about what it might be. + hint := byte('N') + if in != "" { + hint = resolveTable[in[0]] + } + if hint != 0 && tag != strTag && tag != binaryTag { + // Handle things we can lookup in a map. + if item, ok := resolveMap[in]; ok { + return item.tag, item.value + } + + // Base 60 floats are a bad idea, were dropped in YAML 1.2, and + // are purposefully unsupported here. They're still quoted on + // the way out for compatibility with other parser, though. + + switch hint { + case 'M': + // We've already checked the map above. + + case '.': + // Not in the map, so maybe a normal float. + floatv, err := strconv.ParseFloat(in, 64) + if err == nil { + return floatTag, floatv + } + + case 'D', 'S': + // Int, float, or timestamp. + // Only try values as a timestamp if the value is unquoted or there's an explicit + // !!timestamp tag. + if tag == "" || tag == timestampTag { + t, ok := parseTimestamp(in) + if ok { + return timestampTag, t + } + } + + plain := strings.Replace(in, "_", "", -1) + intv, err := strconv.ParseInt(plain, 0, 64) + if err == nil { + if intv == int64(int(intv)) { + return intTag, int(intv) + } else { + return intTag, intv + } + } + uintv, err := strconv.ParseUint(plain, 0, 64) + if err == nil { + return intTag, uintv + } + if yamlStyleFloat.MatchString(plain) { + floatv, err := strconv.ParseFloat(plain, 64) + if err == nil { + return floatTag, floatv + } + } + if strings.HasPrefix(plain, "0b") { + intv, err := strconv.ParseInt(plain[2:], 2, 64) + if err == nil { + if intv == int64(int(intv)) { + return intTag, int(intv) + } else { + return intTag, intv + } + } + uintv, err := strconv.ParseUint(plain[2:], 2, 64) + if err == nil { + return intTag, uintv + } + } else if strings.HasPrefix(plain, "-0b") { + intv, err := strconv.ParseInt("-"+plain[3:], 2, 64) + if err == nil { + if true || intv == int64(int(intv)) { + return intTag, int(intv) + } else { + return intTag, intv + } + } + } + // Octals as introduced in version 1.2 of the spec. + // Octals from the 1.1 spec, spelled as 0777, are still + // decoded by default in v3 as well for compatibility. + // May be dropped in v4 depending on how usage evolves. + if strings.HasPrefix(plain, "0o") { + intv, err := strconv.ParseInt(plain[2:], 8, 64) + if err == nil { + if intv == int64(int(intv)) { + return intTag, int(intv) + } else { + return intTag, intv + } + } + uintv, err := strconv.ParseUint(plain[2:], 8, 64) + if err == nil { + return intTag, uintv + } + } else if strings.HasPrefix(plain, "-0o") { + intv, err := strconv.ParseInt("-"+plain[3:], 8, 64) + if err == nil { + if true || intv == int64(int(intv)) { + return intTag, int(intv) + } else { + return intTag, intv + } + } + } + default: + panic("internal error: missing handler for resolver table: " + string(rune(hint)) + " (with " + in + ")") + } + } + return strTag, in +} + +// encodeBase64 encodes s as base64 that is broken up into multiple lines +// as appropriate for the resulting length. +func encodeBase64(s string) string { + const lineLen = 70 + encLen := base64.StdEncoding.EncodedLen(len(s)) + lines := encLen/lineLen + 1 + buf := make([]byte, encLen*2+lines) + in := buf[0:encLen] + out := buf[encLen:] + base64.StdEncoding.Encode(in, []byte(s)) + k := 0 + for i := 0; i < len(in); i += lineLen { + j := i + lineLen + if j > len(in) { + j = len(in) + } + k += copy(out[k:], in[i:j]) + if lines > 1 { + out[k] = '\n' + k++ + } + } + return string(out[:k]) +} + +// This is a subset of the formats allowed by the regular expression +// defined at http://yaml.org/type/timestamp.html. +var allowedTimestampFormats = []string{ + "2006-1-2T15:4:5.999999999Z07:00", // RCF3339Nano with short date fields. + "2006-1-2t15:4:5.999999999Z07:00", // RFC3339Nano with short date fields and lower-case "t". + "2006-1-2 15:4:5.999999999", // space separated with no time zone + "2006-1-2", // date only + // Notable exception: time.Parse cannot handle: "2001-12-14 21:59:43.10 -5" + // from the set of examples. +} + +// parseTimestamp parses s as a timestamp string and +// returns the timestamp and reports whether it succeeded. +// Timestamp formats are defined at http://yaml.org/type/timestamp.html +func parseTimestamp(s string) (time.Time, bool) { + // TODO write code to check all the formats supported by + // http://yaml.org/type/timestamp.html instead of using time.Parse. + + // Quick check: all date formats start with YYYY-. + i := 0 + for ; i < len(s); i++ { + if c := s[i]; c < '0' || c > '9' { + break + } + } + if i != 4 || i == len(s) || s[i] != '-' { + return time.Time{}, false + } + for _, format := range allowedTimestampFormats { + if t, err := time.Parse(format, s); err == nil { + return t, true + } + } + return time.Time{}, false +} diff --git a/index/server/vendor/github.com/oasdiff/yaml3/scannerc.go b/index/server/vendor/github.com/oasdiff/yaml3/scannerc.go new file mode 100644 index 000000000..ca0070108 --- /dev/null +++ b/index/server/vendor/github.com/oasdiff/yaml3/scannerc.go @@ -0,0 +1,3038 @@ +// +// Copyright (c) 2011-2019 Canonical Ltd +// Copyright (c) 2006-2010 Kirill Simonov +// +// 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. + +package yaml + +import ( + "bytes" + "fmt" +) + +// Introduction +// ************ +// +// The following notes assume that you are familiar with the YAML specification +// (http://yaml.org/spec/1.2/spec.html). We mostly follow it, although in +// some cases we are less restrictive that it requires. +// +// The process of transforming a YAML stream into a sequence of events is +// divided on two steps: Scanning and Parsing. +// +// The Scanner transforms the input stream into a sequence of tokens, while the +// parser transform the sequence of tokens produced by the Scanner into a +// sequence of parsing events. +// +// The Scanner is rather clever and complicated. The Parser, on the contrary, +// is a straightforward implementation of a recursive-descendant parser (or, +// LL(1) parser, as it is usually called). +// +// Actually there are two issues of Scanning that might be called "clever", the +// rest is quite straightforward. The issues are "block collection start" and +// "simple keys". Both issues are explained below in details. +// +// Here the Scanning step is explained and implemented. We start with the list +// of all the tokens produced by the Scanner together with short descriptions. +// +// Now, tokens: +// +// STREAM-START(encoding) # The stream start. +// STREAM-END # The stream end. +// VERSION-DIRECTIVE(major,minor) # The '%YAML' directive. +// TAG-DIRECTIVE(handle,prefix) # The '%TAG' directive. +// DOCUMENT-START # '---' +// DOCUMENT-END # '...' +// BLOCK-SEQUENCE-START # Indentation increase denoting a block +// BLOCK-MAPPING-START # sequence or a block mapping. +// BLOCK-END # Indentation decrease. +// FLOW-SEQUENCE-START # '[' +// FLOW-SEQUENCE-END # ']' +// BLOCK-SEQUENCE-START # '{' +// BLOCK-SEQUENCE-END # '}' +// BLOCK-ENTRY # '-' +// FLOW-ENTRY # ',' +// KEY # '?' or nothing (simple keys). +// VALUE # ':' +// ALIAS(anchor) # '*anchor' +// ANCHOR(anchor) # '&anchor' +// TAG(handle,suffix) # '!handle!suffix' +// SCALAR(value,style) # A scalar. +// +// The following two tokens are "virtual" tokens denoting the beginning and the +// end of the stream: +// +// STREAM-START(encoding) +// STREAM-END +// +// We pass the information about the input stream encoding with the +// STREAM-START token. +// +// The next two tokens are responsible for tags: +// +// VERSION-DIRECTIVE(major,minor) +// TAG-DIRECTIVE(handle,prefix) +// +// Example: +// +// %YAML 1.1 +// %TAG ! !foo +// %TAG !yaml! tag:yaml.org,2002: +// --- +// +// The correspoding sequence of tokens: +// +// STREAM-START(utf-8) +// VERSION-DIRECTIVE(1,1) +// TAG-DIRECTIVE("!","!foo") +// TAG-DIRECTIVE("!yaml","tag:yaml.org,2002:") +// DOCUMENT-START +// STREAM-END +// +// Note that the VERSION-DIRECTIVE and TAG-DIRECTIVE tokens occupy a whole +// line. +// +// The document start and end indicators are represented by: +// +// DOCUMENT-START +// DOCUMENT-END +// +// Note that if a YAML stream contains an implicit document (without '---' +// and '...' indicators), no DOCUMENT-START and DOCUMENT-END tokens will be +// produced. +// +// In the following examples, we present whole documents together with the +// produced tokens. +// +// 1. An implicit document: +// +// 'a scalar' +// +// Tokens: +// +// STREAM-START(utf-8) +// SCALAR("a scalar",single-quoted) +// STREAM-END +// +// 2. An explicit document: +// +// --- +// 'a scalar' +// ... +// +// Tokens: +// +// STREAM-START(utf-8) +// DOCUMENT-START +// SCALAR("a scalar",single-quoted) +// DOCUMENT-END +// STREAM-END +// +// 3. Several documents in a stream: +// +// 'a scalar' +// --- +// 'another scalar' +// --- +// 'yet another scalar' +// +// Tokens: +// +// STREAM-START(utf-8) +// SCALAR("a scalar",single-quoted) +// DOCUMENT-START +// SCALAR("another scalar",single-quoted) +// DOCUMENT-START +// SCALAR("yet another scalar",single-quoted) +// STREAM-END +// +// We have already introduced the SCALAR token above. The following tokens are +// used to describe aliases, anchors, tag, and scalars: +// +// ALIAS(anchor) +// ANCHOR(anchor) +// TAG(handle,suffix) +// SCALAR(value,style) +// +// The following series of examples illustrate the usage of these tokens: +// +// 1. A recursive sequence: +// +// &A [ *A ] +// +// Tokens: +// +// STREAM-START(utf-8) +// ANCHOR("A") +// FLOW-SEQUENCE-START +// ALIAS("A") +// FLOW-SEQUENCE-END +// STREAM-END +// +// 2. A tagged scalar: +// +// !!float "3.14" # A good approximation. +// +// Tokens: +// +// STREAM-START(utf-8) +// TAG("!!","float") +// SCALAR("3.14",double-quoted) +// STREAM-END +// +// 3. Various scalar styles: +// +// --- # Implicit empty plain scalars do not produce tokens. +// --- a plain scalar +// --- 'a single-quoted scalar' +// --- "a double-quoted scalar" +// --- |- +// a literal scalar +// --- >- +// a folded +// scalar +// +// Tokens: +// +// STREAM-START(utf-8) +// DOCUMENT-START +// DOCUMENT-START +// SCALAR("a plain scalar",plain) +// DOCUMENT-START +// SCALAR("a single-quoted scalar",single-quoted) +// DOCUMENT-START +// SCALAR("a double-quoted scalar",double-quoted) +// DOCUMENT-START +// SCALAR("a literal scalar",literal) +// DOCUMENT-START +// SCALAR("a folded scalar",folded) +// STREAM-END +// +// Now it's time to review collection-related tokens. We will start with +// flow collections: +// +// FLOW-SEQUENCE-START +// FLOW-SEQUENCE-END +// FLOW-MAPPING-START +// FLOW-MAPPING-END +// FLOW-ENTRY +// KEY +// VALUE +// +// The tokens FLOW-SEQUENCE-START, FLOW-SEQUENCE-END, FLOW-MAPPING-START, and +// FLOW-MAPPING-END represent the indicators '[', ']', '{', and '}' +// correspondingly. FLOW-ENTRY represent the ',' indicator. Finally the +// indicators '?' and ':', which are used for denoting mapping keys and values, +// are represented by the KEY and VALUE tokens. +// +// The following examples show flow collections: +// +// 1. A flow sequence: +// +// [item 1, item 2, item 3] +// +// Tokens: +// +// STREAM-START(utf-8) +// FLOW-SEQUENCE-START +// SCALAR("item 1",plain) +// FLOW-ENTRY +// SCALAR("item 2",plain) +// FLOW-ENTRY +// SCALAR("item 3",plain) +// FLOW-SEQUENCE-END +// STREAM-END +// +// 2. A flow mapping: +// +// { +// a simple key: a value, # Note that the KEY token is produced. +// ? a complex key: another value, +// } +// +// Tokens: +// +// STREAM-START(utf-8) +// FLOW-MAPPING-START +// KEY +// SCALAR("a simple key",plain) +// VALUE +// SCALAR("a value",plain) +// FLOW-ENTRY +// KEY +// SCALAR("a complex key",plain) +// VALUE +// SCALAR("another value",plain) +// FLOW-ENTRY +// FLOW-MAPPING-END +// STREAM-END +// +// A simple key is a key which is not denoted by the '?' indicator. Note that +// the Scanner still produce the KEY token whenever it encounters a simple key. +// +// For scanning block collections, the following tokens are used (note that we +// repeat KEY and VALUE here): +// +// BLOCK-SEQUENCE-START +// BLOCK-MAPPING-START +// BLOCK-END +// BLOCK-ENTRY +// KEY +// VALUE +// +// The tokens BLOCK-SEQUENCE-START and BLOCK-MAPPING-START denote indentation +// increase that precedes a block collection (cf. the INDENT token in Python). +// The token BLOCK-END denote indentation decrease that ends a block collection +// (cf. the DEDENT token in Python). However YAML has some syntax pecularities +// that makes detections of these tokens more complex. +// +// The tokens BLOCK-ENTRY, KEY, and VALUE are used to represent the indicators +// '-', '?', and ':' correspondingly. +// +// The following examples show how the tokens BLOCK-SEQUENCE-START, +// BLOCK-MAPPING-START, and BLOCK-END are emitted by the Scanner: +// +// 1. Block sequences: +// +// - item 1 +// - item 2 +// - +// - item 3.1 +// - item 3.2 +// - +// key 1: value 1 +// key 2: value 2 +// +// Tokens: +// +// STREAM-START(utf-8) +// BLOCK-SEQUENCE-START +// BLOCK-ENTRY +// SCALAR("item 1",plain) +// BLOCK-ENTRY +// SCALAR("item 2",plain) +// BLOCK-ENTRY +// BLOCK-SEQUENCE-START +// BLOCK-ENTRY +// SCALAR("item 3.1",plain) +// BLOCK-ENTRY +// SCALAR("item 3.2",plain) +// BLOCK-END +// BLOCK-ENTRY +// BLOCK-MAPPING-START +// KEY +// SCALAR("key 1",plain) +// VALUE +// SCALAR("value 1",plain) +// KEY +// SCALAR("key 2",plain) +// VALUE +// SCALAR("value 2",plain) +// BLOCK-END +// BLOCK-END +// STREAM-END +// +// 2. Block mappings: +// +// a simple key: a value # The KEY token is produced here. +// ? a complex key +// : another value +// a mapping: +// key 1: value 1 +// key 2: value 2 +// a sequence: +// - item 1 +// - item 2 +// +// Tokens: +// +// STREAM-START(utf-8) +// BLOCK-MAPPING-START +// KEY +// SCALAR("a simple key",plain) +// VALUE +// SCALAR("a value",plain) +// KEY +// SCALAR("a complex key",plain) +// VALUE +// SCALAR("another value",plain) +// KEY +// SCALAR("a mapping",plain) +// BLOCK-MAPPING-START +// KEY +// SCALAR("key 1",plain) +// VALUE +// SCALAR("value 1",plain) +// KEY +// SCALAR("key 2",plain) +// VALUE +// SCALAR("value 2",plain) +// BLOCK-END +// KEY +// SCALAR("a sequence",plain) +// VALUE +// BLOCK-SEQUENCE-START +// BLOCK-ENTRY +// SCALAR("item 1",plain) +// BLOCK-ENTRY +// SCALAR("item 2",plain) +// BLOCK-END +// BLOCK-END +// STREAM-END +// +// YAML does not always require to start a new block collection from a new +// line. If the current line contains only '-', '?', and ':' indicators, a new +// block collection may start at the current line. The following examples +// illustrate this case: +// +// 1. Collections in a sequence: +// +// - - item 1 +// - item 2 +// - key 1: value 1 +// key 2: value 2 +// - ? complex key +// : complex value +// +// Tokens: +// +// STREAM-START(utf-8) +// BLOCK-SEQUENCE-START +// BLOCK-ENTRY +// BLOCK-SEQUENCE-START +// BLOCK-ENTRY +// SCALAR("item 1",plain) +// BLOCK-ENTRY +// SCALAR("item 2",plain) +// BLOCK-END +// BLOCK-ENTRY +// BLOCK-MAPPING-START +// KEY +// SCALAR("key 1",plain) +// VALUE +// SCALAR("value 1",plain) +// KEY +// SCALAR("key 2",plain) +// VALUE +// SCALAR("value 2",plain) +// BLOCK-END +// BLOCK-ENTRY +// BLOCK-MAPPING-START +// KEY +// SCALAR("complex key") +// VALUE +// SCALAR("complex value") +// BLOCK-END +// BLOCK-END +// STREAM-END +// +// 2. Collections in a mapping: +// +// ? a sequence +// : - item 1 +// - item 2 +// ? a mapping +// : key 1: value 1 +// key 2: value 2 +// +// Tokens: +// +// STREAM-START(utf-8) +// BLOCK-MAPPING-START +// KEY +// SCALAR("a sequence",plain) +// VALUE +// BLOCK-SEQUENCE-START +// BLOCK-ENTRY +// SCALAR("item 1",plain) +// BLOCK-ENTRY +// SCALAR("item 2",plain) +// BLOCK-END +// KEY +// SCALAR("a mapping",plain) +// VALUE +// BLOCK-MAPPING-START +// KEY +// SCALAR("key 1",plain) +// VALUE +// SCALAR("value 1",plain) +// KEY +// SCALAR("key 2",plain) +// VALUE +// SCALAR("value 2",plain) +// BLOCK-END +// BLOCK-END +// STREAM-END +// +// YAML also permits non-indented sequences if they are included into a block +// mapping. In this case, the token BLOCK-SEQUENCE-START is not produced: +// +// key: +// - item 1 # BLOCK-SEQUENCE-START is NOT produced here. +// - item 2 +// +// Tokens: +// +// STREAM-START(utf-8) +// BLOCK-MAPPING-START +// KEY +// SCALAR("key",plain) +// VALUE +// BLOCK-ENTRY +// SCALAR("item 1",plain) +// BLOCK-ENTRY +// SCALAR("item 2",plain) +// BLOCK-END +// + +// Ensure that the buffer contains the required number of characters. +// Return true on success, false on failure (reader error or memory error). +func cache(parser *yaml_parser_t, length int) bool { + // [Go] This was inlined: !cache(A, B) -> unread < B && !update(A, B) + return parser.unread >= length || yaml_parser_update_buffer(parser, length) +} + +// Advance the buffer pointer. +func skip(parser *yaml_parser_t) { + if !is_blank(parser.buffer, parser.buffer_pos) { + parser.newlines = 0 + } + parser.mark.index++ + parser.mark.column++ + parser.unread-- + parser.buffer_pos += width(parser.buffer[parser.buffer_pos]) +} + +func skip_line(parser *yaml_parser_t) { + if is_crlf(parser.buffer, parser.buffer_pos) { + parser.mark.index += 2 + parser.mark.column = 0 + parser.mark.line++ + parser.unread -= 2 + parser.buffer_pos += 2 + parser.newlines++ + } else if is_break(parser.buffer, parser.buffer_pos) { + parser.mark.index++ + parser.mark.column = 0 + parser.mark.line++ + parser.unread-- + parser.buffer_pos += width(parser.buffer[parser.buffer_pos]) + parser.newlines++ + } +} + +// Copy a character to a string buffer and advance pointers. +func read(parser *yaml_parser_t, s []byte) []byte { + if !is_blank(parser.buffer, parser.buffer_pos) { + parser.newlines = 0 + } + w := width(parser.buffer[parser.buffer_pos]) + if w == 0 { + panic("invalid character sequence") + } + if len(s) == 0 { + s = make([]byte, 0, 32) + } + if w == 1 && len(s)+w <= cap(s) { + s = s[:len(s)+1] + s[len(s)-1] = parser.buffer[parser.buffer_pos] + parser.buffer_pos++ + } else { + s = append(s, parser.buffer[parser.buffer_pos:parser.buffer_pos+w]...) + parser.buffer_pos += w + } + parser.mark.index++ + parser.mark.column++ + parser.unread-- + return s +} + +// Copy a line break character to a string buffer and advance pointers. +func read_line(parser *yaml_parser_t, s []byte) []byte { + buf := parser.buffer + pos := parser.buffer_pos + switch { + case buf[pos] == '\r' && buf[pos+1] == '\n': + // CR LF . LF + s = append(s, '\n') + parser.buffer_pos += 2 + parser.mark.index++ + parser.unread-- + case buf[pos] == '\r' || buf[pos] == '\n': + // CR|LF . LF + s = append(s, '\n') + parser.buffer_pos += 1 + case buf[pos] == '\xC2' && buf[pos+1] == '\x85': + // NEL . LF + s = append(s, '\n') + parser.buffer_pos += 2 + case buf[pos] == '\xE2' && buf[pos+1] == '\x80' && (buf[pos+2] == '\xA8' || buf[pos+2] == '\xA9'): + // LS|PS . LS|PS + s = append(s, buf[parser.buffer_pos:pos+3]...) + parser.buffer_pos += 3 + default: + return s + } + parser.mark.index++ + parser.mark.column = 0 + parser.mark.line++ + parser.unread-- + parser.newlines++ + return s +} + +// Get the next token. +func yaml_parser_scan(parser *yaml_parser_t, token *yaml_token_t) bool { + // Erase the token object. + *token = yaml_token_t{} // [Go] Is this necessary? + + // No tokens after STREAM-END or error. + if parser.stream_end_produced || parser.error != yaml_NO_ERROR { + return true + } + + // Ensure that the tokens queue contains enough tokens. + if !parser.token_available { + if !yaml_parser_fetch_more_tokens(parser) { + return false + } + } + + // Fetch the next token from the queue. + *token = parser.tokens[parser.tokens_head] + parser.tokens_head++ + parser.tokens_parsed++ + parser.token_available = false + + if token.typ == yaml_STREAM_END_TOKEN { + parser.stream_end_produced = true + } + return true +} + +// Set the scanner error and return false. +func yaml_parser_set_scanner_error(parser *yaml_parser_t, context string, context_mark yaml_mark_t, problem string) bool { + parser.error = yaml_SCANNER_ERROR + parser.context = context + parser.context_mark = context_mark + parser.problem = problem + parser.problem_mark = parser.mark + return false +} + +func yaml_parser_set_scanner_tag_error(parser *yaml_parser_t, directive bool, context_mark yaml_mark_t, problem string) bool { + context := "while parsing a tag" + if directive { + context = "while parsing a %TAG directive" + } + return yaml_parser_set_scanner_error(parser, context, context_mark, problem) +} + +func trace(args ...interface{}) func() { + pargs := append([]interface{}{"+++"}, args...) + fmt.Println(pargs...) + pargs = append([]interface{}{"---"}, args...) + return func() { fmt.Println(pargs...) } +} + +// Ensure that the tokens queue contains at least one token which can be +// returned to the Parser. +func yaml_parser_fetch_more_tokens(parser *yaml_parser_t) bool { + // While we need more tokens to fetch, do it. + for { + // [Go] The comment parsing logic requires a lookahead of two tokens + // so that foot comments may be parsed in time of associating them + // with the tokens that are parsed before them, and also for line + // comments to be transformed into head comments in some edge cases. + if parser.tokens_head < len(parser.tokens)-2 { + // If a potential simple key is at the head position, we need to fetch + // the next token to disambiguate it. + head_tok_idx, ok := parser.simple_keys_by_tok[parser.tokens_parsed] + if !ok { + break + } else if valid, ok := yaml_simple_key_is_valid(parser, &parser.simple_keys[head_tok_idx]); !ok { + return false + } else if !valid { + break + } + } + // Fetch the next token. + if !yaml_parser_fetch_next_token(parser) { + return false + } + } + + parser.token_available = true + return true +} + +// The dispatcher for token fetchers. +func yaml_parser_fetch_next_token(parser *yaml_parser_t) (ok bool) { + // Ensure that the buffer is initialized. + if parser.unread < 1 && !yaml_parser_update_buffer(parser, 1) { + return false + } + + // Check if we just started scanning. Fetch STREAM-START then. + if !parser.stream_start_produced { + return yaml_parser_fetch_stream_start(parser) + } + + scan_mark := parser.mark + + // Eat whitespaces and comments until we reach the next token. + if !yaml_parser_scan_to_next_token(parser) { + return false + } + + // [Go] While unrolling indents, transform the head comments of prior + // indentation levels observed after scan_start into foot comments at + // the respective indexes. + + // Check the indentation level against the current column. + if !yaml_parser_unroll_indent(parser, parser.mark.column, scan_mark) { + return false + } + + // Ensure that the buffer contains at least 4 characters. 4 is the length + // of the longest indicators ('--- ' and '... '). + if parser.unread < 4 && !yaml_parser_update_buffer(parser, 4) { + return false + } + + // Is it the end of the stream? + if is_z(parser.buffer, parser.buffer_pos) { + return yaml_parser_fetch_stream_end(parser) + } + + // Is it a directive? + if parser.mark.column == 0 && parser.buffer[parser.buffer_pos] == '%' { + return yaml_parser_fetch_directive(parser) + } + + buf := parser.buffer + pos := parser.buffer_pos + + // Is it the document start indicator? + if parser.mark.column == 0 && buf[pos] == '-' && buf[pos+1] == '-' && buf[pos+2] == '-' && is_blankz(buf, pos+3) { + return yaml_parser_fetch_document_indicator(parser, yaml_DOCUMENT_START_TOKEN) + } + + // Is it the document end indicator? + if parser.mark.column == 0 && buf[pos] == '.' && buf[pos+1] == '.' && buf[pos+2] == '.' && is_blankz(buf, pos+3) { + return yaml_parser_fetch_document_indicator(parser, yaml_DOCUMENT_END_TOKEN) + } + + comment_mark := parser.mark + if len(parser.tokens) > 0 && (parser.flow_level == 0 && buf[pos] == ':' || parser.flow_level > 0 && buf[pos] == ',') { + // Associate any following comments with the prior token. + comment_mark = parser.tokens[len(parser.tokens)-1].start_mark + } + defer func() { + if !ok { + return + } + if len(parser.tokens) > 0 && parser.tokens[len(parser.tokens)-1].typ == yaml_BLOCK_ENTRY_TOKEN { + // Sequence indicators alone have no line comments. It becomes + // a head comment for whatever follows. + return + } + if !yaml_parser_scan_line_comment(parser, comment_mark) { + ok = false + return + } + }() + + // Is it the flow sequence start indicator? + if buf[pos] == '[' { + return yaml_parser_fetch_flow_collection_start(parser, yaml_FLOW_SEQUENCE_START_TOKEN) + } + + // Is it the flow mapping start indicator? + if parser.buffer[parser.buffer_pos] == '{' { + return yaml_parser_fetch_flow_collection_start(parser, yaml_FLOW_MAPPING_START_TOKEN) + } + + // Is it the flow sequence end indicator? + if parser.buffer[parser.buffer_pos] == ']' { + return yaml_parser_fetch_flow_collection_end(parser, + yaml_FLOW_SEQUENCE_END_TOKEN) + } + + // Is it the flow mapping end indicator? + if parser.buffer[parser.buffer_pos] == '}' { + return yaml_parser_fetch_flow_collection_end(parser, + yaml_FLOW_MAPPING_END_TOKEN) + } + + // Is it the flow entry indicator? + if parser.buffer[parser.buffer_pos] == ',' { + return yaml_parser_fetch_flow_entry(parser) + } + + // Is it the block entry indicator? + if parser.buffer[parser.buffer_pos] == '-' && is_blankz(parser.buffer, parser.buffer_pos+1) { + return yaml_parser_fetch_block_entry(parser) + } + + // Is it the key indicator? + if parser.buffer[parser.buffer_pos] == '?' && (parser.flow_level > 0 || is_blankz(parser.buffer, parser.buffer_pos+1)) { + return yaml_parser_fetch_key(parser) + } + + // Is it the value indicator? + if parser.buffer[parser.buffer_pos] == ':' && (parser.flow_level > 0 || is_blankz(parser.buffer, parser.buffer_pos+1)) { + return yaml_parser_fetch_value(parser) + } + + // Is it an alias? + if parser.buffer[parser.buffer_pos] == '*' { + return yaml_parser_fetch_anchor(parser, yaml_ALIAS_TOKEN) + } + + // Is it an anchor? + if parser.buffer[parser.buffer_pos] == '&' { + return yaml_parser_fetch_anchor(parser, yaml_ANCHOR_TOKEN) + } + + // Is it a tag? + if parser.buffer[parser.buffer_pos] == '!' { + return yaml_parser_fetch_tag(parser) + } + + // Is it a literal scalar? + if parser.buffer[parser.buffer_pos] == '|' && parser.flow_level == 0 { + return yaml_parser_fetch_block_scalar(parser, true) + } + + // Is it a folded scalar? + if parser.buffer[parser.buffer_pos] == '>' && parser.flow_level == 0 { + return yaml_parser_fetch_block_scalar(parser, false) + } + + // Is it a single-quoted scalar? + if parser.buffer[parser.buffer_pos] == '\'' { + return yaml_parser_fetch_flow_scalar(parser, true) + } + + // Is it a double-quoted scalar? + if parser.buffer[parser.buffer_pos] == '"' { + return yaml_parser_fetch_flow_scalar(parser, false) + } + + // Is it a plain scalar? + // + // A plain scalar may start with any non-blank characters except + // + // '-', '?', ':', ',', '[', ']', '{', '}', + // '#', '&', '*', '!', '|', '>', '\'', '\"', + // '%', '@', '`'. + // + // In the block context (and, for the '-' indicator, in the flow context + // too), it may also start with the characters + // + // '-', '?', ':' + // + // if it is followed by a non-space character. + // + // The last rule is more restrictive than the specification requires. + // [Go] TODO Make this logic more reasonable. + //switch parser.buffer[parser.buffer_pos] { + //case '-', '?', ':', ',', '?', '-', ',', ':', ']', '[', '}', '{', '&', '#', '!', '*', '>', '|', '"', '\'', '@', '%', '-', '`': + //} + if !(is_blankz(parser.buffer, parser.buffer_pos) || parser.buffer[parser.buffer_pos] == '-' || + parser.buffer[parser.buffer_pos] == '?' || parser.buffer[parser.buffer_pos] == ':' || + parser.buffer[parser.buffer_pos] == ',' || parser.buffer[parser.buffer_pos] == '[' || + parser.buffer[parser.buffer_pos] == ']' || parser.buffer[parser.buffer_pos] == '{' || + parser.buffer[parser.buffer_pos] == '}' || parser.buffer[parser.buffer_pos] == '#' || + parser.buffer[parser.buffer_pos] == '&' || parser.buffer[parser.buffer_pos] == '*' || + parser.buffer[parser.buffer_pos] == '!' || parser.buffer[parser.buffer_pos] == '|' || + parser.buffer[parser.buffer_pos] == '>' || parser.buffer[parser.buffer_pos] == '\'' || + parser.buffer[parser.buffer_pos] == '"' || parser.buffer[parser.buffer_pos] == '%' || + parser.buffer[parser.buffer_pos] == '@' || parser.buffer[parser.buffer_pos] == '`') || + (parser.buffer[parser.buffer_pos] == '-' && !is_blank(parser.buffer, parser.buffer_pos+1)) || + (parser.flow_level == 0 && + (parser.buffer[parser.buffer_pos] == '?' || parser.buffer[parser.buffer_pos] == ':') && + !is_blankz(parser.buffer, parser.buffer_pos+1)) { + return yaml_parser_fetch_plain_scalar(parser) + } + + // If we don't determine the token type so far, it is an error. + return yaml_parser_set_scanner_error(parser, + "while scanning for the next token", parser.mark, + "found character that cannot start any token") +} + +func yaml_simple_key_is_valid(parser *yaml_parser_t, simple_key *yaml_simple_key_t) (valid, ok bool) { + if !simple_key.possible { + return false, true + } + + // The 1.2 specification says: + // + // "If the ? indicator is omitted, parsing needs to see past the + // implicit key to recognize it as such. To limit the amount of + // lookahead required, the “:” indicator must appear at most 1024 + // Unicode characters beyond the start of the key. In addition, the key + // is restricted to a single line." + // + if simple_key.mark.line < parser.mark.line || simple_key.mark.index+1024 < parser.mark.index { + // Check if the potential simple key to be removed is required. + if simple_key.required { + return false, yaml_parser_set_scanner_error(parser, + "while scanning a simple key", simple_key.mark, + "could not find expected ':'") + } + simple_key.possible = false + return false, true + } + return true, true +} + +// Check if a simple key may start at the current position and add it if +// needed. +func yaml_parser_save_simple_key(parser *yaml_parser_t) bool { + // A simple key is required at the current position if the scanner is in + // the block context and the current column coincides with the indentation + // level. + + required := parser.flow_level == 0 && parser.indent == parser.mark.column + + // + // If the current position may start a simple key, save it. + // + if parser.simple_key_allowed { + simple_key := yaml_simple_key_t{ + possible: true, + required: required, + token_number: parser.tokens_parsed + (len(parser.tokens) - parser.tokens_head), + mark: parser.mark, + } + + if !yaml_parser_remove_simple_key(parser) { + return false + } + parser.simple_keys[len(parser.simple_keys)-1] = simple_key + parser.simple_keys_by_tok[simple_key.token_number] = len(parser.simple_keys) - 1 + } + return true +} + +// Remove a potential simple key at the current flow level. +func yaml_parser_remove_simple_key(parser *yaml_parser_t) bool { + i := len(parser.simple_keys) - 1 + if parser.simple_keys[i].possible { + // If the key is required, it is an error. + if parser.simple_keys[i].required { + return yaml_parser_set_scanner_error(parser, + "while scanning a simple key", parser.simple_keys[i].mark, + "could not find expected ':'") + } + // Remove the key from the stack. + parser.simple_keys[i].possible = false + delete(parser.simple_keys_by_tok, parser.simple_keys[i].token_number) + } + return true +} + +// max_flow_level limits the flow_level +const max_flow_level = 10000 + +// Increase the flow level and resize the simple key list if needed. +func yaml_parser_increase_flow_level(parser *yaml_parser_t) bool { + // Reset the simple key on the next level. + parser.simple_keys = append(parser.simple_keys, yaml_simple_key_t{ + possible: false, + required: false, + token_number: parser.tokens_parsed + (len(parser.tokens) - parser.tokens_head), + mark: parser.mark, + }) + + // Increase the flow level. + parser.flow_level++ + if parser.flow_level > max_flow_level { + return yaml_parser_set_scanner_error(parser, + "while increasing flow level", parser.simple_keys[len(parser.simple_keys)-1].mark, + fmt.Sprintf("exceeded max depth of %d", max_flow_level)) + } + return true +} + +// Decrease the flow level. +func yaml_parser_decrease_flow_level(parser *yaml_parser_t) bool { + if parser.flow_level > 0 { + parser.flow_level-- + last := len(parser.simple_keys) - 1 + delete(parser.simple_keys_by_tok, parser.simple_keys[last].token_number) + parser.simple_keys = parser.simple_keys[:last] + } + return true +} + +// max_indents limits the indents stack size +const max_indents = 10000 + +// Push the current indentation level to the stack and set the new level +// the current column is greater than the indentation level. In this case, +// append or insert the specified token into the token queue. +func yaml_parser_roll_indent(parser *yaml_parser_t, column, number int, typ yaml_token_type_t, mark yaml_mark_t) bool { + // In the flow context, do nothing. + if parser.flow_level > 0 { + return true + } + + if parser.indent < column { + // Push the current indentation level to the stack and set the new + // indentation level. + parser.indents = append(parser.indents, parser.indent) + parser.indent = column + if len(parser.indents) > max_indents { + return yaml_parser_set_scanner_error(parser, + "while increasing indent level", parser.simple_keys[len(parser.simple_keys)-1].mark, + fmt.Sprintf("exceeded max depth of %d", max_indents)) + } + + // Create a token and insert it into the queue. + token := yaml_token_t{ + typ: typ, + start_mark: mark, + end_mark: mark, + } + if number > -1 { + number -= parser.tokens_parsed + } + yaml_insert_token(parser, number, &token) + } + return true +} + +// Pop indentation levels from the indents stack until the current level +// becomes less or equal to the column. For each indentation level, append +// the BLOCK-END token. +func yaml_parser_unroll_indent(parser *yaml_parser_t, column int, scan_mark yaml_mark_t) bool { + // In the flow context, do nothing. + if parser.flow_level > 0 { + return true + } + + block_mark := scan_mark + block_mark.index-- + + // Loop through the indentation levels in the stack. + for parser.indent > column { + + // [Go] Reposition the end token before potential following + // foot comments of parent blocks. For that, search + // backwards for recent comments that were at the same + // indent as the block that is ending now. + stop_index := block_mark.index + for i := len(parser.comments) - 1; i >= 0; i-- { + comment := &parser.comments[i] + + if comment.end_mark.index < stop_index { + // Don't go back beyond the start of the comment/whitespace scan, unless column < 0. + // If requested indent column is < 0, then the document is over and everything else + // is a foot anyway. + break + } + if comment.start_mark.column == parser.indent+1 { + // This is a good match. But maybe there's a former comment + // at that same indent level, so keep searching. + block_mark = comment.start_mark + } + + // While the end of the former comment matches with + // the start of the following one, we know there's + // nothing in between and scanning is still safe. + stop_index = comment.scan_mark.index + } + + // Create a token and append it to the queue. + token := yaml_token_t{ + typ: yaml_BLOCK_END_TOKEN, + start_mark: block_mark, + end_mark: block_mark, + } + yaml_insert_token(parser, -1, &token) + + // Pop the indentation level. + parser.indent = parser.indents[len(parser.indents)-1] + parser.indents = parser.indents[:len(parser.indents)-1] + } + return true +} + +// Initialize the scanner and produce the STREAM-START token. +func yaml_parser_fetch_stream_start(parser *yaml_parser_t) bool { + + // Set the initial indentation. + parser.indent = -1 + + // Initialize the simple key stack. + parser.simple_keys = append(parser.simple_keys, yaml_simple_key_t{}) + + parser.simple_keys_by_tok = make(map[int]int) + + // A simple key is allowed at the beginning of the stream. + parser.simple_key_allowed = true + + // We have started. + parser.stream_start_produced = true + + // Create the STREAM-START token and append it to the queue. + token := yaml_token_t{ + typ: yaml_STREAM_START_TOKEN, + start_mark: parser.mark, + end_mark: parser.mark, + encoding: parser.encoding, + } + yaml_insert_token(parser, -1, &token) + return true +} + +// Produce the STREAM-END token and shut down the scanner. +func yaml_parser_fetch_stream_end(parser *yaml_parser_t) bool { + + // Force new line. + if parser.mark.column != 0 { + parser.mark.column = 0 + parser.mark.line++ + } + + // Reset the indentation level. + if !yaml_parser_unroll_indent(parser, -1, parser.mark) { + return false + } + + // Reset simple keys. + if !yaml_parser_remove_simple_key(parser) { + return false + } + + parser.simple_key_allowed = false + + // Create the STREAM-END token and append it to the queue. + token := yaml_token_t{ + typ: yaml_STREAM_END_TOKEN, + start_mark: parser.mark, + end_mark: parser.mark, + } + yaml_insert_token(parser, -1, &token) + return true +} + +// Produce a VERSION-DIRECTIVE or TAG-DIRECTIVE token. +func yaml_parser_fetch_directive(parser *yaml_parser_t) bool { + // Reset the indentation level. + if !yaml_parser_unroll_indent(parser, -1, parser.mark) { + return false + } + + // Reset simple keys. + if !yaml_parser_remove_simple_key(parser) { + return false + } + + parser.simple_key_allowed = false + + // Create the YAML-DIRECTIVE or TAG-DIRECTIVE token. + token := yaml_token_t{} + if !yaml_parser_scan_directive(parser, &token) { + return false + } + // Append the token to the queue. + yaml_insert_token(parser, -1, &token) + return true +} + +// Produce the DOCUMENT-START or DOCUMENT-END token. +func yaml_parser_fetch_document_indicator(parser *yaml_parser_t, typ yaml_token_type_t) bool { + // Reset the indentation level. + if !yaml_parser_unroll_indent(parser, -1, parser.mark) { + return false + } + + // Reset simple keys. + if !yaml_parser_remove_simple_key(parser) { + return false + } + + parser.simple_key_allowed = false + + // Consume the token. + start_mark := parser.mark + + skip(parser) + skip(parser) + skip(parser) + + end_mark := parser.mark + + // Create the DOCUMENT-START or DOCUMENT-END token. + token := yaml_token_t{ + typ: typ, + start_mark: start_mark, + end_mark: end_mark, + } + // Append the token to the queue. + yaml_insert_token(parser, -1, &token) + return true +} + +// Produce the FLOW-SEQUENCE-START or FLOW-MAPPING-START token. +func yaml_parser_fetch_flow_collection_start(parser *yaml_parser_t, typ yaml_token_type_t) bool { + + // The indicators '[' and '{' may start a simple key. + if !yaml_parser_save_simple_key(parser) { + return false + } + + // Increase the flow level. + if !yaml_parser_increase_flow_level(parser) { + return false + } + + // A simple key may follow the indicators '[' and '{'. + parser.simple_key_allowed = true + + // Consume the token. + start_mark := parser.mark + skip(parser) + end_mark := parser.mark + + // Create the FLOW-SEQUENCE-START of FLOW-MAPPING-START token. + token := yaml_token_t{ + typ: typ, + start_mark: start_mark, + end_mark: end_mark, + } + // Append the token to the queue. + yaml_insert_token(parser, -1, &token) + return true +} + +// Produce the FLOW-SEQUENCE-END or FLOW-MAPPING-END token. +func yaml_parser_fetch_flow_collection_end(parser *yaml_parser_t, typ yaml_token_type_t) bool { + // Reset any potential simple key on the current flow level. + if !yaml_parser_remove_simple_key(parser) { + return false + } + + // Decrease the flow level. + if !yaml_parser_decrease_flow_level(parser) { + return false + } + + // No simple keys after the indicators ']' and '}'. + parser.simple_key_allowed = false + + // Consume the token. + + start_mark := parser.mark + skip(parser) + end_mark := parser.mark + + // Create the FLOW-SEQUENCE-END of FLOW-MAPPING-END token. + token := yaml_token_t{ + typ: typ, + start_mark: start_mark, + end_mark: end_mark, + } + // Append the token to the queue. + yaml_insert_token(parser, -1, &token) + return true +} + +// Produce the FLOW-ENTRY token. +func yaml_parser_fetch_flow_entry(parser *yaml_parser_t) bool { + // Reset any potential simple keys on the current flow level. + if !yaml_parser_remove_simple_key(parser) { + return false + } + + // Simple keys are allowed after ','. + parser.simple_key_allowed = true + + // Consume the token. + start_mark := parser.mark + skip(parser) + end_mark := parser.mark + + // Create the FLOW-ENTRY token and append it to the queue. + token := yaml_token_t{ + typ: yaml_FLOW_ENTRY_TOKEN, + start_mark: start_mark, + end_mark: end_mark, + } + yaml_insert_token(parser, -1, &token) + return true +} + +// Produce the BLOCK-ENTRY token. +func yaml_parser_fetch_block_entry(parser *yaml_parser_t) bool { + // Check if the scanner is in the block context. + if parser.flow_level == 0 { + // Check if we are allowed to start a new entry. + if !parser.simple_key_allowed { + return yaml_parser_set_scanner_error(parser, "", parser.mark, + "block sequence entries are not allowed in this context") + } + // Add the BLOCK-SEQUENCE-START token if needed. + if !yaml_parser_roll_indent(parser, parser.mark.column, -1, yaml_BLOCK_SEQUENCE_START_TOKEN, parser.mark) { + return false + } + } else { + // It is an error for the '-' indicator to occur in the flow context, + // but we let the Parser detect and report about it because the Parser + // is able to point to the context. + } + + // Reset any potential simple keys on the current flow level. + if !yaml_parser_remove_simple_key(parser) { + return false + } + + // Simple keys are allowed after '-'. + parser.simple_key_allowed = true + + // Consume the token. + start_mark := parser.mark + skip(parser) + end_mark := parser.mark + + // Create the BLOCK-ENTRY token and append it to the queue. + token := yaml_token_t{ + typ: yaml_BLOCK_ENTRY_TOKEN, + start_mark: start_mark, + end_mark: end_mark, + } + yaml_insert_token(parser, -1, &token) + return true +} + +// Produce the KEY token. +func yaml_parser_fetch_key(parser *yaml_parser_t) bool { + + // In the block context, additional checks are required. + if parser.flow_level == 0 { + // Check if we are allowed to start a new key (not nessesary simple). + if !parser.simple_key_allowed { + return yaml_parser_set_scanner_error(parser, "", parser.mark, + "mapping keys are not allowed in this context") + } + // Add the BLOCK-MAPPING-START token if needed. + if !yaml_parser_roll_indent(parser, parser.mark.column, -1, yaml_BLOCK_MAPPING_START_TOKEN, parser.mark) { + return false + } + } + + // Reset any potential simple keys on the current flow level. + if !yaml_parser_remove_simple_key(parser) { + return false + } + + // Simple keys are allowed after '?' in the block context. + parser.simple_key_allowed = parser.flow_level == 0 + + // Consume the token. + start_mark := parser.mark + skip(parser) + end_mark := parser.mark + + // Create the KEY token and append it to the queue. + token := yaml_token_t{ + typ: yaml_KEY_TOKEN, + start_mark: start_mark, + end_mark: end_mark, + } + yaml_insert_token(parser, -1, &token) + return true +} + +// Produce the VALUE token. +func yaml_parser_fetch_value(parser *yaml_parser_t) bool { + + simple_key := &parser.simple_keys[len(parser.simple_keys)-1] + + // Have we found a simple key? + if valid, ok := yaml_simple_key_is_valid(parser, simple_key); !ok { + return false + + } else if valid { + + // Create the KEY token and insert it into the queue. + token := yaml_token_t{ + typ: yaml_KEY_TOKEN, + start_mark: simple_key.mark, + end_mark: simple_key.mark, + } + yaml_insert_token(parser, simple_key.token_number-parser.tokens_parsed, &token) + + // In the block context, we may need to add the BLOCK-MAPPING-START token. + if !yaml_parser_roll_indent(parser, simple_key.mark.column, + simple_key.token_number, + yaml_BLOCK_MAPPING_START_TOKEN, simple_key.mark) { + return false + } + + // Remove the simple key. + simple_key.possible = false + delete(parser.simple_keys_by_tok, simple_key.token_number) + + // A simple key cannot follow another simple key. + parser.simple_key_allowed = false + + } else { + // The ':' indicator follows a complex key. + + // In the block context, extra checks are required. + if parser.flow_level == 0 { + + // Check if we are allowed to start a complex value. + if !parser.simple_key_allowed { + return yaml_parser_set_scanner_error(parser, "", parser.mark, + "mapping values are not allowed in this context") + } + + // Add the BLOCK-MAPPING-START token if needed. + if !yaml_parser_roll_indent(parser, parser.mark.column, -1, yaml_BLOCK_MAPPING_START_TOKEN, parser.mark) { + return false + } + } + + // Simple keys after ':' are allowed in the block context. + parser.simple_key_allowed = parser.flow_level == 0 + } + + // Consume the token. + start_mark := parser.mark + skip(parser) + end_mark := parser.mark + + // Create the VALUE token and append it to the queue. + token := yaml_token_t{ + typ: yaml_VALUE_TOKEN, + start_mark: start_mark, + end_mark: end_mark, + } + yaml_insert_token(parser, -1, &token) + return true +} + +// Produce the ALIAS or ANCHOR token. +func yaml_parser_fetch_anchor(parser *yaml_parser_t, typ yaml_token_type_t) bool { + // An anchor or an alias could be a simple key. + if !yaml_parser_save_simple_key(parser) { + return false + } + + // A simple key cannot follow an anchor or an alias. + parser.simple_key_allowed = false + + // Create the ALIAS or ANCHOR token and append it to the queue. + var token yaml_token_t + if !yaml_parser_scan_anchor(parser, &token, typ) { + return false + } + yaml_insert_token(parser, -1, &token) + return true +} + +// Produce the TAG token. +func yaml_parser_fetch_tag(parser *yaml_parser_t) bool { + // A tag could be a simple key. + if !yaml_parser_save_simple_key(parser) { + return false + } + + // A simple key cannot follow a tag. + parser.simple_key_allowed = false + + // Create the TAG token and append it to the queue. + var token yaml_token_t + if !yaml_parser_scan_tag(parser, &token) { + return false + } + yaml_insert_token(parser, -1, &token) + return true +} + +// Produce the SCALAR(...,literal) or SCALAR(...,folded) tokens. +func yaml_parser_fetch_block_scalar(parser *yaml_parser_t, literal bool) bool { + // Remove any potential simple keys. + if !yaml_parser_remove_simple_key(parser) { + return false + } + + // A simple key may follow a block scalar. + parser.simple_key_allowed = true + + // Create the SCALAR token and append it to the queue. + var token yaml_token_t + if !yaml_parser_scan_block_scalar(parser, &token, literal) { + return false + } + yaml_insert_token(parser, -1, &token) + return true +} + +// Produce the SCALAR(...,single-quoted) or SCALAR(...,double-quoted) tokens. +func yaml_parser_fetch_flow_scalar(parser *yaml_parser_t, single bool) bool { + // A plain scalar could be a simple key. + if !yaml_parser_save_simple_key(parser) { + return false + } + + // A simple key cannot follow a flow scalar. + parser.simple_key_allowed = false + + // Create the SCALAR token and append it to the queue. + var token yaml_token_t + if !yaml_parser_scan_flow_scalar(parser, &token, single) { + return false + } + yaml_insert_token(parser, -1, &token) + return true +} + +// Produce the SCALAR(...,plain) token. +func yaml_parser_fetch_plain_scalar(parser *yaml_parser_t) bool { + // A plain scalar could be a simple key. + if !yaml_parser_save_simple_key(parser) { + return false + } + + // A simple key cannot follow a flow scalar. + parser.simple_key_allowed = false + + // Create the SCALAR token and append it to the queue. + var token yaml_token_t + if !yaml_parser_scan_plain_scalar(parser, &token) { + return false + } + yaml_insert_token(parser, -1, &token) + return true +} + +// Eat whitespaces and comments until the next token is found. +func yaml_parser_scan_to_next_token(parser *yaml_parser_t) bool { + + scan_mark := parser.mark + + // Until the next token is not found. + for { + // Allow the BOM mark to start a line. + if parser.unread < 1 && !yaml_parser_update_buffer(parser, 1) { + return false + } + if parser.mark.column == 0 && is_bom(parser.buffer, parser.buffer_pos) { + skip(parser) + } + + // Eat whitespaces. + // Tabs are allowed: + // - in the flow context + // - in the block context, but not at the beginning of the line or + // after '-', '?', or ':' (complex value). + if parser.unread < 1 && !yaml_parser_update_buffer(parser, 1) { + return false + } + + for parser.buffer[parser.buffer_pos] == ' ' || ((parser.flow_level > 0 || !parser.simple_key_allowed) && parser.buffer[parser.buffer_pos] == '\t') { + skip(parser) + if parser.unread < 1 && !yaml_parser_update_buffer(parser, 1) { + return false + } + } + + // Check if we just had a line comment under a sequence entry that + // looks more like a header to the following content. Similar to this: + // + // - # The comment + // - Some data + // + // If so, transform the line comment to a head comment and reposition. + if len(parser.comments) > 0 && len(parser.tokens) > 1 { + tokenA := parser.tokens[len(parser.tokens)-2] + tokenB := parser.tokens[len(parser.tokens)-1] + comment := &parser.comments[len(parser.comments)-1] + if tokenA.typ == yaml_BLOCK_SEQUENCE_START_TOKEN && tokenB.typ == yaml_BLOCK_ENTRY_TOKEN && len(comment.line) > 0 && !is_break(parser.buffer, parser.buffer_pos) { + // If it was in the prior line, reposition so it becomes a + // header of the follow up token. Otherwise, keep it in place + // so it becomes a header of the former. + comment.head = comment.line + comment.line = nil + if comment.start_mark.line == parser.mark.line-1 { + comment.token_mark = parser.mark + } + } + } + + // Eat a comment until a line break. + if parser.buffer[parser.buffer_pos] == '#' { + if !yaml_parser_scan_comments(parser, scan_mark) { + return false + } + } + + // If it is a line break, eat it. + if is_break(parser.buffer, parser.buffer_pos) { + if parser.unread < 2 && !yaml_parser_update_buffer(parser, 2) { + return false + } + skip_line(parser) + + // In the block context, a new line may start a simple key. + if parser.flow_level == 0 { + parser.simple_key_allowed = true + } + } else { + break // We have found a token. + } + } + + return true +} + +// Scan a YAML-DIRECTIVE or TAG-DIRECTIVE token. +// +// Scope: +// %YAML 1.1 # a comment \n +// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +// %TAG !yaml! tag:yaml.org,2002: \n +// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +// +func yaml_parser_scan_directive(parser *yaml_parser_t, token *yaml_token_t) bool { + // Eat '%'. + start_mark := parser.mark + skip(parser) + + // Scan the directive name. + var name []byte + if !yaml_parser_scan_directive_name(parser, start_mark, &name) { + return false + } + + // Is it a YAML directive? + if bytes.Equal(name, []byte("YAML")) { + // Scan the VERSION directive value. + var major, minor int8 + if !yaml_parser_scan_version_directive_value(parser, start_mark, &major, &minor) { + return false + } + end_mark := parser.mark + + // Create a VERSION-DIRECTIVE token. + *token = yaml_token_t{ + typ: yaml_VERSION_DIRECTIVE_TOKEN, + start_mark: start_mark, + end_mark: end_mark, + major: major, + minor: minor, + } + + // Is it a TAG directive? + } else if bytes.Equal(name, []byte("TAG")) { + // Scan the TAG directive value. + var handle, prefix []byte + if !yaml_parser_scan_tag_directive_value(parser, start_mark, &handle, &prefix) { + return false + } + end_mark := parser.mark + + // Create a TAG-DIRECTIVE token. + *token = yaml_token_t{ + typ: yaml_TAG_DIRECTIVE_TOKEN, + start_mark: start_mark, + end_mark: end_mark, + value: handle, + prefix: prefix, + } + + // Unknown directive. + } else { + yaml_parser_set_scanner_error(parser, "while scanning a directive", + start_mark, "found unknown directive name") + return false + } + + // Eat the rest of the line including any comments. + if parser.unread < 1 && !yaml_parser_update_buffer(parser, 1) { + return false + } + + for is_blank(parser.buffer, parser.buffer_pos) { + skip(parser) + if parser.unread < 1 && !yaml_parser_update_buffer(parser, 1) { + return false + } + } + + if parser.buffer[parser.buffer_pos] == '#' { + // [Go] Discard this inline comment for the time being. + //if !yaml_parser_scan_line_comment(parser, start_mark) { + // return false + //} + for !is_breakz(parser.buffer, parser.buffer_pos) { + skip(parser) + if parser.unread < 1 && !yaml_parser_update_buffer(parser, 1) { + return false + } + } + } + + // Check if we are at the end of the line. + if !is_breakz(parser.buffer, parser.buffer_pos) { + yaml_parser_set_scanner_error(parser, "while scanning a directive", + start_mark, "did not find expected comment or line break") + return false + } + + // Eat a line break. + if is_break(parser.buffer, parser.buffer_pos) { + if parser.unread < 2 && !yaml_parser_update_buffer(parser, 2) { + return false + } + skip_line(parser) + } + + return true +} + +// Scan the directive name. +// +// Scope: +// %YAML 1.1 # a comment \n +// ^^^^ +// %TAG !yaml! tag:yaml.org,2002: \n +// ^^^ +// +func yaml_parser_scan_directive_name(parser *yaml_parser_t, start_mark yaml_mark_t, name *[]byte) bool { + // Consume the directive name. + if parser.unread < 1 && !yaml_parser_update_buffer(parser, 1) { + return false + } + + var s []byte + for is_alpha(parser.buffer, parser.buffer_pos) { + s = read(parser, s) + if parser.unread < 1 && !yaml_parser_update_buffer(parser, 1) { + return false + } + } + + // Check if the name is empty. + if len(s) == 0 { + yaml_parser_set_scanner_error(parser, "while scanning a directive", + start_mark, "could not find expected directive name") + return false + } + + // Check for an blank character after the name. + if !is_blankz(parser.buffer, parser.buffer_pos) { + yaml_parser_set_scanner_error(parser, "while scanning a directive", + start_mark, "found unexpected non-alphabetical character") + return false + } + *name = s + return true +} + +// Scan the value of VERSION-DIRECTIVE. +// +// Scope: +// %YAML 1.1 # a comment \n +// ^^^^^^ +func yaml_parser_scan_version_directive_value(parser *yaml_parser_t, start_mark yaml_mark_t, major, minor *int8) bool { + // Eat whitespaces. + if parser.unread < 1 && !yaml_parser_update_buffer(parser, 1) { + return false + } + for is_blank(parser.buffer, parser.buffer_pos) { + skip(parser) + if parser.unread < 1 && !yaml_parser_update_buffer(parser, 1) { + return false + } + } + + // Consume the major version number. + if !yaml_parser_scan_version_directive_number(parser, start_mark, major) { + return false + } + + // Eat '.'. + if parser.buffer[parser.buffer_pos] != '.' { + return yaml_parser_set_scanner_error(parser, "while scanning a %YAML directive", + start_mark, "did not find expected digit or '.' character") + } + + skip(parser) + + // Consume the minor version number. + if !yaml_parser_scan_version_directive_number(parser, start_mark, minor) { + return false + } + return true +} + +const max_number_length = 2 + +// Scan the version number of VERSION-DIRECTIVE. +// +// Scope: +// %YAML 1.1 # a comment \n +// ^ +// %YAML 1.1 # a comment \n +// ^ +func yaml_parser_scan_version_directive_number(parser *yaml_parser_t, start_mark yaml_mark_t, number *int8) bool { + + // Repeat while the next character is digit. + if parser.unread < 1 && !yaml_parser_update_buffer(parser, 1) { + return false + } + var value, length int8 + for is_digit(parser.buffer, parser.buffer_pos) { + // Check if the number is too long. + length++ + if length > max_number_length { + return yaml_parser_set_scanner_error(parser, "while scanning a %YAML directive", + start_mark, "found extremely long version number") + } + value = value*10 + int8(as_digit(parser.buffer, parser.buffer_pos)) + skip(parser) + if parser.unread < 1 && !yaml_parser_update_buffer(parser, 1) { + return false + } + } + + // Check if the number was present. + if length == 0 { + return yaml_parser_set_scanner_error(parser, "while scanning a %YAML directive", + start_mark, "did not find expected version number") + } + *number = value + return true +} + +// Scan the value of a TAG-DIRECTIVE token. +// +// Scope: +// %TAG !yaml! tag:yaml.org,2002: \n +// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +// +func yaml_parser_scan_tag_directive_value(parser *yaml_parser_t, start_mark yaml_mark_t, handle, prefix *[]byte) bool { + var handle_value, prefix_value []byte + + // Eat whitespaces. + if parser.unread < 1 && !yaml_parser_update_buffer(parser, 1) { + return false + } + + for is_blank(parser.buffer, parser.buffer_pos) { + skip(parser) + if parser.unread < 1 && !yaml_parser_update_buffer(parser, 1) { + return false + } + } + + // Scan a handle. + if !yaml_parser_scan_tag_handle(parser, true, start_mark, &handle_value) { + return false + } + + // Expect a whitespace. + if parser.unread < 1 && !yaml_parser_update_buffer(parser, 1) { + return false + } + if !is_blank(parser.buffer, parser.buffer_pos) { + yaml_parser_set_scanner_error(parser, "while scanning a %TAG directive", + start_mark, "did not find expected whitespace") + return false + } + + // Eat whitespaces. + for is_blank(parser.buffer, parser.buffer_pos) { + skip(parser) + if parser.unread < 1 && !yaml_parser_update_buffer(parser, 1) { + return false + } + } + + // Scan a prefix. + if !yaml_parser_scan_tag_uri(parser, true, nil, start_mark, &prefix_value) { + return false + } + + // Expect a whitespace or line break. + if parser.unread < 1 && !yaml_parser_update_buffer(parser, 1) { + return false + } + if !is_blankz(parser.buffer, parser.buffer_pos) { + yaml_parser_set_scanner_error(parser, "while scanning a %TAG directive", + start_mark, "did not find expected whitespace or line break") + return false + } + + *handle = handle_value + *prefix = prefix_value + return true +} + +func yaml_parser_scan_anchor(parser *yaml_parser_t, token *yaml_token_t, typ yaml_token_type_t) bool { + var s []byte + + // Eat the indicator character. + start_mark := parser.mark + skip(parser) + + // Consume the value. + if parser.unread < 1 && !yaml_parser_update_buffer(parser, 1) { + return false + } + + for is_alpha(parser.buffer, parser.buffer_pos) { + s = read(parser, s) + if parser.unread < 1 && !yaml_parser_update_buffer(parser, 1) { + return false + } + } + + end_mark := parser.mark + + /* + * Check if length of the anchor is greater than 0 and it is followed by + * a whitespace character or one of the indicators: + * + * '?', ':', ',', ']', '}', '%', '@', '`'. + */ + + if len(s) == 0 || + !(is_blankz(parser.buffer, parser.buffer_pos) || parser.buffer[parser.buffer_pos] == '?' || + parser.buffer[parser.buffer_pos] == ':' || parser.buffer[parser.buffer_pos] == ',' || + parser.buffer[parser.buffer_pos] == ']' || parser.buffer[parser.buffer_pos] == '}' || + parser.buffer[parser.buffer_pos] == '%' || parser.buffer[parser.buffer_pos] == '@' || + parser.buffer[parser.buffer_pos] == '`') { + context := "while scanning an alias" + if typ == yaml_ANCHOR_TOKEN { + context = "while scanning an anchor" + } + yaml_parser_set_scanner_error(parser, context, start_mark, + "did not find expected alphabetic or numeric character") + return false + } + + // Create a token. + *token = yaml_token_t{ + typ: typ, + start_mark: start_mark, + end_mark: end_mark, + value: s, + } + + return true +} + +/* + * Scan a TAG token. + */ + +func yaml_parser_scan_tag(parser *yaml_parser_t, token *yaml_token_t) bool { + var handle, suffix []byte + + start_mark := parser.mark + + // Check if the tag is in the canonical form. + if parser.unread < 2 && !yaml_parser_update_buffer(parser, 2) { + return false + } + + if parser.buffer[parser.buffer_pos+1] == '<' { + // Keep the handle as '' + + // Eat '!<' + skip(parser) + skip(parser) + + // Consume the tag value. + if !yaml_parser_scan_tag_uri(parser, false, nil, start_mark, &suffix) { + return false + } + + // Check for '>' and eat it. + if parser.buffer[parser.buffer_pos] != '>' { + yaml_parser_set_scanner_error(parser, "while scanning a tag", + start_mark, "did not find the expected '>'") + return false + } + + skip(parser) + } else { + // The tag has either the '!suffix' or the '!handle!suffix' form. + + // First, try to scan a handle. + if !yaml_parser_scan_tag_handle(parser, false, start_mark, &handle) { + return false + } + + // Check if it is, indeed, handle. + if handle[0] == '!' && len(handle) > 1 && handle[len(handle)-1] == '!' { + // Scan the suffix now. + if !yaml_parser_scan_tag_uri(parser, false, nil, start_mark, &suffix) { + return false + } + } else { + // It wasn't a handle after all. Scan the rest of the tag. + if !yaml_parser_scan_tag_uri(parser, false, handle, start_mark, &suffix) { + return false + } + + // Set the handle to '!'. + handle = []byte{'!'} + + // A special case: the '!' tag. Set the handle to '' and the + // suffix to '!'. + if len(suffix) == 0 { + handle, suffix = suffix, handle + } + } + } + + // Check the character which ends the tag. + if parser.unread < 1 && !yaml_parser_update_buffer(parser, 1) { + return false + } + if !is_blankz(parser.buffer, parser.buffer_pos) { + yaml_parser_set_scanner_error(parser, "while scanning a tag", + start_mark, "did not find expected whitespace or line break") + return false + } + + end_mark := parser.mark + + // Create a token. + *token = yaml_token_t{ + typ: yaml_TAG_TOKEN, + start_mark: start_mark, + end_mark: end_mark, + value: handle, + suffix: suffix, + } + return true +} + +// Scan a tag handle. +func yaml_parser_scan_tag_handle(parser *yaml_parser_t, directive bool, start_mark yaml_mark_t, handle *[]byte) bool { + // Check the initial '!' character. + if parser.unread < 1 && !yaml_parser_update_buffer(parser, 1) { + return false + } + if parser.buffer[parser.buffer_pos] != '!' { + yaml_parser_set_scanner_tag_error(parser, directive, + start_mark, "did not find expected '!'") + return false + } + + var s []byte + + // Copy the '!' character. + s = read(parser, s) + + // Copy all subsequent alphabetical and numerical characters. + if parser.unread < 1 && !yaml_parser_update_buffer(parser, 1) { + return false + } + for is_alpha(parser.buffer, parser.buffer_pos) { + s = read(parser, s) + if parser.unread < 1 && !yaml_parser_update_buffer(parser, 1) { + return false + } + } + + // Check if the trailing character is '!' and copy it. + if parser.buffer[parser.buffer_pos] == '!' { + s = read(parser, s) + } else { + // It's either the '!' tag or not really a tag handle. If it's a %TAG + // directive, it's an error. If it's a tag token, it must be a part of URI. + if directive && string(s) != "!" { + yaml_parser_set_scanner_tag_error(parser, directive, + start_mark, "did not find expected '!'") + return false + } + } + + *handle = s + return true +} + +// Scan a tag. +func yaml_parser_scan_tag_uri(parser *yaml_parser_t, directive bool, head []byte, start_mark yaml_mark_t, uri *[]byte) bool { + //size_t length = head ? strlen((char *)head) : 0 + var s []byte + hasTag := len(head) > 0 + + // Copy the head if needed. + // + // Note that we don't copy the leading '!' character. + if len(head) > 1 { + s = append(s, head[1:]...) + } + + // Scan the tag. + if parser.unread < 1 && !yaml_parser_update_buffer(parser, 1) { + return false + } + + // The set of characters that may appear in URI is as follows: + // + // '0'-'9', 'A'-'Z', 'a'-'z', '_', '-', ';', '/', '?', ':', '@', '&', + // '=', '+', '$', ',', '.', '!', '~', '*', '\'', '(', ')', '[', ']', + // '%'. + // [Go] TODO Convert this into more reasonable logic. + for is_alpha(parser.buffer, parser.buffer_pos) || parser.buffer[parser.buffer_pos] == ';' || + parser.buffer[parser.buffer_pos] == '/' || parser.buffer[parser.buffer_pos] == '?' || + parser.buffer[parser.buffer_pos] == ':' || parser.buffer[parser.buffer_pos] == '@' || + parser.buffer[parser.buffer_pos] == '&' || parser.buffer[parser.buffer_pos] == '=' || + parser.buffer[parser.buffer_pos] == '+' || parser.buffer[parser.buffer_pos] == '$' || + parser.buffer[parser.buffer_pos] == ',' || parser.buffer[parser.buffer_pos] == '.' || + parser.buffer[parser.buffer_pos] == '!' || parser.buffer[parser.buffer_pos] == '~' || + parser.buffer[parser.buffer_pos] == '*' || parser.buffer[parser.buffer_pos] == '\'' || + parser.buffer[parser.buffer_pos] == '(' || parser.buffer[parser.buffer_pos] == ')' || + parser.buffer[parser.buffer_pos] == '[' || parser.buffer[parser.buffer_pos] == ']' || + parser.buffer[parser.buffer_pos] == '%' { + // Check if it is a URI-escape sequence. + if parser.buffer[parser.buffer_pos] == '%' { + if !yaml_parser_scan_uri_escapes(parser, directive, start_mark, &s) { + return false + } + } else { + s = read(parser, s) + } + if parser.unread < 1 && !yaml_parser_update_buffer(parser, 1) { + return false + } + hasTag = true + } + + if !hasTag { + yaml_parser_set_scanner_tag_error(parser, directive, + start_mark, "did not find expected tag URI") + return false + } + *uri = s + return true +} + +// Decode an URI-escape sequence corresponding to a single UTF-8 character. +func yaml_parser_scan_uri_escapes(parser *yaml_parser_t, directive bool, start_mark yaml_mark_t, s *[]byte) bool { + + // Decode the required number of characters. + w := 1024 + for w > 0 { + // Check for a URI-escaped octet. + if parser.unread < 3 && !yaml_parser_update_buffer(parser, 3) { + return false + } + + if !(parser.buffer[parser.buffer_pos] == '%' && + is_hex(parser.buffer, parser.buffer_pos+1) && + is_hex(parser.buffer, parser.buffer_pos+2)) { + return yaml_parser_set_scanner_tag_error(parser, directive, + start_mark, "did not find URI escaped octet") + } + + // Get the octet. + octet := byte((as_hex(parser.buffer, parser.buffer_pos+1) << 4) + as_hex(parser.buffer, parser.buffer_pos+2)) + + // If it is the leading octet, determine the length of the UTF-8 sequence. + if w == 1024 { + w = width(octet) + if w == 0 { + return yaml_parser_set_scanner_tag_error(parser, directive, + start_mark, "found an incorrect leading UTF-8 octet") + } + } else { + // Check if the trailing octet is correct. + if octet&0xC0 != 0x80 { + return yaml_parser_set_scanner_tag_error(parser, directive, + start_mark, "found an incorrect trailing UTF-8 octet") + } + } + + // Copy the octet and move the pointers. + *s = append(*s, octet) + skip(parser) + skip(parser) + skip(parser) + w-- + } + return true +} + +// Scan a block scalar. +func yaml_parser_scan_block_scalar(parser *yaml_parser_t, token *yaml_token_t, literal bool) bool { + // Eat the indicator '|' or '>'. + start_mark := parser.mark + skip(parser) + + // Scan the additional block scalar indicators. + if parser.unread < 1 && !yaml_parser_update_buffer(parser, 1) { + return false + } + + // Check for a chomping indicator. + var chomping, increment int + if parser.buffer[parser.buffer_pos] == '+' || parser.buffer[parser.buffer_pos] == '-' { + // Set the chomping method and eat the indicator. + if parser.buffer[parser.buffer_pos] == '+' { + chomping = +1 + } else { + chomping = -1 + } + skip(parser) + + // Check for an indentation indicator. + if parser.unread < 1 && !yaml_parser_update_buffer(parser, 1) { + return false + } + if is_digit(parser.buffer, parser.buffer_pos) { + // Check that the indentation is greater than 0. + if parser.buffer[parser.buffer_pos] == '0' { + yaml_parser_set_scanner_error(parser, "while scanning a block scalar", + start_mark, "found an indentation indicator equal to 0") + return false + } + + // Get the indentation level and eat the indicator. + increment = as_digit(parser.buffer, parser.buffer_pos) + skip(parser) + } + + } else if is_digit(parser.buffer, parser.buffer_pos) { + // Do the same as above, but in the opposite order. + + if parser.buffer[parser.buffer_pos] == '0' { + yaml_parser_set_scanner_error(parser, "while scanning a block scalar", + start_mark, "found an indentation indicator equal to 0") + return false + } + increment = as_digit(parser.buffer, parser.buffer_pos) + skip(parser) + + if parser.unread < 1 && !yaml_parser_update_buffer(parser, 1) { + return false + } + if parser.buffer[parser.buffer_pos] == '+' || parser.buffer[parser.buffer_pos] == '-' { + if parser.buffer[parser.buffer_pos] == '+' { + chomping = +1 + } else { + chomping = -1 + } + skip(parser) + } + } + + // Eat whitespaces and comments to the end of the line. + if parser.unread < 1 && !yaml_parser_update_buffer(parser, 1) { + return false + } + for is_blank(parser.buffer, parser.buffer_pos) { + skip(parser) + if parser.unread < 1 && !yaml_parser_update_buffer(parser, 1) { + return false + } + } + if parser.buffer[parser.buffer_pos] == '#' { + if !yaml_parser_scan_line_comment(parser, start_mark) { + return false + } + for !is_breakz(parser.buffer, parser.buffer_pos) { + skip(parser) + if parser.unread < 1 && !yaml_parser_update_buffer(parser, 1) { + return false + } + } + } + + // Check if we are at the end of the line. + if !is_breakz(parser.buffer, parser.buffer_pos) { + yaml_parser_set_scanner_error(parser, "while scanning a block scalar", + start_mark, "did not find expected comment or line break") + return false + } + + // Eat a line break. + if is_break(parser.buffer, parser.buffer_pos) { + if parser.unread < 2 && !yaml_parser_update_buffer(parser, 2) { + return false + } + skip_line(parser) + } + + end_mark := parser.mark + + // Set the indentation level if it was specified. + var indent int + if increment > 0 { + if parser.indent >= 0 { + indent = parser.indent + increment + } else { + indent = increment + } + } + + // Scan the leading line breaks and determine the indentation level if needed. + var s, leading_break, trailing_breaks []byte + if !yaml_parser_scan_block_scalar_breaks(parser, &indent, &trailing_breaks, start_mark, &end_mark) { + return false + } + + // Scan the block scalar content. + if parser.unread < 1 && !yaml_parser_update_buffer(parser, 1) { + return false + } + var leading_blank, trailing_blank bool + for parser.mark.column == indent && !is_z(parser.buffer, parser.buffer_pos) { + // We are at the beginning of a non-empty line. + + // Is it a trailing whitespace? + trailing_blank = is_blank(parser.buffer, parser.buffer_pos) + + // Check if we need to fold the leading line break. + if !literal && !leading_blank && !trailing_blank && len(leading_break) > 0 && leading_break[0] == '\n' { + // Do we need to join the lines by space? + if len(trailing_breaks) == 0 { + s = append(s, ' ') + } + } else { + s = append(s, leading_break...) + } + leading_break = leading_break[:0] + + // Append the remaining line breaks. + s = append(s, trailing_breaks...) + trailing_breaks = trailing_breaks[:0] + + // Is it a leading whitespace? + leading_blank = is_blank(parser.buffer, parser.buffer_pos) + + // Consume the current line. + for !is_breakz(parser.buffer, parser.buffer_pos) { + s = read(parser, s) + if parser.unread < 1 && !yaml_parser_update_buffer(parser, 1) { + return false + } + } + + // Consume the line break. + if parser.unread < 2 && !yaml_parser_update_buffer(parser, 2) { + return false + } + + leading_break = read_line(parser, leading_break) + + // Eat the following indentation spaces and line breaks. + if !yaml_parser_scan_block_scalar_breaks(parser, &indent, &trailing_breaks, start_mark, &end_mark) { + return false + } + } + + // Chomp the tail. + if chomping != -1 { + s = append(s, leading_break...) + } + if chomping == 1 { + s = append(s, trailing_breaks...) + } + + // Create a token. + *token = yaml_token_t{ + typ: yaml_SCALAR_TOKEN, + start_mark: start_mark, + end_mark: end_mark, + value: s, + style: yaml_LITERAL_SCALAR_STYLE, + } + if !literal { + token.style = yaml_FOLDED_SCALAR_STYLE + } + return true +} + +// Scan indentation spaces and line breaks for a block scalar. Determine the +// indentation level if needed. +func yaml_parser_scan_block_scalar_breaks(parser *yaml_parser_t, indent *int, breaks *[]byte, start_mark yaml_mark_t, end_mark *yaml_mark_t) bool { + *end_mark = parser.mark + + // Eat the indentation spaces and line breaks. + max_indent := 0 + for { + // Eat the indentation spaces. + if parser.unread < 1 && !yaml_parser_update_buffer(parser, 1) { + return false + } + for (*indent == 0 || parser.mark.column < *indent) && is_space(parser.buffer, parser.buffer_pos) { + skip(parser) + if parser.unread < 1 && !yaml_parser_update_buffer(parser, 1) { + return false + } + } + if parser.mark.column > max_indent { + max_indent = parser.mark.column + } + + // Check for a tab character messing the indentation. + if (*indent == 0 || parser.mark.column < *indent) && is_tab(parser.buffer, parser.buffer_pos) { + return yaml_parser_set_scanner_error(parser, "while scanning a block scalar", + start_mark, "found a tab character where an indentation space is expected") + } + + // Have we found a non-empty line? + if !is_break(parser.buffer, parser.buffer_pos) { + break + } + + // Consume the line break. + if parser.unread < 2 && !yaml_parser_update_buffer(parser, 2) { + return false + } + // [Go] Should really be returning breaks instead. + *breaks = read_line(parser, *breaks) + *end_mark = parser.mark + } + + // Determine the indentation level if needed. + if *indent == 0 { + *indent = max_indent + if *indent < parser.indent+1 { + *indent = parser.indent + 1 + } + if *indent < 1 { + *indent = 1 + } + } + return true +} + +// Scan a quoted scalar. +func yaml_parser_scan_flow_scalar(parser *yaml_parser_t, token *yaml_token_t, single bool) bool { + // Eat the left quote. + start_mark := parser.mark + skip(parser) + + // Consume the content of the quoted scalar. + var s, leading_break, trailing_breaks, whitespaces []byte + for { + // Check that there are no document indicators at the beginning of the line. + if parser.unread < 4 && !yaml_parser_update_buffer(parser, 4) { + return false + } + + if parser.mark.column == 0 && + ((parser.buffer[parser.buffer_pos+0] == '-' && + parser.buffer[parser.buffer_pos+1] == '-' && + parser.buffer[parser.buffer_pos+2] == '-') || + (parser.buffer[parser.buffer_pos+0] == '.' && + parser.buffer[parser.buffer_pos+1] == '.' && + parser.buffer[parser.buffer_pos+2] == '.')) && + is_blankz(parser.buffer, parser.buffer_pos+3) { + yaml_parser_set_scanner_error(parser, "while scanning a quoted scalar", + start_mark, "found unexpected document indicator") + return false + } + + // Check for EOF. + if is_z(parser.buffer, parser.buffer_pos) { + yaml_parser_set_scanner_error(parser, "while scanning a quoted scalar", + start_mark, "found unexpected end of stream") + return false + } + + // Consume non-blank characters. + leading_blanks := false + for !is_blankz(parser.buffer, parser.buffer_pos) { + if single && parser.buffer[parser.buffer_pos] == '\'' && parser.buffer[parser.buffer_pos+1] == '\'' { + // Is is an escaped single quote. + s = append(s, '\'') + skip(parser) + skip(parser) + + } else if single && parser.buffer[parser.buffer_pos] == '\'' { + // It is a right single quote. + break + } else if !single && parser.buffer[parser.buffer_pos] == '"' { + // It is a right double quote. + break + + } else if !single && parser.buffer[parser.buffer_pos] == '\\' && is_break(parser.buffer, parser.buffer_pos+1) { + // It is an escaped line break. + if parser.unread < 3 && !yaml_parser_update_buffer(parser, 3) { + return false + } + skip(parser) + skip_line(parser) + leading_blanks = true + break + + } else if !single && parser.buffer[parser.buffer_pos] == '\\' { + // It is an escape sequence. + code_length := 0 + + // Check the escape character. + switch parser.buffer[parser.buffer_pos+1] { + case '0': + s = append(s, 0) + case 'a': + s = append(s, '\x07') + case 'b': + s = append(s, '\x08') + case 't', '\t': + s = append(s, '\x09') + case 'n': + s = append(s, '\x0A') + case 'v': + s = append(s, '\x0B') + case 'f': + s = append(s, '\x0C') + case 'r': + s = append(s, '\x0D') + case 'e': + s = append(s, '\x1B') + case ' ': + s = append(s, '\x20') + case '"': + s = append(s, '"') + case '\'': + s = append(s, '\'') + case '\\': + s = append(s, '\\') + case 'N': // NEL (#x85) + s = append(s, '\xC2') + s = append(s, '\x85') + case '_': // #xA0 + s = append(s, '\xC2') + s = append(s, '\xA0') + case 'L': // LS (#x2028) + s = append(s, '\xE2') + s = append(s, '\x80') + s = append(s, '\xA8') + case 'P': // PS (#x2029) + s = append(s, '\xE2') + s = append(s, '\x80') + s = append(s, '\xA9') + case 'x': + code_length = 2 + case 'u': + code_length = 4 + case 'U': + code_length = 8 + default: + yaml_parser_set_scanner_error(parser, "while parsing a quoted scalar", + start_mark, "found unknown escape character") + return false + } + + skip(parser) + skip(parser) + + // Consume an arbitrary escape code. + if code_length > 0 { + var value int + + // Scan the character value. + if parser.unread < code_length && !yaml_parser_update_buffer(parser, code_length) { + return false + } + for k := 0; k < code_length; k++ { + if !is_hex(parser.buffer, parser.buffer_pos+k) { + yaml_parser_set_scanner_error(parser, "while parsing a quoted scalar", + start_mark, "did not find expected hexdecimal number") + return false + } + value = (value << 4) + as_hex(parser.buffer, parser.buffer_pos+k) + } + + // Check the value and write the character. + if (value >= 0xD800 && value <= 0xDFFF) || value > 0x10FFFF { + yaml_parser_set_scanner_error(parser, "while parsing a quoted scalar", + start_mark, "found invalid Unicode character escape code") + return false + } + if value <= 0x7F { + s = append(s, byte(value)) + } else if value <= 0x7FF { + s = append(s, byte(0xC0+(value>>6))) + s = append(s, byte(0x80+(value&0x3F))) + } else if value <= 0xFFFF { + s = append(s, byte(0xE0+(value>>12))) + s = append(s, byte(0x80+((value>>6)&0x3F))) + s = append(s, byte(0x80+(value&0x3F))) + } else { + s = append(s, byte(0xF0+(value>>18))) + s = append(s, byte(0x80+((value>>12)&0x3F))) + s = append(s, byte(0x80+((value>>6)&0x3F))) + s = append(s, byte(0x80+(value&0x3F))) + } + + // Advance the pointer. + for k := 0; k < code_length; k++ { + skip(parser) + } + } + } else { + // It is a non-escaped non-blank character. + s = read(parser, s) + } + if parser.unread < 2 && !yaml_parser_update_buffer(parser, 2) { + return false + } + } + + if parser.unread < 1 && !yaml_parser_update_buffer(parser, 1) { + return false + } + + // Check if we are at the end of the scalar. + if single { + if parser.buffer[parser.buffer_pos] == '\'' { + break + } + } else { + if parser.buffer[parser.buffer_pos] == '"' { + break + } + } + + // Consume blank characters. + for is_blank(parser.buffer, parser.buffer_pos) || is_break(parser.buffer, parser.buffer_pos) { + if is_blank(parser.buffer, parser.buffer_pos) { + // Consume a space or a tab character. + if !leading_blanks { + whitespaces = read(parser, whitespaces) + } else { + skip(parser) + } + } else { + if parser.unread < 2 && !yaml_parser_update_buffer(parser, 2) { + return false + } + + // Check if it is a first line break. + if !leading_blanks { + whitespaces = whitespaces[:0] + leading_break = read_line(parser, leading_break) + leading_blanks = true + } else { + trailing_breaks = read_line(parser, trailing_breaks) + } + } + if parser.unread < 1 && !yaml_parser_update_buffer(parser, 1) { + return false + } + } + + // Join the whitespaces or fold line breaks. + if leading_blanks { + // Do we need to fold line breaks? + if len(leading_break) > 0 && leading_break[0] == '\n' { + if len(trailing_breaks) == 0 { + s = append(s, ' ') + } else { + s = append(s, trailing_breaks...) + } + } else { + s = append(s, leading_break...) + s = append(s, trailing_breaks...) + } + trailing_breaks = trailing_breaks[:0] + leading_break = leading_break[:0] + } else { + s = append(s, whitespaces...) + whitespaces = whitespaces[:0] + } + } + + // Eat the right quote. + skip(parser) + end_mark := parser.mark + + // Create a token. + *token = yaml_token_t{ + typ: yaml_SCALAR_TOKEN, + start_mark: start_mark, + end_mark: end_mark, + value: s, + style: yaml_SINGLE_QUOTED_SCALAR_STYLE, + } + if !single { + token.style = yaml_DOUBLE_QUOTED_SCALAR_STYLE + } + return true +} + +// Scan a plain scalar. +func yaml_parser_scan_plain_scalar(parser *yaml_parser_t, token *yaml_token_t) bool { + + var s, leading_break, trailing_breaks, whitespaces []byte + var leading_blanks bool + var indent = parser.indent + 1 + + start_mark := parser.mark + end_mark := parser.mark + + // Consume the content of the plain scalar. + for { + // Check for a document indicator. + if parser.unread < 4 && !yaml_parser_update_buffer(parser, 4) { + return false + } + if parser.mark.column == 0 && + ((parser.buffer[parser.buffer_pos+0] == '-' && + parser.buffer[parser.buffer_pos+1] == '-' && + parser.buffer[parser.buffer_pos+2] == '-') || + (parser.buffer[parser.buffer_pos+0] == '.' && + parser.buffer[parser.buffer_pos+1] == '.' && + parser.buffer[parser.buffer_pos+2] == '.')) && + is_blankz(parser.buffer, parser.buffer_pos+3) { + break + } + + // Check for a comment. + if parser.buffer[parser.buffer_pos] == '#' { + break + } + + // Consume non-blank characters. + for !is_blankz(parser.buffer, parser.buffer_pos) { + + // Check for indicators that may end a plain scalar. + if (parser.buffer[parser.buffer_pos] == ':' && is_blankz(parser.buffer, parser.buffer_pos+1)) || + (parser.flow_level > 0 && + (parser.buffer[parser.buffer_pos] == ',' || + parser.buffer[parser.buffer_pos] == '?' || parser.buffer[parser.buffer_pos] == '[' || + parser.buffer[parser.buffer_pos] == ']' || parser.buffer[parser.buffer_pos] == '{' || + parser.buffer[parser.buffer_pos] == '}')) { + break + } + + // Check if we need to join whitespaces and breaks. + if leading_blanks || len(whitespaces) > 0 { + if leading_blanks { + // Do we need to fold line breaks? + if leading_break[0] == '\n' { + if len(trailing_breaks) == 0 { + s = append(s, ' ') + } else { + s = append(s, trailing_breaks...) + } + } else { + s = append(s, leading_break...) + s = append(s, trailing_breaks...) + } + trailing_breaks = trailing_breaks[:0] + leading_break = leading_break[:0] + leading_blanks = false + } else { + s = append(s, whitespaces...) + whitespaces = whitespaces[:0] + } + } + + // Copy the character. + s = read(parser, s) + + end_mark = parser.mark + if parser.unread < 2 && !yaml_parser_update_buffer(parser, 2) { + return false + } + } + + // Is it the end? + if !(is_blank(parser.buffer, parser.buffer_pos) || is_break(parser.buffer, parser.buffer_pos)) { + break + } + + // Consume blank characters. + if parser.unread < 1 && !yaml_parser_update_buffer(parser, 1) { + return false + } + + for is_blank(parser.buffer, parser.buffer_pos) || is_break(parser.buffer, parser.buffer_pos) { + if is_blank(parser.buffer, parser.buffer_pos) { + + // Check for tab characters that abuse indentation. + if leading_blanks && parser.mark.column < indent && is_tab(parser.buffer, parser.buffer_pos) { + yaml_parser_set_scanner_error(parser, "while scanning a plain scalar", + start_mark, "found a tab character that violates indentation") + return false + } + + // Consume a space or a tab character. + if !leading_blanks { + whitespaces = read(parser, whitespaces) + } else { + skip(parser) + } + } else { + if parser.unread < 2 && !yaml_parser_update_buffer(parser, 2) { + return false + } + + // Check if it is a first line break. + if !leading_blanks { + whitespaces = whitespaces[:0] + leading_break = read_line(parser, leading_break) + leading_blanks = true + } else { + trailing_breaks = read_line(parser, trailing_breaks) + } + } + if parser.unread < 1 && !yaml_parser_update_buffer(parser, 1) { + return false + } + } + + // Check indentation level. + if parser.flow_level == 0 && parser.mark.column < indent { + break + } + } + + // Create a token. + *token = yaml_token_t{ + typ: yaml_SCALAR_TOKEN, + start_mark: start_mark, + end_mark: end_mark, + value: s, + style: yaml_PLAIN_SCALAR_STYLE, + } + + // Note that we change the 'simple_key_allowed' flag. + if leading_blanks { + parser.simple_key_allowed = true + } + return true +} + +func yaml_parser_scan_line_comment(parser *yaml_parser_t, token_mark yaml_mark_t) bool { + if parser.newlines > 0 { + return true + } + + var start_mark yaml_mark_t + var text []byte + + for peek := 0; peek < 512; peek++ { + if parser.unread < peek+1 && !yaml_parser_update_buffer(parser, peek+1) { + break + } + if is_blank(parser.buffer, parser.buffer_pos+peek) { + continue + } + if parser.buffer[parser.buffer_pos+peek] == '#' { + seen := parser.mark.index+peek + for { + if parser.unread < 1 && !yaml_parser_update_buffer(parser, 1) { + return false + } + if is_breakz(parser.buffer, parser.buffer_pos) { + if parser.mark.index >= seen { + break + } + if parser.unread < 2 && !yaml_parser_update_buffer(parser, 2) { + return false + } + skip_line(parser) + } else if parser.mark.index >= seen { + if len(text) == 0 { + start_mark = parser.mark + } + text = read(parser, text) + } else { + skip(parser) + } + } + } + break + } + if len(text) > 0 { + parser.comments = append(parser.comments, yaml_comment_t{ + token_mark: token_mark, + start_mark: start_mark, + line: text, + }) + } + return true +} + +func yaml_parser_scan_comments(parser *yaml_parser_t, scan_mark yaml_mark_t) bool { + token := parser.tokens[len(parser.tokens)-1] + + if token.typ == yaml_FLOW_ENTRY_TOKEN && len(parser.tokens) > 1 { + token = parser.tokens[len(parser.tokens)-2] + } + + var token_mark = token.start_mark + var start_mark yaml_mark_t + var next_indent = parser.indent + if next_indent < 0 { + next_indent = 0 + } + + var recent_empty = false + var first_empty = parser.newlines <= 1 + + var line = parser.mark.line + var column = parser.mark.column + + var text []byte + + // The foot line is the place where a comment must start to + // still be considered as a foot of the prior content. + // If there's some content in the currently parsed line, then + // the foot is the line below it. + var foot_line = -1 + if scan_mark.line > 0 { + foot_line = parser.mark.line-parser.newlines+1 + if parser.newlines == 0 && parser.mark.column > 1 { + foot_line++ + } + } + + var peek = 0 + for ; peek < 512; peek++ { + if parser.unread < peek+1 && !yaml_parser_update_buffer(parser, peek+1) { + break + } + column++ + if is_blank(parser.buffer, parser.buffer_pos+peek) { + continue + } + c := parser.buffer[parser.buffer_pos+peek] + var close_flow = parser.flow_level > 0 && (c == ']' || c == '}') + if close_flow || is_breakz(parser.buffer, parser.buffer_pos+peek) { + // Got line break or terminator. + if close_flow || !recent_empty { + if close_flow || first_empty && (start_mark.line == foot_line && token.typ != yaml_VALUE_TOKEN || start_mark.column-1 < next_indent) { + // This is the first empty line and there were no empty lines before, + // so this initial part of the comment is a foot of the prior token + // instead of being a head for the following one. Split it up. + // Alternatively, this might also be the last comment inside a flow + // scope, so it must be a footer. + if len(text) > 0 { + if start_mark.column-1 < next_indent { + // If dedented it's unrelated to the prior token. + token_mark = start_mark + } + parser.comments = append(parser.comments, yaml_comment_t{ + scan_mark: scan_mark, + token_mark: token_mark, + start_mark: start_mark, + end_mark: yaml_mark_t{parser.mark.index + peek, line, column}, + foot: text, + }) + scan_mark = yaml_mark_t{parser.mark.index + peek, line, column} + token_mark = scan_mark + text = nil + } + } else { + if len(text) > 0 && parser.buffer[parser.buffer_pos+peek] != 0 { + text = append(text, '\n') + } + } + } + if !is_break(parser.buffer, parser.buffer_pos+peek) { + break + } + first_empty = false + recent_empty = true + column = 0 + line++ + continue + } + + if len(text) > 0 && (close_flow || column-1 < next_indent && column != start_mark.column) { + // The comment at the different indentation is a foot of the + // preceding data rather than a head of the upcoming one. + parser.comments = append(parser.comments, yaml_comment_t{ + scan_mark: scan_mark, + token_mark: token_mark, + start_mark: start_mark, + end_mark: yaml_mark_t{parser.mark.index + peek, line, column}, + foot: text, + }) + scan_mark = yaml_mark_t{parser.mark.index + peek, line, column} + token_mark = scan_mark + text = nil + } + + if parser.buffer[parser.buffer_pos+peek] != '#' { + break + } + + if len(text) == 0 { + start_mark = yaml_mark_t{parser.mark.index + peek, line, column} + } else { + text = append(text, '\n') + } + + recent_empty = false + + // Consume until after the consumed comment line. + seen := parser.mark.index+peek + for { + if parser.unread < 1 && !yaml_parser_update_buffer(parser, 1) { + return false + } + if is_breakz(parser.buffer, parser.buffer_pos) { + if parser.mark.index >= seen { + break + } + if parser.unread < 2 && !yaml_parser_update_buffer(parser, 2) { + return false + } + skip_line(parser) + } else if parser.mark.index >= seen { + text = read(parser, text) + } else { + skip(parser) + } + } + + peek = 0 + column = 0 + line = parser.mark.line + next_indent = parser.indent + if next_indent < 0 { + next_indent = 0 + } + } + + if len(text) > 0 { + parser.comments = append(parser.comments, yaml_comment_t{ + scan_mark: scan_mark, + token_mark: start_mark, + start_mark: start_mark, + end_mark: yaml_mark_t{parser.mark.index + peek - 1, line, column}, + head: text, + }) + } + return true +} diff --git a/index/server/vendor/github.com/oasdiff/yaml3/sorter.go b/index/server/vendor/github.com/oasdiff/yaml3/sorter.go new file mode 100644 index 000000000..9210ece7e --- /dev/null +++ b/index/server/vendor/github.com/oasdiff/yaml3/sorter.go @@ -0,0 +1,134 @@ +// +// Copyright (c) 2011-2019 Canonical Ltd +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package yaml + +import ( + "reflect" + "unicode" +) + +type keyList []reflect.Value + +func (l keyList) Len() int { return len(l) } +func (l keyList) Swap(i, j int) { l[i], l[j] = l[j], l[i] } +func (l keyList) Less(i, j int) bool { + a := l[i] + b := l[j] + ak := a.Kind() + bk := b.Kind() + for (ak == reflect.Interface || ak == reflect.Ptr) && !a.IsNil() { + a = a.Elem() + ak = a.Kind() + } + for (bk == reflect.Interface || bk == reflect.Ptr) && !b.IsNil() { + b = b.Elem() + bk = b.Kind() + } + af, aok := keyFloat(a) + bf, bok := keyFloat(b) + if aok && bok { + if af != bf { + return af < bf + } + if ak != bk { + return ak < bk + } + return numLess(a, b) + } + if ak != reflect.String || bk != reflect.String { + return ak < bk + } + ar, br := []rune(a.String()), []rune(b.String()) + digits := false + for i := 0; i < len(ar) && i < len(br); i++ { + if ar[i] == br[i] { + digits = unicode.IsDigit(ar[i]) + continue + } + al := unicode.IsLetter(ar[i]) + bl := unicode.IsLetter(br[i]) + if al && bl { + return ar[i] < br[i] + } + if al || bl { + if digits { + return al + } else { + return bl + } + } + var ai, bi int + var an, bn int64 + if ar[i] == '0' || br[i] == '0' { + for j := i - 1; j >= 0 && unicode.IsDigit(ar[j]); j-- { + if ar[j] != '0' { + an = 1 + bn = 1 + break + } + } + } + for ai = i; ai < len(ar) && unicode.IsDigit(ar[ai]); ai++ { + an = an*10 + int64(ar[ai]-'0') + } + for bi = i; bi < len(br) && unicode.IsDigit(br[bi]); bi++ { + bn = bn*10 + int64(br[bi]-'0') + } + if an != bn { + return an < bn + } + if ai != bi { + return ai < bi + } + return ar[i] < br[i] + } + return len(ar) < len(br) +} + +// keyFloat returns a float value for v if it is a number/bool +// and whether it is a number/bool or not. +func keyFloat(v reflect.Value) (f float64, ok bool) { + switch v.Kind() { + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + return float64(v.Int()), true + case reflect.Float32, reflect.Float64: + return v.Float(), true + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: + return float64(v.Uint()), true + case reflect.Bool: + if v.Bool() { + return 1, true + } + return 0, true + } + return 0, false +} + +// numLess returns whether a < b. +// a and b must necessarily have the same kind. +func numLess(a, b reflect.Value) bool { + switch a.Kind() { + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + return a.Int() < b.Int() + case reflect.Float32, reflect.Float64: + return a.Float() < b.Float() + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: + return a.Uint() < b.Uint() + case reflect.Bool: + return !a.Bool() && b.Bool() + } + panic("not a number") +} diff --git a/index/server/vendor/github.com/oasdiff/yaml3/writerc.go b/index/server/vendor/github.com/oasdiff/yaml3/writerc.go new file mode 100644 index 000000000..b8a116bf9 --- /dev/null +++ b/index/server/vendor/github.com/oasdiff/yaml3/writerc.go @@ -0,0 +1,48 @@ +// +// Copyright (c) 2011-2019 Canonical Ltd +// Copyright (c) 2006-2010 Kirill Simonov +// +// 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. + +package yaml + +// Set the writer error and return false. +func yaml_emitter_set_writer_error(emitter *yaml_emitter_t, problem string) bool { + emitter.error = yaml_WRITER_ERROR + emitter.problem = problem + return false +} + +// Flush the output buffer. +func yaml_emitter_flush(emitter *yaml_emitter_t) bool { + if emitter.write_handler == nil { + panic("write handler not set") + } + + // Check if the buffer is empty. + if emitter.buffer_pos == 0 { + return true + } + + if err := emitter.write_handler(emitter, emitter.buffer[:emitter.buffer_pos]); err != nil { + return yaml_emitter_set_writer_error(emitter, "write error: "+err.Error()) + } + emitter.buffer_pos = 0 + return true +} diff --git a/index/server/vendor/github.com/oasdiff/yaml3/yaml.go b/index/server/vendor/github.com/oasdiff/yaml3/yaml.go new file mode 100644 index 000000000..a49a19b2d --- /dev/null +++ b/index/server/vendor/github.com/oasdiff/yaml3/yaml.go @@ -0,0 +1,701 @@ +// +// Copyright (c) 2011-2019 Canonical Ltd +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Package yaml implements YAML support for the Go language. +// +// Source code and other details for the project are available at GitHub: +// +// https://github.com/go-yaml/yaml +package yaml + +import ( + "errors" + "fmt" + "io" + "reflect" + "strings" + "sync" + "unicode/utf8" +) + +// The Unmarshaler interface may be implemented by types to customize their +// behavior when being unmarshaled from a YAML document. +type Unmarshaler interface { + UnmarshalYAML(value *Node) error +} + +type obsoleteUnmarshaler interface { + UnmarshalYAML(unmarshal func(interface{}) error) error +} + +// The Marshaler interface may be implemented by types to customize their +// behavior when being marshaled into a YAML document. The returned value +// is marshaled in place of the original value implementing Marshaler. +// +// If an error is returned by MarshalYAML, the marshaling procedure stops +// and returns with the provided error. +type Marshaler interface { + MarshalYAML() (interface{}, error) +} + +// Unmarshal decodes the first document found within the in byte slice +// and assigns decoded values into the out value. +// +// Maps and pointers (to a struct, string, int, etc) are accepted as out +// values. If an internal pointer within a struct is not initialized, +// the yaml package will initialize it if necessary for unmarshalling +// the provided data. The out parameter must not be nil. +// +// The type of the decoded values should be compatible with the respective +// values in out. If one or more values cannot be decoded due to a type +// mismatches, decoding continues partially until the end of the YAML +// content, and a *yaml.TypeError is returned with details for all +// missed values. +// +// Struct fields are only unmarshalled if they are exported (have an +// upper case first letter), and are unmarshalled using the field name +// lowercased as the default key. Custom keys may be defined via the +// "yaml" name in the field tag: the content preceding the first comma +// is used as the key, and the following comma-separated options are +// used to tweak the marshalling process (see Marshal). +// Conflicting names result in a runtime error. +// +// For example: +// +// type T struct { +// F int `yaml:"a,omitempty"` +// B int +// } +// var t T +// yaml.Unmarshal([]byte("a: 1\nb: 2"), &t) +// +// See the documentation of Marshal for the format of tags and a list of +// supported tag options. +func Unmarshal(in []byte, out interface{}) (err error) { + return unmarshal(in, out, false) +} + +// A Decoder reads and decodes YAML values from an input stream. +type Decoder struct { + parser *parser + knownFields bool + origin bool +} + +// NewDecoder returns a new decoder that reads from r. +// +// The decoder introduces its own buffering and may read +// data from r beyond the YAML values requested. +func NewDecoder(r io.Reader) *Decoder { + return &Decoder{ + parser: newParserFromReader(r), + } +} + +// KnownFields ensures that the keys in decoded mappings to +// exist as fields in the struct being decoded into. +func (dec *Decoder) KnownFields(enable bool) { + dec.knownFields = enable +} + +// Origin enables the recording of the line and column of the +// decoded values in the YAML content. +func (dec *Decoder) Origin(enable bool) { + dec.origin = enable +} + +// Decode reads the next YAML-encoded value from its input +// and stores it in the value pointed to by v. +// +// See the documentation for Unmarshal for details about the +// conversion of YAML into a Go value. +func (dec *Decoder) Decode(v interface{}) (err error) { + d := newDecoder() + d.knownFields = dec.knownFields + d.origin = dec.origin + defer handleErr(&err) + node := dec.parser.parse() + if node == nil { + return io.EOF + } + out := reflect.ValueOf(v) + if out.Kind() == reflect.Ptr && !out.IsNil() { + out = out.Elem() + } + d.unmarshal(node, out) + if len(d.terrors) > 0 { + return &TypeError{d.terrors} + } + return nil +} + +// Decode decodes the node and stores its data into the value pointed to by v. +// +// See the documentation for Unmarshal for details about the +// conversion of YAML into a Go value. +func (n *Node) Decode(v interface{}) (err error) { + d := newDecoder() + defer handleErr(&err) + out := reflect.ValueOf(v) + if out.Kind() == reflect.Ptr && !out.IsNil() { + out = out.Elem() + } + d.unmarshal(n, out) + if len(d.terrors) > 0 { + return &TypeError{d.terrors} + } + return nil +} + +func unmarshal(in []byte, out interface{}, strict bool) (err error) { + defer handleErr(&err) + d := newDecoder() + p := newParser(in) + defer p.destroy() + node := p.parse() + if node != nil { + v := reflect.ValueOf(out) + if v.Kind() == reflect.Ptr && !v.IsNil() { + v = v.Elem() + } + d.unmarshal(node, v) + } + if len(d.terrors) > 0 { + return &TypeError{d.terrors} + } + return nil +} + +// Marshal serializes the value provided into a YAML document. The structure +// of the generated document will reflect the structure of the value itself. +// Maps and pointers (to struct, string, int, etc) are accepted as the in value. +// +// Struct fields are only marshalled if they are exported (have an upper case +// first letter), and are marshalled using the field name lowercased as the +// default key. Custom keys may be defined via the "yaml" name in the field +// tag: the content preceding the first comma is used as the key, and the +// following comma-separated options are used to tweak the marshalling process. +// Conflicting names result in a runtime error. +// +// The field tag format accepted is: +// +// `(...) yaml:"[][,[,]]" (...)` +// +// The following flags are currently supported: +// +// omitempty Only include the field if it's not set to the zero +// value for the type or to empty slices or maps. +// Zero valued structs will be omitted if all their public +// fields are zero, unless they implement an IsZero +// method (see the IsZeroer interface type), in which +// case the field will be excluded if IsZero returns true. +// +// flow Marshal using a flow style (useful for structs, +// sequences and maps). +// +// inline Inline the field, which must be a struct or a map, +// causing all of its fields or keys to be processed as if +// they were part of the outer struct. For maps, keys must +// not conflict with the yaml keys of other struct fields. +// +// In addition, if the key is "-", the field is ignored. +// +// For example: +// +// type T struct { +// F int `yaml:"a,omitempty"` +// B int +// } +// yaml.Marshal(&T{B: 2}) // Returns "b: 2\n" +// yaml.Marshal(&T{F: 1}} // Returns "a: 1\nb: 0\n" +func Marshal(in interface{}) (out []byte, err error) { + defer handleErr(&err) + e := newEncoder() + defer e.destroy() + e.marshalDoc("", reflect.ValueOf(in)) + e.finish() + out = e.out + return +} + +// An Encoder writes YAML values to an output stream. +type Encoder struct { + encoder *encoder +} + +// NewEncoder returns a new encoder that writes to w. +// The Encoder should be closed after use to flush all data +// to w. +func NewEncoder(w io.Writer) *Encoder { + return &Encoder{ + encoder: newEncoderWithWriter(w), + } +} + +// Encode writes the YAML encoding of v to the stream. +// If multiple items are encoded to the stream, the +// second and subsequent document will be preceded +// with a "---" document separator, but the first will not. +// +// See the documentation for Marshal for details about the conversion of Go +// values to YAML. +func (e *Encoder) Encode(v interface{}) (err error) { + defer handleErr(&err) + e.encoder.marshalDoc("", reflect.ValueOf(v)) + return nil +} + +// Encode encodes value v and stores its representation in n. +// +// See the documentation for Marshal for details about the +// conversion of Go values into YAML. +func (n *Node) Encode(v interface{}) (err error) { + defer handleErr(&err) + e := newEncoder() + defer e.destroy() + e.marshalDoc("", reflect.ValueOf(v)) + e.finish() + p := newParser(e.out) + p.textless = true + defer p.destroy() + doc := p.parse() + *n = *doc.Content[0] + return nil +} + +// SetIndent changes the used indentation used when encoding. +func (e *Encoder) SetIndent(spaces int) { + if spaces < 0 { + panic("yaml: cannot indent to a negative number of spaces") + } + e.encoder.indent = spaces +} + +// Close closes the encoder by writing any remaining data. +// It does not write a stream terminating string "...". +func (e *Encoder) Close() (err error) { + defer handleErr(&err) + e.encoder.finish() + return nil +} + +func handleErr(err *error) { + if v := recover(); v != nil { + if e, ok := v.(yamlError); ok { + *err = e.err + } else { + panic(v) + } + } +} + +type yamlError struct { + err error +} + +func fail(err error) { + panic(yamlError{err}) +} + +func failf(format string, args ...interface{}) { + panic(yamlError{fmt.Errorf("yaml: "+format, args...)}) +} + +// A TypeError is returned by Unmarshal when one or more fields in +// the YAML document cannot be properly decoded into the requested +// types. When this error is returned, the value is still +// unmarshaled partially. +type TypeError struct { + Errors []string +} + +func (e *TypeError) Error() string { + return fmt.Sprintf("yaml: unmarshal errors:\n %s", strings.Join(e.Errors, "\n ")) +} + +type Kind uint32 + +const ( + DocumentNode Kind = 1 << iota + SequenceNode + MappingNode + ScalarNode + AliasNode +) + +type Style uint32 + +const ( + TaggedStyle Style = 1 << iota + DoubleQuotedStyle + SingleQuotedStyle + LiteralStyle + FoldedStyle + FlowStyle +) + +// Node represents an element in the YAML document hierarchy. While documents +// are typically encoded and decoded into higher level types, such as structs +// and maps, Node is an intermediate representation that allows detailed +// control over the content being decoded or encoded. +// +// It's worth noting that although Node offers access into details such as +// line numbers, colums, and comments, the content when re-encoded will not +// have its original textual representation preserved. An effort is made to +// render the data plesantly, and to preserve comments near the data they +// describe, though. +// +// Values that make use of the Node type interact with the yaml package in the +// same way any other type would do, by encoding and decoding yaml data +// directly or indirectly into them. +// +// For example: +// +// var person struct { +// Name string +// Address yaml.Node +// } +// err := yaml.Unmarshal(data, &person) +// +// Or by itself: +// +// var person Node +// err := yaml.Unmarshal(data, &person) +type Node struct { + // Kind defines whether the node is a document, a mapping, a sequence, + // a scalar value, or an alias to another node. The specific data type of + // scalar nodes may be obtained via the ShortTag and LongTag methods. + Kind Kind + + // Style allows customizing the apperance of the node in the tree. + Style Style + + // Tag holds the YAML tag defining the data type for the value. + // When decoding, this field will always be set to the resolved tag, + // even when it wasn't explicitly provided in the YAML content. + // When encoding, if this field is unset the value type will be + // implied from the node properties, and if it is set, it will only + // be serialized into the representation if TaggedStyle is used or + // the implicit tag diverges from the provided one. + Tag string + + // Value holds the unescaped and unquoted represenation of the value. + Value string + + // Anchor holds the anchor name for this node, which allows aliases to point to it. + Anchor string + + // Alias holds the node that this alias points to. Only valid when Kind is AliasNode. + Alias *Node + + // Content holds contained nodes for documents, mappings, and sequences. + Content []*Node + + // HeadComment holds any comments in the lines preceding the node and + // not separated by an empty line. + HeadComment string + + // LineComment holds any comments at the end of the line where the node is in. + LineComment string + + // FootComment holds any comments following the node and before empty lines. + FootComment string + + // Line and Column hold the node position in the decoded YAML text. + // These fields are not respected when encoding the node. + Line int + Column int +} + +// IsZero returns whether the node has all of its fields unset. +func (n *Node) IsZero() bool { + return n.Kind == 0 && n.Style == 0 && n.Tag == "" && n.Value == "" && n.Anchor == "" && n.Alias == nil && n.Content == nil && + n.HeadComment == "" && n.LineComment == "" && n.FootComment == "" && n.Line == 0 && n.Column == 0 +} + +// LongTag returns the long form of the tag that indicates the data type for +// the node. If the Tag field isn't explicitly defined, one will be computed +// based on the node properties. +func (n *Node) LongTag() string { + return longTag(n.ShortTag()) +} + +// ShortTag returns the short form of the YAML tag that indicates data type for +// the node. If the Tag field isn't explicitly defined, one will be computed +// based on the node properties. +func (n *Node) ShortTag() string { + if n.indicatedString() { + return strTag + } + if n.Tag == "" || n.Tag == "!" { + switch n.Kind { + case MappingNode: + return mapTag + case SequenceNode: + return seqTag + case AliasNode: + if n.Alias != nil { + return n.Alias.ShortTag() + } + case ScalarNode: + tag, _ := resolve("", n.Value) + return tag + case 0: + // Special case to make the zero value convenient. + if n.IsZero() { + return nullTag + } + } + return "" + } + return shortTag(n.Tag) +} + +func (n *Node) indicatedString() bool { + return n.Kind == ScalarNode && + (shortTag(n.Tag) == strTag || + (n.Tag == "" || n.Tag == "!") && n.Style&(SingleQuotedStyle|DoubleQuotedStyle|LiteralStyle|FoldedStyle) != 0) +} + +// SetString is a convenience function that sets the node to a string value +// and defines its style in a pleasant way depending on its content. +func (n *Node) SetString(s string) { + n.Kind = ScalarNode + if utf8.ValidString(s) { + n.Value = s + n.Tag = strTag + } else { + n.Value = encodeBase64(s) + n.Tag = binaryTag + } + if strings.Contains(n.Value, "\n") { + n.Style = LiteralStyle + } +} + +// -------------------------------------------------------------------------- +// Maintain a mapping of keys to structure field indexes + +// The code in this section was copied from mgo/bson. + +// structInfo holds details for the serialization of fields of +// a given struct. +type structInfo struct { + FieldsMap map[string]fieldInfo + FieldsList []fieldInfo + + // InlineMap is the number of the field in the struct that + // contains an ,inline map, or -1 if there's none. + InlineMap int + + // InlineUnmarshalers holds indexes to inlined fields that + // contain unmarshaler values. + InlineUnmarshalers [][]int +} + +type fieldInfo struct { + Key string + Num int + OmitEmpty bool + Flow bool + // Id holds the unique field identifier, so we can cheaply + // check for field duplicates without maintaining an extra map. + Id int + + // Inline holds the field index if the field is part of an inlined struct. + Inline []int +} + +var structMap = make(map[reflect.Type]*structInfo) +var fieldMapMutex sync.RWMutex +var unmarshalerType reflect.Type + +func init() { + var v Unmarshaler + unmarshalerType = reflect.ValueOf(&v).Elem().Type() +} + +func getStructInfo(st reflect.Type) (*structInfo, error) { + fieldMapMutex.RLock() + sinfo, found := structMap[st] + fieldMapMutex.RUnlock() + if found { + return sinfo, nil + } + + n := st.NumField() + fieldsMap := make(map[string]fieldInfo) + fieldsList := make([]fieldInfo, 0, n) + inlineMap := -1 + inlineUnmarshalers := [][]int(nil) + for i := 0; i != n; i++ { + field := st.Field(i) + if field.PkgPath != "" && !field.Anonymous { + continue // Private field + } + + info := fieldInfo{Num: i} + + tag := field.Tag.Get("yaml") + if tag == "" && strings.Index(string(field.Tag), ":") < 0 { + tag = string(field.Tag) + } + if tag == "-" { + continue + } + + inline := false + fields := strings.Split(tag, ",") + if len(fields) > 1 { + for _, flag := range fields[1:] { + switch flag { + case "omitempty": + info.OmitEmpty = true + case "flow": + info.Flow = true + case "inline": + inline = true + default: + return nil, errors.New(fmt.Sprintf("unsupported flag %q in tag %q of type %s", flag, tag, st)) + } + } + tag = fields[0] + } + + if inline { + switch field.Type.Kind() { + case reflect.Map: + if inlineMap >= 0 { + return nil, errors.New("multiple ,inline maps in struct " + st.String()) + } + if field.Type.Key() != reflect.TypeOf("") { + return nil, errors.New("option ,inline needs a map with string keys in struct " + st.String()) + } + inlineMap = info.Num + case reflect.Struct, reflect.Ptr: + ftype := field.Type + for ftype.Kind() == reflect.Ptr { + ftype = ftype.Elem() + } + if ftype.Kind() != reflect.Struct { + return nil, errors.New("option ,inline may only be used on a struct or map field") + } + if reflect.PtrTo(ftype).Implements(unmarshalerType) { + inlineUnmarshalers = append(inlineUnmarshalers, []int{i}) + } else { + sinfo, err := getStructInfo(ftype) + if err != nil { + return nil, err + } + for _, index := range sinfo.InlineUnmarshalers { + inlineUnmarshalers = append(inlineUnmarshalers, append([]int{i}, index...)) + } + for _, finfo := range sinfo.FieldsList { + if _, found := fieldsMap[finfo.Key]; found { + msg := "duplicated key '" + finfo.Key + "' in struct " + st.String() + return nil, errors.New(msg) + } + if finfo.Inline == nil { + finfo.Inline = []int{i, finfo.Num} + } else { + finfo.Inline = append([]int{i}, finfo.Inline...) + } + finfo.Id = len(fieldsList) + fieldsMap[finfo.Key] = finfo + fieldsList = append(fieldsList, finfo) + } + } + default: + return nil, errors.New("option ,inline may only be used on a struct or map field") + } + continue + } + + if tag != "" { + info.Key = tag + } else { + info.Key = strings.ToLower(field.Name) + } + + if _, found = fieldsMap[info.Key]; found { + msg := "duplicated key '" + info.Key + "' in struct " + st.String() + return nil, errors.New(msg) + } + + info.Id = len(fieldsList) + fieldsList = append(fieldsList, info) + fieldsMap[info.Key] = info + } + + sinfo = &structInfo{ + FieldsMap: fieldsMap, + FieldsList: fieldsList, + InlineMap: inlineMap, + InlineUnmarshalers: inlineUnmarshalers, + } + + fieldMapMutex.Lock() + structMap[st] = sinfo + fieldMapMutex.Unlock() + return sinfo, nil +} + +// IsZeroer is used to check whether an object is zero to +// determine whether it should be omitted when marshaling +// with the omitempty flag. One notable implementation +// is time.Time. +type IsZeroer interface { + IsZero() bool +} + +func isZero(v reflect.Value) bool { + kind := v.Kind() + if z, ok := v.Interface().(IsZeroer); ok { + if (kind == reflect.Ptr || kind == reflect.Interface) && v.IsNil() { + return true + } + return z.IsZero() + } + switch kind { + case reflect.String: + return len(v.String()) == 0 + case reflect.Interface, reflect.Ptr: + return v.IsNil() + case reflect.Slice: + return v.Len() == 0 + case reflect.Map: + return v.Len() == 0 + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + return v.Int() == 0 + case reflect.Float32, reflect.Float64: + return v.Float() == 0 + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: + return v.Uint() == 0 + case reflect.Bool: + return !v.Bool() + case reflect.Struct: + vt := v.Type() + for i := v.NumField() - 1; i >= 0; i-- { + if vt.Field(i).PkgPath != "" { + continue // Private field + } + if !isZero(v.Field(i)) { + return false + } + } + return true + } + return false +} diff --git a/index/server/vendor/github.com/oasdiff/yaml3/yamlh.go b/index/server/vendor/github.com/oasdiff/yaml3/yamlh.go new file mode 100644 index 000000000..7c6d00770 --- /dev/null +++ b/index/server/vendor/github.com/oasdiff/yaml3/yamlh.go @@ -0,0 +1,807 @@ +// +// Copyright (c) 2011-2019 Canonical Ltd +// Copyright (c) 2006-2010 Kirill Simonov +// +// 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. + +package yaml + +import ( + "fmt" + "io" +) + +// The version directive data. +type yaml_version_directive_t struct { + major int8 // The major version number. + minor int8 // The minor version number. +} + +// The tag directive data. +type yaml_tag_directive_t struct { + handle []byte // The tag handle. + prefix []byte // The tag prefix. +} + +type yaml_encoding_t int + +// The stream encoding. +const ( + // Let the parser choose the encoding. + yaml_ANY_ENCODING yaml_encoding_t = iota + + yaml_UTF8_ENCODING // The default UTF-8 encoding. + yaml_UTF16LE_ENCODING // The UTF-16-LE encoding with BOM. + yaml_UTF16BE_ENCODING // The UTF-16-BE encoding with BOM. +) + +type yaml_break_t int + +// Line break types. +const ( + // Let the parser choose the break type. + yaml_ANY_BREAK yaml_break_t = iota + + yaml_CR_BREAK // Use CR for line breaks (Mac style). + yaml_LN_BREAK // Use LN for line breaks (Unix style). + yaml_CRLN_BREAK // Use CR LN for line breaks (DOS style). +) + +type yaml_error_type_t int + +// Many bad things could happen with the parser and emitter. +const ( + // No error is produced. + yaml_NO_ERROR yaml_error_type_t = iota + + yaml_MEMORY_ERROR // Cannot allocate or reallocate a block of memory. + yaml_READER_ERROR // Cannot read or decode the input stream. + yaml_SCANNER_ERROR // Cannot scan the input stream. + yaml_PARSER_ERROR // Cannot parse the input stream. + yaml_COMPOSER_ERROR // Cannot compose a YAML document. + yaml_WRITER_ERROR // Cannot write to the output stream. + yaml_EMITTER_ERROR // Cannot emit a YAML stream. +) + +// The pointer position. +type yaml_mark_t struct { + index int // The position index. + line int // The position line. + column int // The position column. +} + +// Node Styles + +type yaml_style_t int8 + +type yaml_scalar_style_t yaml_style_t + +// Scalar styles. +const ( + // Let the emitter choose the style. + yaml_ANY_SCALAR_STYLE yaml_scalar_style_t = 0 + + yaml_PLAIN_SCALAR_STYLE yaml_scalar_style_t = 1 << iota // The plain scalar style. + yaml_SINGLE_QUOTED_SCALAR_STYLE // The single-quoted scalar style. + yaml_DOUBLE_QUOTED_SCALAR_STYLE // The double-quoted scalar style. + yaml_LITERAL_SCALAR_STYLE // The literal scalar style. + yaml_FOLDED_SCALAR_STYLE // The folded scalar style. +) + +type yaml_sequence_style_t yaml_style_t + +// Sequence styles. +const ( + // Let the emitter choose the style. + yaml_ANY_SEQUENCE_STYLE yaml_sequence_style_t = iota + + yaml_BLOCK_SEQUENCE_STYLE // The block sequence style. + yaml_FLOW_SEQUENCE_STYLE // The flow sequence style. +) + +type yaml_mapping_style_t yaml_style_t + +// Mapping styles. +const ( + // Let the emitter choose the style. + yaml_ANY_MAPPING_STYLE yaml_mapping_style_t = iota + + yaml_BLOCK_MAPPING_STYLE // The block mapping style. + yaml_FLOW_MAPPING_STYLE // The flow mapping style. +) + +// Tokens + +type yaml_token_type_t int + +// Token types. +const ( + // An empty token. + yaml_NO_TOKEN yaml_token_type_t = iota + + yaml_STREAM_START_TOKEN // A STREAM-START token. + yaml_STREAM_END_TOKEN // A STREAM-END token. + + yaml_VERSION_DIRECTIVE_TOKEN // A VERSION-DIRECTIVE token. + yaml_TAG_DIRECTIVE_TOKEN // A TAG-DIRECTIVE token. + yaml_DOCUMENT_START_TOKEN // A DOCUMENT-START token. + yaml_DOCUMENT_END_TOKEN // A DOCUMENT-END token. + + yaml_BLOCK_SEQUENCE_START_TOKEN // A BLOCK-SEQUENCE-START token. + yaml_BLOCK_MAPPING_START_TOKEN // A BLOCK-SEQUENCE-END token. + yaml_BLOCK_END_TOKEN // A BLOCK-END token. + + yaml_FLOW_SEQUENCE_START_TOKEN // A FLOW-SEQUENCE-START token. + yaml_FLOW_SEQUENCE_END_TOKEN // A FLOW-SEQUENCE-END token. + yaml_FLOW_MAPPING_START_TOKEN // A FLOW-MAPPING-START token. + yaml_FLOW_MAPPING_END_TOKEN // A FLOW-MAPPING-END token. + + yaml_BLOCK_ENTRY_TOKEN // A BLOCK-ENTRY token. + yaml_FLOW_ENTRY_TOKEN // A FLOW-ENTRY token. + yaml_KEY_TOKEN // A KEY token. + yaml_VALUE_TOKEN // A VALUE token. + + yaml_ALIAS_TOKEN // An ALIAS token. + yaml_ANCHOR_TOKEN // An ANCHOR token. + yaml_TAG_TOKEN // A TAG token. + yaml_SCALAR_TOKEN // A SCALAR token. +) + +func (tt yaml_token_type_t) String() string { + switch tt { + case yaml_NO_TOKEN: + return "yaml_NO_TOKEN" + case yaml_STREAM_START_TOKEN: + return "yaml_STREAM_START_TOKEN" + case yaml_STREAM_END_TOKEN: + return "yaml_STREAM_END_TOKEN" + case yaml_VERSION_DIRECTIVE_TOKEN: + return "yaml_VERSION_DIRECTIVE_TOKEN" + case yaml_TAG_DIRECTIVE_TOKEN: + return "yaml_TAG_DIRECTIVE_TOKEN" + case yaml_DOCUMENT_START_TOKEN: + return "yaml_DOCUMENT_START_TOKEN" + case yaml_DOCUMENT_END_TOKEN: + return "yaml_DOCUMENT_END_TOKEN" + case yaml_BLOCK_SEQUENCE_START_TOKEN: + return "yaml_BLOCK_SEQUENCE_START_TOKEN" + case yaml_BLOCK_MAPPING_START_TOKEN: + return "yaml_BLOCK_MAPPING_START_TOKEN" + case yaml_BLOCK_END_TOKEN: + return "yaml_BLOCK_END_TOKEN" + case yaml_FLOW_SEQUENCE_START_TOKEN: + return "yaml_FLOW_SEQUENCE_START_TOKEN" + case yaml_FLOW_SEQUENCE_END_TOKEN: + return "yaml_FLOW_SEQUENCE_END_TOKEN" + case yaml_FLOW_MAPPING_START_TOKEN: + return "yaml_FLOW_MAPPING_START_TOKEN" + case yaml_FLOW_MAPPING_END_TOKEN: + return "yaml_FLOW_MAPPING_END_TOKEN" + case yaml_BLOCK_ENTRY_TOKEN: + return "yaml_BLOCK_ENTRY_TOKEN" + case yaml_FLOW_ENTRY_TOKEN: + return "yaml_FLOW_ENTRY_TOKEN" + case yaml_KEY_TOKEN: + return "yaml_KEY_TOKEN" + case yaml_VALUE_TOKEN: + return "yaml_VALUE_TOKEN" + case yaml_ALIAS_TOKEN: + return "yaml_ALIAS_TOKEN" + case yaml_ANCHOR_TOKEN: + return "yaml_ANCHOR_TOKEN" + case yaml_TAG_TOKEN: + return "yaml_TAG_TOKEN" + case yaml_SCALAR_TOKEN: + return "yaml_SCALAR_TOKEN" + } + return "" +} + +// The token structure. +type yaml_token_t struct { + // The token type. + typ yaml_token_type_t + + // The start/end of the token. + start_mark, end_mark yaml_mark_t + + // The stream encoding (for yaml_STREAM_START_TOKEN). + encoding yaml_encoding_t + + // The alias/anchor/scalar value or tag/tag directive handle + // (for yaml_ALIAS_TOKEN, yaml_ANCHOR_TOKEN, yaml_SCALAR_TOKEN, yaml_TAG_TOKEN, yaml_TAG_DIRECTIVE_TOKEN). + value []byte + + // The tag suffix (for yaml_TAG_TOKEN). + suffix []byte + + // The tag directive prefix (for yaml_TAG_DIRECTIVE_TOKEN). + prefix []byte + + // The scalar style (for yaml_SCALAR_TOKEN). + style yaml_scalar_style_t + + // The version directive major/minor (for yaml_VERSION_DIRECTIVE_TOKEN). + major, minor int8 +} + +// Events + +type yaml_event_type_t int8 + +// Event types. +const ( + // An empty event. + yaml_NO_EVENT yaml_event_type_t = iota + + yaml_STREAM_START_EVENT // A STREAM-START event. + yaml_STREAM_END_EVENT // A STREAM-END event. + yaml_DOCUMENT_START_EVENT // A DOCUMENT-START event. + yaml_DOCUMENT_END_EVENT // A DOCUMENT-END event. + yaml_ALIAS_EVENT // An ALIAS event. + yaml_SCALAR_EVENT // A SCALAR event. + yaml_SEQUENCE_START_EVENT // A SEQUENCE-START event. + yaml_SEQUENCE_END_EVENT // A SEQUENCE-END event. + yaml_MAPPING_START_EVENT // A MAPPING-START event. + yaml_MAPPING_END_EVENT // A MAPPING-END event. + yaml_TAIL_COMMENT_EVENT +) + +var eventStrings = []string{ + yaml_NO_EVENT: "none", + yaml_STREAM_START_EVENT: "stream start", + yaml_STREAM_END_EVENT: "stream end", + yaml_DOCUMENT_START_EVENT: "document start", + yaml_DOCUMENT_END_EVENT: "document end", + yaml_ALIAS_EVENT: "alias", + yaml_SCALAR_EVENT: "scalar", + yaml_SEQUENCE_START_EVENT: "sequence start", + yaml_SEQUENCE_END_EVENT: "sequence end", + yaml_MAPPING_START_EVENT: "mapping start", + yaml_MAPPING_END_EVENT: "mapping end", + yaml_TAIL_COMMENT_EVENT: "tail comment", +} + +func (e yaml_event_type_t) String() string { + if e < 0 || int(e) >= len(eventStrings) { + return fmt.Sprintf("unknown event %d", e) + } + return eventStrings[e] +} + +// The event structure. +type yaml_event_t struct { + + // The event type. + typ yaml_event_type_t + + // The start and end of the event. + start_mark, end_mark yaml_mark_t + + // The document encoding (for yaml_STREAM_START_EVENT). + encoding yaml_encoding_t + + // The version directive (for yaml_DOCUMENT_START_EVENT). + version_directive *yaml_version_directive_t + + // The list of tag directives (for yaml_DOCUMENT_START_EVENT). + tag_directives []yaml_tag_directive_t + + // The comments + head_comment []byte + line_comment []byte + foot_comment []byte + tail_comment []byte + + // The anchor (for yaml_SCALAR_EVENT, yaml_SEQUENCE_START_EVENT, yaml_MAPPING_START_EVENT, yaml_ALIAS_EVENT). + anchor []byte + + // The tag (for yaml_SCALAR_EVENT, yaml_SEQUENCE_START_EVENT, yaml_MAPPING_START_EVENT). + tag []byte + + // The scalar value (for yaml_SCALAR_EVENT). + value []byte + + // Is the document start/end indicator implicit, or the tag optional? + // (for yaml_DOCUMENT_START_EVENT, yaml_DOCUMENT_END_EVENT, yaml_SEQUENCE_START_EVENT, yaml_MAPPING_START_EVENT, yaml_SCALAR_EVENT). + implicit bool + + // Is the tag optional for any non-plain style? (for yaml_SCALAR_EVENT). + quoted_implicit bool + + // The style (for yaml_SCALAR_EVENT, yaml_SEQUENCE_START_EVENT, yaml_MAPPING_START_EVENT). + style yaml_style_t +} + +func (e *yaml_event_t) scalar_style() yaml_scalar_style_t { return yaml_scalar_style_t(e.style) } +func (e *yaml_event_t) sequence_style() yaml_sequence_style_t { return yaml_sequence_style_t(e.style) } +func (e *yaml_event_t) mapping_style() yaml_mapping_style_t { return yaml_mapping_style_t(e.style) } + +// Nodes + +const ( + yaml_NULL_TAG = "tag:yaml.org,2002:null" // The tag !!null with the only possible value: null. + yaml_BOOL_TAG = "tag:yaml.org,2002:bool" // The tag !!bool with the values: true and false. + yaml_STR_TAG = "tag:yaml.org,2002:str" // The tag !!str for string values. + yaml_INT_TAG = "tag:yaml.org,2002:int" // The tag !!int for integer values. + yaml_FLOAT_TAG = "tag:yaml.org,2002:float" // The tag !!float for float values. + yaml_TIMESTAMP_TAG = "tag:yaml.org,2002:timestamp" // The tag !!timestamp for date and time values. + + yaml_SEQ_TAG = "tag:yaml.org,2002:seq" // The tag !!seq is used to denote sequences. + yaml_MAP_TAG = "tag:yaml.org,2002:map" // The tag !!map is used to denote mapping. + + // Not in original libyaml. + yaml_BINARY_TAG = "tag:yaml.org,2002:binary" + yaml_MERGE_TAG = "tag:yaml.org,2002:merge" + + yaml_DEFAULT_SCALAR_TAG = yaml_STR_TAG // The default scalar tag is !!str. + yaml_DEFAULT_SEQUENCE_TAG = yaml_SEQ_TAG // The default sequence tag is !!seq. + yaml_DEFAULT_MAPPING_TAG = yaml_MAP_TAG // The default mapping tag is !!map. +) + +type yaml_node_type_t int + +// Node types. +const ( + // An empty node. + yaml_NO_NODE yaml_node_type_t = iota + + yaml_SCALAR_NODE // A scalar node. + yaml_SEQUENCE_NODE // A sequence node. + yaml_MAPPING_NODE // A mapping node. +) + +// An element of a sequence node. +type yaml_node_item_t int + +// An element of a mapping node. +type yaml_node_pair_t struct { + key int // The key of the element. + value int // The value of the element. +} + +// The node structure. +type yaml_node_t struct { + typ yaml_node_type_t // The node type. + tag []byte // The node tag. + + // The node data. + + // The scalar parameters (for yaml_SCALAR_NODE). + scalar struct { + value []byte // The scalar value. + length int // The length of the scalar value. + style yaml_scalar_style_t // The scalar style. + } + + // The sequence parameters (for YAML_SEQUENCE_NODE). + sequence struct { + items_data []yaml_node_item_t // The stack of sequence items. + style yaml_sequence_style_t // The sequence style. + } + + // The mapping parameters (for yaml_MAPPING_NODE). + mapping struct { + pairs_data []yaml_node_pair_t // The stack of mapping pairs (key, value). + pairs_start *yaml_node_pair_t // The beginning of the stack. + pairs_end *yaml_node_pair_t // The end of the stack. + pairs_top *yaml_node_pair_t // The top of the stack. + style yaml_mapping_style_t // The mapping style. + } + + start_mark yaml_mark_t // The beginning of the node. + end_mark yaml_mark_t // The end of the node. + +} + +// The document structure. +type yaml_document_t struct { + + // The document nodes. + nodes []yaml_node_t + + // The version directive. + version_directive *yaml_version_directive_t + + // The list of tag directives. + tag_directives_data []yaml_tag_directive_t + tag_directives_start int // The beginning of the tag directives list. + tag_directives_end int // The end of the tag directives list. + + start_implicit int // Is the document start indicator implicit? + end_implicit int // Is the document end indicator implicit? + + // The start/end of the document. + start_mark, end_mark yaml_mark_t +} + +// The prototype of a read handler. +// +// The read handler is called when the parser needs to read more bytes from the +// source. The handler should write not more than size bytes to the buffer. +// The number of written bytes should be set to the size_read variable. +// +// [in,out] data A pointer to an application data specified by +// yaml_parser_set_input(). +// [out] buffer The buffer to write the data from the source. +// [in] size The size of the buffer. +// [out] size_read The actual number of bytes read from the source. +// +// On success, the handler should return 1. If the handler failed, +// the returned value should be 0. On EOF, the handler should set the +// size_read to 0 and return 1. +type yaml_read_handler_t func(parser *yaml_parser_t, buffer []byte) (n int, err error) + +// This structure holds information about a potential simple key. +type yaml_simple_key_t struct { + possible bool // Is a simple key possible? + required bool // Is a simple key required? + token_number int // The number of the token. + mark yaml_mark_t // The position mark. +} + +// The states of the parser. +type yaml_parser_state_t int + +const ( + yaml_PARSE_STREAM_START_STATE yaml_parser_state_t = iota + + yaml_PARSE_IMPLICIT_DOCUMENT_START_STATE // Expect the beginning of an implicit document. + yaml_PARSE_DOCUMENT_START_STATE // Expect DOCUMENT-START. + yaml_PARSE_DOCUMENT_CONTENT_STATE // Expect the content of a document. + yaml_PARSE_DOCUMENT_END_STATE // Expect DOCUMENT-END. + yaml_PARSE_BLOCK_NODE_STATE // Expect a block node. + yaml_PARSE_BLOCK_NODE_OR_INDENTLESS_SEQUENCE_STATE // Expect a block node or indentless sequence. + yaml_PARSE_FLOW_NODE_STATE // Expect a flow node. + yaml_PARSE_BLOCK_SEQUENCE_FIRST_ENTRY_STATE // Expect the first entry of a block sequence. + yaml_PARSE_BLOCK_SEQUENCE_ENTRY_STATE // Expect an entry of a block sequence. + yaml_PARSE_INDENTLESS_SEQUENCE_ENTRY_STATE // Expect an entry of an indentless sequence. + yaml_PARSE_BLOCK_MAPPING_FIRST_KEY_STATE // Expect the first key of a block mapping. + yaml_PARSE_BLOCK_MAPPING_KEY_STATE // Expect a block mapping key. + yaml_PARSE_BLOCK_MAPPING_VALUE_STATE // Expect a block mapping value. + yaml_PARSE_FLOW_SEQUENCE_FIRST_ENTRY_STATE // Expect the first entry of a flow sequence. + yaml_PARSE_FLOW_SEQUENCE_ENTRY_STATE // Expect an entry of a flow sequence. + yaml_PARSE_FLOW_SEQUENCE_ENTRY_MAPPING_KEY_STATE // Expect a key of an ordered mapping. + yaml_PARSE_FLOW_SEQUENCE_ENTRY_MAPPING_VALUE_STATE // Expect a value of an ordered mapping. + yaml_PARSE_FLOW_SEQUENCE_ENTRY_MAPPING_END_STATE // Expect the and of an ordered mapping entry. + yaml_PARSE_FLOW_MAPPING_FIRST_KEY_STATE // Expect the first key of a flow mapping. + yaml_PARSE_FLOW_MAPPING_KEY_STATE // Expect a key of a flow mapping. + yaml_PARSE_FLOW_MAPPING_VALUE_STATE // Expect a value of a flow mapping. + yaml_PARSE_FLOW_MAPPING_EMPTY_VALUE_STATE // Expect an empty value of a flow mapping. + yaml_PARSE_END_STATE // Expect nothing. +) + +func (ps yaml_parser_state_t) String() string { + switch ps { + case yaml_PARSE_STREAM_START_STATE: + return "yaml_PARSE_STREAM_START_STATE" + case yaml_PARSE_IMPLICIT_DOCUMENT_START_STATE: + return "yaml_PARSE_IMPLICIT_DOCUMENT_START_STATE" + case yaml_PARSE_DOCUMENT_START_STATE: + return "yaml_PARSE_DOCUMENT_START_STATE" + case yaml_PARSE_DOCUMENT_CONTENT_STATE: + return "yaml_PARSE_DOCUMENT_CONTENT_STATE" + case yaml_PARSE_DOCUMENT_END_STATE: + return "yaml_PARSE_DOCUMENT_END_STATE" + case yaml_PARSE_BLOCK_NODE_STATE: + return "yaml_PARSE_BLOCK_NODE_STATE" + case yaml_PARSE_BLOCK_NODE_OR_INDENTLESS_SEQUENCE_STATE: + return "yaml_PARSE_BLOCK_NODE_OR_INDENTLESS_SEQUENCE_STATE" + case yaml_PARSE_FLOW_NODE_STATE: + return "yaml_PARSE_FLOW_NODE_STATE" + case yaml_PARSE_BLOCK_SEQUENCE_FIRST_ENTRY_STATE: + return "yaml_PARSE_BLOCK_SEQUENCE_FIRST_ENTRY_STATE" + case yaml_PARSE_BLOCK_SEQUENCE_ENTRY_STATE: + return "yaml_PARSE_BLOCK_SEQUENCE_ENTRY_STATE" + case yaml_PARSE_INDENTLESS_SEQUENCE_ENTRY_STATE: + return "yaml_PARSE_INDENTLESS_SEQUENCE_ENTRY_STATE" + case yaml_PARSE_BLOCK_MAPPING_FIRST_KEY_STATE: + return "yaml_PARSE_BLOCK_MAPPING_FIRST_KEY_STATE" + case yaml_PARSE_BLOCK_MAPPING_KEY_STATE: + return "yaml_PARSE_BLOCK_MAPPING_KEY_STATE" + case yaml_PARSE_BLOCK_MAPPING_VALUE_STATE: + return "yaml_PARSE_BLOCK_MAPPING_VALUE_STATE" + case yaml_PARSE_FLOW_SEQUENCE_FIRST_ENTRY_STATE: + return "yaml_PARSE_FLOW_SEQUENCE_FIRST_ENTRY_STATE" + case yaml_PARSE_FLOW_SEQUENCE_ENTRY_STATE: + return "yaml_PARSE_FLOW_SEQUENCE_ENTRY_STATE" + case yaml_PARSE_FLOW_SEQUENCE_ENTRY_MAPPING_KEY_STATE: + return "yaml_PARSE_FLOW_SEQUENCE_ENTRY_MAPPING_KEY_STATE" + case yaml_PARSE_FLOW_SEQUENCE_ENTRY_MAPPING_VALUE_STATE: + return "yaml_PARSE_FLOW_SEQUENCE_ENTRY_MAPPING_VALUE_STATE" + case yaml_PARSE_FLOW_SEQUENCE_ENTRY_MAPPING_END_STATE: + return "yaml_PARSE_FLOW_SEQUENCE_ENTRY_MAPPING_END_STATE" + case yaml_PARSE_FLOW_MAPPING_FIRST_KEY_STATE: + return "yaml_PARSE_FLOW_MAPPING_FIRST_KEY_STATE" + case yaml_PARSE_FLOW_MAPPING_KEY_STATE: + return "yaml_PARSE_FLOW_MAPPING_KEY_STATE" + case yaml_PARSE_FLOW_MAPPING_VALUE_STATE: + return "yaml_PARSE_FLOW_MAPPING_VALUE_STATE" + case yaml_PARSE_FLOW_MAPPING_EMPTY_VALUE_STATE: + return "yaml_PARSE_FLOW_MAPPING_EMPTY_VALUE_STATE" + case yaml_PARSE_END_STATE: + return "yaml_PARSE_END_STATE" + } + return "" +} + +// This structure holds aliases data. +type yaml_alias_data_t struct { + anchor []byte // The anchor. + index int // The node id. + mark yaml_mark_t // The anchor mark. +} + +// The parser structure. +// +// All members are internal. Manage the structure using the +// yaml_parser_ family of functions. +type yaml_parser_t struct { + + // Error handling + + error yaml_error_type_t // Error type. + + problem string // Error description. + + // The byte about which the problem occurred. + problem_offset int + problem_value int + problem_mark yaml_mark_t + + // The error context. + context string + context_mark yaml_mark_t + + // Reader stuff + + read_handler yaml_read_handler_t // Read handler. + + input_reader io.Reader // File input data. + input []byte // String input data. + input_pos int + + eof bool // EOF flag + + buffer []byte // The working buffer. + buffer_pos int // The current position of the buffer. + + unread int // The number of unread characters in the buffer. + + newlines int // The number of line breaks since last non-break/non-blank character + + raw_buffer []byte // The raw buffer. + raw_buffer_pos int // The current position of the buffer. + + encoding yaml_encoding_t // The input encoding. + + offset int // The offset of the current position (in bytes). + mark yaml_mark_t // The mark of the current position. + + // Comments + + head_comment []byte // The current head comments + line_comment []byte // The current line comments + foot_comment []byte // The current foot comments + tail_comment []byte // Foot comment that happens at the end of a block. + stem_comment []byte // Comment in item preceding a nested structure (list inside list item, etc) + + comments []yaml_comment_t // The folded comments for all parsed tokens + comments_head int + + // Scanner stuff + + stream_start_produced bool // Have we started to scan the input stream? + stream_end_produced bool // Have we reached the end of the input stream? + + flow_level int // The number of unclosed '[' and '{' indicators. + + tokens []yaml_token_t // The tokens queue. + tokens_head int // The head of the tokens queue. + tokens_parsed int // The number of tokens fetched from the queue. + token_available bool // Does the tokens queue contain a token ready for dequeueing. + + indent int // The current indentation level. + indents []int // The indentation levels stack. + + simple_key_allowed bool // May a simple key occur at the current position? + simple_keys []yaml_simple_key_t // The stack of simple keys. + simple_keys_by_tok map[int]int // possible simple_key indexes indexed by token_number + + // Parser stuff + + state yaml_parser_state_t // The current parser state. + states []yaml_parser_state_t // The parser states stack. + marks []yaml_mark_t // The stack of marks. + tag_directives []yaml_tag_directive_t // The list of TAG directives. + + // Dumper stuff + + aliases []yaml_alias_data_t // The alias data. + + document *yaml_document_t // The currently parsed document. +} + +type yaml_comment_t struct { + + scan_mark yaml_mark_t // Position where scanning for comments started + token_mark yaml_mark_t // Position after which tokens will be associated with this comment + start_mark yaml_mark_t // Position of '#' comment mark + end_mark yaml_mark_t // Position where comment terminated + + head []byte + line []byte + foot []byte +} + +// Emitter Definitions + +// The prototype of a write handler. +// +// The write handler is called when the emitter needs to flush the accumulated +// characters to the output. The handler should write @a size bytes of the +// @a buffer to the output. +// +// @param[in,out] data A pointer to an application data specified by +// yaml_emitter_set_output(). +// @param[in] buffer The buffer with bytes to be written. +// @param[in] size The size of the buffer. +// +// @returns On success, the handler should return @c 1. If the handler failed, +// the returned value should be @c 0. +// +type yaml_write_handler_t func(emitter *yaml_emitter_t, buffer []byte) error + +type yaml_emitter_state_t int + +// The emitter states. +const ( + // Expect STREAM-START. + yaml_EMIT_STREAM_START_STATE yaml_emitter_state_t = iota + + yaml_EMIT_FIRST_DOCUMENT_START_STATE // Expect the first DOCUMENT-START or STREAM-END. + yaml_EMIT_DOCUMENT_START_STATE // Expect DOCUMENT-START or STREAM-END. + yaml_EMIT_DOCUMENT_CONTENT_STATE // Expect the content of a document. + yaml_EMIT_DOCUMENT_END_STATE // Expect DOCUMENT-END. + yaml_EMIT_FLOW_SEQUENCE_FIRST_ITEM_STATE // Expect the first item of a flow sequence. + yaml_EMIT_FLOW_SEQUENCE_TRAIL_ITEM_STATE // Expect the next item of a flow sequence, with the comma already written out + yaml_EMIT_FLOW_SEQUENCE_ITEM_STATE // Expect an item of a flow sequence. + yaml_EMIT_FLOW_MAPPING_FIRST_KEY_STATE // Expect the first key of a flow mapping. + yaml_EMIT_FLOW_MAPPING_TRAIL_KEY_STATE // Expect the next key of a flow mapping, with the comma already written out + yaml_EMIT_FLOW_MAPPING_KEY_STATE // Expect a key of a flow mapping. + yaml_EMIT_FLOW_MAPPING_SIMPLE_VALUE_STATE // Expect a value for a simple key of a flow mapping. + yaml_EMIT_FLOW_MAPPING_VALUE_STATE // Expect a value of a flow mapping. + yaml_EMIT_BLOCK_SEQUENCE_FIRST_ITEM_STATE // Expect the first item of a block sequence. + yaml_EMIT_BLOCK_SEQUENCE_ITEM_STATE // Expect an item of a block sequence. + yaml_EMIT_BLOCK_MAPPING_FIRST_KEY_STATE // Expect the first key of a block mapping. + yaml_EMIT_BLOCK_MAPPING_KEY_STATE // Expect the key of a block mapping. + yaml_EMIT_BLOCK_MAPPING_SIMPLE_VALUE_STATE // Expect a value for a simple key of a block mapping. + yaml_EMIT_BLOCK_MAPPING_VALUE_STATE // Expect a value of a block mapping. + yaml_EMIT_END_STATE // Expect nothing. +) + +// The emitter structure. +// +// All members are internal. Manage the structure using the @c yaml_emitter_ +// family of functions. +type yaml_emitter_t struct { + + // Error handling + + error yaml_error_type_t // Error type. + problem string // Error description. + + // Writer stuff + + write_handler yaml_write_handler_t // Write handler. + + output_buffer *[]byte // String output data. + output_writer io.Writer // File output data. + + buffer []byte // The working buffer. + buffer_pos int // The current position of the buffer. + + raw_buffer []byte // The raw buffer. + raw_buffer_pos int // The current position of the buffer. + + encoding yaml_encoding_t // The stream encoding. + + // Emitter stuff + + canonical bool // If the output is in the canonical style? + best_indent int // The number of indentation spaces. + best_width int // The preferred width of the output lines. + unicode bool // Allow unescaped non-ASCII characters? + line_break yaml_break_t // The preferred line break. + + state yaml_emitter_state_t // The current emitter state. + states []yaml_emitter_state_t // The stack of states. + + events []yaml_event_t // The event queue. + events_head int // The head of the event queue. + + indents []int // The stack of indentation levels. + + tag_directives []yaml_tag_directive_t // The list of tag directives. + + indent int // The current indentation level. + + flow_level int // The current flow level. + + root_context bool // Is it the document root context? + sequence_context bool // Is it a sequence context? + mapping_context bool // Is it a mapping context? + simple_key_context bool // Is it a simple mapping key context? + + line int // The current line. + column int // The current column. + whitespace bool // If the last character was a whitespace? + indention bool // If the last character was an indentation character (' ', '-', '?', ':')? + open_ended bool // If an explicit document end is required? + + space_above bool // Is there's an empty line above? + foot_indent int // The indent used to write the foot comment above, or -1 if none. + + // Anchor analysis. + anchor_data struct { + anchor []byte // The anchor value. + alias bool // Is it an alias? + } + + // Tag analysis. + tag_data struct { + handle []byte // The tag handle. + suffix []byte // The tag suffix. + } + + // Scalar analysis. + scalar_data struct { + value []byte // The scalar value. + multiline bool // Does the scalar contain line breaks? + flow_plain_allowed bool // Can the scalar be expessed in the flow plain style? + block_plain_allowed bool // Can the scalar be expressed in the block plain style? + single_quoted_allowed bool // Can the scalar be expressed in the single quoted style? + block_allowed bool // Can the scalar be expressed in the literal or folded styles? + style yaml_scalar_style_t // The output style. + } + + // Comments + head_comment []byte + line_comment []byte + foot_comment []byte + tail_comment []byte + + key_line_comment []byte + + // Dumper stuff + + opened bool // If the stream was already opened? + closed bool // If the stream was already closed? + + // The information associated with the document nodes. + anchors *struct { + references int // The number of references. + anchor int // The anchor id. + serialized bool // If the node has been emitted? + } + + last_anchor_id int // The last assigned anchor id. + + document *yaml_document_t // The currently emitted document. +} diff --git a/index/server/vendor/github.com/oasdiff/yaml3/yamlprivateh.go b/index/server/vendor/github.com/oasdiff/yaml3/yamlprivateh.go new file mode 100644 index 000000000..e88f9c54a --- /dev/null +++ b/index/server/vendor/github.com/oasdiff/yaml3/yamlprivateh.go @@ -0,0 +1,198 @@ +// +// Copyright (c) 2011-2019 Canonical Ltd +// Copyright (c) 2006-2010 Kirill Simonov +// +// 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. + +package yaml + +const ( + // The size of the input raw buffer. + input_raw_buffer_size = 512 + + // The size of the input buffer. + // It should be possible to decode the whole raw buffer. + input_buffer_size = input_raw_buffer_size * 3 + + // The size of the output buffer. + output_buffer_size = 128 + + // The size of the output raw buffer. + // It should be possible to encode the whole output buffer. + output_raw_buffer_size = (output_buffer_size*2 + 2) + + // The size of other stacks and queues. + initial_stack_size = 16 + initial_queue_size = 16 + initial_string_size = 16 +) + +// Check if the character at the specified position is an alphabetical +// character, a digit, '_', or '-'. +func is_alpha(b []byte, i int) bool { + return b[i] >= '0' && b[i] <= '9' || b[i] >= 'A' && b[i] <= 'Z' || b[i] >= 'a' && b[i] <= 'z' || b[i] == '_' || b[i] == '-' +} + +// Check if the character at the specified position is a digit. +func is_digit(b []byte, i int) bool { + return b[i] >= '0' && b[i] <= '9' +} + +// Get the value of a digit. +func as_digit(b []byte, i int) int { + return int(b[i]) - '0' +} + +// Check if the character at the specified position is a hex-digit. +func is_hex(b []byte, i int) bool { + return b[i] >= '0' && b[i] <= '9' || b[i] >= 'A' && b[i] <= 'F' || b[i] >= 'a' && b[i] <= 'f' +} + +// Get the value of a hex-digit. +func as_hex(b []byte, i int) int { + bi := b[i] + if bi >= 'A' && bi <= 'F' { + return int(bi) - 'A' + 10 + } + if bi >= 'a' && bi <= 'f' { + return int(bi) - 'a' + 10 + } + return int(bi) - '0' +} + +// Check if the character is ASCII. +func is_ascii(b []byte, i int) bool { + return b[i] <= 0x7F +} + +// Check if the character at the start of the buffer can be printed unescaped. +func is_printable(b []byte, i int) bool { + return ((b[i] == 0x0A) || // . == #x0A + (b[i] >= 0x20 && b[i] <= 0x7E) || // #x20 <= . <= #x7E + (b[i] == 0xC2 && b[i+1] >= 0xA0) || // #0xA0 <= . <= #xD7FF + (b[i] > 0xC2 && b[i] < 0xED) || + (b[i] == 0xED && b[i+1] < 0xA0) || + (b[i] == 0xEE) || + (b[i] == 0xEF && // #xE000 <= . <= #xFFFD + !(b[i+1] == 0xBB && b[i+2] == 0xBF) && // && . != #xFEFF + !(b[i+1] == 0xBF && (b[i+2] == 0xBE || b[i+2] == 0xBF)))) +} + +// Check if the character at the specified position is NUL. +func is_z(b []byte, i int) bool { + return b[i] == 0x00 +} + +// Check if the beginning of the buffer is a BOM. +func is_bom(b []byte, i int) bool { + return b[0] == 0xEF && b[1] == 0xBB && b[2] == 0xBF +} + +// Check if the character at the specified position is space. +func is_space(b []byte, i int) bool { + return b[i] == ' ' +} + +// Check if the character at the specified position is tab. +func is_tab(b []byte, i int) bool { + return b[i] == '\t' +} + +// Check if the character at the specified position is blank (space or tab). +func is_blank(b []byte, i int) bool { + //return is_space(b, i) || is_tab(b, i) + return b[i] == ' ' || b[i] == '\t' +} + +// Check if the character at the specified position is a line break. +func is_break(b []byte, i int) bool { + return (b[i] == '\r' || // CR (#xD) + b[i] == '\n' || // LF (#xA) + b[i] == 0xC2 && b[i+1] == 0x85 || // NEL (#x85) + b[i] == 0xE2 && b[i+1] == 0x80 && b[i+2] == 0xA8 || // LS (#x2028) + b[i] == 0xE2 && b[i+1] == 0x80 && b[i+2] == 0xA9) // PS (#x2029) +} + +func is_crlf(b []byte, i int) bool { + return b[i] == '\r' && b[i+1] == '\n' +} + +// Check if the character is a line break or NUL. +func is_breakz(b []byte, i int) bool { + //return is_break(b, i) || is_z(b, i) + return ( + // is_break: + b[i] == '\r' || // CR (#xD) + b[i] == '\n' || // LF (#xA) + b[i] == 0xC2 && b[i+1] == 0x85 || // NEL (#x85) + b[i] == 0xE2 && b[i+1] == 0x80 && b[i+2] == 0xA8 || // LS (#x2028) + b[i] == 0xE2 && b[i+1] == 0x80 && b[i+2] == 0xA9 || // PS (#x2029) + // is_z: + b[i] == 0) +} + +// Check if the character is a line break, space, or NUL. +func is_spacez(b []byte, i int) bool { + //return is_space(b, i) || is_breakz(b, i) + return ( + // is_space: + b[i] == ' ' || + // is_breakz: + b[i] == '\r' || // CR (#xD) + b[i] == '\n' || // LF (#xA) + b[i] == 0xC2 && b[i+1] == 0x85 || // NEL (#x85) + b[i] == 0xE2 && b[i+1] == 0x80 && b[i+2] == 0xA8 || // LS (#x2028) + b[i] == 0xE2 && b[i+1] == 0x80 && b[i+2] == 0xA9 || // PS (#x2029) + b[i] == 0) +} + +// Check if the character is a line break, space, tab, or NUL. +func is_blankz(b []byte, i int) bool { + //return is_blank(b, i) || is_breakz(b, i) + return ( + // is_blank: + b[i] == ' ' || b[i] == '\t' || + // is_breakz: + b[i] == '\r' || // CR (#xD) + b[i] == '\n' || // LF (#xA) + b[i] == 0xC2 && b[i+1] == 0x85 || // NEL (#x85) + b[i] == 0xE2 && b[i+1] == 0x80 && b[i+2] == 0xA8 || // LS (#x2028) + b[i] == 0xE2 && b[i+1] == 0x80 && b[i+2] == 0xA9 || // PS (#x2029) + b[i] == 0) +} + +// Determine the width of the character. +func width(b byte) int { + // Don't replace these by a switch without first + // confirming that it is being inlined. + if b&0x80 == 0x00 { + return 1 + } + if b&0xE0 == 0xC0 { + return 2 + } + if b&0xF0 == 0xE0 { + return 3 + } + if b&0xF8 == 0xF0 { + return 4 + } + return 0 + +} diff --git a/index/server/vendor/github.com/perimeterx/marshmallow/CHANGELOG.md b/index/server/vendor/github.com/perimeterx/marshmallow/CHANGELOG.md new file mode 100644 index 000000000..92937d057 --- /dev/null +++ b/index/server/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/index/server/vendor/github.com/perimeterx/marshmallow/CODE_OF_CONDUCT.md b/index/server/vendor/github.com/perimeterx/marshmallow/CODE_OF_CONDUCT.md new file mode 100644 index 000000000..0f6c45e79 --- /dev/null +++ b/index/server/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/index/server/vendor/github.com/perimeterx/marshmallow/CONTRIBUTING.md b/index/server/vendor/github.com/perimeterx/marshmallow/CONTRIBUTING.md new file mode 100644 index 000000000..a265c9ab9 --- /dev/null +++ b/index/server/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/index/server/vendor/github.com/perimeterx/marshmallow/README.md b/index/server/vendor/github.com/perimeterx/marshmallow/README.md index 2eee63fe9..bfa903637 100644 --- a/index/server/vendor/github.com/perimeterx/marshmallow/README.md +++ b/index/server/vendor/github.com/perimeterx/marshmallow/README.md @@ -2,11 +2,11 @@ ![Marshmallow Campfire](https://raw.githubusercontent.com/PerimeterX/marshmallow/assets/campfire.png) -[![CodeQL Status](https://img.shields.io/github/workflow/status/perimeterx/marshmallow/CodeQL?label=CodeQL&logo=github)](https://github.com/PerimeterX/marshmallow/actions/workflows/codeql.yml?query=branch%3Amain++) -[![Run Tests](https://img.shields.io/github/workflow/status/perimeterx/marshmallow/Go?label=Run%20Tests&logo=github)](https://github.com/PerimeterX/marshmallow/actions/workflows/go.yml?query=branch%3Amain) -[![Dependency Review](https://img.shields.io/github/workflow/status/perimeterx/marshmallow/Dependency%20Review?label=Dependency%20Review&logo=github)](https://github.com/PerimeterX/marshmallow/actions/workflows/dependency-review.yml?query=branch%3Amain) +[![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-91.9%25-green) +![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) @@ -14,6 +14,7 @@ [![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 @@ -49,7 +50,6 @@ import ( ) func main() { - marshmallow.EnableCache() // this is used to boost performance, read more below v := struct { Foo string `json:"foo"` Boo []int `json:"boo"` @@ -60,6 +60,8 @@ func main() { } ``` +**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. @@ -175,7 +177,7 @@ While unmarshalling, marshmallow supports the following optional options: * 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 [JSONDataHandler](https://github.com/PerimeterX/marshmallow/blob/2d254bf2ed5f9b02cafb8ba6eaa726cba38bc92b/options.go#L65). +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 @@ -183,14 +185,21 @@ Marshmallow also supports caching of refection information using and [EnableCustomCache](https://github.com/PerimeterX/marshmallow/blob/d3500aa5b0f330942b178b155da933c035dd3906/cache.go#L35). -**Examples can be found [here](example_test.go)** +## Contact and Contribute -# Marshmallow Logo +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). -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.
+Any type of contribution is warmly welcome and appreciated ❤️ +Please read our [contribution](CONTRIBUTING.md) guide for more info. -## Contribute +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. -Any type of contribution is warmly welcome and appreciated ❤️ +## 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/index/server/vendor/github.com/perimeterx/marshmallow/options.go b/index/server/vendor/github.com/perimeterx/marshmallow/options.go index 423aa9a9c..ff97d3369 100644 --- a/index/server/vendor/github.com/perimeterx/marshmallow/options.go +++ b/index/server/vendor/github.com/perimeterx/marshmallow/options.go @@ -69,9 +69,28 @@ func buildUnmarshalOptions(options []UnmarshalOption) *unmarshalOptions { return result } -// JSONDataHandler allow types to handle JSON data as maps. +// 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/index/server/vendor/github.com/perimeterx/marshmallow/unmarshal.go b/index/server/vendor/github.com/perimeterx/marshmallow/unmarshal.go index 4eb13d186..160ea30ca 100644 --- a/index/server/vendor/github.com/perimeterx/marshmallow/unmarshal.go +++ b/index/server/vendor/github.com/perimeterx/marshmallow/unmarshal.go @@ -310,16 +310,21 @@ func (d *decoder) buildStruct(structType reflect.Type) (interface{}, bool) { return d.lexer.Interface(), false } value := reflect.New(structType).Interface() - handler, ok := value.(JSONDataHandler) + 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 { - handler.HandleJSONData(data) + if !valid { + return result, false } - return result, valid + err := handler(data) + if err != nil { + d.lexer.AddNonFatalError(err) + return result, false + } + return result, true } func (d *decoder) valueFromCustomUnmarshaler(unmarshaler json.Unmarshaler) { diff --git a/index/server/vendor/github.com/perimeterx/marshmallow/unmarshal_from_json_map.go b/index/server/vendor/github.com/perimeterx/marshmallow/unmarshal_from_json_map.go index fcf1696fe..0907f8f87 100644 --- a/index/server/vendor/github.com/perimeterx/marshmallow/unmarshal_from_json_map.go +++ b/index/server/vendor/github.com/perimeterx/marshmallow/unmarshal_from_json_map.go @@ -262,16 +262,21 @@ func (m *mapDecoder) buildStruct(path []string, v interface{}, structType reflec return v, false } value := reflect.New(structType).Interface() - handler, ok := value.(JSONDataHandler) + 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 { - handler.HandleJSONData(data) + if !valid { + return result, false } - return result, valid + err := handler(data) + if err != nil { + m.addError(err) + return result, false + } + return result, true } func (m *mapDecoder) valueFromCustomUnmarshaler(data interface{}, unmarshaler UnmarshalerFromJSONMap) { diff --git a/index/server/vendor/modules.txt b/index/server/vendor/modules.txt index 1465bdb6e..b420b227d 100644 --- a/index/server/vendor/modules.txt +++ b/index/server/vendor/modules.txt @@ -232,8 +232,8 @@ github.com/gabriel-vasile/mimetype github.com/gabriel-vasile/mimetype/internal/charset github.com/gabriel-vasile/mimetype/internal/json github.com/gabriel-vasile/mimetype/internal/magic -# github.com/getkin/kin-openapi v0.117.0 -## explicit; go 1.16 +# github.com/getkin/kin-openapi v0.131.0 +## explicit; go 1.22.5 github.com/getkin/kin-openapi/openapi3 github.com/getkin/kin-openapi/openapi3filter github.com/getkin/kin-openapi/routers @@ -320,15 +320,15 @@ github.com/go-logr/logr/slogr # github.com/go-logr/stdr v1.2.2 ## explicit; go 1.16 github.com/go-logr/stdr -# github.com/go-openapi/jsonpointer v0.19.6 -## explicit; go 1.13 +# github.com/go-openapi/jsonpointer v0.21.0 +## explicit; go 1.20 github.com/go-openapi/jsonpointer # github.com/go-openapi/jsonreference v0.20.2 ## explicit; go 1.13 github.com/go-openapi/jsonreference github.com/go-openapi/jsonreference/internal -# github.com/go-openapi/swag v0.22.3 -## explicit; go 1.18 +# github.com/go-openapi/swag v0.23.0 +## explicit; go 1.20 github.com/go-openapi/swag # github.com/go-playground/locales v0.14.1 ## explicit; go 1.17 @@ -417,9 +417,6 @@ github.com/hashicorp/go-version # github.com/imdario/mergo v0.3.13 ## explicit; go 1.13 github.com/imdario/mergo -# github.com/invopop/yaml v0.1.0 -## explicit; go 1.14 -github.com/invopop/yaml # github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 ## explicit github.com/jbenet/go-context/io @@ -483,6 +480,12 @@ github.com/mohae/deepcopy # github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 ## explicit github.com/munnerz/goautoneg +# github.com/oasdiff/yaml v0.0.0-20250309154309-f31be36b4037 +## explicit; go 1.22.5 +github.com/oasdiff/yaml +# github.com/oasdiff/yaml3 v0.0.0-20250309153720-d2182401db90 +## explicit; go 1.22.5 +github.com/oasdiff/yaml3 # github.com/opencontainers/go-digest v1.0.0 ## explicit; go 1.13 github.com/opencontainers/go-digest @@ -500,7 +503,7 @@ github.com/pelletier/go-toml/v2/internal/characters github.com/pelletier/go-toml/v2/internal/danger github.com/pelletier/go-toml/v2/internal/tracker github.com/pelletier/go-toml/v2/unstable -# github.com/perimeterx/marshmallow v1.1.4 +# github.com/perimeterx/marshmallow v1.1.5 ## explicit; go 1.17 github.com/perimeterx/marshmallow # github.com/peterbourgon/diskv v2.0.1+incompatible