Skip to content

Commit 9597b0f

Browse files
committed
feat: add validator for IPv4 bind address / host:port
1 parent a947377 commit 9597b0f

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
@@ -9,6 +9,7 @@ import (
99
"fmt"
1010
"io/fs"
1111
"net"
12+
"net/netip"
1213
"net/url"
1314
"os"
1415
"reflect"
@@ -186,6 +187,7 @@ var (
186187
"ipv4": isIPv4,
187188
"ipv6": isIPv6,
188189
"ip": isIP,
190+
"ipv4_port": isIPv4Port,
189191
"cidrv4": isCIDRv4,
190192
"cidrv6": isCIDRv6,
191193
"cidr": isCIDR,
@@ -2704,6 +2706,28 @@ func isHostnamePort(fl FieldLevel) bool {
27042706
return true
27052707
}
27062708

2709+
// isIPv4Port validates a <ipv4>:<port> combination for fields typically used for socket address.
2710+
func isIPv4Port(fl FieldLevel) bool {
2711+
val := fl.Field().String()
2712+
ip, port, err := net.SplitHostPort(val)
2713+
if err != nil {
2714+
return false
2715+
}
2716+
// Port must be a iny <= 65535.
2717+
if portNum, err := strconv.ParseInt(
2718+
port, 10, 32,
2719+
); err != nil || portNum > 65535 || portNum < 1 {
2720+
return false
2721+
}
2722+
2723+
// If IP address is specified, it should match a valid IPv4 address
2724+
if ip != "" {
2725+
addr, err := netip.ParseAddr(ip)
2726+
return err == nil && addr.Is4()
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)