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  }