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