Serge Bazanski | cc25bdf | 2018-10-25 14:02:58 +0200 | [diff] [blame] | 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 | "reflect" |
| 19 | "regexp" |
| 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 | } |
| 39 | |
| 40 | func (o *objectValidator) SetPath(path string) { |
| 41 | o.Path = path |
| 42 | } |
| 43 | |
| 44 | func (o *objectValidator) Applies(source interface{}, kind reflect.Kind) bool { |
| 45 | // TODO: this should also work for structs |
| 46 | // there is a problem in the type validator where it will be unhappy about null values |
| 47 | // so that requires more testing |
| 48 | r := reflect.TypeOf(source) == specSchemaType && (kind == reflect.Map || kind == reflect.Struct) |
| 49 | debugLog("object validator for %q applies %t for %T (kind: %v)\n", o.Path, r, source, kind) |
| 50 | return r |
| 51 | } |
| 52 | |
| 53 | func (o *objectValidator) isPropertyName() bool { |
| 54 | p := strings.Split(o.Path, ".") |
| 55 | return p[len(p)-1] == "properties" && p[len(p)-2] != "properties" |
| 56 | } |
| 57 | |
| 58 | func (o *objectValidator) checkArrayMustHaveItems(res *Result, val map[string]interface{}) { |
| 59 | if t, typeFound := val["type"]; typeFound { |
| 60 | if tpe, ok := t.(string); ok && tpe == "array" { |
| 61 | if _, itemsKeyFound := val["items"]; !itemsKeyFound { |
| 62 | res.AddErrors(errors.Required("items", o.Path)) |
| 63 | } |
| 64 | } |
| 65 | } |
| 66 | } |
| 67 | |
| 68 | func (o *objectValidator) checkItemsMustBeTypeArray(res *Result, val map[string]interface{}) { |
| 69 | if !o.isPropertyName() { |
| 70 | if _, itemsKeyFound := val["items"]; itemsKeyFound { |
| 71 | t, typeFound := val["type"] |
| 72 | if typeFound { |
| 73 | if tpe, ok := t.(string); !ok || tpe != "array" { |
| 74 | res.AddErrors(errors.InvalidType(o.Path, o.In, "array", nil)) |
| 75 | } |
| 76 | } else { |
| 77 | // there is no type |
| 78 | res.AddErrors(errors.Required("type", o.Path)) |
| 79 | } |
| 80 | } |
| 81 | } |
| 82 | } |
| 83 | |
| 84 | func (o *objectValidator) precheck(res *Result, val map[string]interface{}) { |
| 85 | o.checkArrayMustHaveItems(res, val) |
| 86 | o.checkItemsMustBeTypeArray(res, val) |
| 87 | } |
| 88 | |
| 89 | func (o *objectValidator) Validate(data interface{}) *Result { |
| 90 | val := data.(map[string]interface{}) |
| 91 | // TODO: guard against nil data |
| 92 | numKeys := int64(len(val)) |
| 93 | |
| 94 | if o.MinProperties != nil && numKeys < *o.MinProperties { |
| 95 | return errorHelp.sErr(errors.TooFewProperties(o.Path, o.In, *o.MinProperties)) |
| 96 | } |
| 97 | if o.MaxProperties != nil && numKeys > *o.MaxProperties { |
| 98 | return errorHelp.sErr(errors.TooManyProperties(o.Path, o.In, *o.MaxProperties)) |
| 99 | } |
| 100 | |
| 101 | res := new(Result) |
| 102 | |
| 103 | o.precheck(res, val) |
| 104 | |
| 105 | // check validity of field names |
| 106 | if o.AdditionalProperties != nil && !o.AdditionalProperties.Allows { |
| 107 | // Case: additionalProperties: false |
| 108 | for k := range val { |
| 109 | _, regularProperty := o.Properties[k] |
| 110 | matched := false |
| 111 | |
| 112 | for pk := range o.PatternProperties { |
| 113 | if matches, _ := regexp.MatchString(pk, k); matches { |
| 114 | matched = true |
| 115 | break |
| 116 | } |
| 117 | } |
| 118 | |
| 119 | if !regularProperty && k != "$schema" && k != "id" && !matched { |
| 120 | // Special properties "$schema" and "id" are ignored |
| 121 | res.AddErrors(errors.PropertyNotAllowed(o.Path, o.In, k)) |
| 122 | |
| 123 | // BUG(fredbi): This section should move to a part dedicated to spec validation as |
| 124 | // it will conflict with regular schemas where a property "headers" is defined. |
| 125 | |
| 126 | // |
| 127 | // Croaks a more explicit message on top of the standard one |
| 128 | // on some recognized cases. |
| 129 | // |
| 130 | // NOTE: edge cases with invalid type assertion are simply ignored here. |
| 131 | // NOTE: prefix your messages here by "IMPORTANT!" so there are not filtered |
| 132 | // by higher level callers (the IMPORTANT! tag will be eventually |
| 133 | // removed). |
| 134 | switch k { |
| 135 | // $ref is forbidden in header |
| 136 | case "headers": |
| 137 | if val[k] != nil { |
| 138 | if headers, mapOk := val[k].(map[string]interface{}); mapOk { |
| 139 | for headerKey, headerBody := range headers { |
| 140 | if headerBody != nil { |
| 141 | if headerSchema, mapOfMapOk := headerBody.(map[string]interface{}); mapOfMapOk { |
| 142 | if _, found := headerSchema["$ref"]; found { |
| 143 | var msg string |
| 144 | if refString, stringOk := headerSchema["$ref"].(string); stringOk { |
| 145 | msg = strings.Join([]string{", one may not use $ref=\":", refString, "\""}, "") |
| 146 | } |
| 147 | res.AddErrors(refNotAllowedInHeaderMsg(o.Path, headerKey, msg)) |
| 148 | } |
| 149 | } |
| 150 | } |
| 151 | } |
| 152 | } |
| 153 | } |
| 154 | /* |
| 155 | case "$ref": |
| 156 | if val[k] != nil { |
| 157 | // TODO: check context of that ref: warn about siblings, check against invalid context |
| 158 | } |
| 159 | */ |
| 160 | } |
| 161 | } |
| 162 | } |
| 163 | } else { |
| 164 | // Cases: no additionalProperties (implying: true), or additionalProperties: true, or additionalProperties: { <<schema>> } |
| 165 | for key, value := range val { |
| 166 | _, regularProperty := o.Properties[key] |
| 167 | |
| 168 | // Validates property against "patternProperties" if applicable |
| 169 | // BUG(fredbi): succeededOnce is always false |
| 170 | |
| 171 | // NOTE: how about regular properties which do not match patternProperties? |
| 172 | matched, succeededOnce, _ := o.validatePatternProperty(key, value, res) |
| 173 | |
| 174 | if !(regularProperty || matched || succeededOnce) { |
| 175 | |
| 176 | // Cases: properties which are not regular properties and have not been matched by the PatternProperties validator |
| 177 | if o.AdditionalProperties != nil && o.AdditionalProperties.Schema != nil { |
| 178 | // AdditionalProperties as Schema |
| 179 | r := NewSchemaValidator(o.AdditionalProperties.Schema, o.Root, o.Path+"."+key, o.KnownFormats).Validate(value) |
| 180 | res.mergeForField(data.(map[string]interface{}), key, r) |
| 181 | } else if regularProperty && !(matched || succeededOnce) { |
| 182 | // TODO: this is dead code since regularProperty=false here |
| 183 | res.AddErrors(errors.FailedAllPatternProperties(o.Path, o.In, key)) |
| 184 | } |
| 185 | } |
| 186 | } |
| 187 | // Valid cases: additionalProperties: true or undefined |
| 188 | } |
| 189 | |
| 190 | createdFromDefaults := map[string]bool{} |
| 191 | |
| 192 | // Property types: |
| 193 | // - regular Property |
| 194 | for pName := range o.Properties { |
| 195 | pSchema := o.Properties[pName] // one instance per iteration |
| 196 | rName := pName |
| 197 | if o.Path != "" { |
| 198 | rName = o.Path + "." + pName |
| 199 | } |
| 200 | |
| 201 | // Recursively validates each property against its schema |
| 202 | if v, ok := val[pName]; ok { |
| 203 | r := NewSchemaValidator(&pSchema, o.Root, rName, o.KnownFormats).Validate(v) |
| 204 | res.mergeForField(data.(map[string]interface{}), pName, r) |
| 205 | } else if pSchema.Default != nil { |
| 206 | // If a default value is defined, creates the property from defaults |
| 207 | // NOTE: JSON schema does not enforce default values to be valid against schema. Swagger does. |
| 208 | createdFromDefaults[pName] = true |
| 209 | res.addPropertySchemata(data.(map[string]interface{}), pName, &pSchema) |
| 210 | } |
| 211 | } |
| 212 | |
| 213 | // Check required properties |
| 214 | if len(o.Required) > 0 { |
| 215 | for _, k := range o.Required { |
| 216 | if _, ok := val[k]; !ok && !createdFromDefaults[k] { |
| 217 | res.AddErrors(errors.Required(o.Path+"."+k, o.In)) |
| 218 | continue |
| 219 | } |
| 220 | } |
| 221 | } |
| 222 | |
| 223 | // Check patternProperties |
| 224 | // TODO: it looks like we have done that twice in many cases |
| 225 | for key, value := range val { |
| 226 | _, regularProperty := o.Properties[key] |
| 227 | matched, _ /*succeededOnce*/, patterns := o.validatePatternProperty(key, value, res) |
| 228 | if !regularProperty && (matched /*|| succeededOnce*/) { |
| 229 | for _, pName := range patterns { |
| 230 | if v, ok := o.PatternProperties[pName]; ok { |
| 231 | r := NewSchemaValidator(&v, o.Root, o.Path+"."+key, o.KnownFormats).Validate(value) |
| 232 | res.mergeForField(data.(map[string]interface{}), key, r) |
| 233 | } |
| 234 | } |
| 235 | } |
| 236 | } |
| 237 | return res |
| 238 | } |
| 239 | |
| 240 | // TODO: succeededOnce is not used anywhere |
| 241 | func (o *objectValidator) validatePatternProperty(key string, value interface{}, result *Result) (bool, bool, []string) { |
| 242 | matched := false |
| 243 | succeededOnce := false |
| 244 | var patterns []string |
| 245 | |
| 246 | for k, schema := range o.PatternProperties { |
| 247 | if match, _ := regexp.MatchString(k, key); match { |
| 248 | patterns = append(patterns, k) |
| 249 | matched = true |
| 250 | validator := NewSchemaValidator(&schema, o.Root, o.Path+"."+key, o.KnownFormats) |
| 251 | |
| 252 | res := validator.Validate(value) |
| 253 | result.Merge(res) |
| 254 | } |
| 255 | } |
| 256 | |
| 257 | // BUG(fredbi): can't get to here. Should remove dead code (commented out). |
| 258 | |
| 259 | //if succeededOnce { |
| 260 | // result.Inc() |
| 261 | //} |
| 262 | |
| 263 | return matched, succeededOnce, patterns |
| 264 | } |