github.com/mwhudson/juju@v0.0.0-20160512215208-90ff01f3497f/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 "math" 9 "reflect" 10 11 "github.com/juju/utils/set" 12 ) 13 14 // Validator defines operations on constraints attributes which are 15 // used to ensure a constraints value is valid, as well as being able 16 // to handle overridden attributes. 17 type Validator interface { 18 19 // RegisterConflicts is used to define cross-constraint override behaviour. 20 // The red and blue attribute lists contain attribute names which conflict 21 // with those in the other list. 22 // When two constraints conflict: 23 // it is an error to set both constraints in the same constraints Value. 24 // when a constraints Value overrides another which specifies a conflicting 25 // attribute, the attribute in the overridden Value is cleared. 26 RegisterConflicts(reds, blues []string) 27 28 // RegisterUnsupported records attributes which are not supported by a constraints Value. 29 RegisterUnsupported(unsupported []string) 30 31 // RegisterVocabulary records allowed values for the specified constraint attribute. 32 // allowedValues is expected to be a slice/array but is declared as interface{} so 33 // that vocabs of different types can be passed in. 34 RegisterVocabulary(attributeName string, allowedValues interface{}) 35 36 // Validate returns an error if the given constraints are not valid, and also 37 // any unsupported attributes. 38 Validate(cons Value) ([]string, error) 39 40 // Merge merges cons into consFallback, with any conflicting attributes from cons 41 // overriding those from consFallback. 42 Merge(consFallback, cons Value) (Value, error) 43 44 // UpdateVocabulary merges new attribute values with existing values. 45 // This method does not overwrite or delete values, i.e. 46 // if existing values are {a, b} 47 // and new values are {c, d}, 48 // then the merge result would be {a, b, c, d}. 49 UpdateVocabulary(attributeName string, newValues interface{}) 50 } 51 52 // NewValidator returns a new constraints Validator instance. 53 func NewValidator() Validator { 54 return &validator{ 55 conflicts: make(map[string]set.Strings), 56 vocab: make(map[string][]interface{}), 57 } 58 } 59 60 type validator struct { 61 unsupported set.Strings 62 conflicts map[string]set.Strings 63 vocab map[string][]interface{} 64 } 65 66 // RegisterConflicts is defined on Validator. 67 func (v *validator) RegisterConflicts(reds, blues []string) { 68 for _, red := range reds { 69 v.conflicts[red] = set.NewStrings(blues...) 70 } 71 for _, blue := range blues { 72 v.conflicts[blue] = set.NewStrings(reds...) 73 } 74 } 75 76 // RegisterUnsupported is defined on Validator. 77 func (v *validator) RegisterUnsupported(unsupported []string) { 78 v.unsupported = set.NewStrings(unsupported...) 79 } 80 81 // RegisterVocabulary is defined on Validator. 82 func (v *validator) RegisterVocabulary(attributeName string, allowedValues interface{}) { 83 v.vocab[attributeName] = convertToSlice(allowedValues) 84 } 85 86 var checkIsCollection = func(coll interface{}) { 87 k := reflect.TypeOf(coll).Kind() 88 if k != reflect.Slice && k != reflect.Array { 89 panic(fmt.Errorf("invalid vocab: %v of type %T is not a slice", coll, coll)) 90 } 91 } 92 93 var convertToSlice = func(coll interface{}) []interface{} { 94 checkIsCollection(coll) 95 var slice []interface{} 96 val := reflect.ValueOf(coll) 97 for i := 0; i < val.Len(); i++ { 98 slice = append(slice, val.Index(i).Interface()) 99 } 100 return slice 101 } 102 103 // UpdateVocabulary is defined on Validator. 104 func (v *validator) UpdateVocabulary(attributeName string, allowedValues interface{}) { 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 var merged []interface{} 124 for one, _ := range unique { 125 merged = append(merged, one) 126 } 127 v.vocab[attributeName] = merged 128 } 129 130 // checkConflicts returns an error if the constraints Value contains conflicting attributes. 131 func (v *validator) checkConflicts(cons Value) error { 132 attrValues := cons.attributesWithValues() 133 attrSet := make(set.Strings) 134 for attrTag := range attrValues { 135 attrSet.Add(attrTag) 136 } 137 for _, attrTag := range attrSet.SortedValues() { 138 conflicts, ok := v.conflicts[attrTag] 139 if !ok { 140 continue 141 } 142 for _, conflict := range conflicts.SortedValues() { 143 if attrSet.Contains(conflict) { 144 return fmt.Errorf("ambiguous constraints: %q overlaps with %q", attrTag, conflict) 145 } 146 } 147 } 148 return nil 149 } 150 151 // checkUnsupported returns any unsupported attributes. 152 func (v *validator) checkUnsupported(cons Value) []string { 153 return cons.hasAny(v.unsupported.Values()...) 154 } 155 156 // checkValidValues returns an error if the constraints value contains an 157 // attribute value which is not allowed by the vocab which may have been 158 // registered for it. 159 func (v *validator) checkValidValues(cons Value) error { 160 for attrTag, attrValue := range cons.attributesWithValues() { 161 k := reflect.TypeOf(attrValue).Kind() 162 if k == reflect.Slice || k == reflect.Array { 163 // For slices we check that all values are valid. 164 val := reflect.ValueOf(attrValue) 165 for i := 0; i < val.Len(); i++ { 166 if err := v.checkInVocab(attrTag, val.Index(i).Interface()); err != nil { 167 return err 168 } 169 } 170 } else { 171 if err := v.checkInVocab(attrTag, attrValue); err != nil { 172 return err 173 } 174 } 175 } 176 return nil 177 } 178 179 // checkInVocab returns an error if the attribute value is not allowed by the 180 // vocab which may have been registered for it. 181 func (v *validator) checkInVocab(attributeName string, attributeValue interface{}) error { 182 validValues, ok := v.vocab[attributeName] 183 if !ok { 184 return nil 185 } 186 for _, validValue := range validValues { 187 if coerce(validValue) == coerce(attributeValue) { 188 return nil 189 } 190 } 191 return fmt.Errorf( 192 "invalid constraint value: %v=%v\nvalid values are: %v", attributeName, attributeValue, validValues) 193 } 194 195 // coerce returns v in a format that allows constraint values to be easily compared. 196 // Its main purpose is to cast all numeric values to int64 or float64. 197 func coerce(v interface{}) interface{} { 198 if v != nil { 199 switch vv := reflect.TypeOf(v); vv.Kind() { 200 case reflect.String: 201 return v 202 case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: 203 return int64(reflect.ValueOf(v).Int()) 204 case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: 205 uval := reflect.ValueOf(v).Uint() 206 // Just double check the value is in range. 207 if uval > math.MaxInt64 { 208 panic(fmt.Errorf("constraint value %v is too large", uval)) 209 } 210 return int64(uval) 211 case reflect.Float32, reflect.Float64: 212 return float64(reflect.ValueOf(v).Float()) 213 } 214 } 215 return v 216 } 217 218 // withFallbacks returns a copy of v with nil values taken from vFallback. 219 func withFallbacks(v Value, vFallback Value) Value { 220 result := vFallback 221 for _, fieldName := range fieldNames { 222 resultVal := reflect.ValueOf(&result).Elem().FieldByName(fieldName) 223 val := reflect.ValueOf(&v).Elem().FieldByName(fieldName) 224 if !val.IsNil() { 225 resultVal.Set(val) 226 } 227 } 228 return result 229 } 230 231 // Validate is defined on Validator. 232 func (v *validator) Validate(cons Value) ([]string, error) { 233 unsupported := v.checkUnsupported(cons) 234 if err := v.checkConflicts(cons); err != nil { 235 return unsupported, err 236 } 237 if err := v.checkValidValues(cons); err != nil { 238 return unsupported, err 239 } 240 return unsupported, nil 241 } 242 243 // Merge is defined on Validator. 244 func (v *validator) Merge(consFallback, cons Value) (Value, error) { 245 // First ensure both constraints are valid. We don't care if there 246 // are constraint attributes that are unsupported. 247 if _, err := v.Validate(consFallback); err != nil { 248 return Value{}, err 249 } 250 if _, err := v.Validate(cons); err != nil { 251 return Value{}, err 252 } 253 // Gather any attributes from consFallback which conflict with those on cons. 254 attrValues := cons.attributesWithValues() 255 var fallbackConflicts []string 256 for attrTag := range attrValues { 257 fallbackConflicts = append(fallbackConflicts, v.conflicts[attrTag].Values()...) 258 } 259 // Null out the conflicting consFallback attribute values because 260 // cons takes priority. We can't error here because we 261 // know that aConflicts contains valid attr names. 262 consFallbackMinusConflicts, _ := consFallback.without(fallbackConflicts...) 263 // The result is cons with fallbacks coming from any 264 // non conflicting consFallback attributes. 265 return withFallbacks(cons, consFallbackMinusConflicts), nil 266 }