...

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

View as plain text