Skip to content

Commit 5cc4681

Browse files
katbyteappilon
authored andcommitted
validation: add several new validators
IntDivisibleBy IntNotInSlice IsIPv6Address IsIPv4Address IsCIDR IsMACAddress IsPortNumber IsPortNumberOrZero IsDayOfTheWeek IsMonth IsRFC3339Time IsURLWithHTTPS IsURLWithHTTPorHTTPS IsURLWithScheme
1 parent 0ff4a00 commit 5cc4681

File tree

8 files changed

+830
-92
lines changed

8 files changed

+830
-92
lines changed

helper/validation/int.go

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package validation
22

33
import (
44
"fmt"
5+
"math"
56

67
"github.com/hashicorp/terraform-plugin-sdk/helper/schema"
78
)
@@ -63,6 +64,25 @@ func IntAtMost(max int) schema.SchemaValidateFunc {
6364
}
6465
}
6566

67+
// IntDivisibleBy returns a SchemaValidateFunc which tests if the provided value
68+
// is of type int and is divisible by a given number
69+
func IntDivisibleBy(divisor int) schema.SchemaValidateFunc {
70+
return func(i interface{}, k string) (warnings []string, errors []error) {
71+
v, ok := i.(int)
72+
if !ok {
73+
errors = append(errors, fmt.Errorf("expected type of %s to be int", k))
74+
return
75+
}
76+
77+
if math.Mod(float64(v), float64(divisor)) != 0 {
78+
errors = append(errors, fmt.Errorf("expected %s to be divisible by %d, got: %v", k, divisor, i))
79+
return
80+
}
81+
82+
return warnings, errors
83+
}
84+
}
85+
6686
// IntInSlice returns a SchemaValidateFunc which tests if the provided value
6787
// is of type int and matches the value of an element in the valid slice
6888
func IntInSlice(valid []int) schema.SchemaValidateFunc {
@@ -83,3 +103,23 @@ func IntInSlice(valid []int) schema.SchemaValidateFunc {
83103
return
84104
}
85105
}
106+
107+
// IntNotInSlice returns a SchemaValidateFunc which tests if the provided value
108+
// is of type int and matches the value of an element in the valid slice
109+
func IntNotInSlice(valid []int) schema.SchemaValidateFunc {
110+
return func(i interface{}, k string) (_ []string, errors []error) {
111+
v, ok := i.(int)
112+
if !ok {
113+
errors = append(errors, fmt.Errorf("expected type of %s to be an integer", k))
114+
return
115+
}
116+
117+
for _, validInt := range valid {
118+
if v == validInt {
119+
errors = append(errors, fmt.Errorf("expected %s to not be one of %v, got %d", k, valid, v))
120+
}
121+
}
122+
123+
return
124+
}
125+
}

helper/validation/int_test.go

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,42 @@ func TestValidationIntAtMost(t *testing.T) {
7474
})
7575
}
7676

