github.com/juju/juju@v0.0.0-20240430160146-1752b71fcf00/core/constraints/validation.go (about) 1 // Copyright 2014 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package constraints 5 6 import ( 7 "fmt" 8 "reflect" 9 "sort" 10 "strings" 11 12 "github.com/juju/collections/set" 13 ) 14 15 // Validator defines operations on constraints attributes which are 16 // used to ensure a constraints value is valid, as well as being able 17 // to handle overridden attributes. 18 type Validator interface { 19 20 // RegisterConflicts is used to define cross-constraint override behaviour. 21 // The red and blue attribute lists contain attribute names which conflict 22 // with those in the other list. 23 // When two constraints conflict: 24 // it is an error to set both constraints in the same constraints Value. 25 // when a constraints Value overrides another which specifies a conflicting 26 // attribute, the attribute in the overridden Value is cleared. 27 RegisterConflicts(reds, blues []string) 28 29 // RegisterConflictResolver defines a resolver between two conflicting constraints. 30 // When there is a registered conflict between two contraints, it can be resolved by 31 // calling the resolver, if it returns a nil error, the conflict is considered resolved. 32 RegisterConflictResolver(red, blue string, resolver ConflictResolver) 33 34 // RegisterUnsupported records attributes which are not supported by a constraints Value. 35 RegisterUnsupported(unsupported []string) 36 37 // RegisterVocabulary records allowed values for the specified constraint attribute. 38 // allowedValues is expected to be a slice/array but is declared as interface{} so 39 // that vocabs of different types can be passed in. 40 RegisterVocabulary(attributeName string, allowedValues interface{}) 41 42 // Validate returns an error if the given constraints are not valid, and also 43 // any unsupported attributes. 44 Validate(cons Value) ([]string, error) 45 46 // Merge merges cons into consFallback, with any conflicting attributes from cons 47 // overriding those from consFallback. 48 Merge(consFallback, cons Value) (Value, error) 49 50 // UpdateVocabulary merges new attribute values with existing values. 51 // This method does not overwrite or delete values, i.e. 52 // if existing values are {a, b} 53 // and new values are {c, d}, 54 // then the merge result would be {a, b, c, d}. 55 UpdateVocabulary(attributeName string, newValues interface{}) 56 } 57 58 type ConflictResolver func(attrValues map[string]interface{}) error 59 60 // NewValidator returns a new constraints Validator instance. 61 func NewValidator() Validator { 62 return &validator{ 63 conflicts: make(map[string]set.Strings), 64 conflictResolvers: make(map[string]ConflictResolver), 65 vocab: make(map[string][]interface{}), 66 } 67 } 68 69 type validator struct { 70 unsupported set.Strings 71 conflicts map[string]set.Strings 72 conflictResolvers map[string]ConflictResolver 73 vocab map[string][]interface{} 74 } 75 76 // RegisterConflicts is defined on Validator. 77 func (v *validator) RegisterConflicts(reds, blues []string) { 78 for _, red := range reds { 79 v.conflicts[red] = set.NewStrings(blues...) 80 } 81 for _, blue := range blues { 82 v.conflicts[blue] = set.NewStrings(reds...) 83 } 84 } 85 86 func conflictResolverId(red, blue string) string { 87 idx := []string{red, blue} 88 sort.Strings(idx) 89 return strings.Join(idx, " ") 90 } 91 92 func (v *validator) RegisterConflictResolver(red, blue string, resolver ConflictResolver) { 93 id := conflictResolverId(red, blue) 94 v.conflictResolvers[id] = resolver 95 } 96 97 // RegisterUnsupported is defined on Validator. 98 func (v *validator) RegisterUnsupported(unsupported []string) { 99 v.unsupported = set.NewStrings(unsupported...) 100 } 101 102 // RegisterVocabulary is defined on Validator. 103 func (v *validator) RegisterVocabulary(attributeName string, allowedValues interface{}) { 104 v.vocab[resolveAlias(attributeName)] = convertToSlice(allowedValues) 105 } 106 107 var checkIsCollection = func(coll interface{}) { 108 k := reflect.TypeOf(coll).Kind() 109 if k != reflect.Slice && k != reflect.Array { 110 panic(fmt.Errorf("invalid vocab: %v of type %T is not a slice", coll, coll)) 111 } 112 } 113 114 var convertToSlice = func(coll interface{}) []interface{} { 115 checkIsCollection(coll) 116 var slice []interface{} 117 val := reflect.ValueOf(coll) 118 for i := 0; i < val.Len(); i++ { 119 slice = append(slice, val.Index(i).Interface()) 120 } 121 return slice 122 } 123 124 // UpdateVocabulary is defined on Validator. 125 func (v *validator) UpdateVocabulary(attributeName string, allowedValues interface{}) { 126 attributeName = resolveAlias(attributeName) 127 // If this attribute is not registered, delegate to RegisterVocabulary() 128 currentValues, ok := v.vocab[attributeName] 129 if !ok { 130 v.RegisterVocabulary(attributeName, allowedValues) 131 } 132 133 unique := map[interface{}]bool{} 134 writeUnique := func(all []interface{}) { 135 for _, one := range all { 136 unique[one] = true 137 } 138 } 139 140 // merge existing values with new, ensuring uniqueness 141 writeUnique(currentValues) 142 newValues := convertToSlice(allowedValues) 143 writeUnique(newValues) 144 145 v.updateVocabularyFromMap(attributeName, unique) 146 } 147 148 func (v *validator) updateVocabularyFromMap(attributeName string, valuesMap map[interface{}]bool) { 149 attributeName = resolveAlias(attributeName) 150 var merged []interface{} 151 for one := range valuesMap { 152 // TODO (anastasiamac) Because it's coming from the map, the order maybe affected 153 // and can be unreliable. Not sure how to fix it yet... 154 // How can we guarantee the order here? 155 merged = append(merged, one) 156 } 157 v.RegisterVocabulary(attributeName, merged) 158 } 159 160 // checkConflicts returns an error if the constraints Value contains conflicting attributes. 161 func (v *validator) checkConflicts(cons Value) error { 162 attrValues := cons.attributesWithValues() 163 attrSet := make(set.Strings) 164 for attrTag := range attrValues { 165 attrSet.Add(attrTag) 166 } 167 for _, attrTag := range attrSet.SortedValues() { 168 conflicts, ok := v.conflicts[attrTag] 169 if !ok { 170 continue 171 } 172 for _, conflict := range conflicts.SortedValues() { 173 if !attrSet.Contains(conflict) { 174 continue 175 } 176 id := conflictResolverId(attrTag, conflict) 177 if resolver, ok := v.conflictResolvers[id]; ok { 178 err := resolver(attrValues) 179 if err != nil { 180 return fmt.Errorf("ambiguous constraints: %q overlaps with %q: %w", attrTag, conflict, err) 181 } 182 continue 183 } 184 return fmt.Errorf("ambiguous constraints: %q overlaps with %q", attrTag, conflict) 185 } 186 } 187 return nil 188 } 189 190 // checkUnsupported returns any unsupported attributes. 191 func (v *validator) checkUnsupported(cons Value) []string { 192 return cons.hasAny(v.unsupported.Values()...) 193 } 194 195 // checkValidValues returns an error if the constraints value contains an 196 // attribute value which is not allowed by the vocab which may have been 197 // registered for it. 198 func (v *validator) checkValidValues(cons Value) error { 199 for attrTag, attrValue := range cons.attributesWithValues() { 200 k := reflect.TypeOf(attrValue).Kind() 201 if k == reflect.Slice || k == reflect.Array { 202 // For slices we check that all values are valid. 203 val := reflect.ValueOf(attrValue) 204 for i := 0; i < val.Len(); i++ { 205 if err := v.checkInVocab(attrTag, val.Index(i).Interface()); err != nil { 206 return err 207 } 208 } 209 } else { 210 if err := v.checkInVocab(attrTag, attrValue); err != nil { 211 return err 212 } 213 } 214 } 215 return nil 216 } 217 218 // checkInVocab returns an error if the attribute value is not allowed by the 219 // vocab which may have been registered for it. 220 func (v *validator) checkInVocab(attributeName string, attributeValue interface{}) error { 221 validValues, ok := v.vocab[resolveAlias(attributeName)] 222 if !ok { 223 return nil 224 } 225 for _, validValue := range validValues { 226 if coerce(validValue) == coerce(attributeValue) { 227 return nil 228 } 229 } 230 return fmt.Errorf( 231 "invalid constraint value: %v=%v\nvalid values are: %v", attributeName, attributeValue, validValues) 232 } 233 234 // coerce returns v in a format that allows constraint values to be easily 235 // compared. Its main purpose is to cast all numeric values to float64 (since 236 // the numbers we compare are generated from json serialization). 237 func coerce(v interface{}) interface{} { 238 switch val := v.(type) { 239 case string: 240 return v 241 // Yes, these are all the same, however we can't put them into a single 242 // case, or the value becomes interface{}, which can't be converted to a 243 // float64. 244 case int: 245 return float64(val) 246 case int8: 247 return float64(val) 248 case int16: 249 return float64(val) 250 case int32: 251 return float64(val) 252 case int64: 253 return float64(val) 254 case uint: 255 return float64(val) 256 case uint8: 257 return float64(val) 258 case uint16: 259 return float64(val) 260 case uint32: 261 return float64(val) 262 case uint64: 263 return float64(val) 264 case float32: 265 return float64(val) 266 case float64: 267 return val 268 } 269 return v 270 } 271 272 // withFallbacks returns a copy of v with nil values taken from vFallback. 273 func withFallbacks(v Value, vFallback Value) Value { 274 vAttr := v.attributesWithValues() 275 fbAttr := vFallback.attributesWithValues() 276 for k, v := range fbAttr { 277 if _, ok := vAttr[k]; !ok { 278 vAttr[k] = v 279 } 280 } 281 return fromAttributes(vAttr) 282 } 283 284 // Validate is defined on Validator. 285 func (v *validator) Validate(cons Value) ([]string, error) { 286 unsupported := v.checkUnsupported(cons) 287 if err := v.checkValidValues(cons); err != nil { 288 return unsupported, err 289 } 290 // Conflicts are validated after values because normally conflicting 291 // constraints may be valid based on those constraint values. 292 if err := v.checkConflicts(cons); err != nil { 293 return unsupported, err 294 } 295 return unsupported, nil 296 } 297 298 // Merge is defined on Validator. 299 func (v *validator) Merge(consFallback, cons Value) (Value, error) { 300 // First ensure both constraints are valid. We don't care if there 301 // are constraint attributes that are unsupported. 302 if _, err := v.Validate(consFallback); err != nil { 303 return Value{}, err 304 } 305 if _, err := v.Validate(cons); err != nil { 306 return Value{}, err 307 } 308 // Gather any attributes from consFallback which conflict with those on cons. 309 attrValues := cons.attributesWithValues() 310 var fallbackConflicts []string 311 for attrTag := range attrValues { 312 fallbackConflicts = append(fallbackConflicts, v.conflicts[attrTag].Values()...) 313 } 314 // Null out the conflicting consFallback attribute values because 315 // cons takes priority. We can't error here because we 316 // know that aConflicts contains valid attr names. 317 consFallbackMinusConflicts := consFallback.without(fallbackConflicts...) 318 // The result is cons with fallbacks coming from any 319 // non conflicting consFallback attributes. 320 return withFallbacks(cons, consFallbackMinusConflicts), nil 321 }