mirror of
				https://github.com/k3s-io/kubernetes.git
				synced 2025-10-25 01:20:18 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			793 lines
		
	
	
		
			22 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			793 lines
		
	
	
		
			22 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
| // Copyright 2015 go-swagger maintainers
 | |
| //
 | |
| // Licensed under the Apache License, Version 2.0 (the "License");
 | |
| // you may not use this file except in compliance with the License.
 | |
| // You may obtain a copy of the License at
 | |
| //
 | |
| //    http://www.apache.org/licenses/LICENSE-2.0
 | |
| //
 | |
| // Unless required by applicable law or agreed to in writing, software
 | |
| // distributed under the License is distributed on an "AS IS" BASIS,
 | |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | |
| // See the License for the specific language governing permissions and
 | |
| // limitations under the License.
 | |
| 
 | |
| package validate
 | |
| 
 | |
| import (
 | |
| 	"encoding/json"
 | |
| 	"fmt"
 | |
| 	"log"
 | |
| 	"regexp"
 | |
| 	"strings"
 | |
| 
 | |
| 	"github.com/go-openapi/analysis"
 | |
| 	"github.com/go-openapi/errors"
 | |
| 	"github.com/go-openapi/jsonpointer"
 | |
| 	"github.com/go-openapi/loads"
 | |
| 	"github.com/go-openapi/spec"
 | |
| 	"github.com/go-openapi/strfmt"
 | |
| )
 | |
| 
 | |
| // Spec validates a spec document
 | |
| // It validates the spec json against the json schema for swagger
 | |
| // and then validates a number of extra rules that can't be expressed in json schema:
 | |
| //
 | |
| // 	- definition can't declare a property that's already defined by one of its ancestors
 | |
| // 	- definition's ancestor can't be a descendant of the same model
 | |
| // 	- each api path should be non-verbatim (account for path param names) unique per method
 | |
| // 	- each security reference should contain only unique scopes
 | |
| // 	- each security scope in a security definition should be unique
 | |
| // 	- each path parameter should correspond to a parameter placeholder and vice versa
 | |
| // 	- each referencable definition must have references
 | |
| // 	- each definition property listed in the required array must be defined in the properties of the model
 | |
| // 	- each parameter should have a unique `name` and `type` combination
 | |
| // 	- each operation should have only 1 parameter of type body
 | |
| // 	- each reference must point to a valid object
 | |
| // 	- every default value that is specified must validate against the schema for that property
 | |
| // 	- items property is required for all schemas/definitions of type `array`
 | |
| func Spec(doc *loads.Document, formats strfmt.Registry) error {
 | |
| 	errs, _ /*warns*/ := NewSpecValidator(doc.Schema(), formats).Validate(doc)
 | |
| 	if errs.HasErrors() {
 | |
| 		return errors.CompositeValidationError(errs.Errors...)
 | |
| 	}
 | |
| 	return nil
 | |
| }
 | |
| 
 | |
| // AgainstSchema validates the specified data with the provided schema, when no schema
 | |
| // is provided it uses the json schema as default
 | |
| func AgainstSchema(schema *spec.Schema, data interface{}, formats strfmt.Registry) error {
 | |
| 	res := NewSchemaValidator(schema, nil, "", formats).Validate(data)
 | |
| 	if res.HasErrors() {
 | |
| 		return errors.CompositeValidationError(res.Errors...)
 | |
| 	}
 | |
| 	return nil
 | |
| }
 | |
| 
 | |
| // SpecValidator validates a swagger spec
 | |
| type SpecValidator struct {
 | |
| 	schema       *spec.Schema // swagger 2.0 schema
 | |
| 	spec         *loads.Document
 | |
| 	analyzer     *analysis.Spec
 | |
| 	expanded     *loads.Document
 | |
| 	KnownFormats strfmt.Registry
 | |
| }
 | |
| 
 | |
| // NewSpecValidator creates a new swagger spec validator instance
 | |
| func NewSpecValidator(schema *spec.Schema, formats strfmt.Registry) *SpecValidator {
 | |
| 	return &SpecValidator{
 | |
| 		schema:       schema,
 | |
| 		KnownFormats: formats,
 | |
| 	}
 | |
| }
 | |
| 
 | |
| // Validate validates the swagger spec
 | |
