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