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