| func (s *SpecValidator) Validate(data interface{}) (errs *Result, warnings *Result) {
 | |
| 	var sd *loads.Document
 | |
| 
 | |
| 	switch v := data.(type) {
 | |
| 	case *loads.Document:
 | |
| 		sd = v
 | |
| 	}
 | |
| 	if sd == nil {
 | |
| 		errs = sErr(errors.New(500, "spec validator can only validate spec.Document objects"))
 | |
| 		return
 | |
| 	}
 | |
| 	s.spec = sd
 | |
| 	s.analyzer = analysis.New(sd.Spec())
 | |
| 
 | |
| 	errs = new(Result)
 | |
| 	warnings = new(Result)
 | |
| 
 | |
| 	schv := NewSchemaValidator(s.schema, nil, "", s.KnownFormats)
 | |
| 	var obj interface{}
 | |
| 	if err := json.Unmarshal(sd.Raw(), &obj); err != nil {
 | |
| 		errs.AddErrors(err)
 | |
| 		return
 | |
| 	}
 | |
| 	errs.Merge(schv.Validate(obj)) // error -
 | |
| 	if errs.HasErrors() {
 | |
| 		return // no point in continuing
 | |
| 	}
 | |
| 
 | |
| 	errs.Merge(s.validateReferencesValid()) // error -
 | |
| 	if errs.HasErrors() {
 | |
| 		return // no point in continuing
 | |
| 	}
 | |
| 
 | |
| 	errs.Merge(s.validateDuplicateOperationIDs())
 | |
| 	errs.Merge(s.validateDuplicatePropertyNames())         // error -
 | |
| 	errs.Merge(s.validateParameters())                     // error -
 | |
| 	errs.Merge(s.validateItems())                          // error -
 | |
| 	errs.Merge(s.validateRequiredDefinitions())            // error -
 | |
| 	errs.Merge(s.validateDefaultValueValidAgainstSchema()) // error -
 | |
| 	errs.Merge(s.validateExamplesValidAgainstSchema())     // error -
 | |
| 	errs.Merge(s.validateNonEmptyPathParamNames())
 | |
| 
 | |
| 	warnings.Merge(s.validateUniqueSecurityScopes()) // warning
 | |
| 	warnings.Merge(s.validateReferenced())           // warning
 | |
| 
 | |
| 	return
 | |
| }
 | |
| 
 | |
| func (s *SpecValidator) validateNonEmptyPathParamNames() *Result {
 | |
| 	res := new(Result)
 | |
| 	for k := range s.spec.Spec().Paths.Paths {
 | |
| 		if strings.Contains(k, "{}") {
 | |
| 			res.AddErrors(errors.New(422, "%q contains an empty path parameter", k))
 | |
| 		}
 | |
| 	}
 | |
| 	return res
 | |
| }
 | |
| 
 | |
| func (s *SpecValidator) validateDuplicateOperationIDs() *Result {
 | |
| 	res := new(Result)
 | |
| 	known := make(map[string]int)
 | |
| 	for _, v := range s.analyzer.OperationIDs() {
 | |
| 		if v != "" {
 | |
| 			known[v]++
 | |
| 		}
 | |
| 	}
 | |
| 	for k, v := range known {
 | |
| 		if v > 1 {
 | |
| 			res.AddErrors(errors.New(422, "%q is defined %d times", k, v))
 | |
| 		}
 | |
| 	}
 | |
| 	return res
 | |
| }
 | |
| 
 | |
| type dupProp struct {
 | |
| 	Name       string
 | |
| 	Definition string
 | |
| }
 | |
| 
 | |
| func (s *SpecValidator) validateDuplicatePropertyNames() *Result {
 | |
| 	// definition can't declare a property that's already defined by one of its ancestors
 | |
| 	res := new(Result)
 | |
| 	for k, sch := range s.spec.Spec().Definitions {
 | |
| 		if len(sch.AllOf) == 0 {
 | |
| 			continue
 | |
| 		}
 | |
| 
 | |
| 		knownanc := map[string]struct{}{
 | |
| 			"#/definitions/" + k: struct{}{},
 | |
| 		}
 | |
| 
 | |
| 		ancs := s.validateCircularAncestry(k, sch, knownanc)
 | |
| 		if len(ancs) > 0 {
 | |
| 			res.AddErrors(errors.New(422, "definition %q has circular ancestry: %v", k, ancs))
 | |
| 			return res
 | |
| 		}
 | |
| 
 | |
| 		knowns := make(map[string]struct{})
 | |
| 		dups := s.validateSchemaPropertyNames(k, sch, knowns)
 | |
| 		if len(dups) > 0 {
 | |
| 			var pns []string
 | |
| 			for _, v := range dups {
 | |
| 				pns = append(pns, v.Definition+"."+v.Name)
 | |
| 			}
 | |
| 			res.AddErrors(errors.New(422, "definition %q contains duplicate properties: %v", k, pns))
 | |
| 		}
 | |
| 
 | |
| 	}
 | |
| 	return res
 | |
| }
 | |
