cloudapi: validate input
Validate incoming requests with openapi3. Remove unsupported uuid format from the openapi spec. Similarly, change url to uri as uri is a supported format and url is not. Co-authored-by: Ondřej Budai <obudai@redhat.com> Signed-off-by: Ondřej Budai <ondrej@budai.cz>
This commit is contained in:
parent
f616becf39
commit
13c79294b6
83 changed files with 4942 additions and 549 deletions
3
vendor/github.com/getkin/kin-openapi/jsoninfo/field_info.go
generated
vendored
3
vendor/github.com/getkin/kin-openapi/jsoninfo/field_info.go
generated
vendored
|
|
@ -21,6 +21,9 @@ type FieldInfo struct {
|
|||
}
|
||||
|
||||
func AppendFields(fields []FieldInfo, parentIndex []int, t reflect.Type) []FieldInfo {
|
||||
if t.Kind() == reflect.Ptr {
|
||||
t = t.Elem()
|
||||
}
|
||||
// For each field
|
||||
numField := t.NumField()
|
||||
iteration:
|
||||
|
|
|
|||
6
vendor/github.com/getkin/kin-openapi/jsoninfo/marshal_ref.go
generated
vendored
6
vendor/github.com/getkin/kin-openapi/jsoninfo/marshal_ref.go
generated
vendored
|
|
@ -5,7 +5,7 @@ import (
|
|||
)
|
||||
|
||||
func MarshalRef(value string, otherwise interface{}) ([]byte, error) {
|
||||
if len(value) > 0 {
|
||||
if value != "" {
|
||||
return json.Marshal(&refProps{
|
||||
Ref: value,
|
||||
})
|
||||
|
|
@ -17,7 +17,7 @@ func UnmarshalRef(data []byte, destRef *string, destOtherwise interface{}) error
|
|||
refProps := &refProps{}
|
||||
if err := json.Unmarshal(data, refProps); err == nil {
|
||||
ref := refProps.Ref
|
||||
if len(ref) > 0 {
|
||||
if ref != "" {
|
||||
*destRef = ref
|
||||
return nil
|
||||
}
|
||||
|
|
@ -26,5 +26,5 @@ func UnmarshalRef(data []byte, destRef *string, destOtherwise interface{}) error
|
|||
}
|
||||
|
||||
type refProps struct {
|
||||
Ref string `json:"$ref,omitempty"`
|
||||
Ref string `json:"$ref,omitempty" yaml:"$ref,omitempty"`
|
||||
}
|
||||
|
|
|
|||
3
vendor/github.com/getkin/kin-openapi/openapi3/callback.go
generated
vendored
3
vendor/github.com/getkin/kin-openapi/openapi3/callback.go
generated
vendored
|
|
@ -23,7 +23,8 @@ func (c Callbacks) JSONLookup(token string) (interface{}, error) {
|
|||
return ref.Value, nil
|
||||
}
|
||||
|
||||
// Callback is specified by OpenAPI/Swagger standard version 3.0.
|
||||
// Callback is specified by OpenAPI/Swagger standard version 3.
|
||||
// See https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#callbackObject
|
||||
type Callback map[string]*PathItem
|
||||
|
||||
func (value Callback) Validate(ctx context.Context) error {
|
||||
|
|
|
|||
4
vendor/github.com/getkin/kin-openapi/openapi3/components.go
generated
vendored
4
vendor/github.com/getkin/kin-openapi/openapi3/components.go
generated
vendored
|
|
@ -8,9 +8,11 @@ import (
|
|||
"github.com/getkin/kin-openapi/jsoninfo"
|
||||
)
|
||||
|
||||
// Components is specified by OpenAPI/Swagger standard version 3.0.
|
||||
// Components is specified by OpenAPI/Swagger standard version 3.
|
||||
// See https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#componentsObject
|
||||
type Components struct {
|
||||
ExtensionProps
|
||||
|
||||
Schemas Schemas `json:"schemas,omitempty" yaml:"schemas,omitempty"`
|
||||
Parameters ParametersMap `json:"parameters,omitempty" yaml:"parameters,omitempty"`
|
||||
Headers Headers `json:"headers,omitempty" yaml:"headers,omitempty"`
|
||||
|
|
|
|||
4
vendor/github.com/getkin/kin-openapi/openapi3/discriminator.go
generated
vendored
4
vendor/github.com/getkin/kin-openapi/openapi3/discriminator.go
generated
vendored
|
|
@ -6,9 +6,11 @@ import (
|
|||
"github.com/getkin/kin-openapi/jsoninfo"
|
||||
)
|
||||
|
||||
// Discriminator is specified by OpenAPI/Swagger standard version 3.0.
|
||||
// Discriminator is specified by OpenAPI/Swagger standard version 3.
|
||||
// See https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#discriminatorObject
|
||||
type Discriminator struct {
|
||||
ExtensionProps
|
||||
|
||||
PropertyName string `json:"propertyName" yaml:"propertyName"`
|
||||
Mapping map[string]string `json:"mapping,omitempty" yaml:"mapping,omitempty"`
|
||||
}
|
||||
|
|
|
|||
1
vendor/github.com/getkin/kin-openapi/openapi3/encoding.go
generated
vendored
1
vendor/github.com/getkin/kin-openapi/openapi3/encoding.go
generated
vendored
|
|
@ -8,6 +8,7 @@ import (
|
|||
)
|
||||
|
||||
// Encoding is specified by OpenAPI/Swagger 3.0 standard.
|
||||
// See https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#encodingObject
|
||||
type Encoding struct {
|
||||
ExtensionProps
|
||||
|
||||
|
|
|
|||
|
|
@ -25,6 +25,7 @@ func (e Examples) JSONLookup(token string) (interface{}, error) {
|
|||
}
|
||||
|
||||
// Example is specified by OpenAPI/Swagger 3.0 standard.
|
||||
// See https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#exampleObject
|
||||
type Example struct {
|
||||
ExtensionProps
|
||||
|
||||
22
vendor/github.com/getkin/kin-openapi/openapi3/external_docs.go
generated
vendored
22
vendor/github.com/getkin/kin-openapi/openapi3/external_docs.go
generated
vendored
|
|
@ -1,15 +1,21 @@
|
|||
package openapi3
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/url"
|
||||
|
||||
"github.com/getkin/kin-openapi/jsoninfo"
|
||||
)
|
||||
|
||||
// ExternalDocs is specified by OpenAPI/Swagger standard version 3.0.
|
||||
// ExternalDocs is specified by OpenAPI/Swagger standard version 3.
|
||||
// See https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#external-documentation-object
|
||||
type ExternalDocs struct {
|
||||
ExtensionProps
|
||||
|
||||
Description string `json:"description,omitempty"`
|
||||
URL string `json:"url,omitempty"`
|
||||
Description string `json:"description,omitempty" yaml:"description,omitempty"`
|
||||
URL string `json:"url,omitempty" yaml:"url,omitempty"`
|
||||
}
|
||||
|
||||
func (e *ExternalDocs) MarshalJSON() ([]byte, error) {
|
||||
|
|
@ -19,3 +25,13 @@ func (e *ExternalDocs) MarshalJSON() ([]byte, error) {
|
|||
func (e *ExternalDocs) UnmarshalJSON(data []byte) error {
|
||||
return jsoninfo.UnmarshalStrictStruct(data, e)
|
||||
}
|
||||
|
||||
func (e *ExternalDocs) Validate(ctx context.Context) error {
|
||||
if e.URL == "" {
|
||||
return errors.New("url is required")
|
||||
}
|
||||
if _, err := url.Parse(e.URL); err != nil {
|
||||
return fmt.Errorf("url is incorrect: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
|
|
|||
74
vendor/github.com/getkin/kin-openapi/openapi3/header.go
generated
vendored
74
vendor/github.com/getkin/kin-openapi/openapi3/header.go
generated
vendored
|
|
@ -2,6 +2,7 @@ package openapi3
|
|||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/getkin/kin-openapi/jsoninfo"
|
||||
|
|
@ -24,17 +25,10 @@ func (h Headers) JSONLookup(token string) (interface{}, error) {
|
|||
return ref.Value, nil
|
||||
}
|
||||
|
||||
// Header is specified by OpenAPI/Swagger 3.0 standard.
|
||||
// See https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#headerObject
|
||||
type Header struct {
|
||||
ExtensionProps
|
||||
|
||||
// Optional description. Should use CommonMark syntax.
|
||||
Description string `json:"description,omitempty" yaml:"description,omitempty"`
|
||||
Deprecated bool `json:"deprecated,omitempty" yaml:"deprecated,omitempty"`
|
||||
Required bool `json:"required,omitempty" yaml:"required,omitempty"`
|
||||
Schema *SchemaRef `json:"schema,omitempty" yaml:"schema,omitempty"`
|
||||
Example interface{} `json:"example,omitempty" yaml:"example,omitempty"`
|
||||
Examples Examples `json:"examples,omitempty" yaml:"examples,omitempty"`
|
||||
Content Content `json:"content,omitempty" yaml:"content,omitempty"`
|
||||
Parameter
|
||||
}
|
||||
|
||||
var _ jsonpointer.JSONPointable = (*Header)(nil)
|
||||
|
|
@ -43,10 +37,52 @@ func (value *Header) UnmarshalJSON(data []byte) error {
|
|||
return jsoninfo.UnmarshalStrictStruct(data, value)
|
||||
}
|
||||
|
||||
// SerializationMethod returns a header's serialization method.
|
||||
func (value *Header) SerializationMethod() (*SerializationMethod, error) {
|
||||
style := value.Style
|
||||
if style == "" {
|
||||
style = SerializationSimple
|
||||
}
|
||||
explode := false
|
||||
if value.Explode != nil {
|
||||
explode = *value.Explode
|
||||
}
|
||||
return &SerializationMethod{Style: style, Explode: explode}, nil
|
||||
}
|
||||
|
||||
func (value *Header) Validate(ctx context.Context) error {
|
||||
if v := value.Schema; v != nil {
|
||||
if err := v.Validate(ctx); err != nil {
|
||||
return err
|
||||
if value.Name != "" {
|
||||
return errors.New("header 'name' MUST NOT be specified, it is given in the corresponding headers map")
|
||||
}
|
||||
if value.In != "" {
|
||||
return errors.New("header 'in' MUST NOT be specified, it is implicitly in header")
|
||||
}
|
||||
|
||||
// Validate a parameter's serialization method.
|
||||
sm, err := value.SerializationMethod()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if smSupported := false ||
|
||||
sm.Style == SerializationSimple && !sm.Explode ||
|
||||
sm.Style == SerializationSimple && sm.Explode; !smSupported {
|
||||
e := fmt.Errorf("serialization method with style=%q and explode=%v is not supported by a header parameter", sm.Style, sm.Explode)
|
||||
return fmt.Errorf("header schema is invalid: %v", e)
|
||||
}
|
||||
|
||||
if (value.Schema == nil) == (value.Content == nil) {
|
||||
e := fmt.Errorf("parameter must contain exactly one of content and schema: %v", value)
|
||||
return fmt.Errorf("header schema is invalid: %v", e)
|
||||
}
|
||||
if schema := value.Schema; schema != nil {
|
||||
if err := schema.Validate(ctx); err != nil {
|
||||
return fmt.Errorf("header schema is invalid: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
if content := value.Content; content != nil {
|
||||
if err := content.Validate(ctx); err != nil {
|
||||
return fmt.Errorf("header content is invalid: %v", err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
|
|
@ -61,8 +97,20 @@ func (value Header) JSONLookup(token string) (interface{}, error) {
|
|||
}
|
||||
return value.Schema.Value, nil
|
||||
}
|
||||
case "name":
|
||||
return value.Name, nil
|
||||
case "in":
|
||||
return value.In, nil
|
||||
case "description":
|
||||
return value.Description, nil
|
||||
case "style":
|
||||
return value.Style, nil
|
||||
case "explode":
|
||||
return value.Explode, nil
|
||||
case "allowEmptyValue":
|
||||
return value.AllowEmptyValue, nil
|
||||
case "allowReserved":
|
||||
return value.AllowReserved, nil
|
||||
case "deprecated":
|
||||
return value.Deprecated, nil
|
||||
case "required":
|
||||
|
|
|
|||
12
vendor/github.com/getkin/kin-openapi/openapi3/info.go
generated
vendored
12
vendor/github.com/getkin/kin-openapi/openapi3/info.go
generated
vendored
|
|
@ -7,9 +7,11 @@ import (
|
|||
"github.com/getkin/kin-openapi/jsoninfo"
|
||||
)
|
||||
|
||||
// Info is specified by OpenAPI/Swagger standard version 3.0.
|
||||
// Info is specified by OpenAPI/Swagger standard version 3.
|
||||
// See https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#infoObject
|
||||
type Info struct {
|
||||
ExtensionProps
|
||||
|
||||
Title string `json:"title" yaml:"title"` // Required
|
||||
Description string `json:"description,omitempty" yaml:"description,omitempty"`
|
||||
TermsOfService string `json:"termsOfService,omitempty" yaml:"termsOfService,omitempty"`
|
||||
|
|
@ -50,9 +52,11 @@ func (value *Info) Validate(ctx context.Context) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
// Contact is specified by OpenAPI/Swagger standard version 3.0.
|
||||
// Contact is specified by OpenAPI/Swagger standard version 3.
|
||||
// See https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#contactObject
|
||||
type Contact struct {
|
||||
ExtensionProps
|
||||
|
||||
Name string `json:"name,omitempty" yaml:"name,omitempty"`
|
||||
URL string `json:"url,omitempty" yaml:"url,omitempty"`
|
||||
Email string `json:"email,omitempty" yaml:"email,omitempty"`
|
||||
|
|
@ -70,9 +74,11 @@ func (value *Contact) Validate(ctx context.Context) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
// License is specified by OpenAPI/Swagger standard version 3.0.
|
||||
// License is specified by OpenAPI/Swagger standard version 3.
|
||||
// See https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#licenseObject
|
||||
type License struct {
|
||||
ExtensionProps
|
||||
|
||||
Name string `json:"name" yaml:"name"` // Required
|
||||
URL string `json:"url,omitempty" yaml:"url,omitempty"`
|
||||
}
|
||||
|
|
|
|||
369
vendor/github.com/getkin/kin-openapi/openapi3/internalize_refs.go
generated
vendored
Normal file
369
vendor/github.com/getkin/kin-openapi/openapi3/internalize_refs.go
generated
vendored
Normal file
|
|
@ -0,0 +1,369 @@
|
|||
package openapi3
|
||||
|
||||
import (
|
||||
"context"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type RefNameResolver func(string) string
|
||||
|
||||
// DefaultRefResolver is a default implementation of refNameResolver for the
|
||||
// InternalizeRefs function.
|
||||
//
|
||||
// If a reference points to an element inside a document, it returns the last
|
||||
// element in the reference using filepath.Base. Otherwise if the reference points
|
||||
// to a file, it returns the file name trimmed of all extensions.
|
||||
func DefaultRefNameResolver(ref string) string {
|
||||
if ref == "" {
|
||||
return ""
|
||||
}
|
||||
split := strings.SplitN(ref, "#", 2)
|
||||
if len(split) == 2 {
|
||||
return filepath.Base(split[1])
|
||||
}
|
||||
ref = split[0]
|
||||
for ext := filepath.Ext(ref); len(ext) > 0; ext = filepath.Ext(ref) {
|
||||
ref = strings.TrimSuffix(ref, ext)
|
||||
}
|
||||
return filepath.Base(ref)
|
||||
}
|
||||
|
||||
func schemaNames(s Schemas) []string {
|
||||
out := make([]string, 0, len(s))
|
||||
for i := range s {
|
||||
out = append(out, i)
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func parametersMapNames(s ParametersMap) []string {
|
||||
out := make([]string, 0, len(s))
|
||||
for i := range s {
|
||||
out = append(out, i)
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func isExternalRef(ref string) bool {
|
||||
return ref != "" && !strings.HasPrefix(ref, "#/components/")
|
||||
}
|
||||
|
||||
func (doc *T) addSchemaToSpec(s *SchemaRef, refNameResolver RefNameResolver) {
|
||||
if s == nil || !isExternalRef(s.Ref) {
|
||||
return
|
||||
}
|
||||
|
||||
name := refNameResolver(s.Ref)
|
||||
if _, ok := doc.Components.Schemas[name]; ok {
|
||||
s.Ref = "#/components/schemas/" + name
|
||||
return
|
||||
}
|
||||
|
||||
if doc.Components.Schemas == nil {
|
||||
doc.Components.Schemas = make(Schemas)
|
||||
}
|
||||
doc.Components.Schemas[name] = s.Value.NewRef()
|
||||
s.Ref = "#/components/schemas/" + name
|
||||
}
|
||||
|
||||
func (doc *T) addParameterToSpec(p *ParameterRef, refNameResolver RefNameResolver) {
|
||||
if p == nil || !isExternalRef(p.Ref) {
|
||||
return
|
||||
}
|
||||
name := refNameResolver(p.Ref)
|
||||
if _, ok := doc.Components.Parameters[name]; ok {
|
||||
p.Ref = "#/components/parameters/" + name
|
||||
return
|
||||
}
|
||||
|
||||
if doc.Components.Parameters == nil {
|
||||
doc.Components.Parameters = make(ParametersMap)
|
||||
}
|
||||
doc.Components.Parameters[name] = &ParameterRef{Value: p.Value}
|
||||
p.Ref = "#/components/parameters/" + name
|
||||
}
|
||||
|
||||
func (doc *T) addHeaderToSpec(h *HeaderRef, refNameResolver RefNameResolver) {
|
||||
if h == nil || !isExternalRef(h.Ref) {
|
||||
return
|
||||
}
|
||||
name := refNameResolver(h.Ref)
|
||||
if _, ok := doc.Components.Headers[name]; ok {
|
||||
h.Ref = "#/components/headers/" + name
|
||||
return
|
||||
}
|
||||
if doc.Components.Headers == nil {
|
||||
doc.Components.Headers = make(Headers)
|
||||
}
|
||||
doc.Components.Headers[name] = &HeaderRef{Value: h.Value}
|
||||
h.Ref = "#/components/headers/" + name
|
||||
}
|
||||
|
||||
func (doc *T) addRequestBodyToSpec(r *RequestBodyRef, refNameResolver RefNameResolver) {
|
||||
if r == nil || !isExternalRef(r.Ref) {
|
||||
return
|
||||
}
|
||||
name := refNameResolver(r.Ref)
|
||||
if _, ok := doc.Components.RequestBodies[name]; ok {
|
||||
r.Ref = "#/components/requestBodies/" + name
|
||||
return
|
||||
}
|
||||
if doc.Components.RequestBodies == nil {
|
||||
doc.Components.RequestBodies = make(RequestBodies)
|
||||
}
|
||||
doc.Components.RequestBodies[name] = &RequestBodyRef{Value: r.Value}
|
||||
r.Ref = "#/components/requestBodies/" + name
|
||||
}
|
||||
|
||||
func (doc *T) addResponseToSpec(r *ResponseRef, refNameResolver RefNameResolver) {
|
||||
if r == nil || !isExternalRef(r.Ref) {
|
||||
return
|
||||
}
|
||||
name := refNameResolver(r.Ref)
|
||||
if _, ok := doc.Components.Responses[name]; ok {
|
||||
r.Ref = "#/components/responses/" + name
|
||||
return
|
||||
}
|
||||
if doc.Components.Responses == nil {
|
||||
doc.Components.Responses = make(Responses)
|
||||
}
|
||||
doc.Components.Responses[name] = &ResponseRef{Value: r.Value}
|
||||
r.Ref = "#/components/responses/" + name
|
||||
|
||||
}
|
||||
|
||||
func (doc *T) addSecuritySchemeToSpec(ss *SecuritySchemeRef, refNameResolver RefNameResolver) {
|
||||
if ss == nil || !isExternalRef(ss.Ref) {
|
||||
return
|
||||
}
|
||||
name := refNameResolver(ss.Ref)
|
||||
if _, ok := doc.Components.SecuritySchemes[name]; ok {
|
||||
ss.Ref = "#/components/securitySchemes/" + name
|
||||
return
|
||||
}
|
||||
if doc.Components.SecuritySchemes == nil {
|
||||
doc.Components.SecuritySchemes = make(SecuritySchemes)
|
||||
}
|
||||
doc.Components.SecuritySchemes[name] = &SecuritySchemeRef{Value: ss.Value}
|
||||
ss.Ref = "#/components/securitySchemes/" + name
|
||||
|
||||
}
|
||||
|
||||
func (doc *T) addExampleToSpec(e *ExampleRef, refNameResolver RefNameResolver) {
|
||||
if e == nil || !isExternalRef(e.Ref) {
|
||||
return
|
||||
}
|
||||
name := refNameResolver(e.Ref)
|
||||
if _, ok := doc.Components.Examples[name]; ok {
|
||||
e.Ref = "#/components/examples/" + name
|
||||
return
|
||||
}
|
||||
if doc.Components.Examples == nil {
|
||||
doc.Components.Examples = make(Examples)
|
||||
}
|
||||
doc.Components.Examples[name] = &ExampleRef{Value: e.Value}
|
||||
e.Ref = "#/components/examples/" + name
|
||||
|
||||
}
|
||||
|
||||
func (doc *T) addLinkToSpec(l *LinkRef, refNameResolver RefNameResolver) {
|
||||
if l == nil || !isExternalRef(l.Ref) {
|
||||
return
|
||||
}
|
||||
name := refNameResolver(l.Ref)
|
||||
if _, ok := doc.Components.Links[name]; ok {
|
||||
l.Ref = "#/components/links/" + name
|
||||
return
|
||||
}
|
||||
if doc.Components.Links == nil {
|
||||
doc.Components.Links = make(Links)
|
||||
}
|
||||
doc.Components.Links[name] = &LinkRef{Value: l.Value}
|
||||
l.Ref = "#/components/links/" + name
|
||||
|
||||
}
|
||||
|
||||
func (doc *T) addCallbackToSpec(c *CallbackRef, refNameResolver RefNameResolver) {
|
||||
if c == nil || !isExternalRef(c.Ref) {
|
||||
return
|
||||
}
|
||||
name := refNameResolver(c.Ref)
|
||||
if _, ok := doc.Components.Callbacks[name]; ok {
|
||||
c.Ref = "#/components/callbacks/" + name
|
||||
}
|
||||
if doc.Components.Callbacks == nil {
|
||||
doc.Components.Callbacks = make(Callbacks)
|
||||
}
|
||||
doc.Components.Callbacks[name] = &CallbackRef{Value: c.Value}
|
||||
c.Ref = "#/components/callbacks/" + name
|
||||
}
|
||||
|
||||
func (doc *T) derefSchema(s *Schema, refNameResolver RefNameResolver) {
|
||||
if s == nil {
|
||||
return
|
||||
}
|
||||
|
||||
for _, list := range []SchemaRefs{s.AllOf, s.AnyOf, s.OneOf} {
|
||||
for _, s2 := range list {
|
||||
doc.addSchemaToSpec(s2, refNameResolver)
|
||||
if s2 != nil {
|
||||
doc.derefSchema(s2.Value, refNameResolver)
|
||||
}
|
||||
}
|
||||
}
|
||||
for _, s2 := range s.Properties {
|
||||
doc.addSchemaToSpec(s2, refNameResolver)
|
||||
if s2 != nil {
|
||||
doc.derefSchema(s2.Value, refNameResolver)
|
||||
}
|
||||
}
|
||||
for _, ref := range []*SchemaRef{s.Not, s.AdditionalProperties, s.Items} {
|
||||
doc.addSchemaToSpec(ref, refNameResolver)
|
||||
if ref != nil {
|
||||
doc.derefSchema(ref.Value, refNameResolver)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (doc *T) derefHeaders(hs Headers, refNameResolver RefNameResolver) {
|
||||
for _, h := range hs {
|
||||
doc.addHeaderToSpec(h, refNameResolver)
|
||||
doc.derefParameter(h.Value.Parameter, refNameResolver)
|
||||
}
|
||||
}
|
||||
|
||||
func (doc *T) derefExamples(es Examples, refNameResolver RefNameResolver) {
|
||||
for _, e := range es {
|
||||
doc.addExampleToSpec(e, refNameResolver)
|
||||
}
|
||||
}
|
||||
|
||||
func (doc *T) derefContent(c Content, refNameResolver RefNameResolver) {
|
||||
for _, mediatype := range c {
|
||||
doc.addSchemaToSpec(mediatype.Schema, refNameResolver)
|
||||
if mediatype.Schema != nil {
|
||||
doc.derefSchema(mediatype.Schema.Value, refNameResolver)
|
||||
}
|
||||
doc.derefExamples(mediatype.Examples, refNameResolver)
|
||||
for _, e := range mediatype.Encoding {
|
||||
doc.derefHeaders(e.Headers, refNameResolver)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (doc *T) derefLinks(ls Links, refNameResolver RefNameResolver) {
|
||||
for _, l := range ls {
|
||||
doc.addLinkToSpec(l, refNameResolver)
|
||||
}
|
||||
}
|
||||
|
||||
func (doc *T) derefResponses(es Responses, refNameResolver RefNameResolver) {
|
||||
for _, e := range es {
|
||||
doc.addResponseToSpec(e, refNameResolver)
|
||||
if e.Value != nil {
|
||||
doc.derefHeaders(e.Value.Headers, refNameResolver)
|
||||
doc.derefContent(e.Value.Content, refNameResolver)
|
||||
doc.derefLinks(e.Value.Links, refNameResolver)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (doc *T) derefParameter(p Parameter, refNameResolver RefNameResolver) {
|
||||
doc.addSchemaToSpec(p.Schema, refNameResolver)
|
||||
doc.derefContent(p.Content, refNameResolver)
|
||||
if p.Schema != nil {
|
||||
doc.derefSchema(p.Schema.Value, refNameResolver)
|
||||
}
|
||||
}
|
||||
|
||||
func (doc *T) derefRequestBody(r RequestBody, refNameResolver RefNameResolver) {
|
||||
doc.derefContent(r.Content, refNameResolver)
|
||||
}
|
||||
|
||||
func (doc *T) derefPaths(paths map[string]*PathItem, refNameResolver RefNameResolver) {
|
||||
for _, ops := range paths {
|
||||
// inline full operations
|
||||
ops.Ref = ""
|
||||
|
||||
for _, op := range ops.Operations() {
|
||||
doc.addRequestBodyToSpec(op.RequestBody, refNameResolver)
|
||||
if op.RequestBody != nil && op.RequestBody.Value != nil {
|
||||
doc.derefRequestBody(*op.RequestBody.Value, refNameResolver)
|
||||
}
|
||||
for _, cb := range op.Callbacks {
|
||||
doc.addCallbackToSpec(cb, refNameResolver)
|
||||
if cb.Value != nil {
|
||||
doc.derefPaths(*cb.Value, refNameResolver)
|
||||
}
|
||||
}
|
||||
doc.derefResponses(op.Responses, refNameResolver)
|
||||
for _, param := range op.Parameters {
|
||||
doc.addParameterToSpec(param, refNameResolver)
|
||||
if param.Value != nil {
|
||||
doc.derefParameter(*param.Value, refNameResolver)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// InternalizeRefs removes all references to external files from the spec and moves them
|
||||
// to the components section.
|
||||
//
|
||||
// refNameResolver takes in references to returns a name to store the reference under locally.
|
||||
// It MUST return a unique name for each reference type.
|
||||
// A default implementation is provided that will suffice for most use cases. See the function
|
||||
// documention for more details.
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
// doc.InternalizeRefs(context.Background(), nil)
|
||||
func (doc *T) InternalizeRefs(ctx context.Context, refNameResolver func(ref string) string) {
|
||||
if refNameResolver == nil {
|
||||
refNameResolver = DefaultRefNameResolver
|
||||
}
|
||||
|
||||
// Handle components section
|
||||
names := schemaNames(doc.Components.Schemas)
|
||||
for _, name := range names {
|
||||
schema := doc.Components.Schemas[name]
|
||||
doc.addSchemaToSpec(schema, refNameResolver)
|
||||
if schema != nil {
|
||||
schema.Ref = "" // always dereference the top level
|
||||
doc.derefSchema(schema.Value, refNameResolver)
|
||||
}
|
||||
}
|
||||
names = parametersMapNames(doc.Components.Parameters)
|
||||
for _, name := range names {
|
||||
p := doc.Components.Parameters[name]
|
||||
doc.addParameterToSpec(p, refNameResolver)
|
||||
if p != nil && p.Value != nil {
|
||||
p.Ref = "" // always dereference the top level
|
||||
doc.derefParameter(*p.Value, refNameResolver)
|
||||
}
|
||||
}
|
||||
doc.derefHeaders(doc.Components.Headers, refNameResolver)
|
||||
for _, req := range doc.Components.RequestBodies {
|
||||
doc.addRequestBodyToSpec(req, refNameResolver)
|
||||
if req != nil && req.Value != nil {
|
||||
req.Ref = "" // always dereference the top level
|
||||
doc.derefRequestBody(*req.Value, refNameResolver)
|
||||
}
|
||||
}
|
||||
doc.derefResponses(doc.Components.Responses, refNameResolver)
|
||||
for _, ss := range doc.Components.SecuritySchemes {
|
||||
doc.addSecuritySchemeToSpec(ss, refNameResolver)
|
||||
}
|
||||
doc.derefExamples(doc.Components.Examples, refNameResolver)
|
||||
doc.derefLinks(doc.Components.Links, refNameResolver)
|
||||
for _, cb := range doc.Components.Callbacks {
|
||||
doc.addCallbackToSpec(cb, refNameResolver)
|
||||
if cb != nil && cb.Value != nil {
|
||||
cb.Ref = "" // always dereference the top level
|
||||
doc.derefPaths(*cb.Value, refNameResolver)
|
||||
}
|
||||
}
|
||||
|
||||
doc.derefPaths(doc.Paths, refNameResolver)
|
||||
}
|
||||
6
vendor/github.com/getkin/kin-openapi/openapi3/link.go
generated
vendored
6
vendor/github.com/getkin/kin-openapi/openapi3/link.go
generated
vendored
|
|
@ -25,11 +25,13 @@ func (l Links) JSONLookup(token string) (interface{}, error) {
|
|||
|
||||
var _ jsonpointer.JSONPointable = (*Links)(nil)
|
||||
|
||||
// Link is specified by OpenAPI/Swagger standard version 3.0.
|
||||
// Link is specified by OpenAPI/Swagger standard version 3.
|
||||
// See https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#linkObject
|
||||
type Link struct {
|
||||
ExtensionProps
|
||||
OperationID string `json:"operationId,omitempty" yaml:"operationId,omitempty"`
|
||||
|
||||
OperationRef string `json:"operationRef,omitempty" yaml:"operationRef,omitempty"`
|
||||
OperationID string `json:"operationId,omitempty" yaml:"operationId,omitempty"`
|
||||
Description string `json:"description,omitempty" yaml:"description,omitempty"`
|
||||
Parameters map[string]interface{} `json:"parameters,omitempty" yaml:"parameters,omitempty"`
|
||||
Server *Server `json:"server,omitempty" yaml:"server,omitempty"`
|
||||
|
|
|
|||
62
vendor/github.com/getkin/kin-openapi/openapi3/loader.go
generated
vendored
62
vendor/github.com/getkin/kin-openapi/openapi3/loader.go
generated
vendored
|
|
@ -5,8 +5,6 @@ import (
|
|||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"path"
|
||||
"path/filepath"
|
||||
|
|
@ -31,10 +29,12 @@ type Loader struct {
|
|||
IsExternalRefsAllowed bool
|
||||
|
||||
// ReadFromURIFunc allows overriding the any file/URL reading func
|
||||
ReadFromURIFunc func(loader *Loader, url *url.URL) ([]byte, error)
|
||||
ReadFromURIFunc ReadFromURIFunc
|
||||
|
||||
Context context.Context
|
||||
|
||||
rootDir string
|
||||
|
||||
visitedPathItemRefs map[string]struct{}
|
||||
|
||||
visitedDocuments map[string]*T
|
||||
|
|
@ -66,6 +66,7 @@ func (loader *Loader) LoadFromURI(location *url.URL) (*T, error) {
|
|||
|
||||
// LoadFromFile loads a spec from a local file path
|
||||
func (loader *Loader) LoadFromFile(location string) (*T, error) {
|
||||
loader.rootDir = path.Dir(location)
|
||||
return loader.LoadFromURI(&url.URL{Path: filepath.ToSlash(location)})
|
||||
}
|
||||
|
||||
|
|
@ -118,22 +119,7 @@ func (loader *Loader) readURL(location *url.URL) ([]byte, error) {
|
|||
if f := loader.ReadFromURIFunc; f != nil {
|
||||
return f(loader, location)
|
||||
}
|
||||
|
||||
if location.Scheme != "" && location.Host != "" {
|
||||
resp, err := http.Get(location.String())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
if resp.StatusCode > 399 {
|
||||
return nil, fmt.Errorf("error loading %q: request returned status code %d", location.String(), resp.StatusCode)
|
||||
}
|
||||
return ioutil.ReadAll(resp.Body)
|
||||
}
|
||||
if location.Scheme != "" || location.Host != "" || location.RawQuery != "" {
|
||||
return nil, fmt.Errorf("unsupported URI: %q", location.String())
|
||||
}
|
||||
return ioutil.ReadFile(location.Path)
|
||||
return DefaultReadFromURI(loader, location)
|
||||
}
|
||||
|
||||
// LoadFromData loads a spec from a byte array
|
||||
|
|
@ -305,6 +291,9 @@ func (loader *Loader) resolveComponent(
|
|||
}
|
||||
var cursor interface{}
|
||||
if cursor, err = drill(doc); err != nil {
|
||||
if path == nil {
|
||||
return nil, err
|
||||
}
|
||||
var err2 error
|
||||
data, err2 := loader.readURL(path)
|
||||
if err2 != nil {
|
||||
|
|
@ -346,6 +335,14 @@ func (loader *Loader) resolveComponent(
|
|||
}
|
||||
|
||||
func drillIntoField(cursor interface{}, fieldName string) (interface{}, error) {
|
||||
// Special case due to multijson
|
||||
if s, ok := cursor.(*SchemaRef); ok && fieldName == "additionalProperties" {
|
||||
if ap := s.Value.AdditionalPropertiesAllowed; ap != nil {
|
||||
return *ap, nil
|
||||
}
|
||||
return s.Value.AdditionalProperties, nil
|
||||
}
|
||||
|
||||
switch val := reflect.Indirect(reflect.ValueOf(cursor)); val.Kind() {
|
||||
case reflect.Map:
|
||||
elementValue := val.MapIndex(reflect.ValueOf(fieldName))
|
||||
|
|
@ -372,6 +369,10 @@ func drillIntoField(cursor interface{}, fieldName string) (interface{}, error) {
|
|||
field := val.Type().Field(i)
|
||||
tagValue := field.Tag.Get("yaml")
|
||||
yamlKey := strings.Split(tagValue, ",")[0]
|
||||
if yamlKey == "-" {
|
||||
tagValue := field.Tag.Get("multijson")
|
||||
yamlKey = strings.Split(tagValue, ",")[0]
|
||||
}
|
||||
if yamlKey == fieldName {
|
||||
return val.Field(i).Interface(), nil
|
||||
}
|
||||
|
|
@ -400,6 +401,14 @@ func drillIntoField(cursor interface{}, fieldName string) (interface{}, error) {
|
|||
}
|
||||
}
|
||||
|
||||
func (loader *Loader) documentPathForRecursiveRef(current *url.URL, resolvedRef string) *url.URL {
|
||||
if loader.rootDir == "" {
|
||||
return current
|
||||
}
|
||||
return &url.URL{Path: path.Join(loader.rootDir, resolvedRef)}
|
||||
|
||||
}
|
||||
|
||||
func (loader *Loader) resolveRef(doc *T, ref string, path *url.URL) (*T, string, *url.URL, error) {
|
||||
if ref != "" && ref[0] == '#' {
|
||||
return doc, ref, path, nil
|
||||
|
|
@ -459,6 +468,7 @@ func (loader *Loader) resolveHeaderRef(doc *T, component *HeaderRef, documentPat
|
|||
return err
|
||||
}
|
||||
component.Value = resolved.Value
|
||||
documentPath = loader.documentPathForRecursiveRef(documentPath, resolved.Ref)
|
||||
}
|
||||
}
|
||||
value := component.Value
|
||||
|
|
@ -506,6 +516,7 @@ func (loader *Loader) resolveParameterRef(doc *T, component *ParameterRef, docum
|
|||
return err
|
||||
}
|
||||
component.Value = resolved.Value
|
||||
documentPath = loader.documentPathForRecursiveRef(documentPath, resolved.Ref)
|
||||
}
|
||||
}
|
||||
value := component.Value
|
||||
|
|
@ -562,6 +573,7 @@ func (loader *Loader) resolveRequestBodyRef(doc *T, component *RequestBodyRef, d
|
|||
return err
|
||||
}
|
||||
component.Value = resolved.Value
|
||||
documentPath = loader.documentPathForRecursiveRef(documentPath, resolved.Ref)
|
||||
}
|
||||
}
|
||||
value := component.Value
|
||||
|
|
@ -617,6 +629,7 @@ func (loader *Loader) resolveResponseRef(doc *T, component *ResponseRef, documen
|
|||
return err
|
||||
}
|
||||
component.Value = resolved.Value
|
||||
documentPath = loader.documentPathForRecursiveRef(documentPath, resolved.Ref)
|
||||
}
|
||||
}
|
||||
value := component.Value
|
||||
|
|
@ -686,6 +699,7 @@ func (loader *Loader) resolveSchemaRef(doc *T, component *SchemaRef, documentPat
|
|||
return err
|
||||
}
|
||||
component.Value = resolved.Value
|
||||
documentPath = loader.documentPathForRecursiveRef(documentPath, resolved.Ref)
|
||||
}
|
||||
}
|
||||
value := component.Value
|
||||
|
|
@ -749,7 +763,7 @@ func (loader *Loader) resolveSecuritySchemeRef(doc *T, component *SecurityScheme
|
|||
if ref := component.Ref; ref != "" {
|
||||
if isSingleRefElement(ref) {
|
||||
var scheme SecurityScheme
|
||||
if documentPath, err = loader.loadSingleElementFromURI(ref, documentPath, &scheme); err != nil {
|
||||
if _, err = loader.loadSingleElementFromURI(ref, documentPath, &scheme); err != nil {
|
||||
return err
|
||||
}
|
||||
component.Value = &scheme
|
||||
|
|
@ -763,6 +777,7 @@ func (loader *Loader) resolveSecuritySchemeRef(doc *T, component *SecurityScheme
|
|||
return err
|
||||
}
|
||||
component.Value = resolved.Value
|
||||
_ = loader.documentPathForRecursiveRef(documentPath, resolved.Ref)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
|
|
@ -785,7 +800,7 @@ func (loader *Loader) resolveExampleRef(doc *T, component *ExampleRef, documentP
|
|||
if ref := component.Ref; ref != "" {
|
||||
if isSingleRefElement(ref) {
|
||||
var example Example
|
||||
if documentPath, err = loader.loadSingleElementFromURI(ref, documentPath, &example); err != nil {
|
||||
if _, err = loader.loadSingleElementFromURI(ref, documentPath, &example); err != nil {
|
||||
return err
|
||||
}
|
||||
component.Value = &example
|
||||
|
|
@ -799,6 +814,7 @@ func (loader *Loader) resolveExampleRef(doc *T, component *ExampleRef, documentP
|
|||
return err
|
||||
}
|
||||
component.Value = resolved.Value
|
||||
_ = loader.documentPathForRecursiveRef(documentPath, resolved.Ref)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
|
|
@ -826,6 +842,7 @@ func (loader *Loader) resolveCallbackRef(doc *T, component *CallbackRef, documen
|
|||
return err
|
||||
}
|
||||
component.Value = resolved.Value
|
||||
documentPath = loader.documentPathForRecursiveRef(documentPath, resolved.Ref)
|
||||
}
|
||||
}
|
||||
value := component.Value
|
||||
|
|
@ -909,7 +926,7 @@ func (loader *Loader) resolveLinkRef(doc *T, component *LinkRef, documentPath *u
|
|||
if ref := component.Ref; ref != "" {
|
||||
if isSingleRefElement(ref) {
|
||||
var link Link
|
||||
if documentPath, err = loader.loadSingleElementFromURI(ref, documentPath, &link); err != nil {
|
||||
if _, err = loader.loadSingleElementFromURI(ref, documentPath, &link); err != nil {
|
||||
return err
|
||||
}
|
||||
component.Value = &link
|
||||
|
|
@ -923,6 +940,7 @@ func (loader *Loader) resolveLinkRef(doc *T, component *LinkRef, documentPath *u
|
|||
return err
|
||||
}
|
||||
component.Value = resolved.Value
|
||||
_ = loader.documentPathForRecursiveRef(documentPath, resolved.Ref)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
|
|
|
|||
104
vendor/github.com/getkin/kin-openapi/openapi3/loader_uri_reader.go
generated
vendored
Normal file
104
vendor/github.com/getkin/kin-openapi/openapi3/loader_uri_reader.go
generated
vendored
Normal file
|
|
@ -0,0 +1,104 @@
|
|||
package openapi3
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
// ReadFromURIFunc defines a function which reads the contents of a resource
|
||||
// located at a URI.
|
||||
type ReadFromURIFunc func(loader *Loader, url *url.URL) ([]byte, error)
|
||||
|
||||
// ErrURINotSupported indicates the ReadFromURIFunc does not know how to handle a
|
||||
// given URI.
|
||||
var ErrURINotSupported = errors.New("unsupported URI")
|
||||
|
||||
// ReadFromURIs returns a ReadFromURIFunc which tries to read a URI using the
|
||||
// given reader functions, in the same order. If a reader function does not
|
||||
// support the URI and returns ErrURINotSupported, the next function is checked
|
||||
// until a match is found, or the URI is not supported by any.
|
||||
func ReadFromURIs(readers ...ReadFromURIFunc) ReadFromURIFunc {
|
||||
return func(loader *Loader, url *url.URL) ([]byte, error) {
|
||||
for i := range readers {
|
||||
buf, err := readers[i](loader, url)
|
||||
if err == ErrURINotSupported {
|
||||
continue
|
||||
} else if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return buf, nil
|
||||
}
|
||||
return nil, ErrURINotSupported
|
||||
}
|
||||
}
|
||||
|
||||
// DefaultReadFromURI returns a caching ReadFromURIFunc which can read remote
|
||||
// HTTP URIs and local file URIs.
|
||||
var DefaultReadFromURI = URIMapCache(ReadFromURIs(ReadFromHTTP(http.DefaultClient), ReadFromFile))
|
||||
|
||||
// ReadFromHTTP returns a ReadFromURIFunc which uses the given http.Client to
|
||||
// read the contents from a remote HTTP URI. This client may be customized to
|
||||
// implement timeouts, RFC 7234 caching, etc.
|
||||
func ReadFromHTTP(cl *http.Client) ReadFromURIFunc {
|
||||
return func(loader *Loader, location *url.URL) ([]byte, error) {
|
||||
if location.Scheme == "" || location.Host == "" {
|
||||
return nil, ErrURINotSupported
|
||||
}
|
||||
req, err := http.NewRequest("GET", location.String(), nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
resp, err := cl.Do(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
if resp.StatusCode > 399 {
|
||||
return nil, fmt.Errorf("error loading %q: request returned status code %d", location.String(), resp.StatusCode)
|
||||
}
|
||||
return ioutil.ReadAll(resp.Body)
|
||||
}
|
||||
}
|
||||
|
||||
// ReadFromFile is a ReadFromURIFunc which reads local file URIs.
|
||||
func ReadFromFile(loader *Loader, location *url.URL) ([]byte, error) {
|
||||
if location.Host != "" {
|
||||
return nil, ErrURINotSupported
|
||||
}
|
||||
if location.Scheme != "" && location.Scheme != "file" {
|
||||
return nil, ErrURINotSupported
|
||||
}
|
||||
return ioutil.ReadFile(location.Path)
|
||||
}
|
||||
|
||||
// URIMapCache returns a ReadFromURIFunc that caches the contents read from URI
|
||||
// locations in a simple map. This cache implementation is suitable for
|
||||
// short-lived processes such as command-line tools which process OpenAPI
|
||||
// documents.
|
||||
func URIMapCache(reader ReadFromURIFunc) ReadFromURIFunc {
|
||||
cache := map[string][]byte{}
|
||||
return func(loader *Loader, location *url.URL) (buf []byte, err error) {
|
||||
if location.Scheme == "" || location.Scheme == "file" {
|
||||
if !filepath.IsAbs(location.Path) {
|
||||
// Do not cache relative file paths; this can cause trouble if
|
||||
// the current working directory changes when processing
|
||||
// multiple top-level documents.
|
||||
return reader(loader, location)
|
||||
}
|
||||
}
|
||||
uri := location.String()
|
||||
var ok bool
|
||||
if buf, ok = cache[uri]; ok {
|
||||
return
|
||||
}
|
||||
if buf, err = reader(loader, location); err != nil {
|
||||
return
|
||||
}
|
||||
cache[uri] = buf
|
||||
return
|
||||
}
|
||||
}
|
||||
1
vendor/github.com/getkin/kin-openapi/openapi3/media_type.go
generated
vendored
1
vendor/github.com/getkin/kin-openapi/openapi3/media_type.go
generated
vendored
|
|
@ -8,6 +8,7 @@ import (
|
|||
)
|
||||
|
||||
// MediaType is specified by OpenAPI/Swagger 3.0 standard.
|
||||
// See https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#mediaTypeObject
|
||||
type MediaType struct {
|
||||
ExtensionProps
|
||||
|
||||
|
|
|
|||
20
vendor/github.com/getkin/kin-openapi/openapi3/openapi3.go
generated
vendored
20
vendor/github.com/getkin/kin-openapi/openapi3/openapi3.go
generated
vendored
|
|
@ -9,8 +9,10 @@ import (
|
|||
)
|
||||
|
||||
// T is the root of an OpenAPI v3 document
|
||||
// See https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#oasObject
|
||||
type T struct {
|
||||
ExtensionProps
|
||||
|
||||
OpenAPI string `json:"openapi" yaml:"openapi"` // Required
|
||||
Components Components `json:"components,omitempty" yaml:"components,omitempty"`
|
||||
Info *Info `json:"info" yaml:"info"` // Required
|
||||
|
|
@ -101,5 +103,23 @@ func (value *T) Validate(ctx context.Context) error {
|
|||
}
|
||||
}
|
||||
|
||||
{
|
||||
wrap := func(e error) error { return fmt.Errorf("invalid tags: %w", e) }
|
||||
if v := value.Tags; v != nil {
|
||||
if err := v.Validate(ctx); err != nil {
|
||||
return wrap(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
wrap := func(e error) error { return fmt.Errorf("invalid external docs: %w", e) }
|
||||
if v := value.ExternalDocs; v != nil {
|
||||
if err := v.Validate(ctx); err != nil {
|
||||
return wrap(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
|
|
|||
7
vendor/github.com/getkin/kin-openapi/openapi3/operation.go
generated
vendored
7
vendor/github.com/getkin/kin-openapi/openapi3/operation.go
generated
vendored
|
|
@ -3,6 +3,7 @@ package openapi3
|
|||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strconv"
|
||||
|
||||
"github.com/getkin/kin-openapi/jsoninfo"
|
||||
|
|
@ -10,6 +11,7 @@ import (
|
|||
)
|
||||
|
||||
// Operation represents "operation" specified by" OpenAPI/Swagger 3.0 standard.
|
||||
// See https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#operation-object
|
||||
type Operation struct {
|
||||
ExtensionProps
|
||||
|
||||
|
|
@ -138,5 +140,10 @@ func (value *Operation) Validate(ctx context.Context) error {
|
|||
} else {
|
||||
return errors.New("value of responses must be an object")
|
||||
}
|
||||
if v := value.ExternalDocs; v != nil {
|
||||
if err := v.Validate(ctx); err != nil {
|
||||
return fmt.Errorf("invalid external docs: %w", err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
|
|
|||
43
vendor/github.com/getkin/kin-openapi/openapi3/parameter.go
generated
vendored
43
vendor/github.com/getkin/kin-openapi/openapi3/parameter.go
generated
vendored
|
|
@ -83,8 +83,10 @@ func (value Parameters) Validate(ctx context.Context) error {
|
|||
}
|
||||
|
||||
// Parameter is specified by OpenAPI/Swagger 3.0 standard.
|
||||
// See https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#parameterObject
|
||||
type Parameter struct {
|
||||
ExtensionProps
|
||||
|
||||
Name string `json:"name,omitempty" yaml:"name,omitempty"`
|
||||
In string `json:"in,omitempty" yaml:"in,omitempty"`
|
||||
Description string `json:"description,omitempty" yaml:"description,omitempty"`
|
||||
|
|
@ -167,42 +169,42 @@ func (parameter *Parameter) UnmarshalJSON(data []byte) error {
|
|||
return jsoninfo.UnmarshalStrictStruct(data, parameter)
|
||||
}
|
||||
|
||||
func (parameter Parameter) JSONLookup(token string) (interface{}, error) {
|
||||
func (value Parameter) JSONLookup(token string) (interface{}, error) {
|
||||
switch token {
|
||||
case "schema":
|
||||
if parameter.Schema != nil {
|
||||
if parameter.Schema.Ref != "" {
|
||||
return &Ref{Ref: parameter.Schema.Ref}, nil
|
||||
if value.Schema != nil {
|
||||
if value.Schema.Ref != "" {
|
||||
return &Ref{Ref: value.Schema.Ref}, nil
|
||||
}
|
||||
return parameter.Schema.Value, nil
|
||||
return value.Schema.Value, nil
|
||||
}
|
||||
case "name":
|
||||
return parameter.Name, nil
|
||||
return value.Name, nil
|
||||
case "in":
|
||||
return parameter.In, nil
|
||||
return value.In, nil
|
||||
case "description":
|
||||
return parameter.Description, nil
|
||||
return value.Description, nil
|
||||
case "style":
|
||||
return parameter.Style, nil
|
||||
return value.Style, nil
|
||||
case "explode":
|
||||
return parameter.Explode, nil
|
||||
return value.Explode, nil
|
||||
case "allowEmptyValue":
|
||||
return parameter.AllowEmptyValue, nil
|
||||
return value.AllowEmptyValue, nil
|
||||
case "allowReserved":
|
||||
return parameter.AllowReserved, nil
|
||||
return value.AllowReserved, nil
|
||||
case "deprecated":
|
||||
return parameter.Deprecated, nil
|
||||
return value.Deprecated, nil
|
||||
case "required":
|
||||
return parameter.Required, nil
|
||||
return value.Required, nil
|
||||
case "example":
|
||||
return parameter.Example, nil
|
||||
return value.Example, nil
|
||||
case "examples":
|
||||
return parameter.Examples, nil
|
||||
return value.Examples, nil
|
||||
case "content":
|
||||
return parameter.Content, nil
|
||||
return value.Content, nil
|
||||
}
|
||||
|
||||
v, _, err := jsonpointer.GetForToken(parameter.ExtensionProps, token)
|
||||
v, _, err := jsonpointer.GetForToken(value.ExtensionProps, token)
|
||||
return v, err
|
||||
}
|
||||
|
||||
|
|
@ -251,6 +253,10 @@ func (value *Parameter) Validate(ctx context.Context) error {
|
|||
return fmt.Errorf("parameter can't have 'in' value %q", value.In)
|
||||
}
|
||||
|
||||
if in == ParameterInPath && !value.Required {
|
||||
return fmt.Errorf("path parameter %q must be required", value.Name)
|
||||
}
|
||||
|
||||
// Validate a parameter's serialization method.
|
||||
sm, err := value.SerializationMethod()
|
||||
if err != nil {
|
||||
|
|
@ -294,6 +300,7 @@ func (value *Parameter) Validate(ctx context.Context) error {
|
|||
return fmt.Errorf("parameter %q schema is invalid: %v", value.Name, err)
|
||||
}
|
||||
}
|
||||
|
||||
if content := value.Content; content != nil {
|
||||
if err := content.Validate(ctx); err != nil {
|
||||
return fmt.Errorf("parameter %q content is invalid: %v", value.Name, err)
|
||||
|
|
|
|||
3
vendor/github.com/getkin/kin-openapi/openapi3/path_item.go
generated
vendored
3
vendor/github.com/getkin/kin-openapi/openapi3/path_item.go
generated
vendored
|
|
@ -8,8 +8,11 @@ import (
|
|||
"github.com/getkin/kin-openapi/jsoninfo"
|
||||
)
|
||||
|
||||
// PathItem is specified by OpenAPI/Swagger standard version 3.
|
||||
// See https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#pathItemObject
|
||||
type PathItem struct {
|
||||
ExtensionProps
|
||||
|
||||
Ref string `json:"$ref,omitempty" yaml:"$ref,omitempty"`
|
||||
Summary string `json:"summary,omitempty" yaml:"summary,omitempty"`
|
||||
Description string `json:"description,omitempty" yaml:"description,omitempty"`
|
||||
|
|
|
|||
109
vendor/github.com/getkin/kin-openapi/openapi3/paths.go
generated
vendored
109
vendor/github.com/getkin/kin-openapi/openapi3/paths.go
generated
vendored
|
|
@ -6,7 +6,8 @@ import (
|
|||
"strings"
|
||||
)
|
||||
|
||||
// Paths is specified by OpenAPI/Swagger standard version 3.0.
|
||||
// Paths is specified by OpenAPI/Swagger standard version 3.
|
||||
// See https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#paths-object
|
||||
type Paths map[string]*PathItem
|
||||
|
||||
func (value Paths) Validate(ctx context.Context) error {
|
||||
|
|
@ -21,31 +22,60 @@ func (value Paths) Validate(ctx context.Context) error {
|
|||
pathItem = value[path]
|
||||
}
|
||||
|
||||
normalizedPath, pathParamsCount := normalizeTemplatedPath(path)
|
||||
normalizedPath, _, varsInPath := normalizeTemplatedPath(path)
|
||||
if oldPath, ok := normalizedPaths[normalizedPath]; ok {
|
||||
return fmt.Errorf("conflicting paths %q and %q", path, oldPath)
|
||||
}
|
||||
normalizedPaths[path] = path
|
||||
|
||||
var globalCount uint
|
||||
var commonParams []string
|
||||
for _, parameterRef := range pathItem.Parameters {
|
||||
if parameterRef != nil {
|
||||
if parameter := parameterRef.Value; parameter != nil && parameter.In == ParameterInPath {
|
||||
globalCount++
|
||||
commonParams = append(commonParams, parameter.Name)
|
||||
}
|
||||
}
|
||||
}
|
||||
for method, operation := range pathItem.Operations() {
|
||||
var count uint
|
||||
var setParams []string
|
||||
for _, parameterRef := range operation.Parameters {
|
||||
if parameterRef != nil {
|
||||
if parameter := parameterRef.Value; parameter != nil && parameter.In == ParameterInPath {
|
||||
count++
|
||||
setParams = append(setParams, parameter.Name)
|
||||
}
|
||||
}
|
||||
}
|
||||
if count+globalCount != pathParamsCount {
|
||||
return fmt.Errorf("operation %s %s must define exactly all path parameters", method, path)
|
||||
if expected := len(setParams) + len(commonParams); expected != len(varsInPath) {
|
||||
expected -= len(varsInPath)
|
||||
if expected < 0 {
|
||||
expected *= -1
|
||||
}
|
||||
missing := make(map[string]struct{}, expected)
|
||||
definedParams := append(setParams, commonParams...)
|
||||
for _, name := range definedParams {
|
||||
if _, ok := varsInPath[name]; !ok {
|
||||
missing[name] = struct{}{}
|
||||
}
|
||||
}
|
||||
for name := range varsInPath {
|
||||
got := false
|
||||
for _, othername := range definedParams {
|
||||
if othername == name {
|
||||
got = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !got {
|
||||
missing[name] = struct{}{}
|
||||
}
|
||||
}
|
||||
if len(missing) != 0 {
|
||||
missings := make([]string, 0, len(missing))
|
||||
for name := range missing {
|
||||
missings = append(missings, name)
|
||||
}
|
||||
return fmt.Errorf("operation %s %s must define exactly all path parameters (missing: %v)", method, path, missings)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -53,6 +83,11 @@ func (value Paths) Validate(ctx context.Context) error {
|
|||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if err := value.validateUniqueOperationIDs(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
|
|
@ -75,9 +110,9 @@ func (paths Paths) Find(key string) *PathItem {
|
|||
return pathItem
|
||||
}
|
||||
|
||||
normalizedPath, expected := normalizeTemplatedPath(key)
|
||||
normalizedPath, expected, _ := normalizeTemplatedPath(key)
|
||||
for path, pathItem := range paths {
|
||||
pathNormalized, got := normalizeTemplatedPath(path)
|
||||
pathNormalized, got, _ := normalizeTemplatedPath(path)
|
||||
if got == expected && pathNormalized == normalizedPath {
|
||||
return pathItem
|
||||
}
|
||||
|
|
@ -85,43 +120,75 @@ func (paths Paths) Find(key string) *PathItem {
|
|||
return nil
|
||||
}
|
||||
|
||||
func normalizeTemplatedPath(path string) (string, uint) {
|
||||
func (value Paths) validateUniqueOperationIDs() error {
|
||||
operationIDs := make(map[string]string)
|
||||
for urlPath, pathItem := range value {
|
||||
if pathItem == nil {
|
||||
continue
|
||||
}
|
||||
for httpMethod, operation := range pathItem.Operations() {
|
||||
if operation == nil || operation.OperationID == "" {
|
||||
continue
|
||||
}
|
||||
endpoint := httpMethod + " " + urlPath
|
||||
if endpointDup, ok := operationIDs[operation.OperationID]; ok {
|
||||
if endpoint > endpointDup { // For make error message a bit more deterministic. May be useful for tests.
|
||||
endpoint, endpointDup = endpointDup, endpoint
|
||||
}
|
||||
return fmt.Errorf("operations %q and %q have the same operation id %q",
|
||||
endpoint, endpointDup, operation.OperationID)
|
||||
}
|
||||
operationIDs[operation.OperationID] = endpoint
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func normalizeTemplatedPath(path string) (string, uint, map[string]struct{}) {
|
||||
if strings.IndexByte(path, '{') < 0 {
|
||||
return path, 0
|
||||
return path, 0, nil
|
||||
}
|
||||
|
||||
var buf strings.Builder
|
||||
buf.Grow(len(path))
|
||||
var buffTpl strings.Builder
|
||||
buffTpl.Grow(len(path))
|
||||
|
||||
var (
|
||||
cc rune
|
||||
count uint
|
||||
isVariable bool
|
||||
vars = make(map[string]struct{})
|
||||
buffVar strings.Builder
|
||||
)
|
||||
for i, c := range path {
|
||||
if isVariable {
|
||||
if c == '}' {
|
||||
// End path variables
|
||||
// End path variable
|
||||
isVariable = false
|
||||
|
||||
vars[buffVar.String()] = struct{}{}
|
||||
buffVar = strings.Builder{}
|
||||
|
||||
// First append possible '*' before this character
|
||||
// The character '}' will be appended
|
||||
if i > 0 && cc == '*' {
|
||||
buf.WriteRune(cc)
|
||||
buffTpl.WriteRune(cc)
|
||||
}
|
||||
isVariable = false
|
||||
} else {
|
||||
// Skip this character
|
||||
buffVar.WriteRune(c)
|
||||
continue
|
||||
}
|
||||
|
||||
} else if c == '{' {
|
||||
// Begin path variable
|
||||
// The character '{' will be appended
|
||||
isVariable = true
|
||||
|
||||
// The character '{' will be appended
|
||||
count++
|
||||
}
|
||||
|
||||
// Append the character
|
||||
buf.WriteRune(c)
|
||||
buffTpl.WriteRune(c)
|
||||
cc = c
|
||||
}
|
||||
return buf.String(), count
|
||||
return buffTpl.String(), count, vars
|
||||
}
|
||||
|
|
|
|||
19
vendor/github.com/getkin/kin-openapi/openapi3/refs.go
generated
vendored
19
vendor/github.com/getkin/kin-openapi/openapi3/refs.go
generated
vendored
|
|
@ -8,10 +8,13 @@ import (
|
|||
)
|
||||
|
||||
// Ref is specified by OpenAPI/Swagger 3.0 standard.
|
||||
// See https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#referenceObject
|
||||
type Ref struct {
|
||||
Ref string `json:"$ref" yaml:"$ref"`
|
||||
}
|
||||
|
||||
// CallbackRef represents either a Callback or a $ref to a Callback.
|
||||
// When serializing and both fields are set, Ref is preferred over Value.
|
||||
type CallbackRef struct {
|
||||
Ref string
|
||||
Value *Callback
|
||||
|
|
@ -43,6 +46,8 @@ func (value CallbackRef) JSONLookup(token string) (interface{}, error) {
|
|||
return ptr, err
|
||||
}
|
||||
|
||||
// ExampleRef represents either a Example or a $ref to a Example.
|
||||
// When serializing and both fields are set, Ref is preferred over Value.
|
||||
type ExampleRef struct {
|
||||
Ref string
|
||||
Value *Example
|
||||
|
|
@ -74,6 +79,8 @@ func (value ExampleRef) JSONLookup(token string) (interface{}, error) {
|
|||
return ptr, err
|
||||
}
|
||||
|
||||
// HeaderRef represents either a Header or a $ref to a Header.
|
||||
// When serializing and both fields are set, Ref is preferred over Value.
|
||||
type HeaderRef struct {
|
||||
Ref string
|
||||
Value *Header
|
||||
|
|
@ -105,6 +112,8 @@ func (value HeaderRef) JSONLookup(token string) (interface{}, error) {
|
|||
return ptr, err
|
||||
}
|
||||
|
||||
// LinkRef represents either a Link or a $ref to a Link.
|
||||
// When serializing and both fields are set, Ref is preferred over Value.
|
||||
type LinkRef struct {
|
||||
Ref string
|
||||
Value *Link
|
||||
|
|
@ -125,6 +134,8 @@ func (value *LinkRef) Validate(ctx context.Context) error {
|
|||
return foundUnresolvedRef(value.Ref)
|
||||
}
|
||||
|
||||
// ParameterRef represents either a Parameter or a $ref to a Parameter.
|
||||
// When serializing and both fields are set, Ref is preferred over Value.
|
||||
type ParameterRef struct {
|
||||
Ref string
|
||||
Value *Parameter
|
||||
|
|
@ -156,6 +167,8 @@ func (value ParameterRef) JSONLookup(token string) (interface{}, error) {
|
|||
return ptr, err
|
||||
}
|
||||
|
||||
// ResponseRef represents either a Response or a $ref to a Response.
|
||||
// When serializing and both fields are set, Ref is preferred over Value.
|
||||
type ResponseRef struct {
|
||||
Ref string
|
||||
Value *Response
|
||||
|
|
@ -187,6 +200,8 @@ func (value ResponseRef) JSONLookup(token string) (interface{}, error) {
|
|||
return ptr, err
|
||||
}
|
||||
|
||||
// RequestBodyRef represents either a RequestBody or a $ref to a RequestBody.
|
||||
// When serializing and both fields are set, Ref is preferred over Value.
|
||||
type RequestBodyRef struct {
|
||||
Ref string
|
||||
Value *RequestBody
|
||||
|
|
@ -218,6 +233,8 @@ func (value RequestBodyRef) JSONLookup(token string) (interface{}, error) {
|
|||
return ptr, err
|
||||
}
|
||||
|
||||
// SchemaRef represents either a Schema or a $ref to a Schema.
|
||||
// When serializing and both fields are set, Ref is preferred over Value.
|
||||
type SchemaRef struct {
|
||||
Ref string
|
||||
Value *Schema
|
||||
|
|
@ -256,6 +273,8 @@ func (value SchemaRef) JSONLookup(token string) (interface{}, error) {
|
|||
return ptr, err
|
||||
}
|
||||
|
||||
// SecuritySchemeRef represents either a SecurityScheme or a $ref to a SecurityScheme.
|
||||
// When serializing and both fields are set, Ref is preferred over Value.
|
||||
type SecuritySchemeRef struct {
|
||||
Ref string
|
||||
Value *SecurityScheme
|
||||
|
|
|
|||
13
vendor/github.com/getkin/kin-openapi/openapi3/request_body.go
generated
vendored
13
vendor/github.com/getkin/kin-openapi/openapi3/request_body.go
generated
vendored
|
|
@ -2,6 +2,7 @@ package openapi3
|
|||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/getkin/kin-openapi/jsoninfo"
|
||||
|
|
@ -25,11 +26,13 @@ func (r RequestBodies) JSONLookup(token string) (interface{}, error) {
|
|||
}
|
||||
|
||||
// RequestBody is specified by OpenAPI/Swagger 3.0 standard.
|
||||
// See https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#requestBodyObject
|
||||
type RequestBody struct {
|
||||
ExtensionProps
|
||||
|
||||
Description string `json:"description,omitempty" yaml:"description,omitempty"`
|
||||
Required bool `json:"required,omitempty" yaml:"required,omitempty"`
|
||||
Content Content `json:"content,omitempty" yaml:"content,omitempty"`
|
||||
Content Content `json:"content" yaml:"content"`
|
||||
}
|
||||
|
||||
func NewRequestBody() *RequestBody {
|
||||
|
|
@ -98,10 +101,8 @@ func (requestBody *RequestBody) UnmarshalJSON(data []byte) error {
|
|||
}
|
||||
|
||||
func (value *RequestBody) Validate(ctx context.Context) error {
|
||||
if v := value.Content; v != nil {
|
||||
if err := v.Validate(ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
if value.Content == nil {
|
||||
return errors.New("content of the request body is required")
|
||||
}
|
||||
return nil
|
||||
return value.Content.Validate(ctx)
|
||||
}
|
||||
|
|
|
|||
14
vendor/github.com/getkin/kin-openapi/openapi3/response.go
generated
vendored
14
vendor/github.com/getkin/kin-openapi/openapi3/response.go
generated
vendored
|
|
@ -11,6 +11,7 @@ import (
|
|||
)
|
||||
|
||||
// Responses is specified by OpenAPI/Swagger 3.0 standard.
|
||||
// See https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#responsesObject
|
||||
type Responses map[string]*ResponseRef
|
||||
|
||||
var _ jsonpointer.JSONPointable = (*Responses)(nil)
|
||||
|
|
@ -54,8 +55,10 @@ func (responses Responses) JSONLookup(token string) (interface{}, error) {
|
|||
}
|
||||
|
||||
// Response is specified by OpenAPI/Swagger 3.0 standard.
|
||||
// See https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#responseObject
|
||||
type Response struct {
|
||||
ExtensionProps
|
||||
|
||||
Description *string `json:"description,omitempty" yaml:"description,omitempty"`
|
||||
Headers Headers `json:"headers,omitempty" yaml:"headers,omitempty"`
|
||||
Content Content `json:"content,omitempty" yaml:"content,omitempty"`
|
||||
|
|
@ -104,5 +107,16 @@ func (value *Response) Validate(ctx context.Context) error {
|
|||
return err
|
||||
}
|
||||
}
|
||||
for _, header := range value.Headers {
|
||||
if err := header.Validate(ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
for _, link := range value.Links {
|
||||
if err := link.Validate(ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
|
|
|||
263
vendor/github.com/getkin/kin-openapi/openapi3/schema.go
generated
vendored
263
vendor/github.com/getkin/kin-openapi/openapi3/schema.go
generated
vendored
|
|
@ -16,6 +16,15 @@ import (
|
|||
"github.com/go-openapi/jsonpointer"
|
||||
)
|
||||
|
||||
const (
|
||||
TypeArray = "array"
|
||||
TypeBoolean = "boolean"
|
||||
TypeInteger = "integer"
|
||||
TypeNumber = "number"
|
||||
TypeObject = "object"
|
||||
TypeString = "string"
|
||||
)
|
||||
|
||||
var (
|
||||
// SchemaErrorDetailsDisabled disables printing of details about schema errors.
|
||||
SchemaErrorDetailsDisabled = false
|
||||
|
|
@ -93,6 +102,7 @@ func (s SchemaRefs) JSONLookup(token string) (interface{}, error) {
|
|||
}
|
||||
|
||||
// Schema is specified by OpenAPI/Swagger 3.0 standard.
|
||||
// See https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#schemaObject
|
||||
type Schema struct {
|
||||
ExtensionProps
|
||||
|
||||
|
|
@ -109,20 +119,18 @@ type Schema struct {
|
|||
Example interface{} `json:"example,omitempty" yaml:"example,omitempty"`
|
||||
ExternalDocs *ExternalDocs `json:"externalDocs,omitempty" yaml:"externalDocs,omitempty"`
|
||||
|
||||
// Object-related, here for struct compactness
|
||||
AdditionalPropertiesAllowed *bool `json:"-" multijson:"additionalProperties,omitempty" yaml:"-"`
|
||||
// Array-related, here for struct compactness
|
||||
UniqueItems bool `json:"uniqueItems,omitempty" yaml:"uniqueItems,omitempty"`
|
||||
// Number-related, here for struct compactness
|
||||
ExclusiveMin bool `json:"exclusiveMinimum,omitempty" yaml:"exclusiveMinimum,omitempty"`
|
||||
ExclusiveMax bool `json:"exclusiveMaximum,omitempty" yaml:"exclusiveMaximum,omitempty"`
|
||||
// Properties
|
||||
Nullable bool `json:"nullable,omitempty" yaml:"nullable,omitempty"`
|
||||
ReadOnly bool `json:"readOnly,omitempty" yaml:"readOnly,omitempty"`
|
||||
WriteOnly bool `json:"writeOnly,omitempty" yaml:"writeOnly,omitempty"`
|
||||
AllowEmptyValue bool `json:"allowEmptyValue,omitempty" yaml:"allowEmptyValue,omitempty"`
|
||||
XML interface{} `json:"xml,omitempty" yaml:"xml,omitempty"`
|
||||
Deprecated bool `json:"deprecated,omitempty" yaml:"deprecated,omitempty"`
|
||||
Nullable bool `json:"nullable,omitempty" yaml:"nullable,omitempty"`
|
||||
ReadOnly bool `json:"readOnly,omitempty" yaml:"readOnly,omitempty"`
|
||||
WriteOnly bool `json:"writeOnly,omitempty" yaml:"writeOnly,omitempty"`
|
||||
AllowEmptyValue bool `json:"allowEmptyValue,omitempty" yaml:"allowEmptyValue,omitempty"`
|
||||
Deprecated bool `json:"deprecated,omitempty" yaml:"deprecated,omitempty"`
|
||||
XML *XML `json:"xml,omitempty" yaml:"xml,omitempty"`
|
||||
|
||||
// Number
|
||||
Min *float64 `json:"minimum,omitempty" yaml:"minimum,omitempty"`
|
||||
|
|
@ -141,12 +149,13 @@ type Schema struct {
|
|||
Items *SchemaRef `json:"items,omitempty" yaml:"items,omitempty"`
|
||||
|
||||
// Object
|
||||
Required []string `json:"required,omitempty" yaml:"required,omitempty"`
|
||||
Properties Schemas `json:"properties,omitempty" yaml:"properties,omitempty"`
|
||||
MinProps uint64 `json:"minProperties,omitempty" yaml:"minProperties,omitempty"`
|
||||
MaxProps *uint64 `json:"maxProperties,omitempty" yaml:"maxProperties,omitempty"`
|
||||
AdditionalProperties *SchemaRef `json:"-" multijson:"additionalProperties,omitempty" yaml:"-"`
|
||||
Discriminator *Discriminator `json:"discriminator,omitempty" yaml:"discriminator,omitempty"`
|
||||
Required []string `json:"required,omitempty" yaml:"required,omitempty"`
|
||||
Properties Schemas `json:"properties,omitempty" yaml:"properties,omitempty"`
|
||||
MinProps uint64 `json:"minProperties,omitempty" yaml:"minProperties,omitempty"`
|
||||
MaxProps *uint64 `json:"maxProperties,omitempty" yaml:"maxProperties,omitempty"`
|
||||
AdditionalPropertiesAllowed *bool `multijson:"additionalProperties,omitempty" json:"-" yaml:"-"` // In this order...
|
||||
AdditionalProperties *SchemaRef `multijson:"additionalProperties,omitempty" json:"-" yaml:"-"` // ...for multijson
|
||||
Discriminator *Discriminator `json:"discriminator,omitempty" yaml:"discriminator,omitempty"`
|
||||
}
|
||||
|
||||
var _ jsonpointer.JSONPointable = (*Schema)(nil)
|
||||
|
|
@ -298,73 +307,73 @@ func NewAllOfSchema(schemas ...*Schema) *Schema {
|
|||
|
||||
func NewBoolSchema() *Schema {
|
||||
return &Schema{
|
||||
Type: "boolean",
|
||||
Type: TypeBoolean,
|
||||
}
|
||||
}
|
||||
|
||||
func NewFloat64Schema() *Schema {
|
||||
return &Schema{
|
||||
Type: "number",
|
||||
Type: TypeNumber,
|
||||
}
|
||||
}
|
||||
|
||||
func NewIntegerSchema() *Schema {
|
||||
return &Schema{
|
||||
Type: "integer",
|
||||
Type: TypeInteger,
|
||||
}
|
||||
}
|
||||
|
||||
func NewInt32Schema() *Schema {
|
||||
return &Schema{
|
||||
Type: "integer",
|
||||
Type: TypeInteger,
|
||||
Format: "int32",
|
||||
}
|
||||
}
|
||||
|
||||
func NewInt64Schema() *Schema {
|
||||
return &Schema{
|
||||
Type: "integer",
|
||||
Type: TypeInteger,
|
||||
Format: "int64",
|
||||
}
|
||||
}
|
||||
|
||||
func NewStringSchema() *Schema {
|
||||
return &Schema{
|
||||
Type: "string",
|
||||
Type: TypeString,
|
||||
}
|
||||
}
|
||||
|
||||
func NewDateTimeSchema() *Schema {
|
||||
return &Schema{
|
||||
Type: "string",
|
||||
Type: TypeString,
|
||||
Format: "date-time",
|
||||
}
|
||||
}
|
||||
|
||||
func NewUUIDSchema() *Schema {
|
||||
return &Schema{
|
||||
Type: "string",
|
||||
Type: TypeString,
|
||||
Format: "uuid",
|
||||
}
|
||||
}
|
||||
|
||||
func NewBytesSchema() *Schema {
|
||||
return &Schema{
|
||||
Type: "string",
|
||||
Type: TypeString,
|
||||
Format: "byte",
|
||||
}
|
||||
}
|
||||
|
||||
func NewArraySchema() *Schema {
|
||||
return &Schema{
|
||||
Type: "array",
|
||||
Type: TypeArray,
|
||||
}
|
||||
}
|
||||
|
||||
func NewObjectSchema() *Schema {
|
||||
return &Schema{
|
||||
Type: "object",
|
||||
Properties: make(map[string]*SchemaRef),
|
||||
Type: TypeObject,
|
||||
Properties: make(Schemas),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -485,7 +494,7 @@ func (schema *Schema) WithProperty(name string, propertySchema *Schema) *Schema
|
|||
func (schema *Schema) WithPropertyRef(name string, ref *SchemaRef) *Schema {
|
||||
properties := schema.Properties
|
||||
if properties == nil {
|
||||
properties = make(map[string]*SchemaRef)
|
||||
properties = make(Schemas)
|
||||
schema.Properties = properties
|
||||
}
|
||||
properties[name] = ref
|
||||
|
|
@ -493,7 +502,7 @@ func (schema *Schema) WithPropertyRef(name string, ref *SchemaRef) *Schema {
|
|||
}
|
||||
|
||||
func (schema *Schema) WithProperties(properties map[string]*Schema) *Schema {
|
||||
result := make(map[string]*SchemaRef, len(properties))
|
||||
result := make(Schemas, len(properties))
|
||||
for k, v := range properties {
|
||||
result[k] = &SchemaRef{
|
||||
Value: v,
|
||||
|
|
@ -638,8 +647,8 @@ func (schema *Schema) validate(ctx context.Context, stack []*Schema) (err error)
|
|||
schemaType := schema.Type
|
||||
switch schemaType {
|
||||
case "":
|
||||
case "boolean":
|
||||
case "number":
|
||||
case TypeBoolean:
|
||||
case TypeNumber:
|
||||
if format := schema.Format; len(format) > 0 {
|
||||
switch format {
|
||||
case "float", "double":
|
||||
|
|
@ -649,7 +658,7 @@ func (schema *Schema) validate(ctx context.Context, stack []*Schema) (err error)
|
|||
}
|
||||
}
|
||||
}
|
||||
case "integer":
|
||||
case TypeInteger:
|
||||
if format := schema.Format; len(format) > 0 {
|
||||
switch format {
|
||||
case "int32", "int64":
|
||||
|
|
@ -659,17 +668,21 @@ func (schema *Schema) validate(ctx context.Context, stack []*Schema) (err error)
|
|||
}
|
||||
}
|
||||
}
|
||||
case "string":
|
||||
case TypeString:
|
||||
if format := schema.Format; len(format) > 0 {
|
||||
switch format {
|
||||
// Supported by OpenAPIv3.0.1:
|
||||
// Supported by OpenAPIv3.0.3:
|
||||
// https://spec.openapis.org/oas/v3.0.3
|
||||
case "byte", "binary", "date", "date-time", "password":
|
||||
// In JSON Draft-07 (not validated yet though):
|
||||
case "regex":
|
||||
case "time", "email", "idn-email":
|
||||
case "hostname", "idn-hostname", "ipv4", "ipv6":
|
||||
case "uri", "uri-reference", "iri", "iri-reference", "uri-template":
|
||||
case "json-pointer", "relative-json-pointer":
|
||||
// In JSON Draft-07 (not validated yet though):
|
||||
// https://json-schema.org/draft-07/json-schema-release-notes.html#formats
|
||||
case "iri", "iri-reference", "uri-template", "idn-email", "idn-hostname":
|
||||
case "json-pointer", "relative-json-pointer", "regex", "time":
|
||||
// In JSON Draft 2019-09 (not validated yet though):
|
||||
// https://json-schema.org/draft/2019-09/release-notes.html#format-vocabulary
|
||||
case "duration", "uuid":
|
||||
// Defined in some other specification
|
||||
case "email", "hostname", "ipv4", "ipv6", "uri", "uri-reference":
|
||||
default:
|
||||
// Try to check for custom defined formats
|
||||
if _, ok := SchemaStringFormats[format]; !ok && !SchemaFormatValidationDisabled {
|
||||
|
|
@ -677,11 +690,16 @@ func (schema *Schema) validate(ctx context.Context, stack []*Schema) (err error)
|
|||
}
|
||||
}
|
||||
}
|
||||
case "array":
|
||||
if schema.Pattern != "" {
|
||||
if err = schema.compilePattern(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
case TypeArray:
|
||||
if schema.Items == nil {
|
||||
return errors.New("when schema type is 'array', schema 'items' must be non-null")
|
||||
}
|
||||
case "object":
|
||||
case TypeObject:
|
||||
default:
|
||||
return fmt.Errorf("unsupported 'type' value %q", schemaType)
|
||||
}
|
||||
|
|
@ -716,6 +734,12 @@ func (schema *Schema) validate(ctx context.Context, stack []*Schema) (err error)
|
|||
}
|
||||
}
|
||||
|
||||
if v := schema.ExternalDocs; v != nil {
|
||||
if err = v.Validate(ctx); err != nil {
|
||||
return fmt.Errorf("invalid external docs: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
|
|
@ -775,8 +799,6 @@ func (schema *Schema) visitJSON(settings *schemaValidationSettings, value interf
|
|||
}
|
||||
|
||||
switch value := value.(type) {
|
||||
case nil:
|
||||
return schema.visitJSONNull(settings)
|
||||
case bool:
|
||||
return schema.visitJSONBoolean(settings, value)
|
||||
case float64:
|
||||
|
|
@ -787,13 +809,22 @@ func (schema *Schema) visitJSON(settings *schemaValidationSettings, value interf
|
|||
return schema.visitJSONArray(settings, value)
|
||||
case map[string]interface{}:
|
||||
return schema.visitJSONObject(settings, value)
|
||||
default:
|
||||
return &SchemaError{
|
||||
Value: value,
|
||||
Schema: schema,
|
||||
SchemaField: "type",
|
||||
Reason: fmt.Sprintf("unhandled value of type %T", value),
|
||||
case map[interface{}]interface{}: // for YAML cf. issue #444
|
||||
values := make(map[string]interface{}, len(value))
|
||||
for key, v := range value {
|
||||
if k, ok := key.(string); ok {
|
||||
values[k] = v
|
||||
}
|
||||
}
|
||||
if len(value) == len(values) {
|
||||
return schema.visitJSONObject(settings, values)
|
||||
}
|
||||
}
|
||||
return &SchemaError{
|
||||
Value: value,
|
||||
Schema: schema,
|
||||
SchemaField: "type",
|
||||
Reason: fmt.Sprintf("unhandled value of type %T", value),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -820,11 +851,7 @@ func (schema *Schema) visitSetOperations(settings *schemaValidationSettings, val
|
|||
if v == nil {
|
||||
return foundUnresolvedRef(ref.Ref)
|
||||
}
|
||||
var oldfailfast bool
|
||||
oldfailfast, settings.failfast = settings.failfast, true
|
||||
err := v.visitJSON(settings, value)
|
||||
settings.failfast = oldfailfast
|
||||
if err == nil {
|
||||
if err := v.visitJSON(settings, value); err == nil {
|
||||
if settings.failfast {
|
||||
return errSchema
|
||||
}
|
||||
|
|
@ -837,33 +864,57 @@ func (schema *Schema) visitSetOperations(settings *schemaValidationSettings, val
|
|||
}
|
||||
|
||||
if v := schema.OneOf; len(v) > 0 {
|
||||
var discriminatorRef string
|
||||
if schema.Discriminator != nil {
|
||||
pn := schema.Discriminator.PropertyName
|
||||
if valuemap, okcheck := value.(map[string]interface{}); okcheck {
|
||||
discriminatorVal, okcheck := valuemap[pn]
|
||||
if !okcheck {
|
||||
return errors.New("input does not contain the discriminator property")
|
||||
}
|
||||
|
||||
discriminatorValString, okcheck := discriminatorVal.(string)
|
||||
if !okcheck {
|
||||
return errors.New("descriminator value is not a string")
|
||||
}
|
||||
|
||||
if discriminatorRef, okcheck = schema.Discriminator.Mapping[discriminatorValString]; len(schema.Discriminator.Mapping) > 0 && !okcheck {
|
||||
return errors.New("input does not contain a valid discriminator value")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ok := 0
|
||||
validationErrors := []error{}
|
||||
for _, item := range v {
|
||||
v := item.Value
|
||||
if v == nil {
|
||||
return foundUnresolvedRef(item.Ref)
|
||||
}
|
||||
var oldfailfast bool
|
||||
oldfailfast, settings.failfast = settings.failfast, true
|
||||
err := v.visitJSON(settings, value)
|
||||
settings.failfast = oldfailfast
|
||||
if err == nil {
|
||||
if schema.Discriminator != nil {
|
||||
pn := schema.Discriminator.PropertyName
|
||||
if valuemap, okcheck := value.(map[string]interface{}); okcheck {
|
||||
if discriminatorVal, okcheck := valuemap[pn]; okcheck == true {
|
||||
mapref, okcheck := schema.Discriminator.Mapping[discriminatorVal.(string)]
|
||||
if okcheck && mapref == item.Ref {
|
||||
ok++
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
ok++
|
||||
}
|
||||
|
||||
if discriminatorRef != "" && discriminatorRef != item.Ref {
|
||||
continue
|
||||
}
|
||||
|
||||
if err := v.visitJSON(settings, value); err != nil {
|
||||
validationErrors = append(validationErrors, err)
|
||||
continue
|
||||
}
|
||||
|
||||
ok++
|
||||
}
|
||||
|
||||
if ok != 1 {
|
||||
if len(validationErrors) > 1 {
|
||||
errorMessage := ""
|
||||
for _, err := range validationErrors {
|
||||
if errorMessage != "" {
|
||||
errorMessage += " Or "
|
||||
}
|
||||
errorMessage += err.Error()
|
||||
}
|
||||
return errors.New("doesn't match schema due to: " + errorMessage)
|
||||
}
|
||||
if settings.failfast {
|
||||
return errSchema
|
||||
}
|
||||
|
|
@ -874,7 +925,10 @@ func (schema *Schema) visitSetOperations(settings *schemaValidationSettings, val
|
|||
}
|
||||
if ok > 1 {
|
||||
e.Origin = ErrOneOfConflict
|
||||
} else if len(validationErrors) == 1 {
|
||||
e.Origin = validationErrors[0]
|
||||
}
|
||||
|
||||
return e
|
||||
}
|
||||
}
|
||||
|
|
@ -886,11 +940,7 @@ func (schema *Schema) visitSetOperations(settings *schemaValidationSettings, val
|
|||
if v == nil {
|
||||
return foundUnresolvedRef(item.Ref)
|
||||
}
|
||||
var oldfailfast bool
|
||||
oldfailfast, settings.failfast = settings.failfast, true
|
||||
err := v.visitJSON(settings, value)
|
||||
settings.failfast = oldfailfast
|
||||
if err == nil {
|
||||
if err := v.visitJSON(settings, value); err == nil {
|
||||
ok = true
|
||||
break
|
||||
}
|
||||
|
|
@ -912,11 +962,7 @@ func (schema *Schema) visitSetOperations(settings *schemaValidationSettings, val
|
|||
if v == nil {
|
||||
return foundUnresolvedRef(item.Ref)
|
||||
}
|
||||
var oldfailfast bool
|
||||
oldfailfast, settings.failfast = settings.failfast, false
|
||||
err := v.visitJSON(settings, value)
|
||||
settings.failfast = oldfailfast
|
||||
if err != nil {
|
||||
if err := v.visitJSON(settings, value); err != nil {
|
||||
if settings.failfast {
|
||||
return errSchema
|
||||
}
|
||||
|
|
@ -952,8 +998,8 @@ func (schema *Schema) VisitJSONBoolean(value bool) error {
|
|||
}
|
||||
|
||||
func (schema *Schema) visitJSONBoolean(settings *schemaValidationSettings, value bool) (err error) {
|
||||
if schemaType := schema.Type; schemaType != "" && schemaType != "boolean" {
|
||||
return schema.expectedType(settings, "boolean")
|
||||
if schemaType := schema.Type; schemaType != "" && schemaType != TypeBoolean {
|
||||
return schema.expectedType(settings, TypeBoolean)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
|
@ -982,7 +1028,7 @@ func (schema *Schema) visitJSONNumber(settings *schemaValidationSettings, value
|
|||
}
|
||||
me = append(me, err)
|
||||
}
|
||||
} else if schemaType != "" && schemaType != "number" {
|
||||
} else if schemaType != "" && schemaType != TypeNumber {
|
||||
return schema.expectedType(settings, "number, integer")
|
||||
}
|
||||
|
||||
|
|
@ -1046,7 +1092,7 @@ func (schema *Schema) visitJSONNumber(settings *schemaValidationSettings, value
|
|||
Value: value,
|
||||
Schema: schema,
|
||||
SchemaField: "maximum",
|
||||
Reason: fmt.Sprintf("number must be most %g", *v),
|
||||
Reason: fmt.Sprintf("number must be at most %g", *v),
|
||||
}
|
||||
if !settings.multiError {
|
||||
return err
|
||||
|
|
@ -1087,8 +1133,8 @@ func (schema *Schema) VisitJSONString(value string) error {
|
|||
}
|
||||
|
||||
func (schema *Schema) visitJSONString(settings *schemaValidationSettings, value string) error {
|
||||
if schemaType := schema.Type; schemaType != "" && schemaType != "string" {
|
||||
return schema.expectedType(settings, "string")
|
||||
if schemaType := schema.Type; schemaType != "" && schemaType != TypeString {
|
||||
return schema.expectedType(settings, TypeString)
|
||||
}
|
||||
|
||||
var me MultiError
|
||||
|
|
@ -1139,15 +1185,9 @@ func (schema *Schema) visitJSONString(settings *schemaValidationSettings, value
|
|||
}
|
||||
|
||||
// "pattern"
|
||||
if pattern := schema.Pattern; pattern != "" && schema.compiledPattern == nil {
|
||||
if schema.Pattern != "" && schema.compiledPattern == nil {
|
||||
var err error
|
||||
if schema.compiledPattern, err = regexp.Compile(pattern); err != nil {
|
||||
err = &SchemaError{
|
||||
Value: value,
|
||||
Schema: schema,
|
||||
SchemaField: "pattern",
|
||||
Reason: fmt.Sprintf("cannot compile pattern %q: %v", pattern, err),
|
||||
}
|
||||
if err = schema.compilePattern(); err != nil {
|
||||
if !settings.multiError {
|
||||
return err
|
||||
}
|
||||
|
|
@ -1159,7 +1199,7 @@ func (schema *Schema) visitJSONString(settings *schemaValidationSettings, value
|
|||
Value: value,
|
||||
Schema: schema,
|
||||
SchemaField: "pattern",
|
||||
Reason: fmt.Sprintf("string doesn't match the regular expression %q", schema.Pattern),
|
||||
Reason: fmt.Sprintf(`string doesn't match the regular expression "%s"`, schema.Pattern),
|
||||
}
|
||||
if !settings.multiError {
|
||||
return err
|
||||
|
|
@ -1174,7 +1214,7 @@ func (schema *Schema) visitJSONString(settings *schemaValidationSettings, value
|
|||
switch {
|
||||
case f.regexp != nil && f.callback == nil:
|
||||
if cp := f.regexp; !cp.MatchString(value) {
|
||||
formatErr = fmt.Sprintf("string doesn't match the format %q (regular expression %q)", format, cp.String())
|
||||
formatErr = fmt.Sprintf(`string doesn't match the format %q (regular expression "%s")`, format, cp.String())
|
||||
}
|
||||
case f.regexp == nil && f.callback != nil:
|
||||
if err := f.callback(value); err != nil {
|
||||
|
|
@ -1212,8 +1252,8 @@ func (schema *Schema) VisitJSONArray(value []interface{}) error {
|
|||
}
|
||||
|
||||
func (schema *Schema) visitJSONArray(settings *schemaValidationSettings, value []interface{}) error {
|
||||
if schemaType := schema.Type; schemaType != "" && schemaType != "array" {
|
||||
return schema.expectedType(settings, "array")
|
||||
if schemaType := schema.Type; schemaType != "" && schemaType != TypeArray {
|
||||
return schema.expectedType(settings, TypeArray)
|
||||
}
|
||||
|
||||
var me MultiError
|
||||
|
|
@ -1308,8 +1348,8 @@ func (schema *Schema) VisitJSONObject(value map[string]interface{}) error {
|
|||
}
|
||||
|
||||
func (schema *Schema) visitJSONObject(settings *schemaValidationSettings, value map[string]interface{}) error {
|
||||
if schemaType := schema.Type; schemaType != "" && schemaType != "object" {
|
||||
return schema.expectedType(settings, "object")
|
||||
if schemaType := schema.Type; schemaType != "" && schemaType != TypeObject {
|
||||
return schema.expectedType(settings, TypeObject)
|
||||
}
|
||||
|
||||
var me MultiError
|
||||
|
|
@ -1383,7 +1423,7 @@ func (schema *Schema) visitJSONObject(settings *schemaValidationSettings, value
|
|||
}
|
||||
}
|
||||
allowed := schema.AdditionalPropertiesAllowed
|
||||
if additionalProperties != nil || allowed == nil || (allowed != nil && *allowed) {
|
||||
if additionalProperties != nil || allowed == nil || *allowed {
|
||||
if additionalProperties != nil {
|
||||
if err := additionalProperties.visitJSON(settings, v); err != nil {
|
||||
if settings.failfast {
|
||||
|
|
@ -1461,6 +1501,17 @@ func (schema *Schema) expectedType(settings *schemaValidationSettings, typ strin
|
|||
}
|
||||
}
|
||||
|
||||
func (schema *Schema) compilePattern() (err error) {
|
||||
if schema.compiledPattern, err = regexp.Compile(schema.Pattern); err != nil {
|
||||
return &SchemaError{
|
||||
Schema: schema,
|
||||
SchemaField: "pattern",
|
||||
Reason: fmt.Sprintf("cannot compile pattern %q: %v", schema.Pattern, err),
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type SchemaError struct {
|
||||
Value interface{}
|
||||
reversePath []string
|
||||
|
|
@ -1470,6 +1521,8 @@ type SchemaError struct {
|
|||
Origin error
|
||||
}
|
||||
|
||||
var _ interface{ Unwrap() error } = SchemaError{}
|
||||
|
||||
func markSchemaErrorKey(err error, key string) error {
|
||||
if v, ok := err.(*SchemaError); ok {
|
||||
v.reversePath = append(v.reversePath, key)
|
||||
|
|
@ -1545,6 +1598,10 @@ func (err *SchemaError) Error() string {
|
|||
return buf.String()
|
||||
}
|
||||
|
||||
func (err SchemaError) Unwrap() error {
|
||||
return err.Origin
|
||||
}
|
||||
|
||||
func isSliceOfUniqueItems(xs []interface{}) bool {
|
||||
s := len(xs)
|
||||
m := make(map[string]struct{}, s)
|
||||
|
|
|
|||
22
vendor/github.com/getkin/kin-openapi/openapi3/schema_formats.go
generated
vendored
22
vendor/github.com/getkin/kin-openapi/openapi3/schema_formats.go
generated
vendored
|
|
@ -4,11 +4,12 @@ import (
|
|||
"fmt"
|
||||
"net"
|
||||
"regexp"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const (
|
||||
// FormatOfStringForUUIDOfRFC4122 is an optional predefined format for UUID v1-v5 as specified by RFC4122
|
||||
FormatOfStringForUUIDOfRFC4122 = `^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$`
|
||||
FormatOfStringForUUIDOfRFC4122 = `^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-5][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}$`
|
||||
)
|
||||
|
||||
//FormatCallback custom check on exotic formats
|
||||
|
|
@ -37,24 +38,23 @@ func DefineStringFormatCallback(name string, callback FormatCallback) {
|
|||
SchemaStringFormats[name] = Format{callback: callback}
|
||||
}
|
||||
|
||||
func validateIP(ip string) (*net.IP, error) {
|
||||
func validateIP(ip string) error {
|
||||
parsed := net.ParseIP(ip)
|
||||
if parsed == nil {
|
||||
return nil, &SchemaError{
|
||||
return &SchemaError{
|
||||
Value: ip,
|
||||
Reason: "Not an IP address",
|
||||
}
|
||||
}
|
||||
return &parsed, nil
|
||||
return nil
|
||||
}
|
||||
|
||||
func validateIPv4(ip string) error {
|
||||
parsed, err := validateIP(ip)
|
||||
if err != nil {
|
||||
if err := validateIP(ip); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if parsed.To4() == nil {
|
||||
if !(strings.Count(ip, ":") < 2) {
|
||||
return &SchemaError{
|
||||
Value: ip,
|
||||
Reason: "Not an IPv4 address (it's IPv6)",
|
||||
|
|
@ -62,13 +62,13 @@ func validateIPv4(ip string) error {
|
|||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func validateIPv6(ip string) error {
|
||||
parsed, err := validateIP(ip)
|
||||
if err != nil {
|
||||
if err := validateIP(ip); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if parsed.To4() != nil {
|
||||
if !(strings.Count(ip, ":") >= 2) {
|
||||
return &SchemaError{
|
||||
Value: ip,
|
||||
Reason: "Not an IPv6 address (it's IPv4)",
|
||||
|
|
@ -90,7 +90,7 @@ func init() {
|
|||
DefineStringFormat("date", `^[0-9]{4}-(0[0-9]|10|11|12)-([0-2][0-9]|30|31)$`)
|
||||
|
||||
// date-time
|
||||
DefineStringFormat("date-time", `^[0-9]{4}-(0[0-9]|10|11|12)-([0-2][0-9]|30|31)T[0-9]{2}:[0-9]{2}:[0-9]{2}(.[0-9]+)?(Z|(\+|-)[0-9]{2}:[0-9]{2})?$`)
|
||||
DefineStringFormat("date-time", `^[0-9]{4}-(0[0-9]|10|11|12)-([0-2][0-9]|30|31)T[0-9]{2}:[0-9]{2}:[0-9]{2}(\.[0-9]+)?(Z|(\+|-)[0-9]{2}:[0-9]{2})?$`)
|
||||
|
||||
}
|
||||
|
||||
|
|
|
|||
2
vendor/github.com/getkin/kin-openapi/openapi3/security_requirements.go
generated
vendored
2
vendor/github.com/getkin/kin-openapi/openapi3/security_requirements.go
generated
vendored
|
|
@ -24,6 +24,8 @@ func (value SecurityRequirements) Validate(ctx context.Context) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
// SecurityRequirement is specified by OpenAPI/Swagger standard version 3.
|
||||
// See https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#securityRequirementObject
|
||||
type SecurityRequirement map[string][]string
|
||||
|
||||
func NewSecurityRequirement() SecurityRequirement {
|
||||
|
|
|
|||
8
vendor/github.com/getkin/kin-openapi/openapi3/security_scheme.go
generated
vendored
8
vendor/github.com/getkin/kin-openapi/openapi3/security_scheme.go
generated
vendored
|
|
@ -25,6 +25,8 @@ func (s SecuritySchemes) JSONLookup(token string) (interface{}, error) {
|
|||
|
||||
var _ jsonpointer.JSONPointable = (*SecuritySchemes)(nil)
|
||||
|
||||
// SecurityScheme is specified by OpenAPI/Swagger standard version 3.
|
||||
// See https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#securitySchemeObject
|
||||
type SecurityScheme struct {
|
||||
ExtensionProps
|
||||
|
||||
|
|
@ -166,8 +168,11 @@ func (value *SecurityScheme) Validate(ctx context.Context) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
// OAuthFlows is specified by OpenAPI/Swagger standard version 3.
|
||||
// See https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#oauthFlowsObject
|
||||
type OAuthFlows struct {
|
||||
ExtensionProps
|
||||
|
||||
Implicit *OAuthFlow `json:"implicit,omitempty" yaml:"implicit,omitempty"`
|
||||
Password *OAuthFlow `json:"password,omitempty" yaml:"password,omitempty"`
|
||||
ClientCredentials *OAuthFlow `json:"clientCredentials,omitempty" yaml:"clientCredentials,omitempty"`
|
||||
|
|
@ -207,8 +212,11 @@ func (flows *OAuthFlows) Validate(ctx context.Context) error {
|
|||
return errors.New("no OAuth flow is defined")
|
||||
}
|
||||
|
||||
// OAuthFlow is specified by OpenAPI/Swagger standard version 3.
|
||||
// See https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#oauthFlowObject
|
||||
type OAuthFlow struct {
|
||||
ExtensionProps
|
||||
|
||||
AuthorizationURL string `json:"authorizationUrl,omitempty" yaml:"authorizationUrl,omitempty"`
|
||||
TokenURL string `json:"tokenUrl,omitempty" yaml:"tokenUrl,omitempty"`
|
||||
RefreshURL string `json:"refreshUrl,omitempty" yaml:"refreshUrl,omitempty"`
|
||||
|
|
|
|||
10
vendor/github.com/getkin/kin-openapi/openapi3/server.go
generated
vendored
10
vendor/github.com/getkin/kin-openapi/openapi3/server.go
generated
vendored
|
|
@ -11,7 +11,7 @@ import (
|
|||
"github.com/getkin/kin-openapi/jsoninfo"
|
||||
)
|
||||
|
||||
// Servers is specified by OpenAPI/Swagger standard version 3.0.
|
||||
// Servers is specified by OpenAPI/Swagger standard version 3.
|
||||
type Servers []*Server
|
||||
|
||||
// Validate ensures servers are per the OpenAPIv3 specification.
|
||||
|
|
@ -38,9 +38,11 @@ func (servers Servers) MatchURL(parsedURL *url.URL) (*Server, []string, string)
|
|||
return nil, nil, ""
|
||||
}
|
||||
|
||||
// Server is specified by OpenAPI/Swagger standard version 3.0.
|
||||
// Server is specified by OpenAPI/Swagger standard version 3.
|
||||
// See https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#serverObject
|
||||
type Server struct {
|
||||
ExtensionProps
|
||||
|
||||
URL string `json:"url" yaml:"url"`
|
||||
Description string `json:"description,omitempty" yaml:"description,omitempty"`
|
||||
Variables map[string]*ServerVariable `json:"variables,omitempty" yaml:"variables,omitempty"`
|
||||
|
|
@ -147,9 +149,11 @@ func (value *Server) Validate(ctx context.Context) (err error) {
|
|||
return
|
||||
}
|
||||
|
||||
// ServerVariable is specified by OpenAPI/Swagger standard version 3.0.
|
||||
// ServerVariable is specified by OpenAPI/Swagger standard version 3.
|
||||
// See https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#server-variable-object
|
||||
type ServerVariable struct {
|
||||
ExtensionProps
|
||||
|
||||
Enum []string `json:"enum,omitempty" yaml:"enum,omitempty"`
|
||||
Default string `json:"default,omitempty" yaml:"default,omitempty"`
|
||||
Description string `json:"description,omitempty" yaml:"description,omitempty"`
|
||||
|
|
|
|||
27
vendor/github.com/getkin/kin-openapi/openapi3/tag.go
generated
vendored
27
vendor/github.com/getkin/kin-openapi/openapi3/tag.go
generated
vendored
|
|
@ -1,6 +1,11 @@
|
|||
package openapi3
|
||||
|
||||
import "github.com/getkin/kin-openapi/jsoninfo"
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/getkin/kin-openapi/jsoninfo"
|
||||
)
|
||||
|
||||
// Tags is specified by OpenAPI/Swagger 3.0 standard.
|
||||
type Tags []*Tag
|
||||
|
|
@ -14,9 +19,20 @@ func (tags Tags) Get(name string) *Tag {
|
|||
return nil
|
||||
}
|
||||
|
||||
func (tags Tags) Validate(ctx context.Context) error {
|
||||
for _, v := range tags {
|
||||
if err := v.Validate(ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Tag is specified by OpenAPI/Swagger 3.0 standard.
|
||||
// See https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#tagObject
|
||||
type Tag struct {
|
||||
ExtensionProps
|
||||
|
||||
Name string `json:"name,omitempty" yaml:"name,omitempty"`
|
||||
Description string `json:"description,omitempty" yaml:"description,omitempty"`
|
||||
ExternalDocs *ExternalDocs `json:"externalDocs,omitempty" yaml:"externalDocs,omitempty"`
|
||||
|
|
@ -29,3 +45,12 @@ func (t *Tag) MarshalJSON() ([]byte, error) {
|
|||
func (t *Tag) UnmarshalJSON(data []byte) error {
|
||||
return jsoninfo.UnmarshalStrictStruct(data, t)
|
||||
}
|
||||
|
||||
func (t *Tag) Validate(ctx context.Context) error {
|
||||
if v := t.ExternalDocs; v != nil {
|
||||
if err := v.Validate(ctx); err != nil {
|
||||
return fmt.Errorf("invalid external docs: %w", err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
|
|
|||
2
vendor/github.com/getkin/kin-openapi/openapi3/testdata/recursiveRef/components/Bar.yml
generated
vendored
Normal file
2
vendor/github.com/getkin/kin-openapi/openapi3/testdata/recursiveRef/components/Bar.yml
generated
vendored
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
type: string
|
||||
example: bar
|
||||
4
vendor/github.com/getkin/kin-openapi/openapi3/testdata/recursiveRef/components/Foo.yml
generated
vendored
Normal file
4
vendor/github.com/getkin/kin-openapi/openapi3/testdata/recursiveRef/components/Foo.yml
generated
vendored
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
type: object
|
||||
properties:
|
||||
bar:
|
||||
$ref: ../openapi.yml#/components/schemas/Bar
|
||||
4
vendor/github.com/getkin/kin-openapi/openapi3/testdata/recursiveRef/components/Foo/Foo2.yml
generated
vendored
Normal file
4
vendor/github.com/getkin/kin-openapi/openapi3/testdata/recursiveRef/components/Foo/Foo2.yml
generated
vendored
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
type: object
|
||||
properties:
|
||||
foo:
|
||||
$ref: ../../openapi.yml#/components/schemas/Foo
|
||||
15
vendor/github.com/getkin/kin-openapi/openapi3/testdata/recursiveRef/openapi.yml
generated
vendored
Normal file
15
vendor/github.com/getkin/kin-openapi/openapi3/testdata/recursiveRef/openapi.yml
generated
vendored
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
openapi: "3.0.3"
|
||||
info:
|
||||
title: Recursive refs example
|
||||
version: "1.0"
|
||||
paths:
|
||||
/foo:
|
||||
$ref: ./paths/foo.yml
|
||||
components:
|
||||
schemas:
|
||||
Foo:
|
||||
$ref: ./components/Foo.yml
|
||||
Foo2:
|
||||
$ref: ./components/Foo/Foo2.yml
|
||||
Bar:
|
||||
$ref: ./components/Bar.yml
|
||||
11
vendor/github.com/getkin/kin-openapi/openapi3/testdata/recursiveRef/paths/foo.yml
generated
vendored
Normal file
11
vendor/github.com/getkin/kin-openapi/openapi3/testdata/recursiveRef/paths/foo.yml
generated
vendored
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
get:
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
foo2:
|
||||
$ref: ../openapi.yml#/components/schemas/Foo2
|
||||
31
vendor/github.com/getkin/kin-openapi/openapi3/xml.go
generated
vendored
Normal file
31
vendor/github.com/getkin/kin-openapi/openapi3/xml.go
generated
vendored
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
package openapi3
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/getkin/kin-openapi/jsoninfo"
|
||||
)
|
||||
|
||||
// XML is specified by OpenAPI/Swagger standard version 3.
|
||||
// See https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#xmlObject
|
||||
type XML struct {
|
||||
ExtensionProps
|
||||
|
||||
Name string `json:"name,omitempty" yaml:"name,omitempty"`
|
||||
Namespace string `json:"namespace,omitempty" yaml:"namespace,omitempty"`
|
||||
Prefix string `json:"prefix,omitempty" yaml:"prefix,omitempty"`
|
||||
Attribute bool `json:"attribute,omitempty" yaml:"attribute,omitempty"`
|
||||
Wrapped bool `json:"wrapped,omitempty" yaml:"wrapped,omitempty"`
|
||||
}
|
||||
|
||||
func (value *XML) MarshalJSON() ([]byte, error) {
|
||||
return jsoninfo.MarshalStrictStruct(value)
|
||||
}
|
||||
|
||||
func (value *XML) UnmarshalJSON(data []byte) error {
|
||||
return jsoninfo.UnmarshalStrictStruct(data, value)
|
||||
}
|
||||
|
||||
func (value *XML) Validate(ctx context.Context) error {
|
||||
return nil // TODO
|
||||
}
|
||||
29
vendor/github.com/getkin/kin-openapi/openapi3filter/authentication_input.go
generated
vendored
Normal file
29
vendor/github.com/getkin/kin-openapi/openapi3filter/authentication_input.go
generated
vendored
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
package openapi3filter
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/getkin/kin-openapi/openapi3"
|
||||
)
|
||||
|
||||
type AuthenticationInput struct {
|
||||
RequestValidationInput *RequestValidationInput
|
||||
SecuritySchemeName string
|
||||
SecurityScheme *openapi3.SecurityScheme
|
||||
Scopes []string
|
||||
}
|
||||
|
||||
func (input *AuthenticationInput) NewError(err error) error {
|
||||
if err == nil {
|
||||
if len(input.Scopes) == 0 {
|
||||
err = fmt.Errorf("security requirement %q failed", input.SecuritySchemeName)
|
||||
} else {
|
||||
err = fmt.Errorf("security requirement %q (scopes: %+v) failed", input.SecuritySchemeName, input.Scopes)
|
||||
}
|
||||
}
|
||||
return &RequestError{
|
||||
Input: input.RequestValidationInput,
|
||||
Reason: "authorization failed",
|
||||
Err: err,
|
||||
}
|
||||
}
|
||||
82
vendor/github.com/getkin/kin-openapi/openapi3filter/errors.go
generated
vendored
Normal file
82
vendor/github.com/getkin/kin-openapi/openapi3filter/errors.go
generated
vendored
Normal file
|
|
@ -0,0 +1,82 @@
|
|||
package openapi3filter
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/getkin/kin-openapi/openapi3"
|
||||
)
|
||||
|
||||
var _ error = &RequestError{}
|
||||
|
||||
// RequestError is returned by ValidateRequest when request does not match OpenAPI spec
|
||||
type RequestError struct {
|
||||
Input *RequestValidationInput
|
||||
Parameter *openapi3.Parameter
|
||||
RequestBody *openapi3.RequestBody
|
||||
Reason string
|
||||
Err error
|
||||
}
|
||||
|
||||
var _ interface{ Unwrap() error } = RequestError{}
|
||||
|
||||
func (err *RequestError) Error() string {
|
||||
reason := err.Reason
|
||||
if e := err.Err; e != nil {
|
||||
if len(reason) == 0 {
|
||||
reason = e.Error()
|
||||
} else {
|
||||
reason += ": " + e.Error()
|
||||
}
|
||||
}
|
||||
if v := err.Parameter; v != nil {
|
||||
return fmt.Sprintf("parameter %q in %s has an error: %s", v.Name, v.In, reason)
|
||||
} else if v := err.RequestBody; v != nil {
|
||||
return fmt.Sprintf("request body has an error: %s", reason)
|
||||
} else {
|
||||
return reason
|
||||
}
|
||||
}
|
||||
|
||||
func (err RequestError) Unwrap() error {
|
||||
return err.Err
|
||||
}
|
||||
|
||||
var _ error = &ResponseError{}
|
||||
|
||||
// ResponseError is returned by ValidateResponse when response does not match OpenAPI spec
|
||||
type ResponseError struct {
|
||||
Input *ResponseValidationInput
|
||||
Reason string
|
||||
Err error
|
||||
}
|
||||
|
||||
var _ interface{ Unwrap() error } = ResponseError{}
|
||||
|
||||
func (err *ResponseError) Error() string {
|
||||
reason := err.Reason
|
||||
if e := err.Err; e != nil {
|
||||
if len(reason) == 0 {
|
||||
reason = e.Error()
|
||||
} else {
|
||||
reason += ": " + e.Error()
|
||||
}
|
||||
}
|
||||
return reason
|
||||
}
|
||||
|
||||
func (err ResponseError) Unwrap() error {
|
||||
return err.Err
|
||||
}
|
||||
|
||||
var _ error = &SecurityRequirementsError{}
|
||||
|
||||
// SecurityRequirementsError is returned by ValidateSecurityRequirements
|
||||
// when no requirement is met.
|
||||
type SecurityRequirementsError struct {
|
||||
SecurityRequirements openapi3.SecurityRequirements
|
||||
Errors []error
|
||||
}
|
||||
|
||||
func (err *SecurityRequirementsError) Error() string {
|
||||
return "Security requirements failed"
|
||||
}
|
||||
25
vendor/github.com/getkin/kin-openapi/openapi3filter/internal.go
generated
vendored
Normal file
25
vendor/github.com/getkin/kin-openapi/openapi3filter/internal.go
generated
vendored
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
package openapi3filter
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func parseMediaType(contentType string) string {
|
||||
i := strings.IndexByte(contentType, ';')
|
||||
if i < 0 {
|
||||
return contentType
|
||||
}
|
||||
return contentType[:i]
|
||||
}
|
||||
|
||||
func isNilValue(value interface{}) bool {
|
||||
if value == nil {
|
||||
return true
|
||||
}
|
||||
switch reflect.TypeOf(value).Kind() {
|
||||
case reflect.Ptr, reflect.Map, reflect.Array, reflect.Chan, reflect.Slice:
|
||||
return reflect.ValueOf(value).IsNil()
|
||||
}
|
||||
return false
|
||||
}
|
||||
273
vendor/github.com/getkin/kin-openapi/openapi3filter/middleware.go
generated
vendored
Normal file
273
vendor/github.com/getkin/kin-openapi/openapi3filter/middleware.go
generated
vendored
Normal file
|
|
@ -0,0 +1,273 @@
|
|||
package openapi3filter
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"net/http"
|
||||
|
||||
"github.com/getkin/kin-openapi/routers"
|
||||
)
|
||||
|
||||
// Validator provides HTTP request and response validation middleware.
|
||||
type Validator struct {
|
||||
router routers.Router
|
||||
errFunc ErrFunc
|
||||
logFunc LogFunc
|
||||
strict bool
|
||||
}
|
||||
|
||||
// ErrFunc handles errors that may occur during validation.
|
||||
type ErrFunc func(w http.ResponseWriter, status int, code ErrCode, err error)
|
||||
|
||||
// LogFunc handles log messages that may occur during validation.
|
||||
type LogFunc func(message string, err error)
|
||||
|
||||
// ErrCode is used for classification of different types of errors that may
|
||||
// occur during validation. These may be used to write an appropriate response
|
||||
// in ErrFunc.
|
||||
type ErrCode int
|
||||
|
||||
const (
|
||||
// ErrCodeOK indicates no error. It is also the default value.
|
||||
ErrCodeOK = 0
|
||||
// ErrCodeCannotFindRoute happens when the validator fails to resolve the
|
||||
// request to a defined OpenAPI route.
|
||||
ErrCodeCannotFindRoute = iota
|
||||
// ErrCodeRequestInvalid happens when the inbound request does not conform
|
||||
// to the OpenAPI 3 specification.
|
||||
ErrCodeRequestInvalid = iota
|
||||
// ErrCodeResponseInvalid happens when the wrapped handler response does
|
||||
// not conform to the OpenAPI 3 specification.
|
||||
ErrCodeResponseInvalid = iota
|
||||
)
|
||||
|
||||
func (e ErrCode) responseText() string {
|
||||
switch e {
|
||||
case ErrCodeOK:
|
||||
return "OK"
|
||||
case ErrCodeCannotFindRoute:
|
||||
return "not found"
|
||||
case ErrCodeRequestInvalid:
|
||||
return "bad request"
|
||||
default:
|
||||
return "server error"
|
||||
}
|
||||
}
|
||||
|
||||
// NewValidator returns a new response validation middlware, using the given
|
||||
// routes from an OpenAPI 3 specification.
|
||||
func NewValidator(router routers.Router, options ...ValidatorOption) *Validator {
|
||||
v := &Validator{
|
||||
router: router,
|
||||
errFunc: func(w http.ResponseWriter, status int, code ErrCode, _ error) {
|
||||
http.Error(w, code.responseText(), status)
|
||||
},
|
||||
logFunc: func(message string, err error) {
|
||||
log.Printf("%s: %v", message, err)
|
||||
},
|
||||
}
|
||||
for i := range options {
|
||||
options[i](v)
|
||||
}
|
||||
return v
|
||||
}
|
||||
|
||||
// ValidatorOption defines an option that may be specified when creating a
|
||||
// Validator.
|
||||
type ValidatorOption func(*Validator)
|
||||
|
||||
// OnErr provides a callback that handles writing an HTTP response on a
|
||||
// validation error. This allows customization of error responses without
|
||||
// prescribing a particular form. This callback is only called on response
|
||||
// validator errors in Strict mode.
|
||||
func OnErr(f ErrFunc) ValidatorOption {
|
||||
return func(v *Validator) {
|
||||
v.errFunc = f
|
||||
}
|
||||
}
|
||||
|
||||
// OnLog provides a callback that handles logging in the Validator. This allows
|
||||
// the validator to integrate with a services' existing logging system without
|
||||
// prescribing a particular one.
|
||||
func OnLog(f LogFunc) ValidatorOption {
|
||||
return func(v *Validator) {
|
||||
v.logFunc = f
|
||||
}
|
||||
}
|
||||
|
||||
// Strict, if set, causes an internal server error to be sent if the wrapped
|
||||
// handler response fails response validation. If not set, the response is sent
|
||||
// and the error is only logged.
|
||||
func Strict(strict bool) ValidatorOption {
|
||||
return func(v *Validator) {
|
||||
v.strict = strict
|
||||
}
|
||||
}
|
||||
|
||||
// Middleware returns an http.Handler which wraps the given handler with
|
||||
// request and response validation.
|
||||
func (v *Validator) Middleware(h http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
route, pathParams, err := v.router.FindRoute(r)
|
||||
if err != nil {
|
||||
v.logFunc("validation error: failed to find route for "+r.URL.String(), err)
|
||||
v.errFunc(w, http.StatusNotFound, ErrCodeCannotFindRoute, err)
|
||||
return
|
||||
}
|
||||
requestValidationInput := &RequestValidationInput{
|
||||
Request: r,
|
||||
PathParams: pathParams,
|
||||
Route: route,
|
||||
}
|
||||
if err = ValidateRequest(r.Context(), requestValidationInput); err != nil {
|
||||
v.logFunc("invalid request", err)
|
||||
v.errFunc(w, http.StatusBadRequest, ErrCodeRequestInvalid, err)
|
||||
return
|
||||
}
|
||||
|
||||
var wr responseWrapper
|
||||
if v.strict {
|
||||
wr = &strictResponseWrapper{w: w}
|
||||
} else {
|
||||
wr = newWarnResponseWrapper(w)
|
||||
}
|
||||
|
||||
h.ServeHTTP(wr, r)
|
||||
|
||||
if err = ValidateResponse(r.Context(), &ResponseValidationInput{
|
||||
RequestValidationInput: requestValidationInput,
|
||||
Status: wr.statusCode(),
|
||||
Header: wr.Header(),
|
||||
Body: ioutil.NopCloser(bytes.NewBuffer(wr.bodyContents())),
|
||||
}); err != nil {
|
||||
v.logFunc("invalid response", err)
|
||||
if v.strict {
|
||||
v.errFunc(w, http.StatusInternalServerError, ErrCodeResponseInvalid, err)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
if err = wr.flushBodyContents(); err != nil {
|
||||
v.logFunc("failed to write response", err)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
type responseWrapper interface {
|
||||
http.ResponseWriter
|
||||
|
||||
// flushBodyContents writes the buffered response to the client, if it has
|
||||
// not yet been written.
|
||||
flushBodyContents() error
|
||||
|
||||
// statusCode returns the response status code, 0 if not set yet.
|
||||
statusCode() int
|
||||
|
||||
// bodyContents returns the buffered
|
||||
bodyContents() []byte
|
||||
}
|
||||
|
||||
type warnResponseWrapper struct {
|
||||
w http.ResponseWriter
|
||||
headerWritten bool
|
||||
status int
|
||||
body bytes.Buffer
|
||||
tee io.Writer
|
||||
}
|
||||
|
||||
func newWarnResponseWrapper(w http.ResponseWriter) *warnResponseWrapper {
|
||||
wr := &warnResponseWrapper{
|
||||
w: w,
|
||||
}
|
||||
wr.tee = io.MultiWriter(w, &wr.body)
|
||||
return wr
|
||||
}
|
||||
|
||||
// Write implements http.ResponseWriter.
|
||||
func (wr *warnResponseWrapper) Write(b []byte) (int, error) {
|
||||
if !wr.headerWritten {
|
||||
wr.WriteHeader(http.StatusOK)
|
||||
}
|
||||
return wr.tee.Write(b)
|
||||
}
|
||||
|
||||
// WriteHeader implements http.ResponseWriter.
|
||||
func (wr *warnResponseWrapper) WriteHeader(status int) {
|
||||
if !wr.headerWritten {
|
||||
// If the header hasn't been written, record the status for response
|
||||
// validation.
|
||||
wr.status = status
|
||||
wr.headerWritten = true
|
||||
}
|
||||
wr.w.WriteHeader(wr.status)
|
||||
}
|
||||
|
||||
// Header implements http.ResponseWriter.
|
||||
func (wr *warnResponseWrapper) Header() http.Header {
|
||||
return wr.w.Header()
|
||||
}
|
||||
|
||||
// Flush implements the optional http.Flusher interface.
|
||||
func (wr *warnResponseWrapper) Flush() {
|
||||
// If the wrapped http.ResponseWriter implements optional http.Flusher,
|
||||
// pass through.
|
||||
if fl, ok := wr.w.(http.Flusher); ok {
|
||||
fl.Flush()
|
||||
}
|
||||
}
|
||||
|
||||
func (wr *warnResponseWrapper) flushBodyContents() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (wr *warnResponseWrapper) statusCode() int {
|
||||
return wr.status
|
||||
}
|
||||
|
||||
func (wr *warnResponseWrapper) bodyContents() []byte {
|
||||
return wr.body.Bytes()
|
||||
}
|
||||
|
||||
type strictResponseWrapper struct {
|
||||
w http.ResponseWriter
|
||||
headerWritten bool
|
||||
status int
|
||||
body bytes.Buffer
|
||||
}
|
||||
|
||||
// Write implements http.ResponseWriter.
|
||||
func (wr *strictResponseWrapper) Write(b []byte) (int, error) {
|
||||
if !wr.headerWritten {
|
||||
wr.WriteHeader(http.StatusOK)
|
||||
}
|
||||
return wr.body.Write(b)
|
||||
}
|
||||
|
||||
// WriteHeader implements http.ResponseWriter.
|
||||
func (wr *strictResponseWrapper) WriteHeader(status int) {
|
||||
if !wr.headerWritten {
|
||||
wr.status = status
|
||||
wr.headerWritten = true
|
||||
}
|
||||
}
|
||||
|
||||
// Header implements http.ResponseWriter.
|
||||
func (wr *strictResponseWrapper) Header() http.Header {
|
||||
return wr.w.Header()
|
||||
}
|
||||
|
||||
func (wr *strictResponseWrapper) flushBodyContents() error {
|
||||
wr.w.WriteHeader(wr.status)
|
||||
_, err := wr.w.Write(wr.body.Bytes())
|
||||
return err
|
||||
}
|
||||
|
||||
func (wr *strictResponseWrapper) statusCode() int {
|
||||
return wr.status
|
||||
}
|
||||
|
||||
func (wr *strictResponseWrapper) bodyContents() []byte {
|
||||
return wr.body.Bytes()
|
||||
}
|
||||
24
vendor/github.com/getkin/kin-openapi/openapi3filter/options.go
generated
vendored
Normal file
24
vendor/github.com/getkin/kin-openapi/openapi3filter/options.go
generated
vendored
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
package openapi3filter
|
||||
|
||||
// DefaultOptions do not set an AuthenticationFunc.
|
||||
// A spec with security schemes defined will not pass validation
|
||||
// unless an AuthenticationFunc is defined.
|
||||
var DefaultOptions = &Options{}
|
||||
|
||||
// Options used by ValidateRequest and ValidateResponse
|
||||
type Options struct {
|
||||
// Set ExcludeRequestBody so ValidateRequest skips request body validation
|
||||
ExcludeRequestBody bool
|
||||
|
||||
// Set ExcludeResponseBody so ValidateResponse skips response body validation
|
||||
ExcludeResponseBody bool
|
||||
|
||||
// Set IncludeResponseStatus so ValidateResponse fails on response
|
||||
// status not defined in OpenAPI spec
|
||||
IncludeResponseStatus bool
|
||||
|
||||
MultiError bool
|
||||
|
||||
// See NoopAuthenticationFunc
|
||||
AuthenticationFunc AuthenticationFunc
|
||||
}
|
||||
1081
vendor/github.com/getkin/kin-openapi/openapi3filter/req_resp_decoder.go
generated
vendored
Normal file
1081
vendor/github.com/getkin/kin-openapi/openapi3filter/req_resp_decoder.go
generated
vendored
Normal file
File diff suppressed because it is too large
Load diff
332
vendor/github.com/getkin/kin-openapi/openapi3filter/validate_request.go
generated
vendored
Normal file
332
vendor/github.com/getkin/kin-openapi/openapi3filter/validate_request.go
generated
vendored
Normal file
|
|
@ -0,0 +1,332 @@
|
|||
package openapi3filter
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"sort"
|
||||
|
||||
"github.com/getkin/kin-openapi/openapi3"
|
||||
)
|
||||
|
||||
// ErrAuthenticationServiceMissing is returned when no authentication service
|
||||
// is defined for the request validator
|
||||
var ErrAuthenticationServiceMissing = errors.New("missing AuthenticationFunc")
|
||||
|
||||
// ErrInvalidRequired is returned when a required value of a parameter or request body is not defined.
|
||||
var ErrInvalidRequired = errors.New("value is required but missing")
|
||||
|
||||
// ErrInvalidEmptyValue is returned when a value of a parameter or request body is empty while it's not allowed.
|
||||
var ErrInvalidEmptyValue = errors.New("empty value is not allowed")
|
||||
|
||||
// ValidateRequest is used to validate the given input according to previous
|
||||
// loaded OpenAPIv3 spec. If the input does not match the OpenAPIv3 spec, a
|
||||
// non-nil error will be returned.
|
||||
//
|
||||
// Note: One can tune the behavior of uniqueItems: true verification
|
||||
// by registering a custom function with openapi3.RegisterArrayUniqueItemsChecker
|
||||
func ValidateRequest(ctx context.Context, input *RequestValidationInput) error {
|
||||
var (
|
||||
err error
|
||||
me openapi3.MultiError
|
||||
)
|
||||
|
||||
options := input.Options
|
||||
if options == nil {
|
||||
options = DefaultOptions
|
||||
}
|
||||
route := input.Route
|
||||
operation := route.Operation
|
||||
operationParameters := operation.Parameters
|
||||
pathItemParameters := route.PathItem.Parameters
|
||||
|
||||
// Security
|
||||
security := operation.Security
|
||||
// If there aren't any security requirements for the operation
|
||||
if security == nil {
|
||||
// Use the global security requirements.
|
||||
security = &route.Spec.Security
|
||||
}
|
||||
if security != nil {
|
||||
if err = ValidateSecurityRequirements(ctx, input, *security); err != nil && !options.MultiError {
|
||||
return err
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
me = append(me, err)
|
||||
}
|
||||
}
|
||||
|
||||
// For each parameter of the PathItem
|
||||
for _, parameterRef := range pathItemParameters {
|
||||
parameter := parameterRef.Value
|
||||
if operationParameters != nil {
|
||||
if override := operationParameters.GetByInAndName(parameter.In, parameter.Name); override != nil {
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
if err = ValidateParameter(ctx, input, parameter); err != nil && !options.MultiError {
|
||||
return err
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
me = append(me, err)
|
||||
}
|
||||
}
|
||||
|
||||
// For each parameter of the Operation
|
||||
for _, parameter := range operationParameters {
|
||||
if err = ValidateParameter(ctx, input, parameter.Value); err != nil && !options.MultiError {
|
||||
return err
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
me = append(me, err)
|
||||
}
|
||||
}
|
||||
|
||||
// RequestBody
|
||||
requestBody := operation.RequestBody
|
||||
if requestBody != nil && !options.ExcludeRequestBody {
|
||||
if err = ValidateRequestBody(ctx, input, requestBody.Value); err != nil && !options.MultiError {
|
||||
return err
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
me = append(me, err)
|
||||
}
|
||||
}
|
||||
|
||||
if len(me) > 0 {
|
||||
return me
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// ValidateParameter validates a parameter's value by JSON schema.
|
||||
// The function returns RequestError with a ParseError cause when unable to parse a value.
|
||||
// The function returns RequestError with ErrInvalidRequired cause when a value of a required parameter is not defined.
|
||||
// The function returns RequestError with ErrInvalidEmptyValue cause when a value of a required parameter is not defined.
|
||||
// The function returns RequestError with a openapi3.SchemaError cause when a value is invalid by JSON schema.
|
||||
func ValidateParameter(ctx context.Context, input *RequestValidationInput, parameter *openapi3.Parameter) error {
|
||||
if parameter.Schema == nil && parameter.Content == nil {
|
||||
// We have no schema for the parameter. Assume that everything passes
|
||||
// a schema-less check, but this could also be an error. The OpenAPI
|
||||
// validation allows this to happen.
|
||||
return nil
|
||||
}
|
||||
|
||||
options := input.Options
|
||||
if options == nil {
|
||||
options = DefaultOptions
|
||||
}
|
||||
|
||||
var value interface{}
|
||||
var err error
|
||||
var found bool
|
||||
var schema *openapi3.Schema
|
||||
|
||||
// Validation will ensure that we either have content or schema.
|
||||
if parameter.Content != nil {
|
||||
if value, schema, found, err = decodeContentParameter(parameter, input); err != nil {
|
||||
return &RequestError{Input: input, Parameter: parameter, Err: err}
|
||||
}
|
||||
} else {
|
||||
if value, found, err = decodeStyledParameter(parameter, input); err != nil {
|
||||
return &RequestError{Input: input, Parameter: parameter, Err: err}
|
||||
}
|
||||
schema = parameter.Schema.Value
|
||||
}
|
||||
// Validate a parameter's value and presence.
|
||||
if parameter.Required && !found {
|
||||
return &RequestError{Input: input, Parameter: parameter, Reason: ErrInvalidRequired.Error(), Err: ErrInvalidRequired}
|
||||
}
|
||||
|
||||
if isNilValue(value) {
|
||||
if !parameter.AllowEmptyValue && found {
|
||||
return &RequestError{Input: input, Parameter: parameter, Reason: ErrInvalidEmptyValue.Error(), Err: ErrInvalidEmptyValue}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
if schema == nil {
|
||||
// A parameter's schema is not defined so skip validation of a parameter's value.
|
||||
return nil
|
||||
}
|
||||
|
||||
var opts []openapi3.SchemaValidationOption
|
||||
if options.MultiError {
|
||||
opts = make([]openapi3.SchemaValidationOption, 0, 1)
|
||||
opts = append(opts, openapi3.MultiErrors())
|
||||
}
|
||||
if err = schema.VisitJSON(value, opts...); err != nil {
|
||||
return &RequestError{Input: input, Parameter: parameter, Err: err}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
const prefixInvalidCT = "header Content-Type has unexpected value"
|
||||
|
||||
// ValidateRequestBody validates data of a request's body.
|
||||
//
|
||||
// The function returns RequestError with ErrInvalidRequired cause when a value is required but not defined.
|
||||
// The function returns RequestError with a openapi3.SchemaError cause when a value is invalid by JSON schema.
|
||||
func ValidateRequestBody(ctx context.Context, input *RequestValidationInput, requestBody *openapi3.RequestBody) error {
|
||||
var (
|
||||
req = input.Request
|
||||
data []byte
|
||||
)
|
||||
|
||||
options := input.Options
|
||||
if options == nil {
|
||||
options = DefaultOptions
|
||||
}
|
||||
|
||||
if req.Body != http.NoBody && req.Body != nil {
|
||||
defer req.Body.Close()
|
||||
var err error
|
||||
if data, err = ioutil.ReadAll(req.Body); err != nil {
|
||||
return &RequestError{
|
||||
Input: input,
|
||||
RequestBody: requestBody,
|
||||
Reason: "reading failed",
|
||||
Err: err,
|
||||
}
|
||||
}
|
||||
// Put the data back into the input
|
||||
req.Body = ioutil.NopCloser(bytes.NewReader(data))
|
||||
}
|
||||
|
||||
if len(data) == 0 {
|
||||
if requestBody.Required {
|
||||
return &RequestError{Input: input, RequestBody: requestBody, Err: ErrInvalidRequired}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
content := requestBody.Content
|
||||
if len(content) == 0 {
|
||||
// A request's body does not have declared content, so skip validation.
|
||||
return nil
|
||||
}
|
||||
|
||||
inputMIME := req.Header.Get(headerCT)
|
||||
contentType := requestBody.Content.Get(inputMIME)
|
||||
if contentType == nil {
|
||||
return &RequestError{
|
||||
Input: input,
|
||||
RequestBody: requestBody,
|
||||
Reason: fmt.Sprintf("%s %q", prefixInvalidCT, inputMIME),
|
||||
}
|
||||
}
|
||||
|
||||
if contentType.Schema == nil {
|
||||
// A JSON schema that describes the received data is not declared, so skip validation.
|
||||
return nil
|
||||
}
|
||||
|
||||
encFn := func(name string) *openapi3.Encoding { return contentType.Encoding[name] }
|
||||
value, err := decodeBody(bytes.NewReader(data), req.Header, contentType.Schema, encFn)
|
||||
if err != nil {
|
||||
return &RequestError{
|
||||
Input: input,
|
||||
RequestBody: requestBody,
|
||||
Reason: "failed to decode request body",
|
||||
Err: err,
|
||||
}
|
||||
}
|
||||
|
||||
opts := make([]openapi3.SchemaValidationOption, 0, 2) // 2 potential opts here
|
||||
opts = append(opts, openapi3.VisitAsRequest())
|
||||
if options.MultiError {
|
||||
opts = append(opts, openapi3.MultiErrors())
|
||||
}
|
||||
|
||||
// Validate JSON with the schema
|
||||
if err := contentType.Schema.Value.VisitJSON(value, opts...); err != nil {
|
||||
return &RequestError{
|
||||
Input: input,
|
||||
RequestBody: requestBody,
|
||||
Reason: "doesn't match the schema",
|
||||
Err: err,
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ValidateSecurityRequirements goes through multiple OpenAPI 3 security
|
||||
// requirements in order and returns nil on the first valid requirement.
|
||||
// If no requirement is met, errors are returned in order.
|
||||
func ValidateSecurityRequirements(ctx context.Context, input *RequestValidationInput, srs openapi3.SecurityRequirements) error {
|
||||
if len(srs) == 0 {
|
||||
return nil
|
||||
}
|
||||
var errs []error
|
||||
for _, sr := range srs {
|
||||
if err := validateSecurityRequirement(ctx, input, sr); err != nil {
|
||||
if len(errs) == 0 {
|
||||
errs = make([]error, 0, len(srs))
|
||||
}
|
||||
errs = append(errs, err)
|
||||
continue
|
||||
}
|
||||
return nil
|
||||
}
|
||||
return &SecurityRequirementsError{
|
||||
SecurityRequirements: srs,
|
||||
Errors: errs,
|
||||
}
|
||||
}
|
||||
|
||||
// validateSecurityRequirement validates a single OpenAPI 3 security requirement
|
||||
func validateSecurityRequirement(ctx context.Context, input *RequestValidationInput, securityRequirement openapi3.SecurityRequirement) error {
|
||||
doc := input.Route.Spec
|
||||
securitySchemes := doc.Components.SecuritySchemes
|
||||
|
||||
// Ensure deterministic order
|
||||
names := make([]string, 0, len(securityRequirement))
|
||||
for name := range securityRequirement {
|
||||
names = append(names, name)
|
||||
}
|
||||
sort.Strings(names)
|
||||
|
||||
// Get authentication function
|
||||
options := input.Options
|
||||
if options == nil {
|
||||
options = DefaultOptions
|
||||
}
|
||||
f := options.AuthenticationFunc
|
||||
if f == nil {
|
||||
return ErrAuthenticationServiceMissing
|
||||
}
|
||||
|
||||
// For each scheme for the requirement
|
||||
for _, name := range names {
|
||||
var securityScheme *openapi3.SecurityScheme
|
||||
if securitySchemes != nil {
|
||||
if ref := securitySchemes[name]; ref != nil {
|
||||
securityScheme = ref.Value
|
||||
}
|
||||
}
|
||||
if securityScheme == nil {
|
||||
return &RequestError{
|
||||
Input: input,
|
||||
Err: fmt.Errorf("security scheme %q is not declared", name),
|
||||
}
|
||||
}
|
||||
scopes := securityRequirement[name]
|
||||
if err := f(ctx, &AuthenticationInput{
|
||||
RequestValidationInput: input,
|
||||
SecuritySchemeName: name,
|
||||
SecurityScheme: securityScheme,
|
||||
Scopes: scopes,
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
38
vendor/github.com/getkin/kin-openapi/openapi3filter/validate_request_input.go
generated
vendored
Normal file
38
vendor/github.com/getkin/kin-openapi/openapi3filter/validate_request_input.go
generated
vendored
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
package openapi3filter
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"net/url"
|
||||
|
||||
"github.com/getkin/kin-openapi/openapi3"
|
||||
"github.com/getkin/kin-openapi/routers"
|
||||
)
|
||||
|
||||
// A ContentParameterDecoder takes a parameter definition from the OpenAPI spec,
|
||||
// and the value which we received for it. It is expected to return the
|
||||
// value unmarshaled into an interface which can be traversed for
|
||||
// validation, it should also return the schema to be used for validating the
|
||||
// object, since there can be more than one in the content spec.
|
||||
//
|
||||
// If a query parameter appears multiple times, values[] will have more
|
||||
// than one value, but for all other parameter types it should have just
|
||||
// one.
|
||||
type ContentParameterDecoder func(param *openapi3.Parameter, values []string) (interface{}, *openapi3.Schema, error)
|
||||
|
||||
type RequestValidationInput struct {
|
||||
Request *http.Request
|
||||
PathParams map[string]string
|
||||
QueryParams url.Values
|
||||
Route *routers.Route
|
||||
Options *Options
|
||||
ParamDecoder ContentParameterDecoder
|
||||
}
|
||||
|
||||
func (input *RequestValidationInput) GetQueryParams() url.Values {
|
||||
q := input.QueryParams
|
||||
if q == nil {
|
||||
q = input.Request.URL.Query()
|
||||
input.QueryParams = q
|
||||
}
|
||||
return q
|
||||
}
|
||||
138
vendor/github.com/getkin/kin-openapi/openapi3filter/validate_response.go
generated
vendored
Normal file
138
vendor/github.com/getkin/kin-openapi/openapi3filter/validate_response.go
generated
vendored
Normal file
|
|
@ -0,0 +1,138 @@
|
|||
// Package openapi3filter validates that requests and inputs request an OpenAPI 3 specification file.
|
||||
package openapi3filter
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
|
||||
"github.com/getkin/kin-openapi/openapi3"
|
||||
)
|
||||
|
||||
// ValidateResponse is used to validate the given input according to previous
|
||||
// loaded OpenAPIv3 spec. If the input does not match the OpenAPIv3 spec, a
|
||||
// non-nil error will be returned.
|
||||
//
|
||||
// Note: One can tune the behavior of uniqueItems: true verification
|
||||
// by registering a custom function with openapi3.RegisterArrayUniqueItemsChecker
|
||||
func ValidateResponse(ctx context.Context, input *ResponseValidationInput) error {
|
||||
req := input.RequestValidationInput.Request
|
||||
switch req.Method {
|
||||
case "HEAD":
|
||||
return nil
|
||||
}
|
||||
status := input.Status
|
||||
|
||||
// These status codes will never be validated.
|
||||
// TODO: The list is probably missing some.
|
||||
switch status {
|
||||
case http.StatusNotModified,
|
||||
http.StatusPermanentRedirect,
|
||||
http.StatusTemporaryRedirect,
|
||||
http.StatusMovedPermanently:
|
||||
return nil
|
||||
}
|
||||
route := input.RequestValidationInput.Route
|
||||
options := input.Options
|
||||
if options == nil {
|
||||
options = DefaultOptions
|
||||
}
|
||||
|
||||
// Find input for the current status
|
||||
responses := route.Operation.Responses
|
||||
if len(responses) == 0 {
|
||||
return nil
|
||||
}
|
||||
responseRef := responses.Get(status) // Response
|
||||
if responseRef == nil {
|
||||
responseRef = responses.Default() // Default input
|
||||
}
|
||||
if responseRef == nil {
|
||||
// By default, status that is not documented is allowed.
|
||||
if !options.IncludeResponseStatus {
|
||||
return nil
|
||||
}
|
||||
return &ResponseError{Input: input, Reason: "status is not supported"}
|
||||
}
|
||||
response := responseRef.Value
|
||||
if response == nil {
|
||||
return &ResponseError{Input: input, Reason: "response has not been resolved"}
|
||||
}
|
||||
|
||||
if options.ExcludeResponseBody {
|
||||
// A user turned off validation of a response's body.
|
||||
return nil
|
||||
}
|
||||
|
||||
content := response.Content
|
||||
if len(content) == 0 || options.ExcludeResponseBody {
|
||||
// An operation does not contains a validation schema for responses with this status code.
|
||||
return nil
|
||||
}
|
||||
|
||||
inputMIME := input.Header.Get(headerCT)
|
||||
contentType := content.Get(inputMIME)
|
||||
if contentType == nil {
|
||||
return &ResponseError{
|
||||
Input: input,
|
||||
Reason: fmt.Sprintf("response header Content-Type has unexpected value: %q", inputMIME),
|
||||
}
|
||||
}
|
||||
|
||||
if contentType.Schema == nil {
|
||||
// An operation does not contains a validation schema for responses with this status code.
|
||||
return nil
|
||||
}
|
||||
|
||||
// Read response's body.
|
||||
body := input.Body
|
||||
|
||||
// Response would contain partial or empty input body
|
||||
// after we begin reading.
|
||||
// Ensure that this doesn't happen.
|
||||
input.Body = nil
|
||||
|
||||
// Ensure we close the reader
|
||||
defer body.Close()
|
||||
|
||||
// Read all
|
||||
data, err := ioutil.ReadAll(body)
|
||||
if err != nil {
|
||||
return &ResponseError{
|
||||
Input: input,
|
||||
Reason: "failed to read response body",
|
||||
Err: err,
|
||||
}
|
||||
}
|
||||
|
||||
// Put the data back into the response.
|
||||
input.SetBodyBytes(data)
|
||||
|
||||
encFn := func(name string) *openapi3.Encoding { return contentType.Encoding[name] }
|
||||
value, err := decodeBody(bytes.NewBuffer(data), input.Header, contentType.Schema, encFn)
|
||||
if err != nil {
|
||||
return &ResponseError{
|
||||
Input: input,
|
||||
Reason: "failed to decode response body",
|
||||
Err: err,
|
||||
}
|
||||
}
|
||||
|
||||
opts := make([]openapi3.SchemaValidationOption, 0, 2) // 2 potential opts here
|
||||
opts = append(opts, openapi3.VisitAsRequest())
|
||||
if options.MultiError {
|
||||
opts = append(opts, openapi3.MultiErrors())
|
||||
}
|
||||
|
||||
// Validate data with the schema.
|
||||
if err := contentType.Schema.Value.VisitJSON(value, opts...); err != nil {
|
||||
return &ResponseError{
|
||||
Input: input,
|
||||
Reason: "response body doesn't match the schema",
|
||||
Err: err,
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
42
vendor/github.com/getkin/kin-openapi/openapi3filter/validate_response_input.go
generated
vendored
Normal file
42
vendor/github.com/getkin/kin-openapi/openapi3filter/validate_response_input.go
generated
vendored
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
package openapi3filter
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
type ResponseValidationInput struct {
|
||||
RequestValidationInput *RequestValidationInput
|
||||
Status int
|
||||
Header http.Header
|
||||
Body io.ReadCloser
|
||||
Options *Options
|
||||
}
|
||||
|
||||
func (input *ResponseValidationInput) SetBodyBytes(value []byte) *ResponseValidationInput {
|
||||
input.Body = ioutil.NopCloser(bytes.NewReader(value))
|
||||
return input
|
||||
}
|
||||
|
||||
var JSONPrefixes = []string{
|
||||
")]}',\n",
|
||||
}
|
||||
|
||||
// TrimJSONPrefix trims one of the possible prefixes
|
||||
func TrimJSONPrefix(data []byte) []byte {
|
||||
search:
|
||||
for _, prefix := range JSONPrefixes {
|
||||
if len(data) < len(prefix) {
|
||||
continue
|
||||
}
|
||||
for i, b := range data[:len(prefix)] {
|
||||
if b != prefix[i] {
|
||||
continue search
|
||||
}
|
||||
}
|
||||
return data[len(prefix):]
|
||||
}
|
||||
return data
|
||||
}
|
||||
85
vendor/github.com/getkin/kin-openapi/openapi3filter/validation_error.go
generated
vendored
Normal file
85
vendor/github.com/getkin/kin-openapi/openapi3filter/validation_error.go
generated
vendored
Normal file
|
|
@ -0,0 +1,85 @@
|
|||
package openapi3filter
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
// ValidationError struct provides granular error information
|
||||
// useful for communicating issues back to end user and developer.
|
||||
// Based on https://jsonapi.org/format/#error-objects
|
||||
type ValidationError struct {
|
||||
// A unique identifier for this particular occurrence of the problem.
|
||||
Id string `json:"id,omitempty" yaml:"id,omitempty"`
|
||||
// The HTTP status code applicable to this problem.
|
||||
Status int `json:"status,omitempty" yaml:"status,omitempty"`
|
||||
// An application-specific error code, expressed as a string value.
|
||||
Code string `json:"code,omitempty" yaml:"code,omitempty"`
|
||||
// A short, human-readable summary of the problem. It **SHOULD NOT** change from occurrence to occurrence of the problem, except for purposes of localization.
|
||||
Title string `json:"title,omitempty" yaml:"title,omitempty"`
|
||||
// A human-readable explanation specific to this occurrence of the problem.
|
||||
Detail string `json:"detail,omitempty" yaml:"detail,omitempty"`
|
||||
// An object containing references to the source of the error
|
||||
Source *ValidationErrorSource `json:"source,omitempty" yaml:"source,omitempty"`
|
||||
}
|
||||
|
||||
// ValidationErrorSource struct
|
||||
type ValidationErrorSource struct {
|
||||
// A JSON Pointer [RFC6901] to the associated entity in the request document [e.g. \"/data\" for a primary data object, or \"/data/attributes/title\" for a specific attribute].
|
||||
Pointer string `json:"pointer,omitempty" yaml:"pointer,omitempty"`
|
||||
// A string indicating which query parameter caused the error.
|
||||
Parameter string `json:"parameter,omitempty" yaml:"parameter,omitempty"`
|
||||
}
|
||||
|
||||
var _ error = &ValidationError{}
|
||||
|
||||
// Error implements the error interface.
|
||||
func (e *ValidationError) Error() string {
|
||||
b := new(bytes.Buffer)
|
||||
b.WriteString("[")
|
||||
if e.Status != 0 {
|
||||
b.WriteString(strconv.Itoa(e.Status))
|
||||
}
|
||||
b.WriteString("]")
|
||||
b.WriteString("[")
|
||||
if e.Code != "" {
|
||||
b.WriteString(e.Code)
|
||||
}
|
||||
b.WriteString("]")
|
||||
b.WriteString("[")
|
||||
if e.Id != "" {
|
||||
b.WriteString(e.Id)
|
||||
}
|
||||
b.WriteString("]")
|
||||
b.WriteString(" ")
|
||||
if e.Title != "" {
|
||||
b.WriteString(e.Title)
|
||||
b.WriteString(" ")
|
||||
}
|
||||
if e.Detail != "" {
|
||||
b.WriteString("| ")
|
||||
b.WriteString(e.Detail)
|
||||
b.WriteString(" ")
|
||||
}
|
||||
if e.Source != nil {
|
||||
b.WriteString("[source ")
|
||||
if e.Source.Parameter != "" {
|
||||
b.WriteString("parameter=")
|
||||
b.WriteString(e.Source.Parameter)
|
||||
} else if e.Source.Pointer != "" {
|
||||
b.WriteString("pointer=")
|
||||
b.WriteString(e.Source.Pointer)
|
||||
}
|
||||
b.WriteString("]")
|
||||
}
|
||||
|
||||
if b.Len() == 0 {
|
||||
return "no error"
|
||||
}
|
||||
return b.String()
|
||||
}
|
||||
|
||||
// StatusCode implements the StatusCoder interface for DefaultErrorEncoder
|
||||
func (e *ValidationError) StatusCode() int {
|
||||
return e.Status
|
||||
}
|
||||
185
vendor/github.com/getkin/kin-openapi/openapi3filter/validation_error_encoder.go
generated
vendored
Normal file
185
vendor/github.com/getkin/kin-openapi/openapi3filter/validation_error_encoder.go
generated
vendored
Normal file
|
|
@ -0,0 +1,185 @@
|
|||
package openapi3filter
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/getkin/kin-openapi/openapi3"
|
||||
"github.com/getkin/kin-openapi/routers"
|
||||
)
|
||||
|
||||
// ValidationErrorEncoder wraps a base ErrorEncoder to handle ValidationErrors
|
||||
type ValidationErrorEncoder struct {
|
||||
Encoder ErrorEncoder
|
||||
}
|
||||
|
||||
// Encode implements the ErrorEncoder interface for encoding ValidationErrors
|
||||
func (enc *ValidationErrorEncoder) Encode(ctx context.Context, err error, w http.ResponseWriter) {
|
||||
if e, ok := err.(*routers.RouteError); ok {
|
||||
cErr := convertRouteError(e)
|
||||
enc.Encoder(ctx, cErr, w)
|
||||
return
|
||||
}
|
||||
|
||||
e, ok := err.(*RequestError)
|
||||
if !ok {
|
||||
enc.Encoder(ctx, err, w)
|
||||
return
|
||||
}
|
||||
|
||||
var cErr *ValidationError
|
||||
if e.Err == nil {
|
||||
cErr = convertBasicRequestError(e)
|
||||
} else if e.Err == ErrInvalidRequired {
|
||||
cErr = convertErrInvalidRequired(e)
|
||||
} else if e.Err == ErrInvalidEmptyValue {
|
||||
cErr = convertErrInvalidEmptyValue(e)
|
||||
} else if innerErr, ok := e.Err.(*ParseError); ok {
|
||||
cErr = convertParseError(e, innerErr)
|
||||
} else if innerErr, ok := e.Err.(*openapi3.SchemaError); ok {
|
||||
cErr = convertSchemaError(e, innerErr)
|
||||
}
|
||||
|
||||
if cErr != nil {
|
||||
enc.Encoder(ctx, cErr, w)
|
||||
return
|
||||
}
|
||||
enc.Encoder(ctx, err, w)
|
||||
}
|
||||
|
||||
func convertRouteError(e *routers.RouteError) *ValidationError {
|
||||
status := http.StatusNotFound
|
||||
if e.Error() == routers.ErrMethodNotAllowed.Error() {
|
||||
status = http.StatusMethodNotAllowed
|
||||
}
|
||||
return &ValidationError{Status: status, Title: e.Error()}
|
||||
}
|
||||
|
||||
func convertBasicRequestError(e *RequestError) *ValidationError {
|
||||
if strings.HasPrefix(e.Reason, prefixInvalidCT) {
|
||||
if strings.HasSuffix(e.Reason, `""`) {
|
||||
return &ValidationError{
|
||||
Status: http.StatusUnsupportedMediaType,
|
||||
Title: "header Content-Type is required",
|
||||
}
|
||||
}
|
||||
return &ValidationError{
|
||||
Status: http.StatusUnsupportedMediaType,
|
||||
Title: prefixUnsupportedCT + strings.TrimPrefix(e.Reason, prefixInvalidCT),
|
||||
}
|
||||
}
|
||||
return &ValidationError{
|
||||
Status: http.StatusBadRequest,
|
||||
Title: e.Error(),
|
||||
}
|
||||
}
|
||||
|
||||
func convertErrInvalidRequired(e *RequestError) *ValidationError {
|
||||
if e.Err == ErrInvalidRequired && e.Parameter != nil {
|
||||
return &ValidationError{
|
||||
Status: http.StatusBadRequest,
|
||||
Title: fmt.Sprintf("parameter %q in %s is required", e.Parameter.Name, e.Parameter.In),
|
||||
}
|
||||
}
|
||||
return &ValidationError{
|
||||
Status: http.StatusBadRequest,
|
||||
Title: e.Error(),
|
||||
}
|
||||
}
|
||||
|
||||
func convertErrInvalidEmptyValue(e *RequestError) *ValidationError {
|
||||
if e.Err == ErrInvalidEmptyValue && e.Parameter != nil {
|
||||
return &ValidationError{
|
||||
Status: http.StatusBadRequest,
|
||||
Title: fmt.Sprintf("parameter %q in %s is not allowed to be empty", e.Parameter.Name, e.Parameter.In),
|
||||
}
|
||||
}
|
||||
return &ValidationError{
|
||||
Status: http.StatusBadRequest,
|
||||
Title: e.Error(),
|
||||
}
|
||||
}
|
||||
|
||||
func convertParseError(e *RequestError, innerErr *ParseError) *ValidationError {
|
||||
// We treat path params of the wrong type like a 404 instead of a 400
|
||||
if innerErr.Kind == KindInvalidFormat && e.Parameter != nil && e.Parameter.In == "path" {
|
||||
return &ValidationError{
|
||||
Status: http.StatusNotFound,
|
||||
Title: fmt.Sprintf("resource not found with %q value: %v", e.Parameter.Name, innerErr.Value),
|
||||
}
|
||||
} else if strings.HasPrefix(innerErr.Reason, prefixUnsupportedCT) {
|
||||
return &ValidationError{
|
||||
Status: http.StatusUnsupportedMediaType,
|
||||
Title: innerErr.Reason,
|
||||
}
|
||||
} else if innerErr.RootCause() != nil {
|
||||
if rootErr, ok := innerErr.Cause.(*ParseError); ok &&
|
||||
rootErr.Kind == KindInvalidFormat && e.Parameter.In == "query" {
|
||||
return &ValidationError{
|
||||
Status: http.StatusBadRequest,
|
||||
Title: fmt.Sprintf("parameter %q in %s is invalid: %v is %s",
|
||||
e.Parameter.Name, e.Parameter.In, rootErr.Value, rootErr.Reason),
|
||||
}
|
||||
}
|
||||
return &ValidationError{
|
||||
Status: http.StatusBadRequest,
|
||||
Title: innerErr.Reason,
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func convertSchemaError(e *RequestError, innerErr *openapi3.SchemaError) *ValidationError {
|
||||
cErr := &ValidationError{Title: innerErr.Reason}
|
||||
|
||||
// Handle "Origin" error
|
||||
if originErr, ok := innerErr.Origin.(*openapi3.SchemaError); ok {
|
||||
cErr = convertSchemaError(e, originErr)
|
||||
}
|
||||
|
||||
// Add http status code
|
||||
if e.Parameter != nil {
|
||||
cErr.Status = http.StatusBadRequest
|
||||
} else if e.RequestBody != nil {
|
||||
cErr.Status = http.StatusUnprocessableEntity
|
||||
}
|
||||
|
||||
// Add error source
|
||||
if e.Parameter != nil {
|
||||
// We have a JSONPointer in the query param too so need to
|
||||
// make sure 'Parameter' check takes priority over 'Pointer'
|
||||
cErr.Source = &ValidationErrorSource{Parameter: e.Parameter.Name}
|
||||
} else if ptr := innerErr.JSONPointer(); ptr != nil {
|
||||
cErr.Source = &ValidationErrorSource{Pointer: toJSONPointer(ptr)}
|
||||
}
|
||||
|
||||
// Add details on allowed values for enums
|
||||
if innerErr.SchemaField == "enum" {
|
||||
enums := make([]string, 0, len(innerErr.Schema.Enum))
|
||||
for _, enum := range innerErr.Schema.Enum {
|
||||
enums = append(enums, fmt.Sprintf("%v", enum))
|
||||
}
|
||||
cErr.Detail = fmt.Sprintf("value %v at %s must be one of: %s",
|
||||
innerErr.Value,
|
||||
toJSONPointer(innerErr.JSONPointer()),
|
||||
strings.Join(enums, ", "))
|
||||
value := fmt.Sprintf("%v", innerErr.Value)
|
||||
if e.Parameter != nil &&
|
||||
(e.Parameter.Explode == nil || *e.Parameter.Explode) &&
|
||||
(e.Parameter.Style == "" || e.Parameter.Style == "form") &&
|
||||
strings.Contains(value, ",") {
|
||||
parts := strings.Split(value, ",")
|
||||
cErr.Detail = fmt.Sprintf("%s; perhaps you intended '?%s=%s'",
|
||||
cErr.Detail,
|
||||
e.Parameter.Name,
|
||||
strings.Join(parts, "&"+e.Parameter.Name+"="))
|
||||
}
|
||||
}
|
||||
return cErr
|
||||
}
|
||||
|
||||
func toJSONPointer(reversePath []string) string {
|
||||
return "/" + strings.Join(reversePath, "/")
|
||||
}
|
||||
103
vendor/github.com/getkin/kin-openapi/openapi3filter/validation_handler.go
generated
vendored
Normal file
103
vendor/github.com/getkin/kin-openapi/openapi3filter/validation_handler.go
generated
vendored
Normal file
|
|
@ -0,0 +1,103 @@
|
|||
package openapi3filter
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
|
||||
"github.com/getkin/kin-openapi/openapi3"
|
||||
"github.com/getkin/kin-openapi/routers"
|
||||
legacyrouter "github.com/getkin/kin-openapi/routers/legacy"
|
||||
)
|
||||
|
||||
type AuthenticationFunc func(context.Context, *AuthenticationInput) error
|
||||
|
||||
func NoopAuthenticationFunc(context.Context, *AuthenticationInput) error { return nil }
|
||||
|
||||
var _ AuthenticationFunc = NoopAuthenticationFunc
|
||||
|
||||
type ValidationHandler struct {
|
||||
Handler http.Handler
|
||||
AuthenticationFunc AuthenticationFunc
|
||||
File string
|
||||
ErrorEncoder ErrorEncoder
|
||||
router routers.Router
|
||||
}
|
||||
|
||||
func (h *ValidationHandler) Load() error {
|
||||
loader := openapi3.NewLoader()
|
||||
doc, err := loader.LoadFromFile(h.File)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := doc.Validate(loader.Context); err != nil {
|
||||
return err
|
||||
}
|
||||
if h.router, err = legacyrouter.NewRouter(doc); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// set defaults
|
||||
if h.Handler == nil {
|
||||
h.Handler = http.DefaultServeMux
|
||||
}
|
||||
if h.AuthenticationFunc == nil {
|
||||
h.AuthenticationFunc = NoopAuthenticationFunc
|
||||
}
|
||||
if h.ErrorEncoder == nil {
|
||||
h.ErrorEncoder = DefaultErrorEncoder
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (h *ValidationHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
if handled := h.before(w, r); handled {
|
||||
return
|
||||
}
|
||||
// TODO: validateResponse
|
||||
h.Handler.ServeHTTP(w, r)
|
||||
}
|
||||
|
||||
// Middleware implements gorilla/mux MiddlewareFunc
|
||||
func (h *ValidationHandler) Middleware(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if handled := h.before(w, r); handled {
|
||||
return
|
||||
}
|
||||
// TODO: validateResponse
|
||||
next.ServeHTTP(w, r)
|
||||
})
|
||||
}
|
||||
|
||||
func (h *ValidationHandler) before(w http.ResponseWriter, r *http.Request) (handled bool) {
|
||||
if err := h.validateRequest(r); err != nil {
|
||||
h.ErrorEncoder(r.Context(), err, w)
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (h *ValidationHandler) validateRequest(r *http.Request) error {
|
||||
// Find route
|
||||
route, pathParams, err := h.router.FindRoute(r)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
options := &Options{
|
||||
AuthenticationFunc: h.AuthenticationFunc,
|
||||
}
|
||||
|
||||
// Validate request
|
||||
requestValidationInput := &RequestValidationInput{
|
||||
Request: r,
|
||||
PathParams: pathParams,
|
||||
Route: route,
|
||||
Options: options,
|
||||
}
|
||||
if err = ValidateRequest(r.Context(), requestValidationInput); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
85
vendor/github.com/getkin/kin-openapi/openapi3filter/validation_kit.go
generated
vendored
Normal file
85
vendor/github.com/getkin/kin-openapi/openapi3filter/validation_kit.go
generated
vendored
Normal file
|
|
@ -0,0 +1,85 @@
|
|||
package openapi3filter
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
// We didn't want to tie kin-openapi too tightly with go-kit.
|
||||
// This file contains the ErrorEncoder and DefaultErrorEncoder function
|
||||
// borrowed from this project.
|
||||
//
|
||||
// The MIT License (MIT)
|
||||
//
|
||||
// Copyright (c) 2015 Peter Bourgon
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in all
|
||||
// copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
// SOFTWARE.
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
// ErrorEncoder is responsible for encoding an error to the ResponseWriter.
|
||||
// Users are encouraged to use custom ErrorEncoders to encode HTTP errors to
|
||||
// their clients, and will likely want to pass and check for their own error
|
||||
// types. See the example shipping/handling service.
|
||||
type ErrorEncoder func(ctx context.Context, err error, w http.ResponseWriter)
|
||||
|
||||
// StatusCoder is checked by DefaultErrorEncoder. If an error value implements
|
||||
// StatusCoder, the StatusCode will be used when encoding the error. By default,
|
||||
// StatusInternalServerError (500) is used.
|
||||
type StatusCoder interface {
|
||||
StatusCode() int
|
||||
}
|
||||
|
||||
// Headerer is checked by DefaultErrorEncoder. If an error value implements
|
||||
// Headerer, the provided headers will be applied to the response writer, after
|
||||
// the Content-Type is set.
|
||||
type Headerer interface {
|
||||
Headers() http.Header
|
||||
}
|
||||
|
||||
// DefaultErrorEncoder writes the error to the ResponseWriter, by default a
|
||||
// content type of text/plain, a body of the plain text of the error, and a
|
||||
// status code of 500. If the error implements Headerer, the provided headers
|
||||
// will be applied to the response. If the error implements json.Marshaler, and
|
||||
// the marshaling succeeds, a content type of application/json and the JSON
|
||||
// encoded form of the error will be used. If the error implements StatusCoder,
|
||||
// the provided StatusCode will be used instead of 500.
|
||||
func DefaultErrorEncoder(_ context.Context, err error, w http.ResponseWriter) {
|
||||
contentType, body := "text/plain; charset=utf-8", []byte(err.Error())
|
||||
if marshaler, ok := err.(json.Marshaler); ok {
|
||||
if jsonBody, marshalErr := marshaler.MarshalJSON(); marshalErr == nil {
|
||||
contentType, body = "application/json; charset=utf-8", jsonBody
|
||||
}
|
||||
}
|
||||
w.Header().Set("Content-Type", contentType)
|
||||
if headerer, ok := err.(Headerer); ok {
|
||||
for k, values := range headerer.Headers() {
|
||||
for _, v := range values {
|
||||
w.Header().Add(k, v)
|
||||
}
|
||||
}
|
||||
}
|
||||
code := http.StatusInternalServerError
|
||||
if sc, ok := err.(StatusCoder); ok {
|
||||
code = sc.StatusCode()
|
||||
}
|
||||
w.WriteHeader(code)
|
||||
w.Write(body)
|
||||
}
|
||||
328
vendor/github.com/getkin/kin-openapi/routers/legacy/pathpattern/node.go
generated
vendored
Normal file
328
vendor/github.com/getkin/kin-openapi/routers/legacy/pathpattern/node.go
generated
vendored
Normal file
|
|
@ -0,0 +1,328 @@
|
|||
// Package pathpattern implements path matching.
|
||||
//
|
||||
// Examples of supported patterns:
|
||||
// * "/"
|
||||
// * "/abc""
|
||||
// * "/abc/{variable}" (matches until next '/' or end-of-string)
|
||||
// * "/abc/{variable*}" (matches everything, including "/abc" if "/abc" has noot)
|
||||
// * "/abc/{ variable | prefix_(.*}_suffix }" (matches regular expressions)
|
||||
package pathpattern
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"regexp"
|
||||
"sort"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var DefaultOptions = &Options{
|
||||
SupportWildcard: true,
|
||||
}
|
||||
|
||||
type Options struct {
|
||||
SupportWildcard bool
|
||||
SupportRegExp bool
|
||||
}
|
||||
|
||||
// PathFromHost converts a host pattern to a path pattern.
|
||||
//
|
||||
// Examples:
|
||||
// * PathFromHost("some-subdomain.domain.com", false) -> "com/./domain/./some-subdomain"
|
||||
// * PathFromHost("some-subdomain.domain.com", true) -> "com/./domain/./subdomain/-/some"
|
||||
func PathFromHost(host string, specialDashes bool) string {
|
||||
buf := make([]byte, 0, len(host))
|
||||
end := len(host)
|
||||
|
||||
// Go from end to start
|
||||
for start := end - 1; start >= 0; start-- {
|
||||
switch host[start] {
|
||||
case '.':
|
||||
buf = append(buf, host[start+1:end]...)
|
||||
buf = append(buf, '/', '.', '/')
|
||||
end = start
|
||||
case '-':
|
||||
if specialDashes {
|
||||
buf = append(buf, host[start+1:end]...)
|
||||
buf = append(buf, '/', '-', '/')
|
||||
end = start
|
||||
}
|
||||
}
|
||||
}
|
||||
buf = append(buf, host[:end]...)
|
||||
return string(buf)
|
||||
}
|
||||
|
||||
type Node struct {
|
||||
VariableNames []string
|
||||
Value interface{}
|
||||
Suffixes SuffixList
|
||||
}
|
||||
|
||||
func (currentNode *Node) String() string {
|
||||
buf := bytes.NewBuffer(make([]byte, 0, 255))
|
||||
currentNode.toBuffer(buf, "")
|
||||
return buf.String()
|
||||
}
|
||||
|
||||
func (currentNode *Node) toBuffer(buf *bytes.Buffer, linePrefix string) {
|
||||
if value := currentNode.Value; value != nil {
|
||||
buf.WriteString(linePrefix)
|
||||
buf.WriteString("VALUE: ")
|
||||
fmt.Fprint(buf, value)
|
||||
buf.WriteString("\n")
|
||||
}
|
||||
suffixes := currentNode.Suffixes
|
||||
if len(suffixes) > 0 {
|
||||
newLinePrefix := linePrefix + " "
|
||||
for _, suffix := range suffixes {
|
||||
buf.WriteString(linePrefix)
|
||||
buf.WriteString("PATTERN: ")
|
||||
buf.WriteString(suffix.String())
|
||||
buf.WriteString("\n")
|
||||
suffix.Node.toBuffer(buf, newLinePrefix)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type SuffixKind int
|
||||
|
||||
// Note that order is important!
|
||||
const (
|
||||
// SuffixKindConstant matches a constant string
|
||||
SuffixKindConstant = SuffixKind(iota)
|
||||
|
||||
// SuffixKindRegExp matches a regular expression
|
||||
SuffixKindRegExp
|
||||
|
||||
// SuffixKindVariable matches everything until '/'
|
||||
SuffixKindVariable
|
||||
|
||||
// SuffixKindEverything matches everything (until end-of-string)
|
||||
SuffixKindEverything
|
||||
)
|
||||
|
||||
// Suffix describes condition that
|
||||
type Suffix struct {
|
||||
Kind SuffixKind
|
||||
Pattern string
|
||||
|
||||
// compiled regular expression
|
||||
regExp *regexp.Regexp
|
||||
|
||||
// Next node
|
||||
Node *Node
|
||||
}
|
||||
|
||||
func EqualSuffix(a, b Suffix) bool {
|
||||
return a.Kind == b.Kind && a.Pattern == b.Pattern
|
||||
}
|
||||
|
||||
func (suffix Suffix) String() string {
|
||||
switch suffix.Kind {
|
||||
case SuffixKindConstant:
|
||||
return suffix.Pattern
|
||||
case SuffixKindVariable:
|
||||
return "{_}"
|
||||
case SuffixKindEverything:
|
||||
return "{_*}"
|
||||
default:
|
||||
return "{_|" + suffix.Pattern + "}"
|
||||
}
|
||||
}
|
||||
|
||||
type SuffixList []Suffix
|
||||
|
||||
func (list SuffixList) Less(i, j int) bool {
|
||||
a, b := list[i], list[j]
|
||||
ak, bk := a.Kind, b.Kind
|
||||
if ak < bk {
|
||||
return true
|
||||
} else if bk < ak {
|
||||
return false
|
||||
}
|
||||
return a.Pattern > b.Pattern
|
||||
}
|
||||
|
||||
func (list SuffixList) Len() int {
|
||||
return len(list)
|
||||
}
|
||||
|
||||
func (list SuffixList) Swap(i, j int) {
|
||||
a, b := list[i], list[j]
|
||||
list[i], list[j] = b, a
|
||||
}
|
||||
|
||||
func (currentNode *Node) MustAdd(path string, value interface{}, options *Options) {
|
||||
node, err := currentNode.CreateNode(path, options)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
node.Value = value
|
||||
}
|
||||
|
||||
func (currentNode *Node) Add(path string, value interface{}, options *Options) error {
|
||||
node, err := currentNode.CreateNode(path, options)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
node.Value = value
|
||||
return nil
|
||||
}
|
||||
|
||||
func (currentNode *Node) CreateNode(path string, options *Options) (*Node, error) {
|
||||
if options == nil {
|
||||
options = DefaultOptions
|
||||
}
|
||||
for strings.HasSuffix(path, "/") {
|
||||
path = path[:len(path)-1]
|
||||
}
|
||||
remaining := path
|
||||
var variableNames []string
|
||||
loop:
|
||||
for {
|
||||
//remaining = strings.TrimPrefix(remaining, "/")
|
||||
if len(remaining) == 0 {
|
||||
// This node is the right one
|
||||
// Check whether another route already leads to this node
|
||||
currentNode.VariableNames = variableNames
|
||||
return currentNode, nil
|
||||
}
|
||||
|
||||
suffix := Suffix{}
|
||||
var i int
|
||||
if strings.HasPrefix(remaining, "/") {
|
||||
remaining = remaining[1:]
|
||||
suffix.Kind = SuffixKindConstant
|
||||
suffix.Pattern = "/"
|
||||
} else {
|
||||
i = strings.IndexAny(remaining, "/{")
|
||||
if i < 0 {
|
||||
i = len(remaining)
|
||||
}
|
||||
if i > 0 {
|
||||
// Constant string pattern
|
||||
suffix.Kind = SuffixKindConstant
|
||||
suffix.Pattern = remaining[:i]
|
||||
remaining = remaining[i:]
|
||||
} else if remaining[0] == '{' {
|
||||
// This is probably a variable
|
||||
suffix.Kind = SuffixKindVariable
|
||||
|
||||
// Find variable name
|
||||
i := strings.IndexByte(remaining, '}')
|
||||
if i < 0 {
|
||||
return nil, fmt.Errorf("missing '}' in: %s", path)
|
||||
}
|
||||
variableName := strings.TrimSpace(remaining[1:i])
|
||||
remaining = remaining[i+1:]
|
||||
|
||||
if options.SupportRegExp {
|
||||
// See if it has regular expression
|
||||
i = strings.IndexByte(variableName, '|')
|
||||
if i >= 0 {
|
||||
suffix.Kind = SuffixKindRegExp
|
||||
suffix.Pattern = strings.TrimSpace(variableName[i+1:])
|
||||
variableName = strings.TrimSpace(variableName[:i])
|
||||
}
|
||||
}
|
||||
if suffix.Kind == SuffixKindVariable && options.SupportWildcard {
|
||||
if strings.HasSuffix(variableName, "*") {
|
||||
suffix.Kind = SuffixKindEverything
|
||||
}
|
||||
}
|
||||
variableNames = append(variableNames, variableName)
|
||||
}
|
||||
}
|
||||
|
||||
// Find existing matcher
|
||||
for _, existing := range currentNode.Suffixes {
|
||||
if EqualSuffix(existing, suffix) {
|
||||
currentNode = existing.Node
|
||||
continue loop
|
||||
}
|
||||
}
|
||||
|
||||
// Compile regular expression
|
||||
if suffix.Kind == SuffixKindRegExp {
|
||||
regExp, err := regexp.Compile(suffix.Pattern)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("invalid regular expression in: %s", path)
|
||||
}
|
||||
suffix.regExp = regExp
|
||||
}
|
||||
|
||||
// Create new node
|
||||
newNode := &Node{}
|
||||
suffix.Node = newNode
|
||||
currentNode.Suffixes = append(currentNode.Suffixes, suffix)
|
||||
sort.Sort(currentNode.Suffixes)
|
||||
currentNode = newNode
|
||||
continue loop
|
||||
}
|
||||
}
|
||||
|
||||
func (currentNode *Node) Match(path string) (*Node, []string) {
|
||||
for strings.HasSuffix(path, "/") {
|
||||
path = path[:len(path)-1]
|
||||
}
|
||||
variableValues := make([]string, 0, 8)
|
||||
return currentNode.matchRemaining(path, variableValues)
|
||||
}
|
||||
|
||||
func (currentNode *Node) matchRemaining(remaining string, paramValues []string) (*Node, []string) {
|
||||
// Check if this node matches
|
||||
if len(remaining) == 0 && currentNode.Value != nil {
|
||||
return currentNode, paramValues
|
||||
}
|
||||
|
||||
// See if any suffix matches
|
||||
for _, suffix := range currentNode.Suffixes {
|
||||
var resultNode *Node
|
||||
var resultValues []string
|
||||
switch suffix.Kind {
|
||||
case SuffixKindConstant:
|
||||
pattern := suffix.Pattern
|
||||
if strings.HasPrefix(remaining, pattern) {
|
||||
newRemaining := remaining[len(pattern):]
|
||||
resultNode, resultValues = suffix.Node.matchRemaining(newRemaining, paramValues)
|
||||
} else if len(remaining) == 0 && pattern == "/" {
|
||||
resultNode, resultValues = suffix.Node.matchRemaining(remaining, paramValues)
|
||||
}
|
||||
case SuffixKindVariable:
|
||||
i := strings.IndexByte(remaining, '/')
|
||||
if i < 0 {
|
||||
i = len(remaining)
|
||||
}
|
||||
newParamValues := append(paramValues, remaining[:i])
|
||||
newRemaining := remaining[i:]
|
||||
resultNode, resultValues = suffix.Node.matchRemaining(newRemaining, newParamValues)
|
||||
case SuffixKindEverything:
|
||||
newParamValues := append(paramValues, remaining)
|
||||
resultNode, resultValues = suffix.Node, newParamValues
|
||||
case SuffixKindRegExp:
|
||||
i := strings.IndexByte(remaining, '/')
|
||||
if i < 0 {
|
||||
i = len(remaining)
|
||||
}
|
||||
paramValue := remaining[:i]
|
||||
regExp := suffix.regExp
|
||||
if regExp.MatchString(paramValue) {
|
||||
matches := regExp.FindStringSubmatch(paramValue)
|
||||
if len(matches) > 1 {
|
||||
paramValue = matches[1]
|
||||
}
|
||||
newParamValues := append(paramValues, paramValue)
|
||||
newRemaining := remaining[i:]
|
||||
resultNode, resultValues = suffix.Node.matchRemaining(newRemaining, newParamValues)
|
||||
}
|
||||
}
|
||||
if resultNode != nil && resultNode.Value != nil {
|
||||
// This suffix matched
|
||||
return resultNode, resultValues
|
||||
}
|
||||
}
|
||||
|
||||
// No suffix matched
|
||||
return nil, nil
|
||||
}
|
||||
167
vendor/github.com/getkin/kin-openapi/routers/legacy/router.go
generated
vendored
Normal file
167
vendor/github.com/getkin/kin-openapi/routers/legacy/router.go
generated
vendored
Normal file
|
|
@ -0,0 +1,167 @@
|
|||
// Package legacy implements a router.
|
||||
//
|
||||
// It differs from the gorilla/mux router:
|
||||
// * it provides granular errors: "path not found", "method not allowed", "variable missing from path"
|
||||
// * it does not handle matching routes with extensions (e.g. /books/{id}.json)
|
||||
// * it handles path patterns with a different syntax (e.g. /params/{x}/{y}/{z.*})
|
||||
package legacy
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/getkin/kin-openapi/openapi3"
|
||||
"github.com/getkin/kin-openapi/routers"
|
||||
"github.com/getkin/kin-openapi/routers/legacy/pathpattern"
|
||||
)
|
||||
|
||||
// Routers maps a HTTP request to a Router.
|
||||
type Routers []*Router
|
||||
|
||||
// FindRoute extracts the route and parameters of an http.Request
|
||||
func (rs Routers) FindRoute(req *http.Request) (routers.Router, *routers.Route, map[string]string, error) {
|
||||
for _, router := range rs {
|
||||
// Skip routers that have DO NOT have servers
|
||||
if len(router.doc.Servers) == 0 {
|
||||
continue
|
||||
}
|
||||
route, pathParams, err := router.FindRoute(req)
|
||||
if err == nil {
|
||||
return router, route, pathParams, nil
|
||||
}
|
||||
}
|
||||
for _, router := range rs {
|
||||
// Skip routers that DO have servers
|
||||
if len(router.doc.Servers) > 0 {
|
||||
continue
|
||||
}
|
||||
route, pathParams, err := router.FindRoute(req)
|
||||
if err == nil {
|
||||
return router, route, pathParams, nil
|
||||
}
|
||||
}
|
||||
return nil, nil, nil, &routers.RouteError{
|
||||
Reason: "none of the routers match",
|
||||
}
|
||||
}
|
||||
|
||||
// Router maps a HTTP request to an OpenAPI operation.
|
||||
type Router struct {
|
||||
doc *openapi3.T
|
||||
pathNode *pathpattern.Node
|
||||
}
|
||||
|
||||
// NewRouter creates a new router.
|
||||
//
|
||||
// If the given OpenAPIv3 document has servers, router will use them.
|
||||
// All operations of the document will be added to the router.
|
||||
func NewRouter(doc *openapi3.T) (routers.Router, error) {
|
||||
if err := doc.Validate(context.Background()); err != nil {
|
||||
return nil, fmt.Errorf("validating OpenAPI failed: %v", err)
|
||||
}
|
||||
router := &Router{doc: doc}
|
||||
root := router.node()
|
||||
for path, pathItem := range doc.Paths {
|
||||
for method, operation := range pathItem.Operations() {
|
||||
method = strings.ToUpper(method)
|
||||
if err := root.Add(method+" "+path, &routers.Route{
|
||||
Spec: doc,
|
||||
Path: path,
|
||||
PathItem: pathItem,
|
||||
Method: method,
|
||||
Operation: operation,
|
||||
}, nil); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
}
|
||||
return router, nil
|
||||
}
|
||||
|
||||
// AddRoute adds a route in the router.
|
||||
func (router *Router) AddRoute(route *routers.Route) error {
|
||||
method := route.Method
|
||||
if method == "" {
|
||||
return errors.New("route is missing method")
|
||||
}
|
||||
method = strings.ToUpper(method)
|
||||
path := route.Path
|
||||
if path == "" {
|
||||
return errors.New("route is missing path")
|
||||
}
|
||||
return router.node().Add(method+" "+path, router, nil)
|
||||
}
|
||||
|
||||
func (router *Router) node() *pathpattern.Node {
|
||||
root := router.pathNode
|
||||
if root == nil {
|
||||
root = &pathpattern.Node{}
|
||||
router.pathNode = root
|
||||
}
|
||||
return root
|
||||
}
|
||||
|
||||
// FindRoute extracts the route and parameters of an http.Request
|
||||
func (router *Router) FindRoute(req *http.Request) (*routers.Route, map[string]string, error) {
|
||||
method, url := req.Method, req.URL
|
||||
doc := router.doc
|
||||
|
||||
// Get server
|
||||
servers := doc.Servers
|
||||
var server *openapi3.Server
|
||||
var remainingPath string
|
||||
var pathParams map[string]string
|
||||
if len(servers) == 0 {
|
||||
remainingPath = url.Path
|
||||
} else {
|
||||
var paramValues []string
|
||||
server, paramValues, remainingPath = servers.MatchURL(url)
|
||||
if server == nil {
|
||||
return nil, nil, &routers.RouteError{
|
||||
Reason: routers.ErrPathNotFound.Error(),
|
||||
}
|
||||
}
|
||||
pathParams = make(map[string]string, 8)
|
||||
paramNames, err := server.ParameterNames()
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
for i, value := range paramValues {
|
||||
name := paramNames[i]
|
||||
pathParams[name] = value
|
||||
}
|
||||
}
|
||||
|
||||
// Get PathItem
|
||||
root := router.node()
|
||||
var route *routers.Route
|
||||
node, paramValues := root.Match(method + " " + remainingPath)
|
||||
if node != nil {
|
||||
route, _ = node.Value.(*routers.Route)
|
||||
}
|
||||
if route == nil {
|
||||
pathItem := doc.Paths[remainingPath]
|
||||
if pathItem == nil {
|
||||
return nil, nil, &routers.RouteError{Reason: routers.ErrPathNotFound.Error()}
|
||||
}
|
||||
if pathItem.GetOperation(method) == nil {
|
||||
return nil, nil, &routers.RouteError{Reason: routers.ErrMethodNotAllowed.Error()}
|
||||
}
|
||||
}
|
||||
|
||||
if pathParams == nil {
|
||||
pathParams = make(map[string]string, len(paramValues))
|
||||
}
|
||||
paramKeys := node.VariableNames
|
||||
for i, value := range paramValues {
|
||||
key := paramKeys[i]
|
||||
if strings.HasSuffix(key, "*") {
|
||||
key = key[:len(key)-1]
|
||||
}
|
||||
pathParams[key] = value
|
||||
}
|
||||
return route, pathParams, nil
|
||||
}
|
||||
42
vendor/github.com/getkin/kin-openapi/routers/types.go
generated
vendored
Normal file
42
vendor/github.com/getkin/kin-openapi/routers/types.go
generated
vendored
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
package routers
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/getkin/kin-openapi/openapi3"
|
||||
)
|
||||
|
||||
// Router helps link http.Request.s and an OpenAPIv3 spec
|
||||
type Router interface {
|
||||
// FindRoute matches an HTTP request with the operation it resolves to.
|
||||
// Hosts are matched from the OpenAPIv3 servers key.
|
||||
//
|
||||
// If you experience ErrPathNotFound and have localhost hosts specified as your servers,
|
||||
// turning these server URLs as relative (leaving only the path) should resolve this.
|
||||
//
|
||||
// See openapi3filter for example uses with request and response validation.
|
||||
FindRoute(req *http.Request) (route *Route, pathParams map[string]string, err error)
|
||||
}
|
||||
|
||||
// Route describes the operation an http.Request can match
|
||||
type Route struct {
|
||||
Spec *openapi3.T
|
||||
Server *openapi3.Server
|
||||
Path string
|
||||
PathItem *openapi3.PathItem
|
||||
Method string
|
||||
Operation *openapi3.Operation
|
||||
}
|
||||
|
||||
// ErrPathNotFound is returned when no route match is found
|
||||
var ErrPathNotFound error = &RouteError{"no matching operation was found"}
|
||||
|
||||
// ErrMethodNotAllowed is returned when no method of the matched route matches
|
||||
var ErrMethodNotAllowed error = &RouteError{"method not allowed"}
|
||||
|
||||
// RouteError describes Router errors
|
||||
type RouteError struct {
|
||||
Reason string
|
||||
}
|
||||
|
||||
func (e *RouteError) Error() string { return e.Reason }
|
||||
Loading…
Add table
Add a link
Reference in a new issue