blob: 860b30e63eaf3eac3d998d7ebc5dec26540e9b15 [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 "encoding/json"
19 "reflect"
20
21 "github.com/go-openapi/errors"
22 "github.com/go-openapi/spec"
23 "github.com/go-openapi/strfmt"
24 "github.com/go-openapi/swag"
25)
26
27var (
28 specSchemaType = reflect.TypeOf(&spec.Schema{})
29 specParameterType = reflect.TypeOf(&spec.Parameter{})
30 specItemsType = reflect.TypeOf(&spec.Items{})
31 specHeaderType = reflect.TypeOf(&spec.Header{})
32)
33
34// SchemaValidator validates data against a JSON schema
35type SchemaValidator struct {
36 Path string
37 in string
38 Schema *spec.Schema
39 validators []valueValidator
40 Root interface{}
41 KnownFormats strfmt.Registry
42}
43
44// AgainstSchema validates the specified data against the provided schema, using a registry of supported formats.
45//
46// When no pre-parsed *spec.Schema structure is provided, it uses a JSON schema as default. See example.
47func AgainstSchema(schema *spec.Schema, data interface{}, formats strfmt.Registry) error {
48 res := NewSchemaValidator(schema, nil, "", formats).Validate(data)
49 if res.HasErrors() {
50 return errors.CompositeValidationError(res.Errors...)
51 }
52 return nil
53}
54
55// NewSchemaValidator creates a new schema validator.
56//
57// Panics if the provided schema is invalid.
58func NewSchemaValidator(schema *spec.Schema, rootSchema interface{}, root string, formats strfmt.Registry) *SchemaValidator {
59 if schema == nil {
60 return nil
61 }
62
63 if rootSchema == nil {
64 rootSchema = schema
65 }
66
67 if schema.ID != "" || schema.Ref.String() != "" || schema.Ref.IsRoot() {
68 err := spec.ExpandSchema(schema, rootSchema, nil)
69 if err != nil {
70 msg := invalidSchemaProvidedMsg(err).Error()
71 panic(msg)
72 }
73 }
74 s := SchemaValidator{Path: root, in: "body", Schema: schema, Root: rootSchema, KnownFormats: formats}
75 s.validators = []valueValidator{
76 s.typeValidator(),
77 s.schemaPropsValidator(),
78 s.stringValidator(),
79 s.formatValidator(),
80 s.numberValidator(),
81 s.sliceValidator(),
82 s.commonValidator(),
83 s.objectValidator(),
84 }
85 return &s
86}
87
88// SetPath sets the path for this schema valdiator
89func (s *SchemaValidator) SetPath(path string) {
90 s.Path = path
91}
92
93// Applies returns true when this schema validator applies
94func (s *SchemaValidator) Applies(source interface{}, kind reflect.Kind) bool {
95 _, ok := source.(*spec.Schema)
96 return ok
97}
98
99// Validate validates the data against the schema
100func (s *SchemaValidator) Validate(data interface{}) *Result {
101 result := &Result{data: data}
102 if s == nil {
103 return result
104 }
105 if s.Schema != nil {
106 result.addRootObjectSchemata(s.Schema)
107 }
108
109 if data == nil {
110 result.Merge(s.validators[0].Validate(data)) // type validator
111 result.Merge(s.validators[6].Validate(data)) // common validator
112 return result
113 }
114
115 tpe := reflect.TypeOf(data)
116 kind := tpe.Kind()
117 for kind == reflect.Ptr {
118 tpe = tpe.Elem()
119 kind = tpe.Kind()
120 }
121 d := data
122
123 if kind == reflect.Struct {
124 // NOTE: since reflect retrieves the true nature of types
125 // this means that all strfmt types passed here (e.g. strfmt.Datetime, etc..)
126 // are converted here to strings, and structs are systematically converted
127 // to map[string]interface{}.
128 d = swag.ToDynamicJSON(data)
129 }
130
131 // TODO: this part should be handed over to type validator
132 // Handle special case of json.Number data (number marshalled as string)
133 isnumber := s.Schema.Type.Contains("number") || s.Schema.Type.Contains("integer")
134 if num, ok := data.(json.Number); ok && isnumber {
135 if s.Schema.Type.Contains("integer") { // avoid lossy conversion
136 in, erri := num.Int64()
137 if erri != nil {
138 result.AddErrors(invalidTypeConversionMsg(s.Path, erri))
139 result.Inc()
140 return result
141 }
142 d = in
143 } else {
144 nf, errf := num.Float64()
145 if errf != nil {
146 result.AddErrors(invalidTypeConversionMsg(s.Path, errf))
147 result.Inc()
148 return result
149 }
150 d = nf
151 }
152
153 tpe = reflect.TypeOf(d)
154 kind = tpe.Kind()
155 }
156
157 for _, v := range s.validators {
158 if !v.Applies(s.Schema, kind) {
159 debugLog("%T does not apply for %v", v, kind)
160 continue
161 }
162
163 err := v.Validate(d)
164 result.Merge(err)
165 result.Inc()
166 }
167 result.Inc()
168
169 return result
170}
171
172func (s *SchemaValidator) typeValidator() valueValidator {
173 return &typeValidator{Type: s.Schema.Type, Format: s.Schema.Format, In: s.in, Path: s.Path}
174}
175
176func (s *SchemaValidator) commonValidator() valueValidator {
177 return &basicCommonValidator{
178 Path: s.Path,
179 In: s.in,
180 Enum: s.Schema.Enum,
181 }
182}
183
184func (s *SchemaValidator) sliceValidator() valueValidator {
185 return &schemaSliceValidator{
186 Path: s.Path,
187 In: s.in,
188 MaxItems: s.Schema.MaxItems,
189 MinItems: s.Schema.MinItems,
190 UniqueItems: s.Schema.UniqueItems,
191 AdditionalItems: s.Schema.AdditionalItems,
192 Items: s.Schema.Items,
193 Root: s.Root,
194 KnownFormats: s.KnownFormats,
195 }
196}
197
198func (s *SchemaValidator) numberValidator() valueValidator {
199 return &numberValidator{
200 Path: s.Path,
201 In: s.in,
202 Default: s.Schema.Default,
203 MultipleOf: s.Schema.MultipleOf,
204 Maximum: s.Schema.Maximum,
205 ExclusiveMaximum: s.Schema.ExclusiveMaximum,
206 Minimum: s.Schema.Minimum,
207 ExclusiveMinimum: s.Schema.ExclusiveMinimum,
208 }
209}
210
211func (s *SchemaValidator) stringValidator() valueValidator {
212 return &stringValidator{
213 Path: s.Path,
214 In: s.in,
215 MaxLength: s.Schema.MaxLength,
216 MinLength: s.Schema.MinLength,
217 Pattern: s.Schema.Pattern,
218 }
219}
220
221func (s *SchemaValidator) formatValidator() valueValidator {
222 return &formatValidator{
223 Path: s.Path,
224 In: s.in,
225 Format: s.Schema.Format,
226 KnownFormats: s.KnownFormats,
227 }
228}
229
230func (s *SchemaValidator) schemaPropsValidator() valueValidator {
231 sch := s.Schema
232 return newSchemaPropsValidator(s.Path, s.in, sch.AllOf, sch.OneOf, sch.AnyOf, sch.Not, sch.Dependencies, s.Root, s.KnownFormats)
233}
234
235func (s *SchemaValidator) objectValidator() valueValidator {
236 return &objectValidator{
237 Path: s.Path,
238 In: s.in,
239 MaxProperties: s.Schema.MaxProperties,
240 MinProperties: s.Schema.MinProperties,
241 Required: s.Schema.Required,
242 Properties: s.Schema.Properties,
243 AdditionalProperties: s.Schema.AdditionalProperties,
244 PatternProperties: s.Schema.PatternProperties,
245 Root: s.Root,
246 KnownFormats: s.KnownFormats,
247 }
248}