~wallyworld/juju-core/fast-lxc-everywhere

« back to all changes in this revision

Viewing changes to constraints/validation.go

  • Committer: Tarmac
  • Author(s): Ian Booth
  • Date: 2014-04-24 12:33:19 UTC
  • mfrom: (2664.1.4 constraints-vocab)
  • Revision ID: tarmac-20140424123319-iifvboa1kjuprj7l
[r=wallyworld] Support constraints vocabs

Add support to constraints validation for
vocabs for each attribute. eg arch and
instance-type can only have well defined
values for each provider. The vocab check
is part of constraints validation.

https://codereview.appspot.com/96730043/

Show diffs side-by-side

added added

removed removed

Lines of Context:
5
5
 
6
6
import (
7
7
        "fmt"
 
8
        "math"
8
9
        "reflect"
9
10
 
10
11
        "launchpad.net/juju-core/utils/set"
27
28
        // RegisterUnsupported records attributes which are not supported by a constraints Value.
28
29
        RegisterUnsupported(unsupported []string)
29
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
 
30
36
        // Validate returns an error if the given constraints are not valid, and also
31
37
        // any unsupported attributes.
32
38
        Validate(cons Value) ([]string, error)
40
46
func NewValidator() Validator {
41
47
        c := validator{}
42
48
        c.conflicts = make(map[string]set.Strings)
 
49
        c.vocab = make(map[string][]interface{})
43
50
        return &c
44
51
}
45
52
 
46
53
type validator struct {
47
54
        unsupported set.Strings
48
55
        conflicts   map[string]set.Strings
 
56
        vocab       map[string][]interface{}
49
57
}
50
58
 
51
59
// RegisterConflicts is defined on Validator.
63
71
        v.unsupported = set.NewStrings(unsupported...)
64
72
}
65
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
 
66
89
// checkConflicts returns an error if the constraints Value contains conflicting attributes.
67
90
func (v *validator) checkConflicts(cons Value) error {
68
 
        attrNames := cons.attributesWithValues()
69
 
        attrSet := set.NewStrings(attrNames...)
70
 
        for _, attr := range attrNames {
71
 
                conflicts, ok := v.conflicts[attr]
 
91
        attrValues := cons.attributesWithValues()
 
92
        attrSet := set.NewStrings()
 
93
        for attrTag := range attrValues {
 
94
                attrSet.Add(attrTag)
 
95
        }
 
96
        for attrTag := range attrValues {
 
97
                conflicts, ok := v.conflicts[attrTag]
72
98
                if !ok {
73
99
                        continue
74
100
                }
75
101
                for _, conflict := range conflicts.Values() {
76
102
                        if attrSet.Contains(conflict) {
77
 
                                return fmt.Errorf("ambiguous constraints: %q overlaps with %q", attr, conflict)
 
103
                                return fmt.Errorf("ambiguous constraints: %q overlaps with %q", attrTag, conflict)
78
104
                        }
79
105
                }
80
106
        }
86
112
        return cons.hasAny(v.unsupported.Values()...)
87
113
}
88
114
 
 
115
// checkValid returns an error if the constraints value contains an attribute value
 
116
// which is not allowed by the vocab which may have been registered for it.
 
117
func (v *validator) checkValidValues(cons Value) error {
 
118
        for attrTag, attrValue := range cons.attributesWithValues() {
 
119
                k := reflect.TypeOf(attrValue).Kind()
 
120
                if k == reflect.Slice || k == reflect.Array {
 
121
                        // For slices we check that all values are valid.
 
122
                        val := reflect.ValueOf(attrValue)
 
123
                        for i := 0; i < val.Len(); i++ {
 
124
                                if err := v.checkInVocab(attrTag, val.Index(i).Interface()); err != nil {
 
125
                                        return err
 
126
                                }
 
127
                        }
 
128
                } else {
 
129
                        if err := v.checkInVocab(attrTag, attrValue); err != nil {
 
130
                                return err
 
131
                        }
 
132
                }
 
133
        }
 
134
        return nil
 
135
}
 
136
 
 
137
// checkInVocab returns an error if the attribute value is not allowed by the
 
138
// vocab which may have been registered for it.
 
139
func (v *validator) checkInVocab(attributeName string, attributeValue interface{}) error {
 
140
        validValues, ok := v.vocab[attributeName]
 
141
        if !ok {
 
142
                return nil
 
143
        }
 
144
        for _, validValue := range validValues {
 
145
                if coerce(validValue) == coerce(attributeValue) {
 
146
                        return nil
 
147
                }
 
148
        }
 
149
        return fmt.Errorf(
 
150
                "invalid constraint value: %v=%v\nvalid values are: %v", attributeName, attributeValue, validValues)
 
151
}
 
152
 
 
153
// coerce returns v in a format that allows constraint values to be easily compared.
 
154
// Its main purpose is to cast all numeric values to int64 or float64.
 
155
func coerce(v interface{}) interface{} {
 
156
        if v != nil {
 
157
                switch vv := reflect.TypeOf(v); vv.Kind() {
 
158
                case reflect.String:
 
159
                        return v
 
160
                case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
 
161
                        return int64(reflect.ValueOf(v).Int())
 
162
                case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
 
163
                        uval := reflect.ValueOf(v).Uint()
 
164
                        // Just double check the value is in range.
 
165
                        if uval > math.MaxInt64 {
 
166
                                panic(fmt.Errorf("constraint value %v is too large", uval))
 
167
                        }
 
168
                        return int64(uval)
 
169
                case reflect.Float32, reflect.Float64:
 
170
                        return float64(reflect.ValueOf(v).Float())
 
171
                }
 
172
        }
 
173
        return v
 
174
}
 
175
 
89
176
// withFallbacks returns a copy of v with nil values taken from vFallback.
90
177
func withFallbacks(v Value, vFallback Value) Value {
91
178
        result := vFallback
105
192
        if err := v.checkConflicts(cons); err != nil {
106
193
                return unsupported, err
107
194
        }
 
195
        if err := v.checkValidValues(cons); err != nil {
 
196
                return unsupported, err
 
197
        }
108
198
        return unsupported, nil
109
199
}
110
200
 
119
209
                return Value{}, err
120
210
        }
121
211
        // Gather any attributes from consFallback which conflict with those on cons.
122
 
        attrs := cons.attributesWithValues()
 
212
        attrValues := cons.attributesWithValues()
123
213
        var fallbackConflicts []string
124
 
        for _, attr := range attrs {
125
 
                fallbackConflicts = append(fallbackConflicts, v.conflicts[attr].Values()...)
 
214
        for attrTag := range attrValues {
 
215
                fallbackConflicts = append(fallbackConflicts, v.conflicts[attrTag].Values()...)
126
216
        }
127
217
        // Null out the conflicting consFallback attribute values because
128
218
        // cons takes priority. We can't error here because we