github.com/mattyw/juju@v0.0.0-20140610034352-732aecd63861/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 45 // NewValidator returns a new constraints Validator instance. 46 func NewValidator() Validator { 47 return &validator{ 48 conflicts: make(map[string]set.Strings), 49 vocab: make(map[string][]interface{}), 50 } 51 } 52 53 type validator struct { 54 unsupported set.Strings 55 conflicts map[string]set.Strings 56 vocab map[string][]interface{} 57 } 58 59 // RegisterConflicts is defined on Validator. 60 func (v *validator) RegisterConflicts(reds, blues []string) { 61 for _, red := range reds { 62 v.conflicts[red] = set.NewStrings(blues...) 63 } 64 for _, blue := range blues { 65 v.conflicts[blue] = set.NewStrings(reds...) 66 } 67 } 68 69 // RegisterUnsupported is defined on Validator. 70 func (v *validator) RegisterUnsupported(unsupported []string) { 71 v.unsupported = set.NewStrings(unsupported...) 72 } 73 74 // RegisterVocabulary is defined on Validator. 75 func (v *validator) RegisterVocabulary(attributeName string, allowedValues interface{}) { 76 k := reflect.TypeOf(allowedValues).Kind() 77 if k != reflect.Slice && k != reflect.Array { 78 panic(fmt.Errorf("invalid vocab: %v of type %T is not a slice", allowedValues, allowedValues)) 79 } 80 // Convert the vocab to a slice of interface{} 81 var allowedSlice []interface{} 82 val := reflect.ValueOf(allowedValues) 83 for i := 0; i < val.Len(); i++ { 84 allowedSlice = append(allowedSlice, val.Index(i).Interface()) 85 } 86 v.vocab[attributeName] = allowedSlice 87 } 88 89 // checkConflicts returns an error if the constraints Value contains conflicting attributes. 90 func (v *validator) checkConflicts(cons Value) error { 91 attrValues := cons.attributesWithValues() 92 attrSet := set.NewStrings() 93 for attrTag := range attrValues { 94 attrSet.Add(attrTag) 95 } 96 for _, attrTag := range attrSet.SortedValues() { 97 conflicts, ok := v.conflicts[attrTag] 98 if !ok { 99 continue 100 } 101 for _, conflict := range conflicts.SortedValues() { 102 if attrSet.Contains(conflict) { 103 return fmt.Errorf("ambiguous constraints: %q overlaps with %q", attrTag, conflict) 104 } 105 } 106 } 107 return nil 108 } 109 110 // checkUnsupported returns any unsupported attributes. 111 func (v *validator) checkUnsupported(cons Value) []string { 112 return cons.hasAny(v.unsupported.Values()...) 113 } 114 115 // checkValidValues returns an error if the constraints value contains an 116 // attribute value which is not allowed by the vocab which may have been 117 // registered for it. 118 func (v *validator) checkValidValues(cons Value) error { 119 for attrTag, attrValue := range cons.attributesWithValues() { 120 k := reflect.TypeOf(attrValue).Kind() 121 if k == reflect.Slice || k == reflect.Array { 122 // For slices we check that all values are valid. 123 val := reflect.ValueOf(attrValue) 124 for i := 0; i < val.Len(); i++ { 125 if err := v.checkInVocab(attrTag, val.Index(i).Interface()); err != nil { 126 return err 127 } 128 } 129 } else { 130 if err := v.checkInVocab(attrTag, attrValue); err != nil { 131 return err 132 } 133 } 134 } 135 return nil 136 } 137 138 // checkInVocab returns an error if the attribute value is not allowed by the 139 // vocab which may have been registered for it. 140 func (v *validator) checkInVocab(attributeName string, attributeValue interface{}) error { 141 validValues, ok := v.vocab[attributeName] 142 if !ok { 143 return nil 144 } 145 for _, validValue := range validValues { 146 if coerce(validValue) == coerce(attributeValue) { 147 return nil 148 } 149 } 150 return fmt.Errorf( 151 "invalid constraint value: %v=%v\nvalid values are: %v", attributeName, attributeValue, validValues) 152 } 153 154 // coerce returns v in a format that allows constraint values to be easily compared. 155 // Its main purpose is to cast all numeric values to int64 or float64. 156 func coerce(v interface{}) interface{} { 157 if v != nil { 158 switch vv := reflect.TypeOf(v); vv.Kind() { 159 case reflect.String: 160 return v 161 case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: 162 return int64(reflect.ValueOf(v).Int()) 163 case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: 164 uval := reflect.ValueOf(v).Uint() 165 // Just double check the value is in range. 166 if uval > math.MaxInt64 { 167 panic(fmt.Errorf("constraint value %v is too large", uval)) 168 } 169 return int64(uval) 170 case reflect.Float32, reflect.Float64: 171 return float64(reflect.ValueOf(v).Float()) 172 } 173 } 174 return v 175 } 176 177 // withFallbacks returns a copy of v with nil values taken from vFallback. 178 func withFallbacks(v Value, vFallback Value) Value { 179 result := vFallback 180 for _, fieldName := range fieldNames { 181 resultVal := reflect.ValueOf(&result).Elem().FieldByName(fieldName) 182 val := reflect.ValueOf(&v).Elem().FieldByName(fieldName) 183 if !val.IsNil() { 184 resultVal.Set(val) 185 } 186 } 187 return result 188 } 189 190 // Validate is defined on Validator. 191 func (v *validator) Validate(cons Value) ([]string, error) { 192 unsupported := v.checkUnsupported(cons) 193 if err := v.checkConflicts(cons); err != nil { 194 return unsupported, err 195 } 196 if err := v.checkValidValues(cons); err != nil { 197 return unsupported, err 198 } 199 return unsupported, nil 200 } 201 202 // Merge is defined on Validator. 203 func (v *validator) Merge(consFallback, cons Value) (Value, error) { 204 // First ensure both constraints are valid. We don't care if there 205 // are constraint attributes that are unsupported. 206 if _, err := v.Validate(consFallback); err != nil { 207 return Value{}, err 208 } 209 if _, err := v.Validate(cons); err != nil { 210 return Value{}, err 211 } 212 // Gather any attributes from consFallback which conflict with those on cons. 213 attrValues := cons.attributesWithValues() 214 var fallbackConflicts []string 215 for attrTag := range attrValues { 216 fallbackConflicts = append(fallbackConflicts, v.conflicts[attrTag].Values()...) 217 } 218 // Null out the conflicting consFallback attribute values because 219 // cons takes priority. We can't error here because we 220 // know that aConflicts contains valid attr names. 221 consFallbackMinusConflicts, _ := consFallback.without(fallbackConflicts...) 222 // The result is cons with fallbacks coming from any 223 // non conflicting consFallback attributes. 224 return withFallbacks(cons, consFallbackMinusConflicts), nil 225 }