Skip to content

Commit a487121

Browse files
feat: add new network string validators IPV4Range TCPUDPPortRange (#50)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
1 parent 32ce787 commit a487121

16 files changed

+564
-84
lines changed

.changelog/50.txt

+7
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
```release-note:feature
2+
`stringvalidator` - Add new network validator `IPV4Range` to validate IPv4 range (Ex: 192.168.0.1-192.168.0.10).
3+
```
4+
5+
```release-note:feature
6+
`stringvalidator` - Add new network validator `TCPUDPPortRange` to validate TCP/UDP port range (Ex: 80-90).
7+
```

docs/stringvalidator/isnetwork.md

+12-5
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,17 @@ The validator takes a list of NetworkValidatorType and a boolean as argument.
1414

1515
The list can contain one or more of the following values:
1616

17-
* `IPV4` - Check if the string is a valid IPV4 address.
18-
* `IPV4WithCIDR` - Check if the string is a valid IPV4 address with CIDR.
19-
* `IPV4WithNetmask`- Check if the string is a valid IPV4 address with netmask.
20-
* `IsRFC1918` - Check if the string is a valid [RFC1918](https://en.wikipedia.org/wiki/Private_network) address.
17+
**IPV4**
18+
19+
* `IPV4` - Check if the string is a valid IPV4 address (Ex: 192.168.0.1).
20+
* `IPV4WithCIDR` - Check if the string is a valid IPV4 address with CIDR (Ex: 192.168.0.0/24).
21+
* `IPV4WithNetmask`- Check if the string is a valid IPV4 address with netmask (Ex: 192.168.0.0/255.255.255.0).
22+
* `IPV4Range` - Check if the string is a valid IPV4 address range (Ex: 192.168.0.1-192.168.0.10).
23+
* `RFC1918` - Check if the string is a valid [RFC1918](https://en.wikipedia.org/wiki/Private_network) address.
24+
25+
**TCP/UDP**
26+
27+
* `TCPUDPPortRange` - Check if the string is a valid TCP/UDP port range (Ex: 80-90).
2128

2229
The boolean is used to define if the value must be at least one of the network types.
2330

@@ -57,7 +64,7 @@ func (r *xResource) Schema(ctx context.Context, _ resource.SchemaRequest, resp *
5764
Validators: []validator.String{
5865
fstringvalidator.IsNetwork([]fstringvalidator.NetworkValidatorType{
5966
fstringvalidator.IPV4,
60-
fstringvalidator.IsRFC1918,
67+
fstringvalidator.RFC1918,
6168
}, false)
6269
},
6370
},

stringvalidator/network.go

+45-62
Original file line numberDiff line numberDiff line change
@@ -24,19 +24,34 @@ var _ validator.String = networkValidator{}
2424
const (
2525
IPV4 NetworkValidatorType = "ipv4"
2626
IPV4WithCIDR NetworkValidatorType = "ipv4_with_cidr"
27-
IPv4WithNetmask NetworkValidatorType = "ipv4_with_netmask"
27+
IPV4WithNetmask NetworkValidatorType = "ipv4_with_netmask"
28+
IPV4Range NetworkValidatorType = "ipv4_range"
2829
RFC1918 NetworkValidatorType = "rfc1918"
30+
31+
TCPUDPPortRange NetworkValidatorType = "tcpudp_port_range"
2932
)
3033

31-
type NetworkValidatorType string
34+
var networkValidatorTypes = map[NetworkValidatorType]validator.String{
35+
IPV4: networkTypes.IsIPV4(),
36+
IPV4WithCIDR: networkTypes.IsIPV4WithCIDR(),
37+
IPV4WithNetmask: networkTypes.IsIPV4WithNetmask(),
38+
IPV4Range: networkTypes.IsIPV4Range(),
39+
RFC1918: networkTypes.IsRFC1918(),
3240

33-
type networkValidator struct {
34-
NetworkTypes []NetworkValidatorType
35-
ComparatorOR bool
41+
TCPUDPPortRange: networkTypes.IsTCPUDPPortRange(),
3642
}
3743

44+
type (
45+
NetworkValidatorType string
46+
47+
networkValidator struct {
48+
NetworkTypes []NetworkValidatorType
49+
ComparatorOR bool
50+
}
51+
)
52+
3853
// Description describes the validation in plain text formatting.
39-
func (validatorNet networkValidator) Description(_ context.Context) string {
54+
func (validatorNet networkValidator) Description(ctx context.Context) string {
4055
description := ""
4156
switch {
4257
case validatorNet.ComparatorOR && len(validatorNet.NetworkTypes) > 1:
@@ -48,17 +63,17 @@ func (validatorNet networkValidator) Description(_ context.Context) string {
4863
}
4964

5065
for _, networkType := range validatorNet.NetworkTypes {
51-
switch networkType {
52-
case IPV4:
53-
description += fmt.Sprintf("%s, ", networkTypes.IsIPV4().Description(context.Background()))
54-
case IPV4WithCIDR:
55-
description += fmt.Sprintf("%s, ", networkTypes.IsIPV4WithCIDR().Description(context.Background()))
56-
case IPv4WithNetmask:
57-
description += fmt.Sprintf("%s, ", networkTypes.IsIPV4WithNetmask().Description(context.Background()))
58-
case RFC1918:
59-
description += fmt.Sprintf("%s, ", networkTypes.IsRFC1918().Description(context.Background()))
66+
for k, v := range networkValidatorTypes {
67+
if networkType == k {
68+
description += fmt.Sprintf("%s, ", v.Description(ctx))
69+
}
6070
}
6171
}
72+
73+
if len(validatorNet.NetworkTypes) > 1 {
74+
description = description[:len(description)-2]
75+
}
76+
6277
return description
6378
}
6479

@@ -95,15 +110,10 @@ func (validatorNet networkValidator) MarkdownDescription(ctx context.Context) st
95110
}
96111

97112
for i, networkType := range validatorNet.NetworkTypes {
98-
switch networkType {
99-
case IPV4:
100-
markdownDescription += computeDescription(networkTypes.IsIPV4().MarkdownDescription(ctx), i)
101-
case IPV4WithCIDR:
102-
markdownDescription += computeDescription(networkTypes.IsIPV4WithCIDR().MarkdownDescription(ctx), i)
103-
case IPv4WithNetmask:
104-
markdownDescription += computeDescription(networkTypes.IsIPV4WithNetmask().MarkdownDescription(ctx), i)
105-
case RFC1918:
106-
markdownDescription += computeDescription(networkTypes.IsRFC1918().MarkdownDescription(ctx), i)
113+
for k, v := range networkValidatorTypes {
114+
if networkType == k {
115+
markdownDescription += computeDescription(v.MarkdownDescription(ctx), i)
116+
}
107117
}
108118
}
109119

@@ -117,6 +127,7 @@ func (validatorNet networkValidator) ValidateString(
117127
response *validator.StringResponse,
118128
) {
119129
if request.ConfigValue.IsNull() || request.ConfigValue.IsUnknown() {
130+
// skip validation if value is null or unknown
120131
return
121132
}
122133

@@ -131,49 +142,17 @@ func (validatorNet networkValidator) ValidateString(
131142
diags := diag.Diagnostics{}
132143

133144
for _, networkType := range validatorNet.NetworkTypes {
134-
switch networkType {
135-
case IPV4:
136-
d := new(validator.StringResponse)
137-
138-
networkTypes.IsIPV4().ValidateString(ctx, request, d)
139-
if d.Diagnostics.HasError() && !validatorNet.ComparatorOR {
140-
response.Diagnostics.Append(d.Diagnostics...)
141-
} else if d.Diagnostics.HasError() && validatorNet.ComparatorOR {
142-
diags.Append(d.Diagnostics...)
143-
}
144-
case IPV4WithCIDR:
145-
d := new(validator.StringResponse)
146-
147-
networkTypes.IsIPV4WithCIDR().ValidateString(ctx, request, d)
148-
if d.Diagnostics.HasError() && !validatorNet.ComparatorOR {
149-
response.Diagnostics.Append(d.Diagnostics...)
150-
} else if d.Diagnostics.HasError() && validatorNet.ComparatorOR {
151-
diags.Append(d.Diagnostics...)
152-
}
153-
case IPv4WithNetmask:
154-
d := new(validator.StringResponse)
155-
156-
networkTypes.IsIPV4WithNetmask().ValidateString(ctx, request, d)
157-
if d.Diagnostics.HasError() && !validatorNet.ComparatorOR {
158-
response.Diagnostics.Append(d.Diagnostics...)
159-
} else if d.Diagnostics.HasError() && validatorNet.ComparatorOR {
160-
diags.Append(d.Diagnostics...)
161-
}
162-
case RFC1918:
163-
d := new(validator.StringResponse)
164-
165-
networkTypes.IsRFC1918().ValidateString(ctx, request, d)
166-
if d.Diagnostics.HasError() && !validatorNet.ComparatorOR {
167-
response.Diagnostics.Append(d.Diagnostics...)
168-
} else if d.Diagnostics.HasError() && validatorNet.ComparatorOR {
169-
diags.Append(d.Diagnostics...)
170-
}
171-
default:
145+
d := new(validator.StringResponse)
146+
if _, ok := networkValidatorTypes[networkType]; !ok {
172147
response.Diagnostics.AddError(
173148
"Invalid network type",
174149
fmt.Sprintf("invalid network type: %s", networkType),
175150
)
151+
return
176152
}
153+
154+
networkValidatorTypes[networkType].ValidateString(ctx, request, d)
155+
diags.Append(d.Diagnostics...)
177156
}
178157

179158
if validatorNet.ComparatorOR && diags.ErrorsCount() == len(validatorNet.NetworkTypes) {
@@ -182,6 +161,10 @@ func (validatorNet networkValidator) ValidateString(
182161
"Set at least one valid network type",
183162
)
184163
}
164+
165+
if !validatorNet.ComparatorOR {
166+
response.Diagnostics.Append(diags...)
167+
}
185168
}
186169

187170
/*

stringvalidator/networkTypes/type_ipv4.go

+2-2
Original file line numberDiff line numberDiff line change
@@ -21,12 +21,12 @@ type validatorIPV4 struct{}
2121

2222
// Description describes the validation in plain text formatting.
2323
func (validator validatorIPV4) Description(_ context.Context) string {
24-
return "a valid IPV4 address (192.168.0.1)."
24+
return "a valid IPV4 address (Ex: 192.168.0.1)"
2525
}
2626

2727
// MarkdownDescription describes the validation in Markdown formatting.
2828
func (validator validatorIPV4) MarkdownDescription(_ context.Context) string {
29-
return "a valid IPV4 address (`192.168.0.1`)."
29+
return "a valid IPV4 address (Ex: `192.168.0.1`)"
3030
}
3131

3232
// Validate performs the validation.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
/*
2+
* SPDX-FileCopyrightText: Copyright (c) 2025 Orange
3+
* SPDX-License-Identifier: Mozilla Public License 2.0
4+
*
5+
* This software is distributed under the MPL-2.0 license.
6+
* the text of which is available at https://www.mozilla.org/en-US/MPL/2.0/
7+
* or see the "LICENSE" file for more details.
8+
*/
9+
10+
package networktypes
11+
12+
import (
13+
"bytes"
14+
"context"
15+
"fmt"
16+
"net"
17+
"strings"
18+
19+
"github.com/hashicorp/terraform-plugin-framework/schema/validator"
20+
)
21+
22+
type validatorIPV4Range struct{}
23+
24+
// Description describes the validation in plain text formatting.
25+
func (validator validatorIPV4Range) Description(_ context.Context) string {
26+
return "a valid IPV4 address range (Ex: 192.168.0.1-192.168.0.100)"
27+
}
28+
29+
// MarkdownDescription describes the validation in Markdown formatting.
30+
func (validator validatorIPV4Range) MarkdownDescription(_ context.Context) string {
31+
return "a valid IPV4 address range (Ex: `192.168.0.1-192.168.0.100`)"
32+
}
33+
34+
// Validate performs the validation.
35+
func (validator validatorIPV4Range) ValidateString(
36+
_ context.Context,
37+
request validator.StringRequest,
38+
response *validator.StringResponse,
39+
) {
40+
if request.ConfigValue.IsNull() || request.ConfigValue.IsUnknown() {
41+
return
42+
}
43+
44+
// Split the string into two parts
45+
parts := strings.Split(request.ConfigValue.ValueString(), "-")
46+
if len(parts) != 2 {
47+
response.Diagnostics.AddAttributeError(
48+
request.Path,
49+
"Invalid IPV4 range",
50+
fmt.Sprintf("invalid value: %s", request.ConfigValue.String()),
51+
)
52+
return
53+
}
54+
55+
// Check if the first IP address is less than the second IP address
56+
firstIP := net.ParseIP(parts[0])
57+
secondIP := net.ParseIP(parts[1])
58+
if firstIP.To4() == nil {
59+
response.Diagnostics.AddAttributeError(
60+
request.Path,
61+
"Failed to parse IPV4 address",
62+
fmt.Sprintf("the first part of the range is not a valid IPV4 address: %s", request.ConfigValue.String()),
63+
)
64+
return
65+
}
66+
67+
if secondIP.To4() == nil {
68+
response.Diagnostics.AddAttributeError(
69+
request.Path,
70+
"Failed to parse IPV4 address",
71+
fmt.Sprintf("the second part of the range is not a valid IPV4 address: %s", request.ConfigValue.String()),
72+
)
73+
return
74+
}
75+
76+
if bytes.Compare(firstIP.To4(), secondIP.To4()) >= 0 {
77+
response.Diagnostics.AddAttributeError(
78+
request.Path,
79+
"Invalid IPV4 range",
80+
fmt.Sprintf("the first part of the range is not less than the second part: %s", request.ConfigValue.String()),
81+
)
82+
return
83+
}
84+
}
85+
86+
func IsIPV4Range() validator.String {
87+
return &validatorIPV4Range{}
88+
}

0 commit comments

Comments
 (0)