123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265 |
- // 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 (
- "reflect"
- "regexp"
- "strings"
- "github.com/go-openapi/errors"
- "github.com/go-openapi/spec"
- "github.com/go-openapi/strfmt"
- )
- type objectValidator struct {
- Path string
- In string
- MaxProperties *int64
- MinProperties *int64
- Required []string
- Properties map[string]spec.Schema
- AdditionalProperties *spec.SchemaOrBool
- PatternProperties map[string]spec.Schema
- Root interface{}
- KnownFormats strfmt.Registry
- }
- func (o *objectValidator) SetPath(path string) {
- o.Path = path
- }
- func (o *objectValidator) Applies(source interface{}, kind reflect.Kind) bool {
- // TODO: this should also work for structs
- // there is a problem in the type validator where it will be unhappy about null values
- // so that requires more testing
- r := reflect.TypeOf(source) == specSchemaType && (kind == reflect.Map || kind == reflect.Struct)
- debugLog("object validator for %q applies %t for %T (kind: %v)\n", o.Path, r, source, kind)
- return r
- }
- func (o *objectValidator) isPropertyName() bool {
- p := strings.Split(o.Path, ".")
- return p[len(p)-1] == "properties" && p[len(p)-2] != "properties"
- }
- func (o *objectValidator) checkArrayMustHaveItems(res *Result, val map[string]interface{}) {
- if t, typeFound := val["type"]; typeFound {
- if tpe, ok := t.(string); ok && tpe == "array" {
- if _, itemsKeyFound := val["items"]; !itemsKeyFound {
- res.AddErrors(errors.Required("items", o.Path))
- }
- }
- }
- }
- func (o *objectValidator) checkItemsMustBeTypeArray(res *Result, val map[string]interface{}) {
- if !o.isPropertyName() {
- if _, itemsKeyFound := val["items"]; itemsKeyFound {
- t, typeFound := val["type"]
- if typeFound {
- if tpe, ok := t.(string); !ok || tpe != "array" {
- res.AddErrors(errors.InvalidType(o.Path, o.In, "array", nil))
- }
- } else {
- // there is no type
- res.AddErrors(errors.Required("type", o.Path))
- }
- }
- }
- }
- func (o *objectValidator) precheck(res *Result, val map[string]interface{}) {
- o.checkArrayMustHaveItems(res, val)
- o.checkItemsMustBeTypeArray(res, val)
- }
- func (o *objectValidator) Validate(data interface{}) *Result {
- val := data.(map[string]interface{})
- // TODO: guard against nil data
- numKeys := int64(len(val))
- if o.MinProperties != nil && numKeys < *o.MinProperties {
- return errorHelp.sErr(errors.TooFewProperties(o.Path, o.In, *o.MinProperties))
- }
- if o.MaxProperties != nil && numKeys > *o.MaxProperties {
- return errorHelp.sErr(errors.TooManyProperties(o.Path, o.In, *o.MaxProperties))
- }
- res := new(Result)
- o.precheck(res, val)
- // check validity of field names
- if o.AdditionalProperties != nil && !o.AdditionalProperties.Allows {
- // Case: additionalProperties: false
- for k := range val {
- _, regularProperty := o.Properties[k]
- matched := false
- for pk := range o.PatternProperties {
- if matches, _ := regexp.MatchString(pk, k); matches {
- matched = true
- break
- }
- }
- if !regularProperty && k != "$schema" && k != "id" && !matched {
- // Special properties "$schema" and "id" are ignored
- res.AddErrors(errors.PropertyNotAllowed(o.Path, o.In, k))
- // BUG(fredbi): This section should move to a part dedicated to spec validation as
- // it will conflict with regular schemas where a property "headers" is defined.
- //
- // Croaks a more explicit message on top of the standard one
- // on some recognized cases.
- //
- // NOTE: edge cases with invalid type assertion are simply ignored here.
- // NOTE: prefix your messages here by "IMPORTANT!" so there are not filtered
- // by higher level callers (the IMPORTANT! tag will be eventually
- // removed).
- switch k {
- // $ref is forbidden in header
- case "headers":
- if val[k] != nil {
- if headers, mapOk := val[k].(map[string]interface{}); mapOk {
- for headerKey, headerBody := range headers {
- if headerBody != nil {
- if headerSchema, mapOfMapOk := headerBody.(map[string]interface{}); mapOfMapOk {
- if _, found := headerSchema["$ref"]; found {
- var msg string
- if refString, stringOk := headerSchema["$ref"].(string); stringOk {
- msg = strings.Join([]string{", one may not use $ref=\":", refString, "\""}, "")
- }
- res.AddErrors(refNotAllowedInHeaderMsg(o.Path, headerKey, msg))
- }
- }
- }
- }
- }
- }
- /*
- case "$ref":
- if val[k] != nil {
- // TODO: check context of that ref: warn about siblings, check against invalid context
- }
- */
- }
- }
- }
- } else {
- // Cases: no additionalProperties (implying: true), or additionalProperties: true, or additionalProperties: { <<schema>> }
- for key, value := range val {
- _, regularProperty := o.Properties[key]
- // Validates property against "patternProperties" if applicable
- // BUG(fredbi): succeededOnce is always false
- // NOTE: how about regular properties which do not match patternProperties?
- matched, succeededOnce, _ := o.validatePatternProperty(key, value, res)
- if !(regularProperty || matched || succeededOnce) {
- // Cases: properties which are not regular properties and have not been matched by the PatternProperties validator
- if o.AdditionalProperties != nil && o.AdditionalProperties.Schema != nil {
- // AdditionalProperties as Schema
- r := NewSchemaValidator(o.AdditionalProperties.Schema, o.Root, o.Path+"."+key, o.KnownFormats).Validate(value)
- res.mergeForField(data.(map[string]interface{}), key, r)
- } else if regularProperty && !(matched || succeededOnce) {
- // TODO: this is dead code since regularProperty=false here
- res.AddErrors(errors.FailedAllPatternProperties(o.Path, o.In, key))
- }
- }
- }
- // Valid cases: additionalProperties: true or undefined
- }
- createdFromDefaults := map[string]bool{}
- // Property types:
- // - regular Property
- for pName := range o.Properties {
- pSchema := o.Properties[pName] // one instance per iteration
- rName := pName
- if o.Path != "" {
- rName = o.Path + "." + pName
- }
- // Recursively validates each property against its schema
- if v, ok := val[pName]; ok {
- r := NewSchemaValidator(&pSchema, o.Root, rName, o.KnownFormats).Validate(v)
- res.mergeForField(data.(map[string]interface{}), pName, r)
- } else if pSchema.Default != nil {
- // If a default value is defined, creates the property from defaults
- // NOTE: JSON schema does not enforce default values to be valid against schema. Swagger does.
- createdFromDefaults[pName] = true
- res.addPropertySchemata(data.(map[string]interface{}), pName, &pSchema)
- }
- }
- // Check required properties
- if len(o.Required) > 0 {
- for _, k := range o.Required {
- if _, ok := val[k]; !ok && !createdFromDefaults[k] {
- res.AddErrors(errors.Required(o.Path+"."+k, o.In))
- continue
- }
- }
- }
- // Check patternProperties
- // TODO: it looks like we have done that twice in many cases
- for key, value := range val {
- _, regularProperty := o.Properties[key]
- matched, _ /*succeededOnce*/, patterns := o.validatePatternProperty(key, value, res)
- if !regularProperty && (matched /*|| succeededOnce*/) {
- for _, pName := range patterns {
- if v, ok := o.PatternProperties[pName]; ok {
- r := NewSchemaValidator(&v, o.Root, o.Path+"."+key, o.KnownFormats).Validate(value)
- res.mergeForField(data.(map[string]interface{}), key, r)
- }
- }
- }
- }
- return res
- }
- // TODO: succeededOnce is not used anywhere
- func (o *objectValidator) validatePatternProperty(key string, value interface{}, result *Result) (bool, bool, []string) {
- matched := false
- succeededOnce := false
- var patterns []string
- for k, schema := range o.PatternProperties {
- if match, _ := regexp.MatchString(k, key); match {
- patterns = append(patterns, k)
- matched = true
- validator := NewSchemaValidator(&schema, o.Root, o.Path+"."+key, o.KnownFormats)
- res := validator.Validate(value)
- result.Merge(res)
- }
- }
- // BUG(fredbi): can't get to here. Should remove dead code (commented out).
- //if succeededOnce {
- // result.Inc()
- //}
- return matched, succeededOnce, patterns
- }
|