1
2
3
4
5
6
7
8
9
10
11
12
13
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
80
81
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
103
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
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
194 if o.AdditionalProperties != nil && !o.AdditionalProperties.Allows {
195
196 o.validateNoAdditionalProperties(val, res)
197 } else {
198
199 o.validateAdditionalProperties(val, res)
200 }
201
202 o.validatePropertiesSchema(val, res)
203
204
205
206 for key, value := range val {
207 _, regularProperty := o.Properties[key]
208 matched, _, patterns := o.validatePatternProperty(key, value, res)
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
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
254
255
256
257
258
259
260
261
262
263
264 if k != "headers" || val[k] == nil {
265 continue
266 }
267
268
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
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
314
315
316
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
327
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
332 }
333
334 func (o *objectValidator) validatePropertiesSchema(val map[string]interface{}, res *Result) {
335 createdFromDefaults := map[string]struct{}{}
336
337
338
339 pSchema := pools.poolOfSchemas.BorrowSchema()
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
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
364
365 createdFromDefaults[pName] = struct{}{}
366 if !o.Options.skipSchemataResult {
367 res.addPropertySchemata(val, pName, pSchema)
368 }
369 }
370 }
371
372 if len(o.Required) == 0 {
373 return
374 }
375
376
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
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