...

Source file src/github.com/go-openapi/validate/default_validator.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  	"fmt"
    19  	"strings"
    20  
    21  	"github.com/go-openapi/spec"
    22  )
    23  
    24  // defaultValidator validates default values in a spec.
    25  // According to Swagger spec, default values MUST validate their schema.
    26  type defaultValidator struct {
    27  	SpecValidator  *SpecValidator
    28  	visitedSchemas map[string]struct{}
    29  	schemaOptions  *SchemaValidatorOptions
    30  }
    31  
    32  // resetVisited resets the internal state of visited schemas
    33  func (d *defaultValidator) resetVisited() {
    34  	if d.visitedSchemas == nil {
    35  		d.visitedSchemas = make(map[string]struct{})
    36  
    37  		return
    38  	}
    39  
    40  	// TODO(go1.21): clear(ex.visitedSchemas)
    41  	for k := range d.visitedSchemas {
    42  		delete(d.visitedSchemas, k)
    43  	}
    44  }
    45  
    46  func isVisited(path string, visitedSchemas map[string]struct{}) bool {
    47  	_, found := visitedSchemas[path]
    48  	if found {
    49  		return true
    50  	}
    51  
    52  	// search for overlapping paths
    53  	var (
    54  		parent string
    55  		suffix string
    56  	)
    57  	for i := len(path) - 2; i >= 0; i-- {
    58  		r := path[i]
    59  		if r != '.' {
    60  			continue
    61  		}
    62  
    63  		parent = path[0:i]
    64  		suffix = path[i+1:]
    65  
    66  		if strings.HasSuffix(parent, suffix) {
    67  			return true
    68  		}
    69  	}
    70  
    71  	return false
    72  }
    73  
    74  // beingVisited asserts a schema is being visited
    75  func (d *defaultValidator) beingVisited(path string) {
    76  	d.visitedSchemas[path] = struct{}{}
    77  }
    78  
    79  // isVisited tells if a path has already been visited
    80  func (d *defaultValidator) isVisited(path string) bool {
    81  	return isVisited(path, d.visitedSchemas)
    82  }
    83  
    84  // Validate validates the default values declared in the swagger spec
    85  func (d *defaultValidator) Validate() *Result {
    86  	errs := pools.poolOfResults.BorrowResult() // will redeem when merged
    87  
    88  	if d == nil || d.SpecValidator == nil {
    89  		return errs
    90  	}
    91  	d.resetVisited()
    92  	errs.Merge(d.validateDefaultValueValidAgainstSchema()) // error -
    93  	return errs
    94  }
    95  
    96  func (d *defaultValidator) validateDefaultValueValidAgainstSchema() *Result {
    97  	// every default value that is specified must validate against the schema for that property
    98  	// headers, items, parameters, schema
    99  
   100  	res := pools.poolOfResults.BorrowResult() // will redeem when merged
   101  	s := d.SpecValidator
   102  
   103  	for method, pathItem := range s.expandedAnalyzer().Operations() {
   104  		for path, op := range pathItem {
   105  			// parameters
   106  			for _, param := range paramHelp.safeExpandedParamsFor(path, method, op.ID, res, s) {
   107  				if param.Default != nil && param.Required {
   108  					res.AddWarnings(requiredHasDefaultMsg(param.Name, param.In))
   109  				}
   110  
   111  				// reset explored schemas to get depth-first recursive-proof exploration
   112  				d.resetVisited()
   113  
   114  				// Check simple parameters first
   115  				// default values provided must validate against their inline definition (no explicit schema)
   116  				if param.Default != nil && param.Schema == nil {
   117  					// check param default value is valid
   118  					red := newParamValidator(&param, s.KnownFormats, d.schemaOptions).Validate(param.Default) //#nosec
   119  					if red.HasErrorsOrWarnings() {
   120  						res.AddErrors(defaultValueDoesNotValidateMsg(param.Name, param.In))
   121  						res.Merge(red)
   122  					} else if red.wantsRedeemOnMerge {
   123  						pools.poolOfResults.RedeemResult(red)
   124  					}
   125  				}
   126  
   127  				// Recursively follows Items and Schemas
   128  				if param.Items != nil {
   129  					red := d.validateDefaultValueItemsAgainstSchema(param.Name, param.In, &param, param.Items) //#nosec
   130  					if red.HasErrorsOrWarnings() {
   131  						res.AddErrors(defaultValueItemsDoesNotValidateMsg(param.Name, param.In))
   132  						res.Merge(red)
   133  					} else if red.wantsRedeemOnMerge {
   134  						pools.poolOfResults.RedeemResult(red)
   135  					}
   136  				}
   137  
   138  				if param.Schema != nil {
   139  					// Validate default value against schema
   140  					red := d.validateDefaultValueSchemaAgainstSchema(param.Name, param.In, param.Schema)
   141  					if red.HasErrorsOrWarnings() {
   142  						res.AddErrors(defaultValueDoesNotValidateMsg(param.Name, param.In))
   143  						res.Merge(red)
   144  					} else if red.wantsRedeemOnMerge {
   145  						pools.poolOfResults.RedeemResult(red)
   146  					}
   147  				}
   148  			}
   149  
   150  			if op.Responses != nil {
   151  				if op.Responses.Default != nil {
   152  					// Same constraint on default Response
   153  					res.Merge(d.validateDefaultInResponse(op.Responses.Default, jsonDefault, path, 0, op.ID))
   154  				}
   155  				// Same constraint on regular Responses
   156  				if op.Responses.StatusCodeResponses != nil { // Safeguard
   157  					for code, r := range op.Responses.StatusCodeResponses {
   158  						res.Merge(d.validateDefaultInResponse(&r, "response", path, code, op.ID)) //#nosec
   159  					}
   160  				}
   161  			} else if op.ID != "" {
   162  				// Empty op.ID means there is no meaningful operation: no need to report a specific message
   163  				res.AddErrors(noValidResponseMsg(op.ID))
   164  			}
   165  		}
   166  	}
   167  	if s.spec.Spec().Definitions != nil { // Safeguard
   168  		// reset explored schemas to get depth-first recursive-proof exploration
   169  		d.resetVisited()
   170  		for nm, sch := range s.spec.Spec().Definitions {
   171  			res.Merge(d.validateDefaultValueSchemaAgainstSchema("definitions."+nm, "body", &sch)) //#nosec
   172  		}
   173  	}
   174  	return res
   175  }
   176  
   177  func (d *defaultValidator) validateDefaultInResponse(resp *spec.Response, responseType, path string, responseCode int, operationID string) *Result {
   178  	s := d.SpecValidator
   179  
   180  	response, res := responseHelp.expandResponseRef(resp, path, s)
   181  	if !res.IsValid() {
   182  		return res
   183  	}
   184  
   185  	responseName, responseCodeAsStr := responseHelp.responseMsgVariants(responseType, responseCode)
   186  
   187  	if response.Headers != nil { // Safeguard
   188  		for nm, h := range response.Headers {
   189  			// reset explored schemas to get depth-first recursive-proof exploration
   190  			d.resetVisited()
   191  
   192  			if h.Default != nil {
   193  				red := newHeaderValidator(nm, &h, s.KnownFormats, d.schemaOptions).Validate(h.Default) //#nosec
   194  				if red.HasErrorsOrWarnings() {
   195  					res.AddErrors(defaultValueHeaderDoesNotValidateMsg(operationID, nm, responseName))
   196  					res.Merge(red)
   197  				} else if red.wantsRedeemOnMerge {
   198  					pools.poolOfResults.RedeemResult(red)
   199  				}
   200  			}
   201  
   202  			// Headers have inline definition, like params
   203  			if h.Items != nil {
   204  				red := d.validateDefaultValueItemsAgainstSchema(nm, "header", &h, h.Items) //#nosec
   205  				if red.HasErrorsOrWarnings() {
   206  					res.AddErrors(defaultValueHeaderItemsDoesNotValidateMsg(operationID, nm, responseName))
   207  					res.Merge(red)
   208  				} else if red.wantsRedeemOnMerge {
   209  					pools.poolOfResults.RedeemResult(red)
   210  				}
   211  			}
   212  
   213  			if _, err := compileRegexp(h.Pattern); err != nil {
   214  				res.AddErrors(invalidPatternInHeaderMsg(operationID, nm, responseName, h.Pattern, err))
   215  			}
   216  
   217  			// Headers don't have schema
   218  		}
   219  	}
   220  	if response.Schema != nil {
   221  		// reset explored schemas to get depth-first recursive-proof exploration
   222  		d.resetVisited()
   223  
   224  		red := d.validateDefaultValueSchemaAgainstSchema(responseCodeAsStr, "response", response.Schema)
   225  		if red.HasErrorsOrWarnings() {
   226  			// Additional message to make sure the context of the error is not lost
   227  			res.AddErrors(defaultValueInDoesNotValidateMsg(operationID, responseName))
   228  			res.Merge(red)
   229  		} else if red.wantsRedeemOnMerge {
   230  			pools.poolOfResults.RedeemResult(red)
   231  		}
   232  	}
   233  	return res
   234  }
   235  
   236  func (d *defaultValidator) validateDefaultValueSchemaAgainstSchema(path, in string, schema *spec.Schema) *Result {
   237  	if schema == nil || d.isVisited(path) {
   238  		// Avoids recursing if we are already done with that check
   239  		return nil
   240  	}
   241  	d.beingVisited(path)
   242  	res := pools.poolOfResults.BorrowResult()
   243  	s := d.SpecValidator
   244  
   245  	if schema.Default != nil {
   246  		res.Merge(
   247  			newSchemaValidator(schema, s.spec.Spec(), path+".default", s.KnownFormats, d.schemaOptions).Validate(schema.Default),
   248  		)
   249  	}
   250  	if schema.Items != nil {
   251  		if schema.Items.Schema != nil {
   252  			res.Merge(d.validateDefaultValueSchemaAgainstSchema(path+".items.default", in, schema.Items.Schema))
   253  		}
   254  		// Multiple schemas in items
   255  		if schema.Items.Schemas != nil { // Safeguard
   256  			for i, sch := range schema.Items.Schemas {
   257  				res.Merge(d.validateDefaultValueSchemaAgainstSchema(fmt.Sprintf("%s.items[%d].default", path, i), in, &sch)) //#nosec
   258  			}
   259  		}
   260  	}
   261  	if _, err := compileRegexp(schema.Pattern); err != nil {
   262  		res.AddErrors(invalidPatternInMsg(path, in, schema.Pattern))
   263  	}
   264  	if schema.AdditionalItems != nil && schema.AdditionalItems.Schema != nil {
   265  		// NOTE: we keep validating values, even though additionalItems is not supported by Swagger 2.0 (and 3.0 as well)
   266  		res.Merge(d.validateDefaultValueSchemaAgainstSchema(path+".additionalItems", in, schema.AdditionalItems.Schema))
   267  	}
   268  	for propName, prop := range schema.Properties {
   269  		res.Merge(d.validateDefaultValueSchemaAgainstSchema(path+"."+propName, in, &prop)) //#nosec
   270  	}
   271  	for propName, prop := range schema.PatternProperties {
   272  		res.Merge(d.validateDefaultValueSchemaAgainstSchema(path+"."+propName, in, &prop)) //#nosec
   273  	}
   274  	if schema.AdditionalProperties != nil && schema.AdditionalProperties.Schema != nil {
   275  		res.Merge(d.validateDefaultValueSchemaAgainstSchema(path+".additionalProperties", in, schema.AdditionalProperties.Schema))
   276  	}
   277  	if schema.AllOf != nil {
   278  		for i, aoSch := range schema.AllOf {
   279  			res.Merge(d.validateDefaultValueSchemaAgainstSchema(fmt.Sprintf("%s.allOf[%d]", path, i), in, &aoSch)) //#nosec
   280  		}
   281  	}
   282  	return res
   283  }
   284  
   285  // TODO: Temporary duplicated code. Need to refactor with examples
   286  
   287  func (d *defaultValidator) validateDefaultValueItemsAgainstSchema(path, in string, root interface{}, items *spec.Items) *Result {
   288  	res := pools.poolOfResults.BorrowResult()
   289  	s := d.SpecValidator
   290  	if items != nil {
   291  		if items.Default != nil {
   292  			res.Merge(
   293  				newItemsValidator(path, in, items, root, s.KnownFormats, d.schemaOptions).Validate(0, items.Default),
   294  			)
   295  		}
   296  		if items.Items != nil {
   297  			res.Merge(d.validateDefaultValueItemsAgainstSchema(path+"[0].default", in, root, items.Items))
   298  		}
   299  		if _, err := compileRegexp(items.Pattern); err != nil {
   300  			res.AddErrors(invalidPatternInMsg(path, in, items.Pattern))
   301  		}
   302  	}
   303  	return res
   304  }
   305  

View as plain text