| 
 | |
| func (s *SpecValidator) validateSchemaPropertyNames(nm string, sch spec.Schema, knowns map[string]struct{}) []dupProp {
 | |
| 	var dups []dupProp
 | |
| 
 | |
| 	schn := nm
 | |
| 	schc := &sch
 | |
| 	for schc.Ref.String() != "" {
 | |
| 		// gather property names
 | |
| 		reso, err := spec.ResolveRef(s.spec.Spec(), &schc.Ref)
 | |
| 		if err != nil {
 | |
| 			panic(err)
 | |
| 		}
 | |
| 		schc = reso
 | |
| 		schn = sch.Ref.String()
 | |
| 	}
 | |
| 
 | |
| 	if len(schc.AllOf) > 0 {
 | |
| 		for _, chld := range schc.AllOf {
 | |
| 			dups = append(dups, s.validateSchemaPropertyNames(schn, chld, knowns)...)
 | |
| 		}
 | |
| 		return dups
 | |
| 	}
 | |
| 
 | |
| 	for k := range schc.Properties {
 | |
| 		_, ok := knowns[k]
 | |
| 		if ok {
 | |
| 			dups = append(dups, dupProp{Name: k, Definition: schn})
 | |
| 		} else {
 | |
| 			knowns[k] = struct{}{}
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	return dups
 | |
| }
 | |
| 
 | |
| func (s *SpecValidator) validateCircularAncestry(nm string, sch spec.Schema, knowns map[string]struct{}) []string {
 | |
| 	if sch.Ref.String() == "" && len(sch.AllOf) == 0 {
 | |
| 		return nil
 | |
| 	}
 | |
| 	var ancs []string
 | |
| 
 | |
| 	schn := nm
 | |
| 	schc := &sch
 | |
| 	for schc.Ref.String() != "" {
 | |
| 		reso, err := spec.ResolveRef(s.spec.Spec(), &schc.Ref)
 | |
| 		if err != nil {
 | |
| 			panic(err)
 | |
| 		}
 | |
| 		schc = reso
 | |
| 		schn = sch.Ref.String()
 | |
| 	}
 | |
| 
 | |
| 	if schn != nm && schn != "" {
 | |
| 		if _, ok := knowns[schn]; ok {
 | |
| 			ancs = append(ancs, schn)
 | |
| 		}
 | |
| 		knowns[schn] = struct{}{}
 | |
| 
 | |
| 		if len(ancs) > 0 {
 | |
| 			return ancs
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	if len(schc.AllOf) > 0 {
 | |
| 		for _, chld := range schc.AllOf {
 | |
| 			if chld.Ref.String() != "" || len(chld.AllOf) > 0 {
 | |
| 				ancs = append(ancs, s.validateCircularAncestry(schn, chld, knowns)...)
 | |
| 				if len(ancs) > 0 {
 | |
| 					return ancs
 | |
| 				}
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	return ancs
 | |
| }
 | |
| 
 | |
| func (s *SpecValidator) validateItems() *Result {
 | |
| 	// validate parameter, items, schema and response objects for presence of item if type is array
 | |
| 	res := new(Result)
 | |
| 
 | |
| 	// TODO: implement support for lookups of refs
 | |
| 	for method, pi := range s.analyzer.Operations() {
 | |
| 		for path, op := range pi {
 | |
| 			for _, param := range s.analyzer.ParamsFor(method, path) {
 | |
| 				if param.TypeName() == "array" && param.ItemsTypeName() == "" {
 | |
| 					res.AddErrors(errors.New(422, "param %q for %q is a collection without an element type", param.Name, op.ID))
 | |
| 					continue
 | |
| 				}
 | |
| 				if param.In != "body" {
 | |
| 					if param.Items != nil {
 | |
| 						items := param.Items
 | |
| 						for items.TypeName() == "array" {
 | |
| 							if items.ItemsTypeName() == "" {
 | |
| 								res.AddErrors(errors.New(422, "param %q for %q is a collection without an element type", param.Name, op.ID))
 | |
| 								break
 | |
| 							}
 | |
| 							items = items.Items
 | |
| 						}
 | |
| 					}
 | |
| 				} else {
 | |
| 					if err := s.validateSchemaItems(*param.Schema, fmt.Sprintf("body param %q", param.Name), op.ID); err != nil {
 | |
| 						res.AddErrors(err)
 | |
| 					}
 | |
| 				}
 | |
| 			}
 | |
| 
 | |
| 			var responses []spec.Response
 | |
| 			if op.Responses != nil {
 | |
| 				if op.Responses.Default != nil {
 | |
| 					responses = append(responses, *op.Responses.Default)
 | |
| 				}
 | |
| 				for _, v := range op.Responses.StatusCodeResponses {
 | |
| 					responses = append(responses, v)
 | |
| 				}
 | |
| 			}
 | |
| 
 | |
| 			for _, resp := range responses {
 | |
| 				for hn, hv := range resp.Headers {
 | |
| 					if hv.TypeName() == "array" && hv.ItemsTypeName() == "" {
 | |
| 						res.AddErrors(errors.New(422, "header %q for %q is a collection without an element type", hn, op.ID))
 | |
| 					}
 | |
| 				}
 | |
| 				if resp.Schema != nil {
 | |
| 					if err := s.validateSchemaItems(*resp.Schema, "response body", op.ID); err != nil {
 | |
| 						res.AddErrors(err)
 | |
| 					}
 | |
| 				}
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| 	return res
 | |
| }
 | |
| 
 | |
| func (s *SpecValidator) validateSchemaItems(schema spec.Schema, prefix, opID string) error {
 | |
| 	if !schema.Type.Contains("array") {
 | |
| 		return nil
 | |
| 	}
 | |
| 
 | |
| 	if schema.Items == nil || schema.Items.Len() == 0 {
 | |
| 		return errors.New(422, "%s for %q is a collection without an element type", prefix, opID)
 | |
| 	}
 | |
| 
 | |
| 	schemas := schema.Items.Schemas
 | |
| 	if schema.Items.Schema != nil {
 | |
| 		schemas = []spec.Schema{*schema.Items.Schema}
 | |
| 	}
 | |
| 	for _, sch := range schemas {
 | |
| 		if err := s.validateSchemaItems(sch, prefix, opID); err != nil {
 | |
| 			return err
 | |
| 		}
 | |
| 	}
 | |
| 	return nil
 | |
| }
 | |
| 
 | |
| func (s *SpecValidator) validateUniqueSecurityScopes() *Result {
 | |
| 	// Each authorization/security reference should contain only unique scopes.
 | |
| 	// (Example: For an oauth2 authorization/security requirement, when listing the required scopes,
 | |
| 	// each scope should only be listed once.)
 | |
| 
 | |
| 	return nil
 | |
| }
 | |
| 
 | |
| func (s *SpecValidator) validatePathParamPresence(path string, fromPath, fromOperation []string) *Result {
 | |
| 	// Each defined operation path parameters must correspond to a named element in the API's path pattern.
 | |
| 	// (For example, you cannot have a path parameter named id for the following path /pets/{petId} but you must have a path parameter named petId.)
 | |
| 	res := new(Result)
 | |
| 	for _, l := range fromPath {
 | |
| 		var matched bool
 | |
| 		for _, r := range fromOperation {
 | |
| 			if l == "{"+r+"}" {
 | |
| 				matched = true
 | |
| 				break
 | |
| 			}
 | |
| 		}
 | |
| 		if !matched {
 | |
| 			res.Errors = append(res.Errors, errors.New(422, "path param %q has no parameter definition", l))
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	for _, p := range fromOperation {
 | |
| 		var matched bool
 | |
| 		for _, r := range fromPath {
 | |
| 			if "{"+p+"}" == r {
 | |
| 				matched = true
 | |
| 				break
 | |
| 			}
 | |
| 		}
 | |
| 		if !matched {
 | |
| 			res.AddErrors(errors.New(422, "path param %q is not present in path %q", p, path))
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	return res
 | |
| }
 | |
| 
 | |
| func (s *SpecValidator) validateReferenced() *Result {
 | |
| 	var res Result
 | |
| 	res.Merge(s.validateReferencedParameters())
 | |
| 	res.Merge(s.validateReferencedResponses())
 | |
| 	res.Merge(s.validateReferencedDefinitions())
 | |
| 	return &res
 | |
| }
 | |
| 
 | |
| func (s *SpecValidator) validateReferencedParameters() *Result {
 | |
| 	// Each referenceable definition must have references.
 | |
| 	params := s.spec.Spec().Parameters
 | |
| 	if len(params) == 0 {
 | |
| 		return nil
 | |
| 	}
 | |
| 
 | |
| 	expected := make(map[string]struct{})
 | |
| 	for k := range params {
 | |
| 		expected["#/parameters/"+jsonpointer.Escape(k)] = struct{}{}
 | |
| 	}
 | |
| 	for _, k := range s.analyzer.AllParameterReferences() {
 | |
| 		if _, ok := expected[k]; ok {
 | |
| 			delete(expected, k)
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	if len(expected) == 0 {
 | |
| 		return nil
 | |
| 	}
 | |
| 	var result Result
 | |
| 	for k := range expected {
 | |
| 		result.AddErrors(errors.New(422, "parameter %q is not used anywhere", k))
 | |
| 	}
 | |
| 	return &result
 | |
| }
 | |
| 
 | |
| func (s *SpecValidator) validateReferencedResponses() *Result {
 | |
| 	// Each referenceable definition must have references.
 | |
| 	responses := s.spec.Spec().Responses
 | |
| 	if len(responses) == 0 {
 | |
| 		return nil
 | |
| 	}
 | |
| 
 | |
| 	expected := make(map[string]struct{})
 | |
| 	for k := range responses {
 | |
| 		expected["#/responses/"+jsonpointer.Escape(k)] = struct{}{}
 | |
| 	}
 | |
| 	for _, k := range s.analyzer.AllResponseReferences() {
 | |
| 		if _, ok := expected[k]; ok {
 | |
| 			delete(expected, k)
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	if len(expected) == 0 {
 | |
| 		return nil
 | |
| 	}
 | |
| 	var result Result
 | |
| 	for k := range expected {
 | |
| 		result.AddErrors(errors.New(422, "response %q is not used anywhere", k))
 | |
| 	}
 | |
| 	return &result
 | |
| }
 | |
| 
 | |
| func (s *SpecValidator) validateReferencedDefinitions() *Result {
 | |
| 	// Each referenceable definition must have references.
 | |
| 	defs := s.spec.Spec().Definitions
 | |
| 	if len(defs) == 0 {
 | |
| 		return nil
 | |
| 	}
 | |
| 
 | |
| 	expected := make(map[string]struct{})
 | |
| 	for k := range defs {
 | |
| 		expected["#/definitions/"+jsonpointer.Escape(k)] = struct{}{}
 | |
| 	}
 | |
| 	for _, k := range s.analyzer.AllDefinitionReferences() {
 | |
| 		if _, ok := expected[k]; ok {
 | |
| 			delete(expected, k)
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	if len(expected) == 0 {
 | |
| 		return nil
 | |
| 	}
 | |
| 	var result Result
 | |
| 	for k := range expected {
 | |
| 		result.AddErrors(errors.New(422, "definition %q is not used anywhere", k))
 | |
| 	}
 | |
| 	return &result
 | |
| }
 | |
| 
 | |
| func (s *SpecValidator) validateRequiredDefinitions() *Result {
 | |
| 	// Each definition property listed in the required array must be defined in the properties of the model
 | |
| 	res := new(Result)
 | |
| 	for d, v := range s.spec.Spec().Definitions {
 | |
| 	REQUIRED:
 | |
| 		for _, pn := range v.Required {
 | |
| 			if _, ok := v.Properties[pn]; ok {
 | |
| 				continue
 | |
| 			}
 | |
| 
 | |
| 			for pp := range v.PatternProperties {
 | |
| 				re := regexp.MustCompile(pp)
 | |
| 				if re.MatchString(pn) {
 | |
| 					continue REQUIRED
 | |
| 				}
 | |
| 			}
 | |
| 
 | |
| 			if v.AdditionalProperties != nil {
 | |
| 				if v.AdditionalProperties.Allows {
 | |
| 					continue
 | |
| 				}
 | |
| 				if v.AdditionalProperties.Schema != nil {
 | |
| 					continue
 | |
| 				}
 | |
| 			}
 | |
| 
 | |
| 			res.AddErrors(errors.New(422, "%q is present in required but not defined as property in definition %q", pn, d))
 | |
| 		}
 | |
| 	}
 | |
| 	return res
 | |
| }
 | |
| 
 | |
| func (s *SpecValidator) validateParameters() *Result {
 | |
| 	// each parameter should have a unique `name` and `type` combination
 | |
| 	// each operation should have only 1 parameter of type body
 | |
| 	// each api path should be non-verbatim (account for path param names) unique per method
 | |
| 	res := new(Result)
 | |
| 	for method, pi := range s.analyzer.Operations() {
 | |
| 		knownPaths := make(map[string]string)
 | |
| 		for path, op := range pi {
 | |
| 			segments, params := parsePath(path)
 | |
| 			knowns := make([]string, 0, len(segments))
 | |
| 			for _, s := range segments {
 | |
| 				knowns = append(knowns, s)
 | |
| 			}
 | |
| 			var fromPath []string
 | |
| 			for _, i := range params {
 | |
| 				fromPath = append(fromPath, knowns[i])
 | |
| 				knowns[i] = "!"
 | |
| 			}
 | |
| 			knownPath := strings.Join(knowns, "/")
 | |
| 			if orig, ok := knownPaths[knownPath]; ok {
 | |
| 				res.AddErrors(errors.New(422, "path %s overlaps with %s", path, orig))
 | |
| 			} else {
 | |
| 				knownPaths[knownPath] = path
 | |
| 			}
 | |
| 
 | |
| 			ptypes := make(map[string]map[string]struct{})
 | |
| 			var firstBodyParam string
 | |
| 			sw := s.spec.Spec()
 | |
| 			var paramNames []string
 | |
| 		PARAMETERS:
 | |
| 			for _, ppr := range op.Parameters {
 | |
| 				pr := ppr
 | |
| 				for pr.Ref.String() != "" {
 | |
| 					obj, _, err := pr.Ref.GetPointer().Get(sw)
 | |
| 					if err != nil {
 | |
| 						log.Println(err)
 | |
| 						res.AddErrors(err)
 | |
| 						break PARAMETERS
 | |
| 					}
 | |
| 					pr = obj.(spec.Parameter)
 | |
| 				}
 | |
| 				pnames, ok := ptypes[pr.In]
 | |
| 				if !ok {
 | |
| 					pnames = make(map[string]struct{})
 | |
| 					ptypes[pr.In] = pnames
 | |
| 				}
 | |
| 
 | |
| 				_, ok = pnames[pr.Name]
 | |
| 				if ok {
 | |
| 					res.AddErrors(errors.New(422, "duplicate parameter name %q for %q in operation %q", pr.Name, pr.In, op.ID))
 | |
| 				}
 | |
| 				pnames[pr.Name] = struct{}{}
 | |
| 			}
 | |
| 
 | |
| 		PARAMETERS2:
 | |
| 			for _, ppr := range s.analyzer.ParamsFor(method, path) {
 | |
| 				pr := ppr
 | |
| 				for pr.Ref.String() != "" {
 | |
| 					obj, _, err := pr.Ref.GetPointer().Get(sw)
 | |
| 					if err != nil {
 | |
| 						res.AddErrors(err)
 | |
| 						break PARAMETERS2
 | |
| 					}
 | |
| 					pr = obj.(spec.Parameter)
 | |
| 				}
 | |
| 
 | |
| 				if pr.In == "body" {
 | |
| 					if firstBodyParam != "" {
 | |
| 						res.AddErrors(errors.New(422, "operation %q has more than 1 body param (accepted: %q, dropped: %q)", op.ID, firstBodyParam, pr.Name))
 | |
| 					}
 | |
| 					firstBodyParam = pr.Name
 | |
| 				}
 | |
| 
 | |
| 				if pr.In == "path" {
 | |
| 					paramNames = append(paramNames, pr.Name)
 | |
| 				}
 | |
| 			}
 | |
| 			res.Merge(s.validatePathParamPresence(path, fromPath, paramNames))
 | |
| 		}
 | |
| 	}
 | |
| 	return res
 | |
| }
 | |
| 
 | |
| func parsePath(path string) (segments []string, params []int) {
 | |
| 	for i, p := range strings.Split(path, "/") {
 | |
| 		segments = append(segments, p)
 | |
| 		if len(p) > 0 && p[0] == '{' && p[len(p)-1] == '}' {
 | |
| 			params = append(params, i)
 | |
| 		}
 | |
| 	}
 | |
| 	return
 | |
| }
 | |
| 
 | |
| func (s *SpecValidator) validateReferencesValid() *Result {
 | |
| 	// each reference must point to a valid object
 | |
| 	res := new(Result)
 | |
| 	for _, r := range s.analyzer.AllRefs() {
 | |
| 		if !r.IsValidURI() {
 | |
| 			res.AddErrors(errors.New(404, "invalid ref %q", r.String()))
 | |
| 		}
 | |
| 	}
 | |
| 	if !res.HasErrors() {
 | |
| 		exp, err := s.spec.Expanded()
 | |
| 		if err != nil {
 | |
| 			res.AddErrors(err)
 | |
| 		}
 | |
| 		s.expanded = exp
 | |
| 	}
 | |
| 	return res
 | |
| }
 | |
| 
 | |
| func (s *SpecValidator) validateResponseExample(path string, r *spec.Response) *Result {
 | |
| 	res := new(Result)
 | |
| 	if r.Ref.String() != "" {
 | |
| 		nr, _, err := r.Ref.GetPointer().Get(s.spec.Spec())
 | |
| 		if err != nil {
 | |
| 			res.AddErrors(err)
 | |
| 			return res
 | |
| 		}
 | |
| 		rr := nr.(spec.Response)
 | |
| 		return s.validateResponseExample(path, &rr)
 | |
| 	}
 | |
| 
 | |
| 	if r.Examples != nil {
 | |
| 		if r.Schema != nil {
 | |
| 			if example, ok := r.Examples["application/json"]; ok {
 | |
| 				res.Merge(NewSchemaValidator(r.Schema, s.spec.Spec(), path, s.KnownFormats).Validate(example))
 | |
| 			}
 | |
| 
 | |
| 			// TODO: validate other media types too
 | |
| 		}
 | |
| 	}
 | |
| 	return res
 | |
| }
 | |
| 
 | |
| func (s *SpecValidator) validateExamplesValidAgainstSchema() *Result {
 | |
| 	res := new(Result)
 | |
| 
 | |
| 	for _, pathItem := range s.analyzer.Operations() {
 | |
| 		for path, op := range pathItem {
 | |
| 			if op.Responses.Default != nil {
 | |
| 				dr := op.Responses.Default
 | |
| 				res.Merge(s.validateResponseExample(path, dr))
 | |
| 			}
 | |
| 			for _, r := range op.Responses.StatusCodeResponses {
 | |
| 				res.Merge(s.validateResponseExample(path, &r))
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	return res
 | |
| }
 | |
| 
 | |
| func (s *SpecValidator) validateDefaultValueValidAgainstSchema() *Result {
 | |
| 	// every default value that is specified must validate against the schema for that property
 | |
| 	// headers, items, parameters, schema
 | |
| 
 | |
| 	res := new(Result)
 | |
| 
 | |
| 	for method, pathItem := range s.analyzer.Operations() {
 | |
| 		for path, op := range pathItem {
 | |
| 			// parameters
 | |
| 			var hasForm, hasBody bool
 | |
| 		PARAMETERS:
 | |
| 			for _, pr := range s.analyzer.ParamsFor(method, path) {
 | |
| 				// expand ref is necessary
 | |
| 				param := pr
 | |
| 				for param.Ref.String() != "" {
 | |
| 					obj, _, err := param.Ref.GetPointer().Get(s.spec.Spec())
 | |
| 					if err != nil {
 | |
| 						res.AddErrors(err)
 | |
| 						break PARAMETERS
 | |
| 					}
 | |
| 					param = obj.(spec.Parameter)
 | |
| 				}
 | |
| 				if param.In == "formData" {
 | |
| 					if hasBody && !hasForm {
 | |
| 						res.AddErrors(errors.New(422, "operation %q has both formData and body parameters", op.ID))
 | |
| 					}
 | |
| 					hasForm = true
 | |
| 				}
 | |
| 				if param.In == "body" {
 | |
| 					if hasForm && !hasBody {
 | |
| 						res.AddErrors(errors.New(422, "operation %q has both body and formData parameters", op.ID))
 | |
| 					}
 | |
| 					hasBody = true
 | |
| 				}
 | |
| 				// check simple paramters first
 | |
| 				if param.Default != nil && param.Schema == nil {
 | |
| 					//fmt.Println(param.Name, "in", param.In, "has a default without a schema")
 | |
| 					// check param valid
 | |
| 					res.Merge(NewParamValidator(¶m, s.KnownFormats).Validate(param.Default))
 | |
| 				}
 | |
| 
 | |
| 				if param.Items != nil {
 | |
| 					res.Merge(s.validateDefaultValueItemsAgainstSchema(param.Name, param.In, ¶m, param.Items))
 | |
| 				}
 | |
| 
 | |
| 				if param.Schema != nil {
 | |
| 					res.Merge(s.validateDefaultValueSchemaAgainstSchema(param.Name, param.In, param.Schema))
 | |
| 				}
 | |
| 			}
 | |
| 
 | |
| 			if op.Responses.Default != nil {
 | |
| 				dr := op.Responses.Default
 | |
| 				for nm, h := range dr.Headers {
 | |
| 					if h.Default != nil {
 | |
| 						res.Merge(NewHeaderValidator(nm, &h, s.KnownFormats).Validate(h.Default))
 | |
| 					}
 | |
| 					if h.Items != nil {
 | |
| 						res.Merge(s.validateDefaultValueItemsAgainstSchema(nm, "header", &h, h.Items))
 | |
| 					}
 | |
| 				}
 | |
| 			}
 | |
| 			for _, r := range op.Responses.StatusCodeResponses {
 | |
| 				for nm, h := range r.Headers {
 | |
| 					if h.Default != nil {
 | |
| 						res.Merge(NewHeaderValidator(nm, &h, s.KnownFormats).Validate(h.Default))
 | |
| 					}
 | |
| 					if h.Items != nil {
 | |
| 						res.Merge(s.validateDefaultValueItemsAgainstSchema(nm, "header", &h, h.Items))
 | |
| 					}
 | |
| 				}
 | |
| 			}
 | |
| 
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	for nm, sch := range s.spec.Spec().Definitions {
 | |
| 		res.Merge(s.validateDefaultValueSchemaAgainstSchema(fmt.Sprintf("definitions.%s", nm), "body", &sch))
 | |
| 	}
 | |
| 
 | |
| 	return res
 | |
| }
 | |
| 
 | |
| func (s *SpecValidator) validateDefaultValueSchemaAgainstSchema(path, in string, schema *spec.Schema) *Result {
 | |
| 	res := new(Result)
 | |
| 	if schema != nil {
 | |
| 		if schema.Default != nil {
 | |
| 			res.Merge(NewSchemaValidator(schema, s.spec.Spec(), path, s.KnownFormats).Validate(schema.Default))
 | |
| 		}
 | |
| 		if schema.Items != nil {
 | |
| 			if schema.Items.Schema != nil {
 | |
| 				res.Merge(s.validateDefaultValueSchemaAgainstSchema(path+".items", in, schema.Items.Schema))
 | |
| 			}
 | |
| 			for i, sch := range schema.Items.Schemas {
 | |
| 				res.Merge(s.validateDefaultValueSchemaAgainstSchema(fmt.Sprintf("%s.items[%d]", path, i), in, &sch))
 | |
| 			}
 | |
| 		}
 | |
| 		if schema.AdditionalItems != nil && schema.AdditionalItems.Schema != nil {
 | |
| 			res.Merge(s.validateDefaultValueSchemaAgainstSchema(fmt.Sprintf("%s.additionalItems", path), in, schema.AdditionalItems.Schema))
 | |
| 		}
 | |
| 		for propName, prop := range schema.Properties {
 | |
| 			res.Merge(s.validateDefaultValueSchemaAgainstSchema(path+"."+propName, in, &prop))
 | |
| 		}
 | |
| 		for propName, prop := range schema.PatternProperties {
 | |
| 			res.Merge(s.validateDefaultValueSchemaAgainstSchema(path+"."+propName, in, &prop))
 | |
| 		}
 | |
| 		if schema.AdditionalProperties != nil && schema.AdditionalProperties.Schema != nil {
 | |
| 			res.Merge(s.validateDefaultValueSchemaAgainstSchema(fmt.Sprintf("%s.additionalProperties", path), in, schema.AdditionalProperties.Schema))
 | |
| 		}
 | |
| 		for i, aoSch := range schema.AllOf {
 | |
| 			res.Merge(s.validateDefaultValueSchemaAgainstSchema(fmt.Sprintf("%s.allOf[%d]", path, i), in, &aoSch))
 | |
| 		}
 | |
| 
 | |
| 	}
 | |
| 	return res
 | |
| }
 | |
| 
 | |
| func (s *SpecValidator) validateDefaultValueItemsAgainstSchema(path, in string, root interface{}, items *spec.Items) *Result {
 | |
| 	res := new(Result)
 | |
| 	if items != nil {
 | |
| 		if items.Default != nil {
 | |
| 			res.Merge(newItemsValidator(path, in, items, root, s.KnownFormats).Validate(0, items.Default))
 | |
| 		}
 | |
| 		if items.Items != nil {
 | |
| 			res.Merge(s.validateDefaultValueItemsAgainstSchema(path+"[0]", in, root, items.Items))
 | |
| 		}
 | |
| 	}
 | |
| 	return res
 | |
| }
 |