...

Source file src/github.com/go-openapi/validate/object_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  	"reflect"
    20  	"strings"
    21  
    22  	"github.com/go-openapi/errors"
    23  	"github.com/go-openapi/spec"
    24  	"github.com/go-openapi/strfmt"
    25  )
    26  
    27  type objectValidator struct {
    28  	Path                 string
    29  	In                   string
    30  	MaxProperties        *int64
    31  	MinProperties        *int64
    32  	Required             []string
    33  	Properties           map[string]spec.Schema
    34  	AdditionalProperties *spec.SchemaOrBool
    35  	PatternProperties    map[string]spec.Schema
    36  	Root                 interface{}
    37  	KnownFormats         strfmt.Registry
    38  	Options              *SchemaValidatorOptions
    39  	splitPath            []string
    40  }
    41  
    42  func newObjectValidator(path, in string,
    43  	maxProperties, minProperties *int64, required []string, properties spec.SchemaProperties,
    44  	additionalProperties *spec.SchemaOrBool, patternProperties spec.SchemaProperties,
    45  	root interface{}, formats strfmt.Registry, opts *SchemaValidatorOptions) *objectValidator {
    46  	if opts == nil {
    47  		opts = new(SchemaValidatorOptions)
    48  	}
    49  
    50  	var v *objectValidator
    51  	if opts.recycleValidators {
    52  		v = pools.poolOfObjectValidators.BorrowValidator()
    53  	} else {
    54  		v = new(objectValidator)
    55  	}
    56  
    57  	v.Path = path
    58  	v.In = in
    59  	v.MaxProperties = maxProperties
    60  	v.MinProperties = minProperties
    61  	v.Required = required
    62  	v.Properties = properties
    63  	v.AdditionalProperties = additionalProperties
    64  	v.PatternProperties = patternProperties
    65  	v.Root = root
    66  	v.KnownFormats = formats
    67  	v.Options = opts
    68  	v.splitPath = strings.Split(v.Path, ".")
    69  
    70  	return v
    71  }
    72  
    73  func (o *objectValidator) SetPath(path string) {
    74  	o.Path = path
    75  	o.splitPath = strings.Split(path, ".")
    76  }
    77  
    78  func (o *objectValidator) Applies(source interface{}, kind reflect.Kind) bool {
    79  	// TODO: this should also work for structs
    80  	// there is a problem in the type validator where it will be unhappy about null values
    81  	// so that requires more testing
    82  	_, isSchema := source.(*spec.Schema)
    83  	return isSchema && (kind == reflect.Map || kind == reflect.Struct)
    84  }
    85  
    86  func (o *objectValidator) isProperties() bool {
    87  	p := o.splitPath
    88  	return len(p) > 1 && p[len(p)-1] == jsonProperties && p[len(p)-2] != jsonProperties
    89  }
    90  
    91  func (o *objectValidator) isDefault() bool {
    92  	p := o.splitPath
    93  	return len(p) > 1 && p[len(p)-1] == jsonDefault && p[len(p)-2] != jsonDefault
    94  }
    95  
    96  func (o *objectValidator) isExample() bool {
    97  	p := o.splitPath
    98  	return len(p) > 1 && (p[len(p)-1] == swaggerExample || p[len(p)-1] == swaggerExamples) && p[len(p)-2] != swaggerExample
    99  }
   100  
   101  func (o *objectValidator) checkArrayMustHaveItems(res *Result, val map[string]interface{}) {
   102  	// for swagger 2.0 schemas, there is an additional constraint to have array items defined explicitly.
   103  	// with pure jsonschema draft 4, one may have arrays with undefined items (i.e. any type).
   104  	if val == nil {
   105  		return
   106  	}
   107  
   108  	t, typeFound := val[jsonType]
   109  	if !typeFound {
   110  		return
   111  	}
   112  
   113  	tpe, isString := t.(string)
   114  	if !isString || tpe != arrayType {
   115  		return
   116  	}
   117  
   118  	item, itemsKeyFound := val[jsonItems]
   119  	if itemsKeyFound {
   120  		return
   121  	}
   122  
   123  	res.AddErrors(errors.Required(jsonItems, o.Path, item))
   124  }
   125  
   126  func (o *objectValidator) checkItemsMustBeTypeArray(res *Result, val map[string]interface{}) {
   127  	if val == nil {
   128  		return
   129  	}
   130  
   131  	if o.isProperties() || o.isDefault() || o.isExample() {
   132  		return
   133  	}
   134  
   135  	_, itemsKeyFound := val[jsonItems]
   136  	if !itemsKeyFound {
   137  		return
   138  	}
   139  
   140  	t, typeFound := val[jsonType]
   141  	if !typeFound {
   142  		// there is no type
   143  		res.AddErrors(errors.Required(jsonType, o.Path, t))
   144  	}
   145  
   146  	if tpe, isString := t.(string); !isString || tpe != arrayType {
   147  		res.AddErrors(errors.InvalidType(o.Path, o.In, arrayType, nil))
   148  	}
   149  }
   150  
   151  func (o *objectValidator) precheck(res *Result, val map[string]interface{}) {
   152  	if o.Options.EnableArrayMustHaveItemsCheck {
   153  		o.checkArrayMustHaveItems(res, val)
   154  	}
   155  	if o.Options.EnableObjectArrayTypeCheck {
   156  		o.checkItemsMustBeTypeArray(res, val)
   157  	}
   158  }
   159  
   160  func (o *objectValidator) Validate(data interface{}) *Result {
   161  	if o.Options.recycleValidators {
   162  		defer func() {
   163  			o.redeem()
   164  		}()
   165  	}
   166  
   167  	var val map[string]interface{}
   168  	if data != nil {
   169  		var ok bool
   170  		val, ok = data.(map[string]interface{})
   171  		if !ok {
   172  			return errorHelp.sErr(invalidObjectMsg(o.Path, o.In), o.Options.recycleResult)
   173  		}
   174  	}
   175  	numKeys := int64(len(val))
   176  
   177  	if o.MinProperties != nil && numKeys < *o.MinProperties {
   178  		return errorHelp.sErr(errors.TooFewProperties(o.Path, o.In, *o.MinProperties), o.Options.recycleResult)
   179  	}
   180  	if o.MaxProperties != nil && numKeys > *o.MaxProperties {
   181  		return errorHelp.sErr(errors.TooManyProperties(o.Path, o.In, *o.MaxProperties), o.Options.recycleResult)
   182  	}
   183  
   184  	var res *Result
   185  	if o.Options.recycleResult {
   186  		res = pools.poolOfResults.BorrowResult()
   187  	} else {
   188  		res = new(Result)
   189  	}
   190  
   191  	o.precheck(res, val)
   192  
   193  	// check validity of field names
   194  	if o.AdditionalProperties != nil && !o.AdditionalProperties.Allows {
   195  		// Case: additionalProperties: false
   196  		o.validateNoAdditionalProperties(val, res)
   197  	} else {
   198  		// Cases: empty additionalProperties (implying: true), or additionalProperties: true, or additionalProperties: { <<schema>> }
   199  		o.validateAdditionalProperties(val, res)
   200  	}
   201  
   202  	o.validatePropertiesSchema(val, res)
   203  
   204  	// Check patternProperties
   205  	// TODO: it looks like we have done that twice in many cases
   206  	for key, value := range val {
   207  		_, regularProperty := o.Properties[key]
   208  		matched, _, patterns := o.validatePatternProperty(key, value, res) // applies to regular properties as well
   209  		if regularProperty || !matched {
   210  			continue
   211  		}
   212  
   213  		for _, pName := range patterns {
   214  			if v, ok := o.PatternProperties[pName]; ok {
   215  				r := newSchemaValidator(&v, o.Root, o.Path+"."+key, o.KnownFormats, o.Options).Validate(value)
   216  				res.mergeForField(data.(map[string]interface{}), key, r)
   217  			}
   218  		}
   219  	}
   220  
   221  	return res
   222  }
   223  
   224  func (o *objectValidator) validateNoAdditionalProperties(val map[string]interface{}, res *Result) {
   225  	for k := range val {
   226  		if k == "$schema" || k == "id" {
   227  			// special properties "$schema" and "id" are ignored
   228  			continue
   229  		}
   230  
   231  		_, regularProperty := o.Properties[k]
   232  		if regularProperty {
   233  			continue
   234  		}
   235  
   236  		matched := false
   237  		for pk := range o.PatternProperties {
   238  			re, err := compileRegexp(pk)
   239  			if err != nil {
   240  				continue
   241  			}
   242  			if matches := re.MatchString(k); matches {
   243  				matched = true
   244  				break
   245  			}
   246  		}
   247  		if matched {
   248  			continue
   249  		}
   250  
   251  		res.AddErrors(errors.PropertyNotAllowed(o.Path, o.In, k))
   252  
   253  		// BUG(fredbi): This section should move to a part dedicated to spec validation as
   254  		// it will conflict with regular schemas where a property "headers" is defined.
   255  
   256  		//
   257  		// Croaks a more explicit message on top of the standard one
   258  		// on some recognized cases.
   259  		//
   260  		// NOTE: edge cases with invalid type assertion are simply ignored here.
   261  		// NOTE: prefix your messages here by "IMPORTANT!" so there are not filtered
   262  		// by higher level callers (the IMPORTANT! tag will be eventually
   263  		// removed).
   264  		if k != "headers" || val[k] == nil {
   265  			continue
   266  		}
   267  
   268  		// $ref is forbidden in header
   269  		headers, mapOk := val[k].(map[string]interface{})
   270  		if !mapOk {
   271  			continue
   272  		}
   273  
   274  		for headerKey, headerBody := range headers {
   275  			if headerBody == nil {
   276  				continue
   277  			}
   278  
   279  			headerSchema, mapOfMapOk := headerBody.(map[string]interface{})
   280  			if !mapOfMapOk {
   281  				continue
   282  			}
   283  
   284  			_, found := headerSchema["$ref"]
   285  			if !found {
   286  				continue
   287  			}
   288  
   289  			refString, stringOk := headerSchema["$ref"].(string)
   290  			if !stringOk {
   291  				continue
   292  			}
   293  
   294  			msg := strings.Join([]string{", one may not use $ref=\":", refString, "\""}, "")
   295  			res.AddErrors(refNotAllowedInHeaderMsg(o.Path, headerKey, msg))
   296  			/*
   297  				case "$ref":
   298  					if val[k] != nil {
   299  						// TODO: check context of that ref: warn about siblings, check against invalid context
   300  					}
   301  			*/
   302  		}
   303  	}
   304  }
   305  
   306  func (o *objectValidator) validateAdditionalProperties(val map[string]interface{}, res *Result) {
   307  	for key, value := range val {
   308  		_, regularProperty := o.Properties[key]
   309  		if regularProperty {
   310  			continue
   311  		}
   312  
   313  		// Validates property against "patternProperties" if applicable
   314  		// BUG(fredbi): succeededOnce is always false
   315  
   316  		// NOTE: how about regular properties which do not match patternProperties?
   317  		matched, succeededOnce, _ := o.validatePatternProperty(key, value, res)
   318  		if matched || succeededOnce {
   319  			continue
   320  		}
   321  
   322  		if o.AdditionalProperties == nil || o.AdditionalProperties.Schema == nil {
   323  			continue
   324  		}
   325  
   326  		// Cases: properties which are not regular properties and have not been matched by the PatternProperties validator
   327  		// AdditionalProperties as Schema
   328  		r := newSchemaValidator(o.AdditionalProperties.Schema, o.Root, o.Path+"."+key, o.KnownFormats, o.Options).Validate(value)
   329  		res.mergeForField(val, key, r)
   330  	}
   331  	// Valid cases: additionalProperties: true or undefined
   332  }
   333  
   334  func (o *objectValidator) validatePropertiesSchema(val map[string]interface{}, res *Result) {
   335  	createdFromDefaults := map[string]struct{}{}
   336  
   337  	// Property types:
   338  	// - regular Property
   339  	pSchema := pools.poolOfSchemas.BorrowSchema() // recycle a spec.Schema object which lifespan extends only to the validation of properties
   340  	defer func() {
   341  		pools.poolOfSchemas.RedeemSchema(pSchema)
   342  	}()
   343  
   344  	for pName := range o.Properties {
   345  		*pSchema = o.Properties[pName]
   346  		var rName string
   347  		if o.Path == "" {
   348  			rName = pName
   349  		} else {
   350  			rName = o.Path + "." + pName
   351  		}
   352  
   353  		// Recursively validates each property against its schema
   354  		v, ok := val[pName]
   355  		if ok {
   356  			r := newSchemaValidator(pSchema, o.Root, rName, o.KnownFormats, o.Options).Validate(v)
   357  			res.mergeForField(val, pName, r)
   358  
   359  			continue
   360  		}
   361  
   362  		if pSchema.Default != nil {
   363  			// if a default value is defined, creates the property from defaults
   364  			// NOTE: JSON schema does not enforce default values to be valid against schema. Swagger does.
   365  			createdFromDefaults[pName] = struct{}{}
   366  			if !o.Options.skipSchemataResult {
   367  				res.addPropertySchemata(val, pName, pSchema) // this shallow-clones the content of the pSchema pointer
   368  			}
   369  		}
   370  	}
   371  
   372  	if len(o.Required) == 0 {
   373  		return
   374  	}
   375  
   376  	// Check required properties
   377  	for _, k := range o.Required {
   378  		v, ok := val[k]
   379  		if ok {
   380  			continue
   381  		}
   382  		_, isCreatedFromDefaults := createdFromDefaults[k]
   383  		if isCreatedFromDefaults {
   384  			continue
   385  		}
   386  
   387  		res.AddErrors(errors.Required(fmt.Sprintf("%s.%s", o.Path, k), o.In, v))
   388  	}
   389  }
   390  
   391  // TODO: succeededOnce is not used anywhere
   392  func (o *objectValidator) validatePatternProperty(key string, value interface{}, result *Result) (bool, bool, []string) {
   393  	if len(o.PatternProperties) == 0 {
   394  		return false, false, nil
   395  	}
   396  
   397  	matched := false
   398  	succeededOnce := false
   399  	patterns := make([]string, 0, len(o.PatternProperties))
   400  
   401  	schema := pools.poolOfSchemas.BorrowSchema()
   402  	defer func() {
   403  		pools.poolOfSchemas.RedeemSchema(schema)
   404  	}()
   405  
   406  	for k := range o.PatternProperties {
   407  		re, err := compileRegexp(k)
   408  		if err != nil {
   409  			continue
   410  		}
   411  
   412  		match := re.MatchString(key)
   413  		if !match {
   414  			continue
   415  		}
   416  
   417  		*schema = o.PatternProperties[k]
   418  		patterns = append(patterns, k)
   419  		matched = true
   420  		validator := newSchemaValidator(schema, o.Root, fmt.Sprintf("%s.%s", o.Path, key), o.KnownFormats, o.Options)
   421  
   422  		res := validator.Validate(value)
   423  		result.Merge(res)
   424  	}
   425  
   426  	return matched, succeededOnce, patterns
   427  }
   428  
   429  func (o *objectValidator) redeem() {
   430  	pools.poolOfObjectValidators.RedeemValidator(o)
   431  }
   432  

View as plain text