mirror of
				https://github.com/go-gitea/gitea.git
				synced 2025-10-31 16:36:41 +00:00 
			
		
		
		
	Use vendored go-swagger (#8087)
* Use vendored go-swagger * vendor go-swagger * revert un wanteed change * remove un-needed GO111MODULE * Update Makefile Co-Authored-By: techknowlogick <matti@mdranta.net>
This commit is contained in:
		
				
					committed by
					
						 Lauris BH
						Lauris BH
					
				
			
			
				
	
			
			
			
						parent
						
							4cb1bdddc8
						
					
				
				
					commit
					9fe4437bda
				
			
							
								
								
									
										777
									
								
								vendor/github.com/go-openapi/validate/spec.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										777
									
								
								vendor/github.com/go-openapi/validate/spec.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,777 @@ | ||||
| // 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" | ||||
| 	"sort" | ||||
| 	"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 an OpenAPI 2.0 specification document. | ||||
| // | ||||
| // Returns an error flattening in a single standard error, all validation messages. | ||||
| // | ||||
| //  - TODO: $ref should not have siblings | ||||
| //  - TODO: make sure documentation reflects all checks and warnings | ||||
| //  - TODO: check on discriminators | ||||
| //  - TODO: explicit message on unsupported keywords (better than "forbidden property"...) | ||||
| //  - TODO: full list of unresolved refs | ||||
| //  - TODO: validate numeric constraints (issue#581): this should be handled like defaults and examples | ||||
| //  - TODO: option to determine if we validate for go-swagger or in a more general context | ||||
| //  - TODO: check on required properties to support anyOf, allOf, oneOf | ||||
| // | ||||
| // NOTE: SecurityScopes are maps: no need to check uniqueness | ||||
| // | ||||
| 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 | ||||
| } | ||||
|  | ||||
| // SpecValidator validates a swagger 2.0 spec | ||||
| type SpecValidator struct { | ||||
| 	schema       *spec.Schema // swagger 2.0 schema | ||||
| 	spec         *loads.Document | ||||
| 	analyzer     *analysis.Spec | ||||
| 	expanded     *loads.Document | ||||
| 	KnownFormats strfmt.Registry | ||||
| 	Options      Opts // validation options | ||||
| } | ||||
|  | ||||
| // NewSpecValidator creates a new swagger spec validator instance | ||||
| func NewSpecValidator(schema *spec.Schema, formats strfmt.Registry) *SpecValidator { | ||||
| 	return &SpecValidator{ | ||||
| 		schema:       schema, | ||||
| 		KnownFormats: formats, | ||||
| 		Options:      defaultOpts, | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // Validate validates the swagger spec | ||||
| func (s *SpecValidator) Validate(data interface{}) (errs *Result, warnings *Result) { | ||||
| 	var sd *loads.Document | ||||
| 	errs = new(Result) | ||||
|  | ||||
| 	switch v := data.(type) { | ||||
| 	case *loads.Document: | ||||
| 		sd = v | ||||
| 	} | ||||
| 	if sd == nil { | ||||
| 		errs.AddErrors(invalidDocumentMsg()) | ||||
| 		return | ||||
| 	} | ||||
| 	s.spec = sd | ||||
| 	s.analyzer = analysis.New(sd.Spec()) | ||||
|  | ||||
| 	warnings = new(Result) | ||||
|  | ||||
| 	// Swagger schema validator | ||||
| 	schv := NewSchemaValidator(s.schema, nil, "", s.KnownFormats) | ||||
| 	var obj interface{} | ||||
|  | ||||
| 	// Raw spec unmarshalling errors | ||||
| 	if err := json.Unmarshal(sd.Raw(), &obj); err != nil { | ||||
| 		// NOTE: under normal conditions, the *load.Document has been already unmarshalled | ||||
| 		// So this one is just a paranoid check on the behavior of the spec package | ||||
| 		panic(InvalidDocumentError) | ||||
| 	} | ||||
|  | ||||
| 	defer func() { | ||||
| 		// errs holds all errors and warnings, | ||||
| 		// warnings only warnings | ||||
| 		errs.MergeAsWarnings(warnings) | ||||
| 		warnings.AddErrors(errs.Warnings...) | ||||
| 	}() | ||||
|  | ||||
| 	errs.Merge(schv.Validate(obj)) // error - | ||||
| 	// There may be a point in continuing to try and determine more accurate errors | ||||
| 	if !s.Options.ContinueOnErrors && errs.HasErrors() { | ||||
| 		return // no point in continuing | ||||
| 	} | ||||
|  | ||||
| 	errs.Merge(s.validateReferencesValid()) // error - | ||||
| 	// There may be a point in continuing to try and determine more accurate errors | ||||
| 	if !s.Options.ContinueOnErrors && 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 - | ||||
|  | ||||
| 	// Properties in required definition MUST validate their schema | ||||
| 	// Properties SHOULD NOT be declared as both required and readOnly (warning) | ||||
| 	errs.Merge(s.validateRequiredDefinitions()) // error and warning | ||||
|  | ||||
| 	// There may be a point in continuing to try and determine more accurate errors | ||||
| 	if !s.Options.ContinueOnErrors && errs.HasErrors() { | ||||
| 		return // no point in continuing | ||||
| 	} | ||||
|  | ||||
| 	// Values provided as default MUST validate their schema | ||||
| 	df := &defaultValidator{SpecValidator: s} | ||||
| 	errs.Merge(df.Validate()) | ||||
|  | ||||
| 	// Values provided as examples MUST validate their schema | ||||
| 	// Value provided as examples in a response without schema generate a warning | ||||
| 	// Known limitations: examples in responses for mime type not application/json are ignored (warning) | ||||
| 	ex := &exampleValidator{SpecValidator: s} | ||||
| 	errs.Merge(ex.Validate()) | ||||
|  | ||||
| 	errs.Merge(s.validateNonEmptyPathParamNames()) | ||||
|  | ||||
| 	//errs.Merge(s.validateRefNoSibling()) // warning only | ||||
| 	errs.Merge(s.validateReferenced()) // warning only | ||||
|  | ||||
| 	return | ||||
| } | ||||
|  | ||||
| func (s *SpecValidator) validateNonEmptyPathParamNames() *Result { | ||||
| 	res := new(Result) | ||||
| 	if s.spec.Spec().Paths == nil { | ||||
| 		// There is no Paths object: error | ||||
| 		res.AddErrors(noValidPathMsg()) | ||||
| 	} else { | ||||
| 		if s.spec.Spec().Paths.Paths == nil { | ||||
| 			// Paths may be empty: warning | ||||
| 			res.AddWarnings(noValidPathMsg()) | ||||
| 		} else { | ||||
| 			for k := range s.spec.Spec().Paths.Paths { | ||||
| 				if strings.Contains(k, "{}") { | ||||
| 					res.AddErrors(emptyPathParameterMsg(k)) | ||||
| 				} | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| 	return res | ||||
| } | ||||
|  | ||||
| func (s *SpecValidator) validateDuplicateOperationIDs() *Result { | ||||
| 	// OperationID, if specified, must be unique across the board | ||||
| 	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(nonUniqueOperationIDMsg(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: {}, | ||||
| 		} | ||||
|  | ||||
| 		ancs, rec := s.validateCircularAncestry(k, sch, knownanc) | ||||
| 		if rec != nil && (rec.HasErrors() || !rec.HasWarnings()) { | ||||
| 			res.Merge(rec) | ||||
| 		} | ||||
| 		if len(ancs) > 0 { | ||||
| 			res.AddErrors(circularAncestryDefinitionMsg(k, ancs)) | ||||
| 			return res | ||||
| 		} | ||||
|  | ||||
| 		knowns := make(map[string]struct{}) | ||||
| 		dups, rep := s.validateSchemaPropertyNames(k, sch, knowns) | ||||
| 		if rep != nil && (rep.HasErrors() || rep.HasWarnings()) { | ||||
| 			res.Merge(rep) | ||||
| 		} | ||||
| 		if len(dups) > 0 { | ||||
| 			var pns []string | ||||
| 			for _, v := range dups { | ||||
| 				pns = append(pns, v.Definition+"."+v.Name) | ||||
| 			} | ||||
| 			res.AddErrors(duplicatePropertiesMsg(k, pns)) | ||||
| 		} | ||||
|  | ||||
| 	} | ||||
| 	return res | ||||
| } | ||||
|  | ||||
| func (s *SpecValidator) resolveRef(ref *spec.Ref) (*spec.Schema, error) { | ||||
| 	if s.spec.SpecFilePath() != "" { | ||||
| 		return spec.ResolveRefWithBase(s.spec.Spec(), ref, &spec.ExpandOptions{RelativeBase: s.spec.SpecFilePath()}) | ||||
| 	} | ||||
| 	// NOTE: it looks like with the new spec resolver, this code is now unrecheable | ||||
| 	return spec.ResolveRef(s.spec.Spec(), ref) | ||||
| } | ||||
|  | ||||
| func (s *SpecValidator) validateSchemaPropertyNames(nm string, sch spec.Schema, knowns map[string]struct{}) ([]dupProp, *Result) { | ||||
| 	var dups []dupProp | ||||
|  | ||||
| 	schn := nm | ||||
| 	schc := &sch | ||||
| 	res := new(Result) | ||||
|  | ||||
| 	for schc.Ref.String() != "" { | ||||
| 		// gather property names | ||||
| 		reso, err := s.resolveRef(&schc.Ref) | ||||
| 		if err != nil { | ||||
| 			errorHelp.addPointerError(res, err, schc.Ref.String(), nm) | ||||
| 			return dups, res | ||||
| 		} | ||||
| 		schc = reso | ||||
| 		schn = sch.Ref.String() | ||||
| 	} | ||||
|  | ||||
| 	if len(schc.AllOf) > 0 { | ||||
| 		for _, chld := range schc.AllOf { | ||||
| 			dup, rep := s.validateSchemaPropertyNames(schn, chld, knowns) | ||||
| 			if rep != nil && (rep.HasErrors() || rep.HasWarnings()) { | ||||
| 				res.Merge(rep) | ||||
| 			} | ||||
| 			dups = append(dups, dup...) | ||||
| 		} | ||||
| 		return dups, res | ||||
| 	} | ||||
|  | ||||
| 	for k := range schc.Properties { | ||||
| 		_, ok := knowns[k] | ||||
| 		if ok { | ||||
| 			dups = append(dups, dupProp{Name: k, Definition: schn}) | ||||
| 		} else { | ||||
| 			knowns[k] = struct{}{} | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	return dups, res | ||||
| } | ||||
|  | ||||
| func (s *SpecValidator) validateCircularAncestry(nm string, sch spec.Schema, knowns map[string]struct{}) ([]string, *Result) { | ||||
| 	res := new(Result) | ||||
|  | ||||
| 	if sch.Ref.String() == "" && len(sch.AllOf) == 0 { // Safeguard. We should not be able to actually get there | ||||
| 		return nil, res | ||||
| 	} | ||||
| 	var ancs []string | ||||
|  | ||||
| 	schn := nm | ||||
| 	schc := &sch | ||||
|  | ||||
| 	for schc.Ref.String() != "" { | ||||
| 		reso, err := s.resolveRef(&schc.Ref) | ||||
| 		if err != nil { | ||||
| 			errorHelp.addPointerError(res, err, schc.Ref.String(), nm) | ||||
| 			return ancs, res | ||||
| 		} | ||||
| 		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, res | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	if len(schc.AllOf) > 0 { | ||||
| 		for _, chld := range schc.AllOf { | ||||
| 			if chld.Ref.String() != "" || len(chld.AllOf) > 0 { | ||||
| 				anc, rec := s.validateCircularAncestry(schn, chld, knowns) | ||||
| 				if rec != nil && (rec.HasErrors() || !rec.HasWarnings()) { | ||||
| 					res.Merge(rec) | ||||
| 				} | ||||
| 				ancs = append(ancs, anc...) | ||||
| 				if len(ancs) > 0 { | ||||
| 					return ancs, res | ||||
| 				} | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| 	return ancs, res | ||||
| } | ||||
|  | ||||
| func (s *SpecValidator) validateItems() *Result { | ||||
| 	// validate parameter, items, schema and response objects for presence of item if type is array | ||||
| 	res := new(Result) | ||||
|  | ||||
| 	for method, pi := range s.analyzer.Operations() { | ||||
| 		for path, op := range pi { | ||||
| 			for _, param := range paramHelp.safeExpandedParamsFor(path, method, op.ID, res, s) { | ||||
|  | ||||
| 				if param.TypeName() == "array" && param.ItemsTypeName() == "" { | ||||
| 					res.AddErrors(arrayInParamRequiresItemsMsg(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(arrayInParamRequiresItemsMsg(param.Name, op.ID)) | ||||
| 								break | ||||
| 							} | ||||
| 							items = items.Items | ||||
| 						} | ||||
| 					} | ||||
| 				} else { | ||||
| 					// In: body | ||||
| 					if param.Schema != nil { | ||||
| 						res.Merge(s.validateSchemaItems(*param.Schema, fmt.Sprintf("body param %q", param.Name), op.ID)) | ||||
| 					} | ||||
| 				} | ||||
| 			} | ||||
|  | ||||
| 			var responses []spec.Response | ||||
| 			if op.Responses != nil { | ||||
| 				if op.Responses.Default != nil { | ||||
| 					responses = append(responses, *op.Responses.Default) | ||||
| 				} | ||||
| 				if op.Responses.StatusCodeResponses != nil { | ||||
| 					for _, v := range op.Responses.StatusCodeResponses { | ||||
| 						responses = append(responses, v) | ||||
| 					} | ||||
| 				} | ||||
| 			} | ||||
|  | ||||
| 			for _, resp := range responses { | ||||
| 				// Response headers with array | ||||
| 				for hn, hv := range resp.Headers { | ||||
| 					if hv.TypeName() == "array" && hv.ItemsTypeName() == "" { | ||||
| 						res.AddErrors(arrayInHeaderRequiresItemsMsg(hn, op.ID)) | ||||
| 					} | ||||
| 				} | ||||
| 				if resp.Schema != nil { | ||||
| 					res.Merge(s.validateSchemaItems(*resp.Schema, "response body", op.ID)) | ||||
| 				} | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| 	return res | ||||
| } | ||||
|  | ||||
| // Verifies constraints on array type | ||||
| func (s *SpecValidator) validateSchemaItems(schema spec.Schema, prefix, opID string) *Result { | ||||
| 	res := new(Result) | ||||
| 	if !schema.Type.Contains("array") { | ||||
| 		return res | ||||
| 	} | ||||
|  | ||||
| 	if schema.Items == nil || schema.Items.Len() == 0 { | ||||
| 		res.AddErrors(arrayRequiresItemsMsg(prefix, opID)) | ||||
| 		return res | ||||
| 	} | ||||
|  | ||||
| 	if schema.Items.Schema != nil { | ||||
| 		schema = *schema.Items.Schema | ||||
| 		if _, err := compileRegexp(schema.Pattern); err != nil { | ||||
| 			res.AddErrors(invalidItemsPatternMsg(prefix, opID, schema.Pattern)) | ||||
| 		} | ||||
|  | ||||
| 		res.Merge(s.validateSchemaItems(schema, prefix, opID)) | ||||
| 	} | ||||
| 	return res | ||||
| } | ||||
|  | ||||
| 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.AddErrors(noParameterInPathMsg(l)) | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	for _, p := range fromOperation { | ||||
| 		var matched bool | ||||
| 		for _, r := range fromPath { | ||||
| 			if "{"+p+"}" == r { | ||||
| 				matched = true | ||||
| 				break | ||||
| 			} | ||||
| 		} | ||||
| 		if !matched { | ||||
| 			res.AddErrors(pathParamNotInPathMsg(path, p)) | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	return res | ||||
| } | ||||
|  | ||||
| func (s *SpecValidator) validateReferenced() *Result { | ||||
| 	var res Result | ||||
| 	res.MergeAsWarnings(s.validateReferencedParameters()) | ||||
| 	res.MergeAsWarnings(s.validateReferencedResponses()) | ||||
| 	res.MergeAsWarnings(s.validateReferencedDefinitions()) | ||||
| 	return &res | ||||
| } | ||||
|  | ||||
| func (s *SpecValidator) validateReferencedParameters() *Result { | ||||
| 	// Each referenceable definition should 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 | ||||
| 	} | ||||
| 	result := new(Result) | ||||
| 	for k := range expected { | ||||
| 		result.AddWarnings(unusedParamMsg(k)) | ||||
| 	} | ||||
| 	return result | ||||
| } | ||||
|  | ||||
| func (s *SpecValidator) validateReferencedResponses() *Result { | ||||
| 	// Each referenceable definition should 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 | ||||
| 	} | ||||
| 	result := new(Result) | ||||
| 	for k := range expected { | ||||
| 		result.AddWarnings(unusedResponseMsg(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 | ||||
| 	} | ||||
|  | ||||
| 	result := new(Result) | ||||
| 	for k := range expected { | ||||
| 		result.AddWarnings(unusedDefinitionMsg(k)) | ||||
| 	} | ||||
| 	return result | ||||
| } | ||||
|  | ||||
| func (s *SpecValidator) validateRequiredDefinitions() *Result { | ||||
| 	// Each property listed in the required array must be defined in the properties of the model | ||||
| 	res := new(Result) | ||||
|  | ||||
| DEFINITIONS: | ||||
| 	for d, schema := range s.spec.Spec().Definitions { | ||||
| 		if schema.Required != nil { // Safeguard | ||||
| 			for _, pn := range schema.Required { | ||||
| 				red := s.validateRequiredProperties(pn, d, &schema) | ||||
| 				res.Merge(red) | ||||
| 				if !red.IsValid() && !s.Options.ContinueOnErrors { | ||||
| 					break DEFINITIONS // there is an error, let's stop that bleeding | ||||
| 				} | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| 	return res | ||||
| } | ||||
|  | ||||
| func (s *SpecValidator) validateRequiredProperties(path, in string, v *spec.Schema) *Result { | ||||
| 	// Takes care of recursive property definitions, which may be nested in additionalProperties schemas | ||||
| 	res := new(Result) | ||||
| 	propertyMatch := false | ||||
| 	patternMatch := false | ||||
| 	additionalPropertiesMatch := false | ||||
| 	isReadOnly := false | ||||
|  | ||||
| 	// Regular properties | ||||
| 	if _, ok := v.Properties[path]; ok { | ||||
| 		propertyMatch = true | ||||
| 		isReadOnly = v.Properties[path].ReadOnly | ||||
| 	} | ||||
|  | ||||
| 	// NOTE: patternProperties are not supported in swagger. Even though, we continue validation here | ||||
| 	// We check all defined patterns: if one regexp is invalid, croaks an error | ||||
| 	for pp, pv := range v.PatternProperties { | ||||
| 		re, err := compileRegexp(pp) | ||||
| 		if err != nil { | ||||
| 			res.AddErrors(invalidPatternMsg(pp, in)) | ||||
| 		} else if re.MatchString(path) { | ||||
| 			patternMatch = true | ||||
| 			if !propertyMatch { | ||||
| 				isReadOnly = pv.ReadOnly | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	if !(propertyMatch || patternMatch) { | ||||
| 		if v.AdditionalProperties != nil { | ||||
| 			if v.AdditionalProperties.Allows && v.AdditionalProperties.Schema == nil { | ||||
| 				additionalPropertiesMatch = true | ||||
| 			} else if v.AdditionalProperties.Schema != nil { | ||||
| 				// additionalProperties as schema are upported in swagger | ||||
| 				// recursively validates additionalProperties schema | ||||
| 				// TODO : anyOf, allOf, oneOf like in schemaPropsValidator | ||||
| 				red := s.validateRequiredProperties(path, in, v.AdditionalProperties.Schema) | ||||
| 				if red.IsValid() { | ||||
| 					additionalPropertiesMatch = true | ||||
| 					if !propertyMatch && !patternMatch { | ||||
| 						isReadOnly = v.AdditionalProperties.Schema.ReadOnly | ||||
| 					} | ||||
| 				} | ||||
| 				res.Merge(red) | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	if !(propertyMatch || patternMatch || additionalPropertiesMatch) { | ||||
| 		res.AddErrors(requiredButNotDefinedMsg(path, in)) | ||||
| 	} | ||||
|  | ||||
| 	if isReadOnly { | ||||
| 		res.AddWarnings(readOnlyAndRequiredMsg(in, path)) | ||||
| 	} | ||||
| 	return res | ||||
| } | ||||
|  | ||||
| func (s *SpecValidator) validateParameters() *Result { | ||||
| 	// - for each method, path is unique, regardless of path parameters | ||||
| 	//   e.g. GET:/petstore/{id}, GET:/petstore/{pet}, GET:/petstore are | ||||
| 	//   considered duplicate paths | ||||
| 	// - each parameter should have a unique `name` and `type` combination | ||||
| 	// - each operation should have only 1 parameter of type body | ||||
| 	// - there must be at most 1 parameter in body | ||||
| 	// - parameters with pattern property must specify valid patterns | ||||
| 	// - $ref in parameters must resolve | ||||
| 	// - path param must be required | ||||
| 	res := new(Result) | ||||
| 	rexGarbledPathSegment := mustCompileRegexp(`.*[{}\s]+.*`) | ||||
| 	for method, pi := range s.analyzer.Operations() { | ||||
| 		methodPaths := make(map[string]map[string]string) | ||||
| 		if pi != nil { // Safeguard | ||||
| 			for path, op := range pi { | ||||
| 				pathToAdd := pathHelp.stripParametersInPath(path) | ||||
|  | ||||
| 				// Warn on garbled path afer param stripping | ||||
| 				if rexGarbledPathSegment.MatchString(pathToAdd) { | ||||
| 					res.AddWarnings(pathStrippedParamGarbledMsg(pathToAdd)) | ||||
| 				} | ||||
|  | ||||
| 				// Check uniqueness of stripped paths | ||||
| 				if _, found := methodPaths[method][pathToAdd]; found { | ||||
|  | ||||
| 					// Sort names for stable, testable output | ||||
| 					if strings.Compare(path, methodPaths[method][pathToAdd]) < 0 { | ||||
| 						res.AddErrors(pathOverlapMsg(path, methodPaths[method][pathToAdd])) | ||||
| 					} else { | ||||
| 						res.AddErrors(pathOverlapMsg(methodPaths[method][pathToAdd], path)) | ||||
| 					} | ||||
| 				} else { | ||||
| 					if _, found := methodPaths[method]; !found { | ||||
| 						methodPaths[method] = map[string]string{} | ||||
| 					} | ||||
| 					methodPaths[method][pathToAdd] = path //Original non stripped path | ||||
|  | ||||
| 				} | ||||
|  | ||||
| 				var bodyParams []string | ||||
| 				var paramNames []string | ||||
| 				var hasForm, hasBody bool | ||||
|  | ||||
| 				// Check parameters names uniqueness for operation | ||||
| 				// TODO: should be done after param expansion | ||||
| 				res.Merge(s.checkUniqueParams(path, method, op)) | ||||
|  | ||||
| 				for _, pr := range paramHelp.safeExpandedParamsFor(path, method, op.ID, res, s) { | ||||
| 					// Validate pattern regexp for parameters with a Pattern property | ||||
| 					if _, err := compileRegexp(pr.Pattern); err != nil { | ||||
| 						res.AddErrors(invalidPatternInParamMsg(op.ID, pr.Name, pr.Pattern)) | ||||
| 					} | ||||
|  | ||||
| 					// There must be at most one parameter in body: list them all | ||||
| 					if pr.In == "body" { | ||||
| 						bodyParams = append(bodyParams, fmt.Sprintf("%q", pr.Name)) | ||||
| 						hasBody = true | ||||
| 					} | ||||
|  | ||||
| 					if pr.In == "path" { | ||||
| 						paramNames = append(paramNames, pr.Name) | ||||
| 						// Path declared in path must have the required: true property | ||||
| 						if !pr.Required { | ||||
| 							res.AddErrors(pathParamRequiredMsg(op.ID, pr.Name)) | ||||
| 						} | ||||
| 					} | ||||
|  | ||||
| 					if pr.In == "formData" { | ||||
| 						hasForm = true | ||||
| 					} | ||||
| 				} | ||||
|  | ||||
| 				// In:formData and In:body are mutually exclusive | ||||
| 				if hasBody && hasForm { | ||||
| 					res.AddErrors(bothFormDataAndBodyMsg(op.ID)) | ||||
| 				} | ||||
| 				// There must be at most one body param | ||||
| 				// Accurately report situations when more than 1 body param is declared (possibly unnamed) | ||||
| 				if len(bodyParams) > 1 { | ||||
| 					sort.Strings(bodyParams) | ||||
| 					res.AddErrors(multipleBodyParamMsg(op.ID, bodyParams)) | ||||
| 				} | ||||
|  | ||||
| 				// Check uniqueness of parameters in path | ||||
| 				paramsInPath := pathHelp.extractPathParams(path) | ||||
| 				for i, p := range paramsInPath { | ||||
| 					for j, q := range paramsInPath { | ||||
| 						if p == q && i > j { | ||||
| 							res.AddErrors(pathParamNotUniqueMsg(path, p, q)) | ||||
| 							break | ||||
| 						} | ||||
| 					} | ||||
| 				} | ||||
|  | ||||
| 				// Warns about possible malformed params in path | ||||
| 				rexGarbledParam := mustCompileRegexp(`{.*[{}\s]+.*}`) | ||||
| 				for _, p := range paramsInPath { | ||||
| 					if rexGarbledParam.MatchString(p) { | ||||
| 						res.AddWarnings(pathParamGarbledMsg(path, p)) | ||||
| 					} | ||||
| 				} | ||||
|  | ||||
| 				// Match params from path vs params from params section | ||||
| 				res.Merge(s.validatePathParamPresence(path, paramsInPath, paramNames)) | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| 	return res | ||||
| } | ||||
|  | ||||
| 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(s.spec.SpecFilePath()) { // Safeguard - spec should always yield a valid URI | ||||
| 			res.AddErrors(invalidRefMsg(r.String())) | ||||
| 		} | ||||
| 	} | ||||
| 	if !res.HasErrors() { | ||||
| 		// NOTE: with default settings, loads.Document.Expanded() | ||||
| 		// stops on first error. Anyhow, the expand option to continue | ||||
| 		// on errors fails to report errors at all. | ||||
| 		exp, err := s.spec.Expanded() | ||||
| 		if err != nil { | ||||
| 			res.AddErrors(unresolvedReferencesMsg(err)) | ||||
| 		} | ||||
| 		s.expanded = exp | ||||
| 	} | ||||
| 	return res | ||||
| } | ||||
|  | ||||
| func (s *SpecValidator) checkUniqueParams(path, method string, op *spec.Operation) *Result { | ||||
| 	// Check for duplicate parameters declaration in param section. | ||||
| 	// Each parameter should have a unique `name` and `type` combination | ||||
| 	// NOTE: this could be factorized in analysis (when constructing the params map) | ||||
| 	// However, there are some issues with such a factorization: | ||||
| 	// - analysis does not seem to fully expand params | ||||
| 	// - param keys may be altered by x-go-name | ||||
| 	res := new(Result) | ||||
| 	pnames := make(map[string]struct{}) | ||||
|  | ||||
| 	if op.Parameters != nil { // Safeguard | ||||
| 		for _, ppr := range op.Parameters { | ||||
| 			var ok bool | ||||
| 			pr, red := paramHelp.resolveParam(path, method, op.ID, &ppr, s) | ||||
| 			res.Merge(red) | ||||
|  | ||||
| 			if pr != nil && pr.Name != "" { // params with empty name does no participate the check | ||||
| 				key := fmt.Sprintf("%s#%s", pr.In, pr.Name) | ||||
|  | ||||
| 				if _, ok = pnames[key]; ok { | ||||
| 					res.AddErrors(duplicateParamNameMsg(pr.In, pr.Name, op.ID)) | ||||
| 				} | ||||
| 				pnames[key] = struct{}{} | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| 	return res | ||||
| } | ||||
|  | ||||
| // SetContinueOnErrors sets the ContinueOnErrors option for this validator. | ||||
| func (s *SpecValidator) SetContinueOnErrors(c bool) { | ||||
| 	s.Options.ContinueOnErrors = c | ||||
| } | ||||
		Reference in New Issue
	
	Block a user