blob: ae9b8dbf2b504d41581393f41da3ef43d44d8659 [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 "fmt"
19 "reflect"
20 "strings"
21
22 "github.com/go-openapi/errors"
23 "github.com/go-openapi/spec"
24)
25
26// Result represents a validation result set, composed of
27// errors and warnings.
28//
29// It is used to keep track of all detected errors and warnings during
30// the validation of a specification.
31//
32// Matchcount is used to determine
33// which errors are relevant in the case of AnyOf, OneOf
34// schema validation. Results from the validation branch
35// with most matches get eventually selected.
36//
37// TODO: keep path of key originating the error
38type Result struct {
39 Errors []error
40 Warnings []error
41 MatchCount int
42
43 // the object data
44 data interface{}
45
46 // Schemata for the root object
47 rootObjectSchemata schemata
48 // Schemata for object fields
49 fieldSchemata []fieldSchemata
50 // Schemata for slice items
51 itemSchemata []itemSchemata
52
53 cachedFieldSchemta map[FieldKey][]*spec.Schema
54 cachedItemSchemata map[ItemKey][]*spec.Schema
55}
56
57// FieldKey is a pair of an object and a field, usable as a key for a map.
58type FieldKey struct {
59 object reflect.Value // actually a map[string]interface{}, but the latter cannot be a key
60 field string
61}
62
63// ItemKey is a pair of a slice and an index, usable as a key for a map.
64type ItemKey struct {
65 slice reflect.Value // actually a []interface{}, but the latter cannot be a key
66 index int
67}
68
69// NewFieldKey returns a pair of an object and field usable as a key of a map.
70func NewFieldKey(obj map[string]interface{}, field string) FieldKey {
71 return FieldKey{object: reflect.ValueOf(obj), field: field}
72}
73
74// Object returns the underlying object of this key.
75func (fk *FieldKey) Object() map[string]interface{} {
76 return fk.object.Interface().(map[string]interface{})
77}
78
79// Field returns the underlying field of this key.
80func (fk *FieldKey) Field() string {
81 return fk.field
82}
83
84// NewItemKey returns a pair of a slice and index usable as a key of a map.
85func NewItemKey(slice interface{}, i int) ItemKey {
86 return ItemKey{slice: reflect.ValueOf(slice), index: i}
87}
88
89// Slice returns the underlying slice of this key.
90func (ik *ItemKey) Slice() []interface{} {
91 return ik.slice.Interface().([]interface{})
92}
93
94// Index returns the underlying index of this key.
95func (ik *ItemKey) Index() int {
96 return ik.index
97}
98
99type fieldSchemata struct {
100 obj map[string]interface{}
101 field string
102 schemata schemata
103}
104
105type itemSchemata struct {
106 slice reflect.Value
107 index int
108 schemata schemata
109}
110
111// Merge merges this result with the other one(s), preserving match counts etc.
112func (r *Result) Merge(others ...*Result) *Result {
113 for _, other := range others {
114 if other == nil {
115 continue
116 }
117 r.mergeWithoutRootSchemata(other)
118 r.rootObjectSchemata.Append(other.rootObjectSchemata)
119 }
120 return r
121}
122
123// Data returns the original data object used for validation. Mutating this renders
124// the result invalid.
125func (r *Result) Data() interface{} {
126 return r.data
127}
128
129// RootObjectSchemata returns the schemata which apply to the root object.
130func (r *Result) RootObjectSchemata() []*spec.Schema {
131 return r.rootObjectSchemata.Slice()
132}
133
134// FieldSchemata returns the schemata which apply to fields in objects.
135func (r *Result) FieldSchemata() map[FieldKey][]*spec.Schema {
136 if r.cachedFieldSchemta != nil {
137 return r.cachedFieldSchemta
138 }
139
140 ret := make(map[FieldKey][]*spec.Schema, len(r.fieldSchemata))
141 for _, fs := range r.fieldSchemata {
142 key := NewFieldKey(fs.obj, fs.field)
143 if fs.schemata.one != nil {
144 ret[key] = append(ret[key], fs.schemata.one)
145 } else if len(fs.schemata.multiple) > 0 {
146 ret[key] = append(ret[key], fs.schemata.multiple...)
147 }
148 }
149 r.cachedFieldSchemta = ret
150 return ret
151}
152
153// ItemSchemata returns the schemata which apply to items in slices.
154func (r *Result) ItemSchemata() map[ItemKey][]*spec.Schema {
155 if r.cachedItemSchemata != nil {
156 return r.cachedItemSchemata
157 }
158
159 ret := make(map[ItemKey][]*spec.Schema, len(r.itemSchemata))
160 for _, ss := range r.itemSchemata {
161 key := NewItemKey(ss.slice, ss.index)
162 if ss.schemata.one != nil {
163 ret[key] = append(ret[key], ss.schemata.one)
164 } else if len(ss.schemata.multiple) > 0 {
165 ret[key] = append(ret[key], ss.schemata.multiple...)
166 }
167 }
168 r.cachedItemSchemata = ret
169 return ret
170}
171
172func (r *Result) resetCaches() {
173 r.cachedFieldSchemta = nil
174 r.cachedItemSchemata = nil
175}
176
177// mergeForField merges other into r, assigning other's root schemata to the given Object and field name.
178func (r *Result) mergeForField(obj map[string]interface{}, field string, other *Result) *Result {
179 if other == nil {
180 return r
181 }
182 r.mergeWithoutRootSchemata(other)
183
184 if other.rootObjectSchemata.Len() > 0 {
185 if r.fieldSchemata == nil {
186 r.fieldSchemata = make([]fieldSchemata, len(obj))
187 }
188 r.fieldSchemata = append(r.fieldSchemata, fieldSchemata{
189 obj: obj,
190 field: field,
191 schemata: other.rootObjectSchemata,
192 })
193 }
194
195 return r
196}
197
198// mergeForSlice merges other into r, assigning other's root schemata to the given slice and index.
199func (r *Result) mergeForSlice(slice reflect.Value, i int, other *Result) *Result {
200 if other == nil {
201 return r
202 }
203 r.mergeWithoutRootSchemata(other)
204
205 if other.rootObjectSchemata.Len() > 0 {
206 if r.itemSchemata == nil {
207 r.itemSchemata = make([]itemSchemata, slice.Len())
208 }
209 r.itemSchemata = append(r.itemSchemata, itemSchemata{
210 slice: slice,
211 index: i,
212 schemata: other.rootObjectSchemata,
213 })
214 }
215
216 return r
217}
218
219// addRootObjectSchemata adds the given schemata for the root object of the result.
220// The slice schemata might be reused. I.e. do not modify it after being added to a result.
221func (r *Result) addRootObjectSchemata(s *spec.Schema) {
222 r.rootObjectSchemata.Append(schemata{one: s})
223}
224
225// addPropertySchemata adds the given schemata for the object and field.
226// The slice schemata might be reused. I.e. do not modify it after being added to a result.
227func (r *Result) addPropertySchemata(obj map[string]interface{}, fld string, schema *spec.Schema) {
228 if r.fieldSchemata == nil {
229 r.fieldSchemata = make([]fieldSchemata, 0, len(obj))
230 }
231 r.fieldSchemata = append(r.fieldSchemata, fieldSchemata{obj: obj, field: fld, schemata: schemata{one: schema}})
232}
233
234// addSliceSchemata adds the given schemata for the slice and index.
235// The slice schemata might be reused. I.e. do not modify it after being added to a result.
236func (r *Result) addSliceSchemata(slice reflect.Value, i int, schema *spec.Schema) {
237 if r.itemSchemata == nil {
238 r.itemSchemata = make([]itemSchemata, 0, slice.Len())
239 }
240 r.itemSchemata = append(r.itemSchemata, itemSchemata{slice: slice, index: i, schemata: schemata{one: schema}})
241}
242
243// mergeWithoutRootSchemata merges other into r, ignoring the rootObject schemata.
244func (r *Result) mergeWithoutRootSchemata(other *Result) {
245 r.resetCaches()
246 r.AddErrors(other.Errors...)
247 r.AddWarnings(other.Warnings...)
248 r.MatchCount += other.MatchCount
249
250 if other.fieldSchemata != nil {
251 if r.fieldSchemata == nil {
252 r.fieldSchemata = other.fieldSchemata
253 } else {
254 for _, x := range other.fieldSchemata {
255 r.fieldSchemata = append(r.fieldSchemata, x)
256 }
257 }
258 }
259
260 if other.itemSchemata != nil {
261 if r.itemSchemata == nil {
262 r.itemSchemata = other.itemSchemata
263 } else {
264 for _, x := range other.itemSchemata {
265 r.itemSchemata = append(r.itemSchemata, x)
266 }
267 }
268 }
269}
270
271// MergeAsErrors merges this result with the other one(s), preserving match counts etc.
272//
273// Warnings from input are merged as Errors in the returned merged Result.
274func (r *Result) MergeAsErrors(others ...*Result) *Result {
275 for _, other := range others {
276 if other != nil {
277 r.resetCaches()
278 r.AddErrors(other.Errors...)
279 r.AddErrors(other.Warnings...)
280 r.MatchCount += other.MatchCount
281 }
282 }
283 return r
284}
285
286// MergeAsWarnings merges this result with the other one(s), preserving match counts etc.
287//
288// Errors from input are merged as Warnings in the returned merged Result.
289func (r *Result) MergeAsWarnings(others ...*Result) *Result {
290 for _, other := range others {
291 if other != nil {
292 r.resetCaches()
293 r.AddWarnings(other.Errors...)
294 r.AddWarnings(other.Warnings...)
295 r.MatchCount += other.MatchCount
296 }
297 }
298 return r
299}
300
301// AddErrors adds errors to this validation result (if not already reported).
302//
303// Since the same check may be passed several times while exploring the
304// spec structure (via $ref, ...) reported messages are kept
305// unique.
306func (r *Result) AddErrors(errors ...error) {
307 for _, e := range errors {
308 found := false
309 if e != nil {
310 for _, isReported := range r.Errors {
311 if e.Error() == isReported.Error() {
312 found = true
313 break
314 }
315 }
316 if !found {
317 r.Errors = append(r.Errors, e)
318 }
319 }
320 }
321}
322
323// AddWarnings adds warnings to this validation result (if not already reported).
324func (r *Result) AddWarnings(warnings ...error) {
325 for _, e := range warnings {
326 found := false
327 if e != nil {
328 for _, isReported := range r.Warnings {
329 if e.Error() == isReported.Error() {
330 found = true
331 break
332 }
333 }
334 if !found {
335 r.Warnings = append(r.Warnings, e)
336 }
337 }
338 }
339}
340
341func (r *Result) keepRelevantErrors() *Result {
342 // TODO: this one is going to disapear...
343 // keepRelevantErrors strips a result from standard errors and keeps
344 // the ones which are supposedly more accurate.
345 //
346 // The original result remains unaffected (creates a new instance of Result).
347 // This method is used to work around the "matchCount" filter which would otherwise
348 // strip our result from some accurate error reporting from lower level validators.
349 //
350 // NOTE: this implementation with a placeholder (IMPORTANT!) is neither clean nor
351 // very efficient. On the other hand, relying on go-openapi/errors to manipulate
352 // codes would require to change a lot here. So, for the moment, let's go with
353 // placeholders.
354 strippedErrors := []error{}
355 for _, e := range r.Errors {
356 if strings.HasPrefix(e.Error(), "IMPORTANT!") {
357 strippedErrors = append(strippedErrors, fmt.Errorf(strings.TrimPrefix(e.Error(), "IMPORTANT!")))
358 }
359 }
360 strippedWarnings := []error{}
361 for _, e := range r.Warnings {
362 if strings.HasPrefix(e.Error(), "IMPORTANT!") {
363 strippedWarnings = append(strippedWarnings, fmt.Errorf(strings.TrimPrefix(e.Error(), "IMPORTANT!")))
364 }
365 }
366 strippedResult := new(Result)
367 strippedResult.Errors = strippedErrors
368 strippedResult.Warnings = strippedWarnings
369 return strippedResult
370}
371
372// IsValid returns true when this result is valid.
373//
374// Returns true on a nil *Result.
375func (r *Result) IsValid() bool {
376 if r == nil {
377 return true
378 }
379 return len(r.Errors) == 0
380}
381
382// HasErrors returns true when this result is invalid.
383//
384// Returns false on a nil *Result.
385func (r *Result) HasErrors() bool {
386 if r == nil {
387 return false
388 }
389 return !r.IsValid()
390}
391
392// HasWarnings returns true when this result contains warnings.
393//
394// Returns false on a nil *Result.
395func (r *Result) HasWarnings() bool {
396 if r == nil {
397 return false
398 }
399 return len(r.Warnings) > 0
400}
401
402// HasErrorsOrWarnings returns true when this result contains
403// either errors or warnings.
404//
405// Returns false on a nil *Result.
406func (r *Result) HasErrorsOrWarnings() bool {
407 if r == nil {
408 return false
409 }
410 return len(r.Errors) > 0 || len(r.Warnings) > 0
411}
412
413// Inc increments the match count
414func (r *Result) Inc() {
415 r.MatchCount++
416}
417
418// AsError renders this result as an error interface
419//
420// TODO: reporting / pretty print with path ordered and indented
421func (r *Result) AsError() error {
422 if r.IsValid() {
423 return nil
424 }
425 return errors.CompositeValidationError(r.Errors...)
426}
427
428// schemata is an arbitrary number of schemata. It does a distinction between zero,
429// one and many schemata to avoid slice allocations.
430type schemata struct {
431 // one is set if there is exactly one schema. In that case multiple must be nil.
432 one *spec.Schema
433 // multiple is an arbitrary number of schemas. If it is set, one must be nil.
434 multiple []*spec.Schema
435}
436
437func (s *schemata) Len() int {
438 if s.one != nil {
439 return 1
440 }
441 return len(s.multiple)
442}
443
444func (s *schemata) Slice() []*spec.Schema {
445 if s == nil {
446 return nil
447 }
448 if s.one != nil {
449 return []*spec.Schema{s.one}
450 }
451 return s.multiple
452}
453
454// appendSchemata appends the schemata in other to s. It mutated s in-place.
455func (s *schemata) Append(other schemata) {
456 if other.one == nil && len(other.multiple) == 0 {
457 return
458 }
459 if s.one == nil && len(s.multiple) == 0 {
460 *s = other
461 return
462 }
463
464 if s.one != nil {
465 if other.one != nil {
466 s.multiple = []*spec.Schema{s.one, other.one}
467 } else {
468 t := make([]*spec.Schema, 0, 1+len(other.multiple))
469 s.multiple = append(append(t, s.one), other.multiple...)
470 }
471 s.one = nil
472 } else {
473 if other.one != nil {
474 s.multiple = append(s.multiple, other.one)
475 } else {
476 if cap(s.multiple) >= len(s.multiple)+len(other.multiple) {
477 s.multiple = append(s.multiple, other.multiple...)
478 } else {
479 t := make([]*spec.Schema, 0, len(s.multiple)+len(other.multiple))
480 s.multiple = append(append(t, s.multiple...), other.multiple...)
481 }
482 }
483 }
484}