blob: 14e198263ae73872a3e8143db93fcc8f49f44cac [file] [log] [blame]
Serge Bazanskicc25bdf2018-10-25 14:02:58 +02001// 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
15package validate
16
17import (
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
27type 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
40func (o *objectValidator) SetPath(path string) {
41 o.Path = path
42}
43
44func (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
53func (o *objectValidator) isPropertyName() bool {
54 p := strings.Split(o.Path, ".")
55 return p[len(p)-1] == "properties" && p[len(p)-2] != "properties"
56}
57
58func (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
68func (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
84func (o *objectValidator) precheck(res *Result, val map[string]interface{}) {
85 o.checkArrayMustHaveItems(res, val)
86 o.checkItemsMustBeTypeArray(res, val)
87}
88
89func (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
241func (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}