...

Source file src/github.com/go-openapi/validate/spec.go

Documentation: github.com/go-openapi/validate

     1  // Copyright 2015 go-swagger maintainers
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //    http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package validate
    16  
    17  import (
    18  	"bytes"
    19  	"encoding/gob"
    20  	"encoding/json"
    21  	"fmt"
    22  	"sort"
    23  	"strings"
    24  
    25  	"github.com/go-openapi/analysis"
    26  	"github.com/go-openapi/errors"
    27  	"github.com/go-openapi/jsonpointer"
    28  	"github.com/go-openapi/loads"
    29  	"github.com/go-openapi/spec"
    30  	"github.com/go-openapi/strfmt"
    31  	"github.com/go-openapi/swag"
    32  )
    33  
    34  // Spec validates an OpenAPI 2.0 specification document.
    35  //
    36  // Returns an error flattening in a single standard error, all validation messages.
    37  //
    38  //   - TODO: $ref should not have siblings
    39  //   - TODO: make sure documentation reflects all checks and warnings
    40  //   - TODO: check on discriminators
    41  //   - TODO: explicit message on unsupported keywords (better than "forbidden property"...)
    42  //   - TODO: full list of unresolved refs
    43  //   - TODO: validate numeric constraints (issue#581): this should be handled like defaults and examples
    44  //   - TODO: option to determine if we validate for go-swagger or in a more general context
    45  //   - TODO: check on required properties to support anyOf, allOf, oneOf
    46  //
    47  // NOTE: SecurityScopes are maps: no need to check uniqueness
    48  func Spec(doc *loads.Document, formats strfmt.Registry) error {
    49  	errs, _ /*warns*/ := NewSpecValidator(doc.Schema(), formats).Validate(doc)
    50  	if errs.HasErrors() {
    51  		return errors.CompositeValidationError(errs.Errors...)
    52  	}
    53  	return nil
    54  }
    55  
    56  // SpecValidator validates a swagger 2.0 spec
    57  type SpecValidator struct {
    58  	schema        *spec.Schema // swagger 2.0 schema
    59  	spec          *loads.Document
    60  	analyzer      *analysis.Spec
    61  	expanded      *loads.Document
    62  	KnownFormats  strfmt.Registry
    63  	Options       Opts // validation options
    64  	schemaOptions *SchemaValidatorOptions
    65  }
    66  
    67  // NewSpecValidator creates a new swagger spec validator instance
    68  func NewSpecValidator(schema *spec.Schema, formats strfmt.Registry) *SpecValidator {
    69  	// schema options that apply to all called validators
    70  	schemaOptions := new(SchemaValidatorOptions)
    71  	for _, o := range []Option{
    72  		SwaggerSchema(true),
    73  		WithRecycleValidators(true),
    74  		// withRecycleResults(true),
    75  	} {
    76  		o(schemaOptions)
    77  	}
    78  
    79  	return &SpecValidator{
    80  		schema:        schema,
    81  		KnownFormats:  formats,
    82  		Options:       defaultOpts,
    83  		schemaOptions: schemaOptions,
    84  	}
    85  }
    86  
    87  // Validate validates the swagger spec
    88  func (s *SpecValidator) Validate(data interface{}) (*Result, *Result) {
    89  	s.schemaOptions.skipSchemataResult = s.Options.SkipSchemataResult
    90  	var sd *loads.Document
    91  	errs, warnings := new(Result), new(Result)
    92  
    93  	if v, ok := data.(*loads.Document); ok {
    94  		sd = v
    95  	}
    96  	if sd == nil {
    97  		errs.AddErrors(invalidDocumentMsg())
    98  		return errs, warnings // no point in continuing
    99  	}
   100  	s.spec = sd
   101  	s.analyzer = analysis.New(sd.Spec())
   102  
   103  	// Raw spec unmarshalling errors
   104  	var obj interface{}
   105  	if err := json.Unmarshal(sd.Raw(), &obj); err != nil {
   106  		// NOTE: under normal conditions, the *load.Document has been already unmarshalled
   107  		// So this one is just a paranoid check on the behavior of the spec package
   108  		panic(InvalidDocumentError)
   109  	}
   110  
   111  	defer func() {
   112  		// errs holds all errors and warnings,
   113  		// warnings only warnings
   114  		errs.MergeAsWarnings(warnings)
   115  		warnings.AddErrors(errs.Warnings...)
   116  	}()
   117  
   118  	// Swagger schema validator
   119  	schv := newSchemaValidator(s.schema, nil, "", s.KnownFormats, s.schemaOptions)
   120  	errs.Merge(schv.Validate(obj)) // error -
   121  	// There may be a point in continuing to try and determine more accurate errors
   122  	if !s.Options.ContinueOnErrors && errs.HasErrors() {
   123  		return errs, warnings // no point in continuing
   124  	}
   125  
   126  	errs.Merge(s.validateReferencesValid()) // error -
   127  	// There may be a point in continuing to try and determine more accurate errors
   128  	if !s.Options.ContinueOnErrors && errs.HasErrors() {
   129  		return errs, warnings // no point in continuing
   130  	}
   131  
   132  	errs.Merge(s.validateDuplicateOperationIDs())
   133  	errs.Merge(s.validateDuplicatePropertyNames()) // error -
   134  	errs.Merge(s.validateParameters())             // error -
   135  	errs.Merge(s.validateItems())                  // error -
   136  
   137  	// Properties in required definition MUST validate their schema
   138  	// Properties SHOULD NOT be declared as both required and readOnly (warning)
   139  	errs.Merge(s.validateRequiredDefinitions()) // error and warning
   140  
   141  	// There may be a point in continuing to try and determine more accurate errors
   142  	if !s.Options.ContinueOnErrors && errs.HasErrors() {
   143  		return errs, warnings // no point in continuing
   144  	}
   145  
   146  	// Values provided as default MUST validate their schema
   147  	df := &defaultValidator{SpecValidator: s, schemaOptions: s.schemaOptions}
   148  	errs.Merge(df.Validate())
   149  
   150  	// Values provided as examples MUST validate their schema
   151  	// Value provided as examples in a response without schema generate a warning
   152  	// Known limitations: examples in responses for mime type not application/json are ignored (warning)
   153  	ex := &exampleValidator{SpecValidator: s, schemaOptions: s.schemaOptions}
   154  	errs.Merge(ex.Validate())
   155  
   156  	errs.Merge(s.validateNonEmptyPathParamNames())
   157  
   158  	// errs.Merge(s.validateRefNoSibling()) // warning only
   159  	errs.Merge(s.validateReferenced()) // warning only
   160  
   161  	return errs, warnings
   162  }
   163  
   164  func (s *SpecValidator) validateNonEmptyPathParamNames() *Result {
   165  	res := pools.poolOfResults.BorrowResult()
   166  	if s.spec.Spec().Paths == nil {
   167  		// There is no Paths object: error
   168  		res.AddErrors(noValidPathMsg())
   169  
   170  		return res
   171  	}
   172  
   173  	if s.spec.Spec().Paths.Paths == nil {
   174  		// Paths may be empty: warning
   175  		res.AddWarnings(noValidPathMsg())
   176  
   177  		return res
   178  	}
   179  
   180  	for k := range s.spec.Spec().Paths.Paths {
   181  		if strings.Contains(k, "{}") {
   182  			res.AddErrors(emptyPathParameterMsg(k))
   183  		}
   184  	}
   185  
   186  	return res
   187  }
   188  
   189  func (s *SpecValidator) validateDuplicateOperationIDs() *Result {
   190  	// OperationID, if specified, must be unique across the board
   191  	var analyzer *analysis.Spec
   192  	if s.expanded != nil {
   193  		// $ref are valid: we can analyze operations on an expanded spec
   194  		analyzer = analysis.New(s.expanded.Spec())
   195  	} else {
   196  		// fallback on possible incomplete picture because of previous errors
   197  		analyzer = s.analyzer
   198  	}
   199  	res := pools.poolOfResults.BorrowResult()
   200  	known := make(map[string]int)
   201  	for _, v := range analyzer.OperationIDs() {
   202  		if v != "" {
   203  			known[v]++
   204  		}
   205  	}
   206  	for k, v := range known {
   207  		if v > 1 {
   208  			res.AddErrors(nonUniqueOperationIDMsg(k, v))
   209  		}
   210  	}
   211  	return res
   212  }
   213  
   214  type dupProp struct {
   215  	Name       string
   216  	Definition string
   217  }
   218  
   219  func (s *SpecValidator) validateDuplicatePropertyNames() *Result {
   220  	// definition can't declare a property that's already defined by one of its ancestors
   221  	res := pools.poolOfResults.BorrowResult()
   222  	for k, sch := range s.spec.Spec().Definitions {
   223  		if len(sch.AllOf) == 0 {
   224  			continue
   225  		}
   226  
   227  		knownanc := map[string]struct{}{
   228  			"#/definitions/" + k: {},
   229  		}
   230  
   231  		ancs, rec := s.validateCircularAncestry(k, sch, knownanc)
   232  		if rec != nil && (rec.HasErrors() || !rec.HasWarnings()) {
   233  			res.Merge(rec)
   234  		}
   235  		if len(ancs) > 0 {
   236  			res.AddErrors(circularAncestryDefinitionMsg(k, ancs))
   237  			return res
   238  		}
   239  
   240  		knowns := make(map[string]struct{})
   241  		dups, rep := s.validateSchemaPropertyNames(k, sch, knowns)
   242  		if rep != nil && (rep.HasErrors() || rep.HasWarnings()) {
   243  			res.Merge(rep)
   244  		}
   245  		if len(dups) > 0 {
   246  			var pns []string
   247  			for _, v := range dups {
   248  				pns = append(pns, v.Definition+"."+v.Name)
   249  			}
   250  			res.AddErrors(duplicatePropertiesMsg(k, pns))
   251  		}
   252  
   253  	}
   254  	return res
   255  }
   256  
   257  func (s *SpecValidator) resolveRef(ref *spec.Ref) (*spec.Schema, error) {
   258  	if s.spec.SpecFilePath() != "" {
   259  		return spec.ResolveRefWithBase(s.spec.Spec(), ref, &spec.ExpandOptions{RelativeBase: s.spec.SpecFilePath()})
   260  	}
   261  	// NOTE: it looks like with the new spec resolver, this code is now unrecheable
   262  	return spec.ResolveRef(s.spec.Spec(), ref)
   263  }
   264  
   265  func (s *SpecValidator) validateSchemaPropertyNames(nm string, sch spec.Schema, knowns map[string]struct{}) ([]dupProp, *Result) {
   266  	var dups []dupProp
   267  
   268  	schn := nm
   269  	schc := &sch
   270  	res := pools.poolOfResults.BorrowResult()
   271  
   272  	for schc.Ref.String() != "" {
   273  		// gather property names
   274  		reso, err := s.resolveRef(&schc.Ref)
   275  		if err != nil {
   276  			errorHelp.addPointerError(res, err, schc.Ref.String(), nm)
   277  			return dups, res
   278  		}
   279  		schc = reso
   280  		schn = sch.Ref.String()
   281  	}
   282  
   283  	if len(schc.AllOf) > 0 {
   284  		for _, chld := range schc.AllOf {
   285  			dup, rep := s.validateSchemaPropertyNames(schn, chld, knowns)
   286  			if rep != nil && (rep.HasErrors() || rep.HasWarnings()) {
   287  				res.Merge(rep)
   288  			}
   289  			dups = append(dups, dup...)
   290  		}
   291  		return dups, res
   292  	}
   293  
   294  	for k := range schc.Properties {
   295  		_, ok := knowns[k]
   296  		if ok {
   297  			dups = append(dups, dupProp{Name: k, Definition: schn})
   298  		} else {
   299  			knowns[k] = struct{}{}
   300  		}
   301  	}
   302  
   303  	return dups, res
   304  }
   305  
   306  func (s *SpecValidator) validateCircularAncestry(nm string, sch spec.Schema, knowns map[string]struct{}) ([]string, *Result) {
   307  	res := pools.poolOfResults.BorrowResult()
   308  
   309  	if sch.Ref.String() == "" && len(sch.AllOf) == 0 { // Safeguard. We should not be able to actually get there
   310  		return nil, res
   311  	}
   312  	var ancs []string
   313  
   314  	schn := nm
   315  	schc := &sch
   316  
   317  	for schc.Ref.String() != "" {
   318  		reso, err := s.resolveRef(&schc.Ref)
   319  		if err != nil {
   320  			errorHelp.addPointerError(res, err, schc.Ref.String(), nm)
   321  			return ancs, res
   322  		}
   323  		schc = reso
   324  		schn = sch.Ref.String()
   325  	}
   326  
   327  	if schn != nm && schn != "" {
   328  		if _, ok := knowns[schn]; ok {
   329  			ancs = append(ancs, schn)
   330  		}
   331  		knowns[schn] = struct{}{}
   332  
   333  		if len(ancs) > 0 {
   334  			return ancs, res
   335  		}
   336  	}
   337  
   338  	if len(schc.AllOf) > 0 {
   339  		for _, chld := range schc.AllOf {
   340  			if chld.Ref.String() != "" || len(chld.AllOf) > 0 {
   341  				anc, rec := s.validateCircularAncestry(schn, chld, knowns)
   342  				if rec != nil && (rec.HasErrors() || !rec.HasWarnings()) {
   343  					res.Merge(rec)
   344  				}
   345  				ancs = append(ancs, anc...)
   346  				if len(ancs) > 0 {
   347  					return ancs, res
   348  				}
   349  			}
   350  		}
   351  	}
   352  	return ancs, res
   353  }
   354  
   355  func (s *SpecValidator) validateItems() *Result {
   356  	// validate parameter, items, schema and response objects for presence of item if type is array
   357  	res := pools.poolOfResults.BorrowResult()
   358  
   359  	for method, pi := range s.analyzer.Operations() {
   360  		for path, op := range pi {
   361  			for _, param := range paramHelp.safeExpandedParamsFor(path, method, op.ID, res, s) {
   362  
   363  				if param.TypeName() == arrayType && param.ItemsTypeName() == "" {
   364  					res.AddErrors(arrayInParamRequiresItemsMsg(param.Name, op.ID))
   365  					continue
   366  				}
   367  				if param.In != swaggerBody {
   368  					if param.Items != nil {
   369  						items := param.Items
   370  						for items.TypeName() == arrayType {
   371  							if items.ItemsTypeName() == "" {
   372  								res.AddErrors(arrayInParamRequiresItemsMsg(param.Name, op.ID))
   373  								break
   374  							}
   375  							items = items.Items
   376  						}
   377  					}
   378  				} else {
   379  					// In: body
   380  					if param.Schema != nil {
   381  						res.Merge(s.validateSchemaItems(*param.Schema, fmt.Sprintf("body param %q", param.Name), op.ID))
   382  					}
   383  				}
   384  			}
   385  
   386  			var responses []spec.Response
   387  			if op.Responses != nil {
   388  				if op.Responses.Default != nil {
   389  					responses = append(responses, *op.Responses.Default)
   390  				}
   391  				if op.Responses.StatusCodeResponses != nil {
   392  					for _, v := range op.Responses.StatusCodeResponses {
   393  						responses = append(responses, v)
   394  					}
   395  				}
   396  			}
   397  
   398  			for _, resp := range responses {
   399  				// Response headers with array
   400  				for hn, hv := range resp.Headers {
   401  					if hv.TypeName() == arrayType && hv.ItemsTypeName() == "" {
   402  						res.AddErrors(arrayInHeaderRequiresItemsMsg(hn, op.ID))
   403  					}
   404  				}
   405  				if resp.Schema != nil {
   406  					res.Merge(s.validateSchemaItems(*resp.Schema, "response body", op.ID))
   407  				}
   408  			}
   409  		}
   410  	}
   411  	return res
   412  }
   413  
   414  // Verifies constraints on array type
   415  func (s *SpecValidator) validateSchemaItems(schema spec.Schema, prefix, opID string) *Result {
   416  	res := pools.poolOfResults.BorrowResult()
   417  	if !schema.Type.Contains(arrayType) {
   418  		return res
   419  	}
   420  
   421  	if schema.Items == nil || schema.Items.Len() == 0 {
   422  		res.AddErrors(arrayRequiresItemsMsg(prefix, opID))
   423  		return res
   424  	}
   425  
   426  	if schema.Items.Schema != nil {
   427  		schema = *schema.Items.Schema
   428  		if _, err := compileRegexp(schema.Pattern); err != nil {
   429  			res.AddErrors(invalidItemsPatternMsg(prefix, opID, schema.Pattern))
   430  		}
   431  
   432  		res.Merge(s.validateSchemaItems(schema, prefix, opID))
   433  	}
   434  	return res
   435  }
   436  
   437  func (s *SpecValidator) validatePathParamPresence(path string, fromPath, fromOperation []string) *Result {
   438  	// Each defined operation path parameters must correspond to a named element in the API's path pattern.
   439  	// (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.)
   440  	res := pools.poolOfResults.BorrowResult()
   441  	for _, l := range fromPath {
   442  		var matched bool
   443  		for _, r := range fromOperation {
   444  			if l == "{"+r+"}" {
   445  				matched = true
   446  				break
   447  			}
   448  		}
   449  		if !matched {
   450  			res.AddErrors(noParameterInPathMsg(l))
   451  		}
   452  	}
   453  
   454  	for _, p := range fromOperation {
   455  		var matched bool
   456  		for _, r := range fromPath {
   457  			if "{"+p+"}" == r {
   458  				matched = true
   459  				break
   460  			}
   461  		}
   462  		if !matched {
   463  			res.AddErrors(pathParamNotInPathMsg(path, p))
   464  		}
   465  	}
   466  
   467  	return res
   468  }
   469  
   470  func (s *SpecValidator) validateReferenced() *Result {
   471  	var res Result
   472  	res.MergeAsWarnings(s.validateReferencedParameters())
   473  	res.MergeAsWarnings(s.validateReferencedResponses())
   474  	res.MergeAsWarnings(s.validateReferencedDefinitions())
   475  	return &res
   476  }
   477  
   478  func (s *SpecValidator) validateReferencedParameters() *Result {
   479  	// Each referenceable definition should have references.
   480  	params := s.spec.Spec().Parameters
   481  	if len(params) == 0 {
   482  		return nil
   483  	}
   484  
   485  	expected := make(map[string]struct{})
   486  	for k := range params {
   487  		expected["#/parameters/"+jsonpointer.Escape(k)] = struct{}{}
   488  	}
   489  	for _, k := range s.analyzer.AllParameterReferences() {
   490  		delete(expected, k)
   491  	}
   492  
   493  	if len(expected) == 0 {
   494  		return nil
   495  	}
   496  	result := pools.poolOfResults.BorrowResult()
   497  	for k := range expected {
   498  		result.AddWarnings(unusedParamMsg(k))
   499  	}
   500  	return result
   501  }
   502  
   503  func (s *SpecValidator) validateReferencedResponses() *Result {
   504  	// Each referenceable definition should have references.
   505  	responses := s.spec.Spec().Responses
   506  	if len(responses) == 0 {
   507  		return nil
   508  	}
   509  
   510  	expected := make(map[string]struct{})
   511  	for k := range responses {
   512  		expected["#/responses/"+jsonpointer.Escape(k)] = struct{}{}
   513  	}
   514  	for _, k := range s.analyzer.AllResponseReferences() {
   515  		delete(expected, k)
   516  	}
   517  
   518  	if len(expected) == 0 {
   519  		return nil
   520  	}
   521  	result := pools.poolOfResults.BorrowResult()
   522  	for k := range expected {
   523  		result.AddWarnings(unusedResponseMsg(k))
   524  	}
   525  	return result
   526  }
   527  
   528  func (s *SpecValidator) validateReferencedDefinitions() *Result {
   529  	// Each referenceable definition must have references.
   530  	defs := s.spec.Spec().Definitions
   531  	if len(defs) == 0 {
   532  		return nil
   533  	}
   534  
   535  	expected := make(map[string]struct{})
   536  	for k := range defs {
   537  		expected["#/definitions/"+jsonpointer.Escape(k)] = struct{}{}
   538  	}
   539  	for _, k := range s.analyzer.AllDefinitionReferences() {
   540  		delete(expected, k)
   541  	}
   542  
   543  	if len(expected) == 0 {
   544  		return nil
   545  	}
   546  
   547  	result := new(Result)
   548  	for k := range expected {
   549  		result.AddWarnings(unusedDefinitionMsg(k))
   550  	}
   551  	return result
   552  }
   553  
   554  func (s *SpecValidator) validateRequiredDefinitions() *Result {
   555  	// Each property listed in the required array must be defined in the properties of the model
   556  	res := pools.poolOfResults.BorrowResult()
   557  
   558  DEFINITIONS:
   559  	for d, schema := range s.spec.Spec().Definitions {
   560  		if schema.Required != nil { // Safeguard
   561  			for _, pn := range schema.Required {
   562  				red := s.validateRequiredProperties(pn, d, &schema) //#nosec
   563  				res.Merge(red)
   564  				if !red.IsValid() && !s.Options.ContinueOnErrors {
   565  					break DEFINITIONS // there is an error, let's stop that bleeding
   566  				}
   567  			}
   568  		}
   569  	}
   570  	return res
   571  }
   572  
   573  func (s *SpecValidator) validateRequiredProperties(path, in string, v *spec.Schema) *Result {
   574  	// Takes care of recursive property definitions, which may be nested in additionalProperties schemas
   575  	res := pools.poolOfResults.BorrowResult()
   576  	propertyMatch := false
   577  	patternMatch := false
   578  	additionalPropertiesMatch := false
   579  	isReadOnly := false
   580  
   581  	// Regular properties
   582  	if _, ok := v.Properties[path]; ok {
   583  		propertyMatch = true
   584  		isReadOnly = v.Properties[path].ReadOnly
   585  	}
   586  
   587  	// NOTE: patternProperties are not supported in swagger. Even though, we continue validation here
   588  	// We check all defined patterns: if one regexp is invalid, croaks an error
   589  	for pp, pv := range v.PatternProperties {
   590  		re, err := compileRegexp(pp)
   591  		if err != nil {
   592  			res.AddErrors(invalidPatternMsg(pp, in))
   593  		} else if re.MatchString(path) {
   594  			patternMatch = true
   595  			if !propertyMatch {
   596  				isReadOnly = pv.ReadOnly
   597  			}
   598  		}
   599  	}
   600  
   601  	if !(propertyMatch || patternMatch) {
   602  		if v.AdditionalProperties != nil {
   603  			if v.AdditionalProperties.Allows && v.AdditionalProperties.Schema == nil {
   604  				additionalPropertiesMatch = true
   605  			} else if v.AdditionalProperties.Schema != nil {
   606  				// additionalProperties as schema are upported in swagger
   607  				// recursively validates additionalProperties schema
   608  				// TODO : anyOf, allOf, oneOf like in schemaPropsValidator
   609  				red := s.validateRequiredProperties(path, in, v.AdditionalProperties.Schema)
   610  				if red.IsValid() {
   611  					additionalPropertiesMatch = true
   612  					if !propertyMatch && !patternMatch {
   613  						isReadOnly = v.AdditionalProperties.Schema.ReadOnly
   614  					}
   615  				}
   616  				res.Merge(red)
   617  			}
   618  		}
   619  	}
   620  
   621  	if !(propertyMatch || patternMatch || additionalPropertiesMatch) {
   622  		res.AddErrors(requiredButNotDefinedMsg(path, in))
   623  	}
   624  
   625  	if isReadOnly {
   626  		res.AddWarnings(readOnlyAndRequiredMsg(in, path))
   627  	}
   628  	return res
   629  }
   630  
   631  func (s *SpecValidator) validateParameters() *Result {
   632  	// - for each method, path is unique, regardless of path parameters
   633  	//   e.g. GET:/petstore/{id}, GET:/petstore/{pet}, GET:/petstore are
   634  	//   considered duplicate paths, if StrictPathParamUniqueness is enabled.
   635  	// - each parameter should have a unique `name` and `type` combination
   636  	// - each operation should have only 1 parameter of type body
   637  	// - there must be at most 1 parameter in body
   638  	// - parameters with pattern property must specify valid patterns
   639  	// - $ref in parameters must resolve
   640  	// - path param must be required
   641  	res := pools.poolOfResults.BorrowResult()
   642  	rexGarbledPathSegment := mustCompileRegexp(`.*[{}\s]+.*`)
   643  	for method, pi := range s.expandedAnalyzer().Operations() {
   644  		methodPaths := make(map[string]map[string]string)
   645  		for path, op := range pi {
   646  			if s.Options.StrictPathParamUniqueness {
   647  				pathToAdd := pathHelp.stripParametersInPath(path)
   648  
   649  				// Warn on garbled path afer param stripping
   650  				if rexGarbledPathSegment.MatchString(pathToAdd) {
   651  					res.AddWarnings(pathStrippedParamGarbledMsg(pathToAdd))
   652  				}
   653  
   654  				// Check uniqueness of stripped paths
   655  				if _, found := methodPaths[method][pathToAdd]; found {
   656  
   657  					// Sort names for stable, testable output
   658  					if strings.Compare(path, methodPaths[method][pathToAdd]) < 0 {
   659  						res.AddErrors(pathOverlapMsg(path, methodPaths[method][pathToAdd]))
   660  					} else {
   661  						res.AddErrors(pathOverlapMsg(methodPaths[method][pathToAdd], path))
   662  					}
   663  				} else {
   664  					if _, found := methodPaths[method]; !found {
   665  						methodPaths[method] = map[string]string{}
   666  					}
   667  					methodPaths[method][pathToAdd] = path // Original non stripped path
   668  
   669  				}
   670  			}
   671  
   672  			var bodyParams []string
   673  			var paramNames []string
   674  			var hasForm, hasBody bool
   675  
   676  			// Check parameters names uniqueness for operation
   677  			// TODO: should be done after param expansion
   678  			res.Merge(s.checkUniqueParams(path, method, op))
   679  
   680  			// pick the root schema from the swagger specification which describes a parameter
   681  			origSchema, ok := s.schema.Definitions["parameter"]
   682  			if !ok {
   683  				panic("unexpected swagger schema: missing #/definitions/parameter")
   684  			}
   685  			// clone it once to avoid expanding a global schema (e.g. swagger spec)
   686  			paramSchema, err := deepCloneSchema(origSchema)
   687  			if err != nil {
   688  				panic(fmt.Errorf("can't clone schema: %v", err))
   689  			}
   690  
   691  			for _, pr := range paramHelp.safeExpandedParamsFor(path, method, op.ID, res, s) {
   692  				// An expanded parameter must validate the Parameter schema (an unexpanded $ref always passes high-level schema validation)
   693  				schv := newSchemaValidator(&paramSchema, s.schema, fmt.Sprintf("%s.%s.parameters.%s", path, method, pr.Name), s.KnownFormats, s.schemaOptions)
   694  				obj := swag.ToDynamicJSON(pr)
   695  				res.Merge(schv.Validate(obj))
   696  
   697  				// Validate pattern regexp for parameters with a Pattern property
   698  				if _, err := compileRegexp(pr.Pattern); err != nil {
   699  					res.AddErrors(invalidPatternInParamMsg(op.ID, pr.Name, pr.Pattern))
   700  				}
   701  
   702  				// There must be at most one parameter in body: list them all
   703  				if pr.In == swaggerBody {
   704  					bodyParams = append(bodyParams, fmt.Sprintf("%q", pr.Name))
   705  					hasBody = true
   706  				}
   707  
   708  				if pr.In == "path" {
   709  					paramNames = append(paramNames, pr.Name)
   710  					// Path declared in path must have the required: true property
   711  					if !pr.Required {
   712  						res.AddErrors(pathParamRequiredMsg(op.ID, pr.Name))
   713  					}
   714  				}
   715  
   716  				if pr.In == "formData" {
   717  					hasForm = true
   718  				}
   719  
   720  				if !(pr.Type == numberType || pr.Type == integerType) &&
   721  					(pr.Maximum != nil || pr.Minimum != nil || pr.MultipleOf != nil) {
   722  					// A non-numeric parameter has validation keywords for numeric instances (number and integer)
   723  					res.AddWarnings(parameterValidationTypeMismatchMsg(pr.Name, path, pr.Type))
   724  				}
   725  
   726  				if !(pr.Type == stringType) &&
   727  					// A non-string parameter has validation keywords for strings
   728  					(pr.MaxLength != nil || pr.MinLength != nil || pr.Pattern != "") {
   729  					res.AddWarnings(parameterValidationTypeMismatchMsg(pr.Name, path, pr.Type))
   730  				}
   731  
   732  				if !(pr.Type == arrayType) &&
   733  					// A non-array parameter has validation keywords for arrays
   734  					(pr.MaxItems != nil || pr.MinItems != nil || pr.UniqueItems) {
   735  					res.AddWarnings(parameterValidationTypeMismatchMsg(pr.Name, path, pr.Type))
   736  				}
   737  			}
   738  
   739  			// In:formData and In:body are mutually exclusive
   740  			if hasBody && hasForm {
   741  				res.AddErrors(bothFormDataAndBodyMsg(op.ID))
   742  			}
   743  			// There must be at most one body param
   744  			// Accurately report situations when more than 1 body param is declared (possibly unnamed)
   745  			if len(bodyParams) > 1 {
   746  				sort.Strings(bodyParams)
   747  				res.AddErrors(multipleBodyParamMsg(op.ID, bodyParams))
   748  			}
   749  
   750  			// Check uniqueness of parameters in path
   751  			paramsInPath := pathHelp.extractPathParams(path)
   752  			for i, p := range paramsInPath {
   753  				for j, q := range paramsInPath {
   754  					if p == q && i > j {
   755  						res.AddErrors(pathParamNotUniqueMsg(path, p, q))
   756  						break
   757  					}
   758  				}
   759  			}
   760  
   761  			// Warns about possible malformed params in path
   762  			rexGarbledParam := mustCompileRegexp(`{.*[{}\s]+.*}`)
   763  			for _, p := range paramsInPath {
   764  				if rexGarbledParam.MatchString(p) {
   765  					res.AddWarnings(pathParamGarbledMsg(path, p))
   766  				}
   767  			}
   768  
   769  			// Match params from path vs params from params section
   770  			res.Merge(s.validatePathParamPresence(path, paramsInPath, paramNames))
   771  		}
   772  	}
   773  	return res
   774  }
   775  
   776  func (s *SpecValidator) validateReferencesValid() *Result {
   777  	// each reference must point to a valid object
   778  	res := pools.poolOfResults.BorrowResult()
   779  	for _, r := range s.analyzer.AllRefs() {
   780  		if !r.IsValidURI(s.spec.SpecFilePath()) { // Safeguard - spec should always yield a valid URI
   781  			res.AddErrors(invalidRefMsg(r.String()))
   782  		}
   783  	}
   784  	if !res.HasErrors() {
   785  		// NOTE: with default settings, loads.Document.Expanded()
   786  		// stops on first error. Anyhow, the expand option to continue
   787  		// on errors fails to report errors at all.
   788  		exp, err := s.spec.Expanded()
   789  		if err != nil {
   790  			res.AddErrors(unresolvedReferencesMsg(err))
   791  		}
   792  		s.expanded = exp
   793  	}
   794  	return res
   795  }
   796  
   797  func (s *SpecValidator) checkUniqueParams(path, method string, op *spec.Operation) *Result {
   798  	// Check for duplicate parameters declaration in param section.
   799  	// Each parameter should have a unique `name` and `type` combination
   800  	// NOTE: this could be factorized in analysis (when constructing the params map)
   801  	// However, there are some issues with such a factorization:
   802  	// - analysis does not seem to fully expand params
   803  	// - param keys may be altered by x-go-name
   804  	res := pools.poolOfResults.BorrowResult()
   805  	pnames := make(map[string]struct{})
   806  
   807  	if op.Parameters != nil { // Safeguard
   808  		for _, ppr := range op.Parameters {
   809  			var ok bool
   810  			pr, red := paramHelp.resolveParam(path, method, op.ID, &ppr, s) //#nosec
   811  			res.Merge(red)
   812  
   813  			if pr != nil && pr.Name != "" { // params with empty name does no participate the check
   814  				key := fmt.Sprintf("%s#%s", pr.In, pr.Name)
   815  
   816  				if _, ok = pnames[key]; ok {
   817  					res.AddErrors(duplicateParamNameMsg(pr.In, pr.Name, op.ID))
   818  				}
   819  				pnames[key] = struct{}{}
   820  			}
   821  		}
   822  	}
   823  	return res
   824  }
   825  
   826  // SetContinueOnErrors sets the ContinueOnErrors option for this validator.
   827  func (s *SpecValidator) SetContinueOnErrors(c bool) {
   828  	s.Options.ContinueOnErrors = c
   829  }
   830  
   831  // expandedAnalyzer returns expanded.Analyzer when it is available.
   832  // otherwise just analyzer.
   833  func (s *SpecValidator) expandedAnalyzer() *analysis.Spec {
   834  	if s.expanded != nil && s.expanded.Analyzer != nil {
   835  		return s.expanded.Analyzer
   836  	}
   837  	return s.analyzer
   838  }
   839  
   840  func deepCloneSchema(src spec.Schema) (spec.Schema, error) {
   841  	var b bytes.Buffer
   842  	if err := gob.NewEncoder(&b).Encode(src); err != nil {
   843  		return spec.Schema{}, err
   844  	}
   845  
   846  	var dst spec.Schema
   847  	if err := gob.NewDecoder(&b).Decode(&dst); err != nil {
   848  		return spec.Schema{}, err
   849  	}
   850  
   851  	return dst, nil
   852  }
   853  

View as plain text