77+
func TestValidationIntDivisibleBy(t *testing.T) {
78+
cases := map[string]struct {
79+
Value interface{}
80+
Divisor int
81+
Error bool
82+
}{
83+
"NotInt": {
84+
Value: "words",
85+
Divisor: 2,
86+
Error: true,
87+
},
88+
"NotDivisible": {
89+
Value: 15,
90+
Divisor: 7,
91+
Error: true,
92+
},
93+
"Divisible": {
94+
Value: 14,
95+
Divisor: 7,
96+
Error: false,
97+
},
98+
}
99+
100+
for tn, tc := range cases {
101+
t.Run(tn, func(t *testing.T) {
102+
_, errors := IntDivisibleBy(tc.Divisor)(tc.Value, tn)
103+
104+
if len(errors) > 0 && !tc.Error {
105+
t.Errorf("IntDivisibleBy(%v) produced an unexpected error for %v", tc.Divisor, tc.Value)
106+
} else if len(errors) == 0 && tc.Error {
107+
t.Errorf("IntDivisibleBy(%v) did not error for %v", tc.Divisor, tc.Value)
108+
}
109+
})
110+
}
111+
}
112+
77113
func TestValidationIntInSlice(t *testing.T) {
78114
runTestCases(t, []testCase{
79115
{
@@ -92,3 +128,49 @@ func TestValidationIntInSlice(t *testing.T) {
92128
},
93129
})
94130
}
131+
132+
func TestValidationIntNotInSlice(t *testing.T) {
133+
cases := map[string]struct {
134+
Value interface{}
135+
Slice []int
136+
Error bool
137+
}{
138+
"NotInt": {
139+
Value: "words",
140+
Slice: []int{7, 77},
141+
Error: true,
142+
},
143+
"NotInSlice": {
144+
Value: 1,
145+
Slice: []int{7, 77},
146+
Error: false,
147+
},
148+
"InSlice": {
149+
Value: 7,
150+
Slice: []int{7, 77},
151+
Error: true,
152+
},
153+
"InSliceOfOne": {
154+
Value: 7,
155+
Slice: []int{7},
156+
Error: true,
157+
},
158+
"NotInSliceOfOne": {
159+
Value: 1,
160+
Slice: []int{7},
161+
Error: false,
162+
},
163+
}
164+
165+
for tn, tc := range cases {
166+
t.Run(tn, func(t *testing.T) {
167+
_, errors := IntNotInSlice(tc.Slice)(tc.Value, tn)
168+
169+
if len(errors) > 0 && !tc.Error {
170+
t.Errorf("IntNotInSlice(%v) produced an unexpected error for %v", tc.Slice, tc.Value)
171+
} else if len(errors) == 0 && tc.Error {
172+
t.Errorf("IntNotInSlice(%v) did not error for %v", tc.Slice, tc.Value)
173+
}
174+
})
175+
}
176+
}

helper/validation/network.go

Lines changed: 126 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -9,81 +9,172 @@ import (
99
"github.com/hashicorp/terraform-plugin-sdk/helper/schema"
1010
)
1111

12-
// CIDRNetwork returns a SchemaValidateFunc which tests if the provided value
13-
// is of type string, is in valid CIDR network notation, and has significant bits between min and max (inclusive)
14-
func CIDRNetwork(min, max int) schema.SchemaValidateFunc {
12+
// SingleIP returns a SchemaValidateFunc which tests if the provided value
13+
// is of type string, and in valid single Value notation
14+
func SingleIP() schema.SchemaValidateFunc {
1515
return func(i interface{}, k string) (s []string, es []error) {
1616
v, ok := i.(string)
1717
if !ok {
1818
es = append(es, fmt.Errorf("expected type of %s to be string", k))
1919
return
2020
}
2121

22-
_, ipnet, err := net.ParseCIDR(v)
23-
if err != nil {
24-
es = append(es, fmt.Errorf(
25-
"expected %s to contain a valid CIDR, got: %s with err: %s", k, v, err))
26-
return
22+
ip := net.ParseIP(v)
23+
if ip == nil {
24+
es = append(es, fmt.Errorf("expected %s to contain a valid IP, got: %s", k, v))
2725
}
26+
return
27+
}
28+
}
2829

29-
if ipnet == nil || v != ipnet.String() {
30-
es = append(es, fmt.Errorf(
31-
"expected %s to contain a valid network CIDR, expected %s, got %s",
32-
k, ipnet, v))
33-
}
30+
// IsIPv6Address is a SchemaValidateFunc which tests if the provided value is of type string and a valid IPv6 address
31+
func IsIPv6Address(i interface{}, k string) (warnings []string, errors []error) {
32+
v, ok := i.(string)
33+
if !ok {
34+
errors = append(errors, fmt.Errorf("expected type of %q to be string", k))
35+
return
36+
}
3437

35-
sigbits, _ := ipnet.Mask.Size()
36-
if sigbits < min || sigbits > max {
37-
es = append(es, fmt.Errorf(
38-
"expected %q to contain a network CIDR with between %d and %d significant bits, got: %d",
39-
k, min, max, sigbits))
40-
}
38+
ip := net.ParseIP(v)
39+
if six := ip.To16(); six == nil {
40+
errors = append(errors, fmt.Errorf("expected %s to contain a valid IPv6 address, got: %s", k, v))
41+
}
42+
43+
return warnings, errors
44+
}
4145

46+
// IsIPv4Address is a SchemaValidateFunc which tests if the provided value is of type string and a valid IPv4 address
47+
func IsIPv4Address(i interface{}, k string) (warnings []string, errors []error) {
48+
v, ok := i.(string)
49+
if !ok {
50+
errors = append(errors, fmt.Errorf("expected type of %q to be string", k))
4251
return
4352
}
53+
54+
ip := net.ParseIP(v)
55+
if four := ip.To4(); four == nil {
56+
errors = append(errors, fmt.Errorf("expected %s to contain a valid IPv4 address, got: %s", k, v))
57+
}
58+
59+
return warnings, errors
4460
}
4561

46-
// SingleIP returns a SchemaValidateFunc which tests if the provided value
47-
// is of type string, and in valid single IP notation
48-
func SingleIP() schema.SchemaValidateFunc {
62+
// IPRange returns a SchemaValidateFunc which tests if the provided value is of type string, and in valid IP range
63+
func IPRange() schema.SchemaValidateFunc {
4964
return func(i interface{}, k string) (s []string, es []error) {
5065
v, ok := i.(string)
5166
if !ok {
5267
es = append(es, fmt.Errorf("expected type of %s to be string", k))
5368
return
5469
}
5570

56-
ip := net.ParseIP(v)
57-
if ip == nil {
71+
ips := strings.Split(v, "-")
72+
if len(ips) != 2 {
73+
es = append(es, fmt.Errorf(
74+
"expected %s to contain a valid IP range, got: %s", k, v))
75+
return
76+
}
77+
ip1 := net.ParseIP(ips[0])
78+
ip2 := net.ParseIP(ips[1])
79+
if ip1 == nil || ip2 == nil || bytes.Compare(ip1, ip2) > 0 {
5880
es = append(es, fmt.Errorf(
59-
"expected %s to contain a valid IP, got: %s", k, v))
81+
"expected %s to contain a valid IP range, got: %s", k, v))
6082
}
6183
return
6284
}
6385
}
6486

65-
// IPRange returns a SchemaValidateFunc which tests if the provided value
66-
// is of type string, and in valid IP range notation
67-
func IPRange() schema.SchemaValidateFunc {
87+
// IsCIDR is a SchemaValidateFunc which tests if the provided value is of type string and a valid CIDR
88+
func IsCIDR(i interface{}, k string) (warnings []string, errors []error) {
89+
v, ok := i.(string)
90+
if !ok {
91+
errors = append(errors, fmt.Errorf("expected type of %s to be string", k))
92+
return
93+
}
94+
95+
_, _, err := net.ParseCIDR(v)
96+
if err != nil {
97+
errors = append(errors, fmt.Errorf("expected %q to be a valid IPv4 Value, got %v: %v", k, i, err))
98+
}
99+
100+
return warnings, errors
101+
}
102+
103+
// CIDRNetwork returns a SchemaValidateFunc which tests if the provided value
104+
// is of type string, is in valid Value network notation, and has significant bits between min and max (inclusive)
105+
func CIDRNetwork(min, max int) schema.SchemaValidateFunc {
68106
return func(i interface{}, k string) (s []string, es []error) {
69107
v, ok := i.(string)
70108
if !ok {
71109
es = append(es, fmt.Errorf("expected type of %s to be string", k))
72110
return
73111
}
74112

75-
ips := strings.Split(v, "-")
76-
if len(ips) != 2 {
113+
_, ipnet, err := net.ParseCIDR(v)
114+
if err != nil {
77115
es = append(es, fmt.Errorf(
78-
"expected %s to contain a valid IP range, got: %s", k, v))
116+
"expected %s to contain a valid Value, got: %s with err: %s", k, v, err))
79117
return
80118
}
81-
ip1 := net.ParseIP(ips[0])
82-
ip2 := net.ParseIP(ips[1])
83-
if ip1 == nil || ip2 == nil || bytes.Compare(ip1, ip2) > 0 {
119+
120+
if ipnet == nil || v != ipnet.String() {
84121
es = append(es, fmt.Errorf(
85-
"expected %s to contain a valid IP range, got: %s", k, v))
122+
"expected %s to contain a valid network Value, expected %s, got %s",
123+
k, ipnet, v))
86124
}
125+
126+
sigbits, _ := ipnet.Mask.Size()
127+
if sigbits < min || sigbits > max {
128+
es = append(es, fmt.Errorf(
129+
"expected %q to contain a network Value with between %d and %d significant bits, got: %d",
130+
k, min, max, sigbits))
131+
}
132+
133+
return
134+
}
135+
}
136+
137+
// IsMACAddress is a SchemaValidateFunc which tests if the provided value is of type string and a valid MAC address
138+
func IsMACAddress(i interface{}, k string) (warnings []string, errors []error) {
139+
v, ok := i.(string)
140+
if !ok {
141+
errors = append(errors, fmt.Errorf("expected type of %q to be string", k))
87142
return
88143
}
144+
145+
if _, err := net.ParseMAC(v); err != nil {
146+
errors = append(errors, fmt.Errorf("expected %q to be a valid MAC address, got %v: %v", k, i, err))
147+
}
148+
149+
return warnings, errors
150+
}
151+
152+
// IsPortNumber is a SchemaValidateFunc which tests if the provided value is of type string and a valid TCP Port Number
153+
func IsPortNumber(i interface{}, k string) (warnings []string, errors []error) {
154+
v, ok := i.(int)
155+
if !ok {
156+
errors = append(errors, fmt.Errorf("expected type of %q to be int", k))
157+
return
158+
}
159+
160+
if 1 > v || v > 65535 {
161+
errors = append(errors, fmt.Errorf("expected %q to be a valid port number, got: %v", k, v))
162+
}
163+
164+
return warnings, errors
165+
}
166+
167+
// IsPortNumberOrZero is a SchemaValidateFunc which tests if the provided value is of type string and a valid TCP Port Number or zero
168+
func IsPortNumberOrZero(i interface{}, k string) (warnings []string, errors []error) {
169+
v, ok := i.(int)
170+
if !ok {
171+
errors = append(errors, fmt.Errorf("expected type of %q to be int", k))
172+
return
173+
}
174+
175+
if 0 > v || v > 65535 {
176+
errors = append(errors, fmt.Errorf("expected %q to be a valid port number or 0, got: %v", k, v))
177+
}
178+
179+
return warnings, errors
89180
}

0 commit comments

Comments
 (0)