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  }