Skip to content

Commit e022a95

Browse files
committed
feat: add validator for IPv4 bind address / host:port
supporting older Golang versions by using net.ParseIP, not netip.ParseAddr
1 parent a947377 commit e022a95

File tree

2 files changed

+56
-0
lines changed

2 files changed

+56
-0
lines changed

baked_in.go

+24
Original file line numberDiff line numberDiff line change
@@ -186,6 +186,7 @@ var (
186186
"ipv4": isIPv4,
187187
"ipv6": isIPv6,
188188
"ip": isIP,
189+
"ipv4_port": isIPv4Port,
189190
"cidrv4": isCIDRv4,
190191
"cidrv6": isCIDRv6,
191192
"cidr": isCIDR,
@@ -2704,6 +2705,29 @@ func isHostnamePort(fl FieldLevel) bool {
27042705
return true
27052706
}
27062707

2708+
// isIPv4Port validates a <ipv4>:<port> combination for fields typically used for socket address.
2709+
func isIPv4Port(fl FieldLevel) bool {
2710+
val := fl.Field().String()
2711+
ip, port, err := net.SplitHostPort(val)
2712+
if err != nil {
2713+
return false
2714+
}
2715+
// Port must be a iny <= 65535.
2716+
if portNum, err := strconv.ParseInt(
2717+
port, 10, 32,
2718+
); err != nil || portNum > 65535 || portNum < 1 {
2719+
return false
2720+
}
2721+
2722+
// If IP address is specified, it should match a valid IPv4 address
2723+
if ip != "" {
2724+
// we need to support older Golang versions, so we can not use netip.ParseAddr
2725+
parsedIp := net.ParseIP(ip)
2726+
return parsedIp != nil && parsedIp.To4() != nil
2727+
}
2728+
return true
2729+
}
2730+
27072731
// isLowercase is the validation function for validating if the current field's value is a lowercase string.
27082732
func isLowercase(fl FieldLevel) bool {
27092733
field := fl.Field()

validator_test.go

+32
Original file line numberDiff line numberDiff line change
@@ -12377,6 +12377,38 @@ func Test_hostnameport_validator(t *testing.T) {
1237712377
}
1237812378
}
1237912379

12380+
func Test_ipv4_port_validator(t *testing.T) {
12381+
type IPv4Port struct {
12382+
BindAddr string `validate:"ipv4_port"`
12383+
}
12384+
12385+
type testInput struct {
12386+
data string
12387+
expected bool
12388+
}
12389+
testData := []testInput{
12390+
{"192.168.1.1:1234", true},
12391+
{":1234", true},
12392+
{"localhost:1234", false},
12393+
{"aaa.bbb.ccc.ddd:234", false},
12394+
{":alpha", false},
12395+
{"1.2.3.4", false},
12396+
{"2001:db8::1:0.0.0.0:234", false},
12397+
{"2001:db8::1:0.0.0.0", false},
12398+
{"2001:db8::1:0.0.0.0:", false},
12399+
{"2001:db8::1:0.0.0.0:123456", false},
12400+
{"2001:db8::1:0.0.0.0:123456:", false},
12401+
}
12402+
for _, td := range testData {
12403+
h := IPv4Port{BindAddr: td.data}
12404+
v := New()
12405+
err := v.Struct(h)
12406+
if td.expected != (err == nil) {
12407+
t.Fatalf("Test failed for data: %v Error: %v", td.data, err)
12408+
}
12409+
}
12410+
}
12411+
1238012412
func TestLowercaseValidation(t *testing.T) {
1238112413
tests := []struct {
1238212414
param string

0 commit comments

Comments
 (0)