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