github.com/scottwinkler/terraform@v0.11.6-0.20180329211809-05143987aea8/helper/validation/validation.go (about) 1 package validation 2 3 import ( 4 "fmt" 5 "net" 6 "reflect" 7 "regexp" 8 "strings" 9 "time" 10 11 "github.com/hashicorp/terraform/helper/schema" 12 "github.com/hashicorp/terraform/helper/structure" 13 ) 14 15 // IntBetween returns a SchemaValidateFunc which tests if the provided value 16 // is of type int and is between min and max (inclusive) 17 func IntBetween(min, max int) schema.SchemaValidateFunc { 18 return func(i interface{}, k string) (s []string, es []error) { 19 v, ok := i.(int) 20 if !ok { 21 es = append(es, fmt.Errorf("expected type of %s to be int", k)) 22 return 23 } 24 25 if v < min || v > max { 26 es = append(es, fmt.Errorf("expected %s to be in the range (%d - %d), got %d", k, min, max, v)) 27 return 28 } 29 30 return 31 } 32 } 33 34 // IntAtLeast returns a SchemaValidateFunc which tests if the provided value 35 // is of type int and is at least min (inclusive) 36 func IntAtLeast(min int) schema.SchemaValidateFunc { 37 return func(i interface{}, k string) (s []string, es []error) { 38 v, ok := i.(int) 39 if !ok { 40 es = append(es, fmt.Errorf("expected type of %s to be int", k)) 41 return 42 } 43 44 if v < min { 45 es = append(es, fmt.Errorf("expected %s to be at least (%d), got %d", k, min, v)) 46 return 47 } 48 49 return 50 } 51 } 52 53 // IntAtMost returns a SchemaValidateFunc which tests if the provided value 54 // is of type int and is at most max (inclusive) 55 func IntAtMost(max int) schema.SchemaValidateFunc { 56 return func(i interface{}, k string) (s []string, es []error) { 57 v, ok := i.(int) 58 if !ok { 59 es = append(es, fmt.Errorf("expected type of %s to be int", k)) 60 return 61 } 62 63 if v > max { 64 es = append(es, fmt.Errorf("expected %s to be at most (%d), got %d", k, max, v)) 65 return 66 } 67 68 return 69 } 70 } 71 72 // StringInSlice returns a SchemaValidateFunc which tests if the provided value 73 // is of type string and matches the value of an element in the valid slice 74 // will test with in lower case if ignoreCase is true 75 func StringInSlice(valid []string, ignoreCase bool) schema.SchemaValidateFunc { 76 return func(i interface{}, k string) (s []string, es []error) { 77 v, ok := i.(string) 78 if !ok { 79 es = append(es, fmt.Errorf("expected type of %s to be string", k)) 80 return 81 } 82 83 for _, str := range valid { 84 if v == str || (ignoreCase && strings.ToLower(v) == strings.ToLower(str)) { 85 return 86 } 87 } 88 89 es = append(es, fmt.Errorf("expected %s to be one of %v, got %s", k, valid, v)) 90 return 91 } 92 } 93 94 // StringLenBetween returns a SchemaValidateFunc which tests if the provided value 95 // is of type string and has length between min and max (inclusive) 96 func StringLenBetween(min, max int) schema.SchemaValidateFunc { 97 return func(i interface{}, k string) (s []string, es []error) { 98 v, ok := i.(string) 99 if !ok { 100 es = append(es, fmt.Errorf("expected type of %s to be string", k)) 101 return 102 } 103 if len(v) < min || len(v) > max { 104 es = append(es, fmt.Errorf("expected length of %s to be in the range (%d - %d), got %s", k, min, max, v)) 105 } 106 return 107 } 108 } 109 110 // StringMatch returns a SchemaValidateFunc which tests if the provided value 111 // matches a given regexp. Optionally an error message can be provided to 112 // return something friendlier than "must match some globby regexp". 113 func StringMatch(r *regexp.Regexp, message string) schema.SchemaValidateFunc { 114 return func(i interface{}, k string) ([]string, []error) { 115 v, ok := i.(string) 116 if !ok { 117 return nil, []error{fmt.Errorf("expected type of %s to be string", k)} 118 } 119 120 if ok := r.MatchString(v); !ok { 121 if message != "" { 122 return nil, []error{fmt.Errorf("invalid value for %s (%s)", k, message)} 123 124 } 125 return nil, []error{fmt.Errorf("expected value of %s to match regular expression %q", k, r)} 126 } 127 return nil, nil 128 } 129 } 130 131 // NoZeroValues is a SchemaValidateFunc which tests if the provided value is 132 // not a zero value. It's useful in situations where you want to catch 133 // explicit zero values on things like required fields during validation. 134 func NoZeroValues(i interface{}, k string) (s []string, es []error) { 135 if reflect.ValueOf(i).Interface() == reflect.Zero(reflect.TypeOf(i)).Interface() { 136 switch reflect.TypeOf(i).Kind() { 137 case reflect.String: 138 es = append(es, fmt.Errorf("%s must not be empty", k)) 139 case reflect.Int, reflect.Float64: 140 es = append(es, fmt.Errorf("%s must not be zero", k)) 141 default: 142 // this validator should only ever be applied to TypeString, TypeInt and TypeFloat 143 panic(fmt.Errorf("can't use NoZeroValues with %T attribute %s", i, k)) 144 } 145 } 146 return 147 } 148 149 // CIDRNetwork returns a SchemaValidateFunc which tests if the provided value 150 // is of type string, is in valid CIDR network notation, and has significant bits between min and max (inclusive) 151 func CIDRNetwork(min, max int) schema.SchemaValidateFunc { 152 return func(i interface{}, k string) (s []string, es []error) { 153 v, ok := i.(string) 154 if !ok { 155 es = append(es, fmt.Errorf("expected type of %s to be string", k)) 156 return 157 } 158 159 _, ipnet, err := net.ParseCIDR(v) 160 if err != nil { 161 es = append(es, fmt.Errorf( 162 "expected %s to contain a valid CIDR, got: %s with err: %s", k, v, err)) 163 return 164 } 165 166 if ipnet == nil || v != ipnet.String() { 167 es = append(es, fmt.Errorf( 168 "expected %s to contain a valid network CIDR, expected %s, got %s", 169 k, ipnet, v)) 170 } 171 172 sigbits, _ := ipnet.Mask.Size() 173 if sigbits < min || sigbits > max { 174 es = append(es, fmt.Errorf( 175 "expected %q to contain a network CIDR with between %d and %d significant bits, got: %d", 176 k, min, max, sigbits)) 177 } 178 179 return 180 } 181 } 182 183 // ValidateJsonString is a SchemaValidateFunc which tests to make sure the 184 // supplied string is valid JSON. 185 func ValidateJsonString(v interface{}, k string) (ws []string, errors []error) { 186 if _, err := structure.NormalizeJsonString(v); err != nil { 187 errors = append(errors, fmt.Errorf("%q contains an invalid JSON: %s", k, err)) 188 } 189 return 190 } 191 192 // ValidateListUniqueStrings is a ValidateFunc that ensures a list has no 193 // duplicate items in it. It's useful for when a list is needed over a set 194 // because order matters, yet the items still need to be unique. 195 func ValidateListUniqueStrings(v interface{}, k string) (ws []string, errors []error) { 196 for n1, v1 := range v.([]interface{}) { 197 for n2, v2 := range v.([]interface{}) { 198 if v1.(string) == v2.(string) && n1 != n2 { 199 errors = append(errors, fmt.Errorf("%q: duplicate entry - %s", k, v1.(string))) 200 } 201 } 202 } 203 return 204 } 205 206 // ValidateRegexp returns a SchemaValidateFunc which tests to make sure the 207 // supplied string is a valid regular expression. 208 func ValidateRegexp(v interface{}, k string) (ws []string, errors []error) { 209 if _, err := regexp.Compile(v.(string)); err != nil { 210 errors = append(errors, fmt.Errorf("%q: %s", k, err)) 211 } 212 return 213 } 214 215 // ValidateRFC3339TimeString is a ValidateFunc that ensures a string parses 216 // as time.RFC3339 format 217 func ValidateRFC3339TimeString(v interface{}, k string) (ws []string, errors []error) { 218 if _, err := time.Parse(time.RFC3339, v.(string)); err != nil { 219 errors = append(errors, fmt.Errorf("%q: invalid RFC3339 timestamp", k)) 220 } 221 return 222 }