podman
486 строк · 13.3 Кб
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 validate16
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 {39Errors []error40Warnings []error41MatchCount int42
43// the object data44data interface{}45
46// Schemata for the root object47rootObjectSchemata schemata
48// Schemata for object fields49fieldSchemata []fieldSchemata50// Schemata for slice items51itemSchemata []itemSchemata52
53cachedFieldSchemta map[FieldKey][]*spec.Schema54cachedItemSchemata map[ItemKey][]*spec.Schema55}
56
57// FieldKey is a pair of an object and a field, usable as a key for a map.
58type FieldKey struct {59object reflect.Value // actually a map[string]interface{}, but the latter cannot be a key60field string61}
62
63// ItemKey is a pair of a slice and an index, usable as a key for a map.
64type ItemKey struct {65slice reflect.Value // actually a []interface{}, but the latter cannot be a key66index int67}
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 {71return 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{} {76return fk.object.Interface().(map[string]interface{})77}
78
79// Field returns the underlying field of this key.
80func (fk *FieldKey) Field() string {81return fk.field82}
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 {86return ItemKey{slice: reflect.ValueOf(slice), index: i}87}
88
89// Slice returns the underlying slice of this key.
90func (ik *ItemKey) Slice() []interface{} {91return ik.slice.Interface().([]interface{})92}
93
94// Index returns the underlying index of this key.
95func (ik *ItemKey) Index() int {96return ik.index97}
98
99type fieldSchemata struct {100obj map[string]interface{}101field string102schemata schemata
103}
104
105type itemSchemata struct {106slice reflect.Value107index int108schemata schemata
109}
110
111// Merge merges this result with the other one(s), preserving match counts etc.
112func (r *Result) Merge(others ...*Result) *Result {113for _, other := range others {114if other == nil {115continue116}117r.mergeWithoutRootSchemata(other)118r.rootObjectSchemata.Append(other.rootObjectSchemata)119}120return r121}
122
123// Data returns the original data object used for validation. Mutating this renders
124// the result invalid.
125func (r *Result) Data() interface{} {126return r.data127}
128
129// RootObjectSchemata returns the schemata which apply to the root object.
130func (r *Result) RootObjectSchemata() []*spec.Schema {131return r.rootObjectSchemata.Slice()132}
133
134// FieldSchemata returns the schemata which apply to fields in objects.
135// nolint: dupl
136func (r *Result) FieldSchemata() map[FieldKey][]*spec.Schema {137if r.cachedFieldSchemta != nil {138return r.cachedFieldSchemta139}140
141ret := make(map[FieldKey][]*spec.Schema, len(r.fieldSchemata))142for _, fs := range r.fieldSchemata {143key := NewFieldKey(fs.obj, fs.field)144if fs.schemata.one != nil {145ret[key] = append(ret[key], fs.schemata.one)146} else if len(fs.schemata.multiple) > 0 {147ret[key] = append(ret[key], fs.schemata.multiple...)148}149}150r.cachedFieldSchemta = ret151return ret152}
153
154// ItemSchemata returns the schemata which apply to items in slices.
155// nolint: dupl
156func (r *Result) ItemSchemata() map[ItemKey][]*spec.Schema {157if r.cachedItemSchemata != nil {158return r.cachedItemSchemata159}160
161ret := make(map[ItemKey][]*spec.Schema, len(r.itemSchemata))162for _, ss := range r.itemSchemata {163key := NewItemKey(ss.slice, ss.index)164if ss.schemata.one != nil {165ret[key] = append(ret[key], ss.schemata.one)166} else if len(ss.schemata.multiple) > 0 {167ret[key] = append(ret[key], ss.schemata.multiple...)168}169}170r.cachedItemSchemata = ret171return ret172}
173
174func (r *Result) resetCaches() {175r.cachedFieldSchemta = nil176r.cachedItemSchemata = nil177}
178
179// mergeForField merges other into r, assigning other's root schemata to the given Object and field name.
180// nolint: unparam
181func (r *Result) mergeForField(obj map[string]interface{}, field string, other *Result) *Result {182if other == nil {183return r184}185r.mergeWithoutRootSchemata(other)186
187if other.rootObjectSchemata.Len() > 0 {188if r.fieldSchemata == nil {189r.fieldSchemata = make([]fieldSchemata, len(obj))190}191r.fieldSchemata = append(r.fieldSchemata, fieldSchemata{192obj: obj,193field: field,194schemata: other.rootObjectSchemata,195})196}197
198return r199}
200
201// mergeForSlice merges other into r, assigning other's root schemata to the given slice and index.
202// nolint: unparam
203func (r *Result) mergeForSlice(slice reflect.Value, i int, other *Result) *Result {204if other == nil {205return r206}207r.mergeWithoutRootSchemata(other)208
209if other.rootObjectSchemata.Len() > 0 {210if r.itemSchemata == nil {211r.itemSchemata = make([]itemSchemata, slice.Len())212}213r.itemSchemata = append(r.itemSchemata, itemSchemata{214slice: slice,215index: i,216schemata: other.rootObjectSchemata,217})218}219
220return r221}
222
223// addRootObjectSchemata adds the given schemata for the root object of the result.
224// The slice schemata might be reused. I.e. do not modify it after being added to a result.
225func (r *Result) addRootObjectSchemata(s *spec.Schema) {226r.rootObjectSchemata.Append(schemata{one: s})227}
228
229// addPropertySchemata adds the given schemata for the object and field.
230// The slice schemata might be reused. I.e. do not modify it after being added to a result.
231func (r *Result) addPropertySchemata(obj map[string]interface{}, fld string, schema *spec.Schema) {232if r.fieldSchemata == nil {233r.fieldSchemata = make([]fieldSchemata, 0, len(obj))234}235r.fieldSchemata = append(r.fieldSchemata, fieldSchemata{obj: obj, field: fld, schemata: schemata{one: schema}})236}
237
238/*
239// addSliceSchemata adds the given schemata for the slice and index.
240// The slice schemata might be reused. I.e. do not modify it after being added to a result.
241func (r *Result) addSliceSchemata(slice reflect.Value, i int, schema *spec.Schema) {
242if r.itemSchemata == nil {
243r.itemSchemata = make([]itemSchemata, 0, slice.Len())
244}
245r.itemSchemata = append(r.itemSchemata, itemSchemata{slice: slice, index: i, schemata: schemata{one: schema}})
246}
247*/
248
249// mergeWithoutRootSchemata merges other into r, ignoring the rootObject schemata.
250func (r *Result) mergeWithoutRootSchemata(other *Result) {251r.resetCaches()252r.AddErrors(other.Errors...)253r.AddWarnings(other.Warnings...)254r.MatchCount += other.MatchCount255
256if other.fieldSchemata != nil {257if r.fieldSchemata == nil {258r.fieldSchemata = other.fieldSchemata259} else {260r.fieldSchemata = append(r.fieldSchemata, other.fieldSchemata...)261}262}263
264if other.itemSchemata != nil {265if r.itemSchemata == nil {266r.itemSchemata = other.itemSchemata267} else {268r.itemSchemata = append(r.itemSchemata, other.itemSchemata...)269}270}271}
272
273// MergeAsErrors merges this result with the other one(s), preserving match counts etc.
274//
275// Warnings from input are merged as Errors in the returned merged Result.
276func (r *Result) MergeAsErrors(others ...*Result) *Result {277for _, other := range others {278if other != nil {279r.resetCaches()280r.AddErrors(other.Errors...)281r.AddErrors(other.Warnings...)282r.MatchCount += other.MatchCount283}284}285return r286}
287
288// MergeAsWarnings merges this result with the other one(s), preserving match counts etc.
289//
290// Errors from input are merged as Warnings in the returned merged Result.
291func (r *Result) MergeAsWarnings(others ...*Result) *Result {292for _, other := range others {293if other != nil {294r.resetCaches()295r.AddWarnings(other.Errors...)296r.AddWarnings(other.Warnings...)297r.MatchCount += other.MatchCount298}299}300return r301}
302
303// AddErrors adds errors to this validation result (if not already reported).
304//
305// Since the same check may be passed several times while exploring the
306// spec structure (via $ref, ...) reported messages are kept
307// unique.
308func (r *Result) AddErrors(errors ...error) {309for _, e := range errors {310found := false311if e != nil {312for _, isReported := range r.Errors {313if e.Error() == isReported.Error() {314found = true315break316}317}318if !found {319r.Errors = append(r.Errors, e)320}321}322}323}
324
325// AddWarnings adds warnings to this validation result (if not already reported).
326func (r *Result) AddWarnings(warnings ...error) {327for _, e := range warnings {328found := false329if e != nil {330for _, isReported := range r.Warnings {331if e.Error() == isReported.Error() {332found = true333break334}335}336if !found {337r.Warnings = append(r.Warnings, e)338}339}340}341}
342
343func (r *Result) keepRelevantErrors() *Result {344// TODO: this one is going to disapear...345// keepRelevantErrors strips a result from standard errors and keeps346// the ones which are supposedly more accurate.347//348// The original result remains unaffected (creates a new instance of Result).349// This method is used to work around the "matchCount" filter which would otherwise350// strip our result from some accurate error reporting from lower level validators.351//352// NOTE: this implementation with a placeholder (IMPORTANT!) is neither clean nor353// very efficient. On the other hand, relying on go-openapi/errors to manipulate354// codes would require to change a lot here. So, for the moment, let's go with355// placeholders.356strippedErrors := []error{}357for _, e := range r.Errors {358if strings.HasPrefix(e.Error(), "IMPORTANT!") {359strippedErrors = append(strippedErrors, fmt.Errorf(strings.TrimPrefix(e.Error(), "IMPORTANT!")))360}361}362strippedWarnings := []error{}363for _, e := range r.Warnings {364if strings.HasPrefix(e.Error(), "IMPORTANT!") {365strippedWarnings = append(strippedWarnings, fmt.Errorf(strings.TrimPrefix(e.Error(), "IMPORTANT!")))366}367}368strippedResult := new(Result)369strippedResult.Errors = strippedErrors370strippedResult.Warnings = strippedWarnings371return strippedResult372}
373
374// IsValid returns true when this result is valid.
375//
376// Returns true on a nil *Result.
377func (r *Result) IsValid() bool {378if r == nil {379return true380}381return len(r.Errors) == 0382}
383
384// HasErrors returns true when this result is invalid.
385//
386// Returns false on a nil *Result.
387func (r *Result) HasErrors() bool {388if r == nil {389return false390}391return !r.IsValid()392}
393
394// HasWarnings returns true when this result contains warnings.
395//
396// Returns false on a nil *Result.
397func (r *Result) HasWarnings() bool {398if r == nil {399return false400}401return len(r.Warnings) > 0402}
403
404// HasErrorsOrWarnings returns true when this result contains
405// either errors or warnings.
406//
407// Returns false on a nil *Result.
408func (r *Result) HasErrorsOrWarnings() bool {409if r == nil {410return false411}412return len(r.Errors) > 0 || len(r.Warnings) > 0413}
414
415// Inc increments the match count
416func (r *Result) Inc() {417r.MatchCount++418}
419
420// AsError renders this result as an error interface
421//
422// TODO: reporting / pretty print with path ordered and indented
423func (r *Result) AsError() error {424if r.IsValid() {425return nil426}427return errors.CompositeValidationError(r.Errors...)428}
429
430// schemata is an arbitrary number of schemata. It does a distinction between zero,
431// one and many schemata to avoid slice allocations.
432type schemata struct {433// one is set if there is exactly one schema. In that case multiple must be nil.434one *spec.Schema435// multiple is an arbitrary number of schemas. If it is set, one must be nil.436multiple []*spec.Schema437}
438
439func (s *schemata) Len() int {440if s.one != nil {441return 1442}443return len(s.multiple)444}
445
446func (s *schemata) Slice() []*spec.Schema {447if s == nil {448return nil449}450if s.one != nil {451return []*spec.Schema{s.one}452}453return s.multiple454}
455
456// appendSchemata appends the schemata in other to s. It mutated s in-place.
457func (s *schemata) Append(other schemata) {458if other.one == nil && len(other.multiple) == 0 {459return460}461if s.one == nil && len(s.multiple) == 0 {462*s = other463return464}465
466if s.one != nil {467if other.one != nil {468s.multiple = []*spec.Schema{s.one, other.one}469} else {470t := make([]*spec.Schema, 0, 1+len(other.multiple))471s.multiple = append(append(t, s.one), other.multiple...)472}473s.one = nil474} else {475if other.one != nil {476s.multiple = append(s.multiple, other.one)477} else {478if cap(s.multiple) >= len(s.multiple)+len(other.multiple) {479s.multiple = append(s.multiple, other.multiple...)480} else {481t := make([]*spec.Schema, 0, len(s.multiple)+len(other.multiple))482s.multiple = append(append(t, s.multiple...), other.multiple...)483}484}485}486}
487