...

Source file src/sigs.k8s.io/structured-merge-diff/v4/typed/validate.go

Documentation: sigs.k8s.io/structured-merge-diff/v4/typed

     1  /*
     2  Copyright 2018 The Kubernetes Authors.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package typed
    18  
    19  import (
    20  	"sync"
    21  
    22  	"sigs.k8s.io/structured-merge-diff/v4/fieldpath"
    23  	"sigs.k8s.io/structured-merge-diff/v4/schema"
    24  	"sigs.k8s.io/structured-merge-diff/v4/value"
    25  )
    26  
    27  var vPool = sync.Pool{
    28  	New: func() interface{} { return &validatingObjectWalker{} },
    29  }
    30  
    31  func (tv TypedValue) walker() *validatingObjectWalker {
    32  	v := vPool.Get().(*validatingObjectWalker)
    33  	v.value = tv.value
    34  	v.schema = tv.schema
    35  	v.typeRef = tv.typeRef
    36  	v.allowDuplicates = false
    37  	if v.allocator == nil {
    38  		v.allocator = value.NewFreelistAllocator()
    39  	}
    40  	return v
    41  }
    42  
    43  func (v *validatingObjectWalker) finished() {
    44  	v.schema = nil
    45  	v.typeRef = schema.TypeRef{}
    46  	vPool.Put(v)
    47  }
    48  
    49  type validatingObjectWalker struct {
    50  	value   value.Value
    51  	schema  *schema.Schema
    52  	typeRef schema.TypeRef
    53  	// If set to true, duplicates will be allowed in
    54  	// associativeLists/sets.
    55  	allowDuplicates bool
    56  
    57  	// Allocate only as many walkers as needed for the depth by storing them here.
    58  	spareWalkers *[]*validatingObjectWalker
    59  	allocator    value.Allocator
    60  }
    61  
    62  func (v *validatingObjectWalker) prepareDescent(tr schema.TypeRef) *validatingObjectWalker {
    63  	if v.spareWalkers == nil {
    64  		// first descent.
    65  		v.spareWalkers = &[]*validatingObjectWalker{}
    66  	}
    67  	var v2 *validatingObjectWalker
    68  	if n := len(*v.spareWalkers); n > 0 {
    69  		v2, *v.spareWalkers = (*v.spareWalkers)[n-1], (*v.spareWalkers)[:n-1]
    70  	} else {
    71  		v2 = &validatingObjectWalker{}
    72  	}
    73  	*v2 = *v
    74  	v2.typeRef = tr
    75  	return v2
    76  }
    77  
    78  func (v *validatingObjectWalker) finishDescent(v2 *validatingObjectWalker) {
    79  	// if the descent caused a realloc, ensure that we reuse the buffer
    80  	// for the next sibling.
    81  	*v.spareWalkers = append(*v.spareWalkers, v2)
    82  }
    83  
    84  func (v *validatingObjectWalker) validate(prefixFn func() string) ValidationErrors {
    85  	return resolveSchema(v.schema, v.typeRef, v.value, v).WithLazyPrefix(prefixFn)
    86  }
    87  
    88  func validateScalar(t *schema.Scalar, v value.Value, prefix string) (errs ValidationErrors) {
    89  	if v == nil {
    90  		return nil
    91  	}
    92  	if v.IsNull() {
    93  		return nil
    94  	}
    95  	switch *t {
    96  	case schema.Numeric:
    97  		if !v.IsFloat() && !v.IsInt() {
    98  			// TODO: should the schema separate int and float?
    99  			return errorf("%vexpected numeric (int or float), got %T", prefix, v.Unstructured())
   100  		}
   101  	case schema.String:
   102  		if !v.IsString() {
   103  			return errorf("%vexpected string, got %#v", prefix, v)
   104  		}
   105  	case schema.Boolean:
   106  		if !v.IsBool() {
   107  			return errorf("%vexpected boolean, got %v", prefix, v)
   108  		}
   109  	case schema.Untyped:
   110  		if !v.IsFloat() && !v.IsInt() && !v.IsString() && !v.IsBool() {
   111  			return errorf("%vexpected any scalar, got %v", prefix, v)
   112  		}
   113  	default:
   114  		return errorf("%vunexpected scalar type in schema: %v", prefix, *t)
   115  	}
   116  	return nil
   117  }
   118  
   119  func (v *validatingObjectWalker) doScalar(t *schema.Scalar) ValidationErrors {
   120  	if errs := validateScalar(t, v.value, ""); len(errs) > 0 {
   121  		return errs
   122  	}
   123  	return nil
   124  }
   125  
   126  func (v *validatingObjectWalker) visitListItems(t *schema.List, list value.List) (errs ValidationErrors) {
   127  	observedKeys := fieldpath.MakePathElementSet(list.Length())
   128  	for i := 0; i < list.Length(); i++ {
   129  		child := list.AtUsing(v.allocator, i)
   130  		defer v.allocator.Free(child)
   131  		var pe fieldpath.PathElement
   132  		if t.ElementRelationship != schema.Associative {
   133  			pe.Index = &i
   134  		} else {
   135  			var err error
   136  			pe, err = listItemToPathElement(v.allocator, v.schema, t, child)
   137  			if err != nil {
   138  				errs = append(errs, errorf("element %v: %v", i, err.Error())...)
   139  				// If we can't construct the path element, we can't
   140  				// even report errors deeper in the schema, so bail on
   141  				// this element.
   142  				return
   143  			}
   144  			if observedKeys.Has(pe) && !v.allowDuplicates {
   145  				errs = append(errs, errorf("duplicate entries for key %v", pe.String())...)
   146  			}
   147  			observedKeys.Insert(pe)
   148  		}
   149  		v2 := v.prepareDescent(t.ElementType)
   150  		v2.value = child
   151  		errs = append(errs, v2.validate(pe.String)...)
   152  		v.finishDescent(v2)
   153  	}
   154  	return errs
   155  }
   156  
   157  func (v *validatingObjectWalker) doList(t *schema.List) (errs ValidationErrors) {
   158  	list, err := listValue(v.allocator, v.value)
   159  	if err != nil {
   160  		return errorf(err.Error())
   161  	}
   162  
   163  	if list == nil {
   164  		return nil
   165  	}
   166  
   167  	defer v.allocator.Free(list)
   168  	errs = v.visitListItems(t, list)
   169  
   170  	return errs
   171  }
   172  
   173  func (v *validatingObjectWalker) visitMapItems(t *schema.Map, m value.Map) (errs ValidationErrors) {
   174  	m.IterateUsing(v.allocator, func(key string, val value.Value) bool {
   175  		pe := fieldpath.PathElement{FieldName: &key}
   176  		tr := t.ElementType
   177  		if sf, ok := t.FindField(key); ok {
   178  			tr = sf.Type
   179  		} else if (t.ElementType == schema.TypeRef{}) {
   180  			errs = append(errs, errorf("field not declared in schema").WithPrefix(pe.String())...)
   181  			return false
   182  		}
   183  		v2 := v.prepareDescent(tr)
   184  		v2.value = val
   185  		// Giving pe.String as a parameter actually increases the allocations.
   186  		errs = append(errs, v2.validate(func() string { return pe.String() })...)
   187  		v.finishDescent(v2)
   188  		return true
   189  	})
   190  	return errs
   191  }
   192  
   193  func (v *validatingObjectWalker) doMap(t *schema.Map) (errs ValidationErrors) {
   194  	m, err := mapValue(v.allocator, v.value)
   195  	if err != nil {
   196  		return errorf(err.Error())
   197  	}
   198  	if m == nil {
   199  		return nil
   200  	}
   201  	defer v.allocator.Free(m)
   202  	errs = v.visitMapItems(t, m)
   203  
   204  	return errs
   205  }
   206  

View as plain text