...

Source file src/github.com/go-openapi/validate/result.go

Documentation: github.com/go-openapi/validate

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

View as plain text