From 9e49c3e538cc08d5376b582d506d5b3bd9613d45 Mon Sep 17 00:00:00 2001 From: Barash Asenov Date: Thu, 30 Oct 2025 18:38:56 +0100 Subject: [PATCH 1/3] [fix] fix unix addr resolution --- README.md | 1 + baked_in.go | 60 ++++++++++++++++++++++++++++++++++++ doc.go | 9 ++++++ validator_test.go | 78 +++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 148 insertions(+) diff --git a/README.md b/README.md index cb5d4194..b21290ae 100644 --- a/README.md +++ b/README.md @@ -123,6 +123,7 @@ validate := validator.New(validator.WithRequiredStructEnabled()) | udp6_addr | User Datagram Protocol Address UDPv6 | | udp_addr | User Datagram Protocol Address UDP | | unix_addr | Unix domain socket end point Address | +| uds_exists | Unix domain socket exists (checks filesystem sockets and Linux abstract sockets) | | uri | URI String | | url | URL String | | http_url | HTTP(s) URL String | diff --git a/baked_in.go b/baked_in.go index 8fd55e77..2b1087d2 100644 --- a/baked_in.go +++ b/baked_in.go @@ -1,6 +1,7 @@ package validator import ( + "bufio" "bytes" "cmp" "context" @@ -15,6 +16,7 @@ import ( "net/url" "os" "reflect" + "runtime" "strconv" "strings" "sync" @@ -205,6 +207,7 @@ var ( "ip6_addr": isIP6AddrResolvable, "ip_addr": isIPAddrResolvable, "unix_addr": isUnixAddrResolvable, + "uds_exists": isUnixDomainSocketExists, "mac": isMAC, "hostname": isHostnameRFC952, // RFC 952 "hostname_rfc1123": isHostnameRFC1123, // RFC 1123 @@ -2595,6 +2598,63 @@ func isUnixAddrResolvable(fl FieldLevel) bool { return err == nil } +// isUnixDomainSocketExists is the validation function for validating if the field's value is an existing Unix domain socket. +// It handles both filesystem-based sockets and Linux abstract sockets. +func isUnixDomainSocketExists(fl FieldLevel) bool { + sockpath := fl.Field().String() + + if sockpath == "" { + return false + } + + // On Linux, check for abstract sockets (prefixed with @) + if runtime.GOOS == "linux" && strings.HasPrefix(sockpath, "@") { + return isAbstractSocketExists(sockpath) + } + + // For filesystem-based sockets, check if the path exists and is a socket + stats, err := os.Stat(sockpath) + if err != nil { + return false + } + + return stats.Mode().Type() == fs.ModeSocket +} + +// isAbstractSocketExists checks if a Linux abstract socket exists by reading /proc/net/unix. +// Abstract sockets are identified by an @ prefix in human-readable form. +func isAbstractSocketExists(sockpath string) bool { + file, err := os.Open("/proc/net/unix") + if err != nil { + return false + } + defer file.Close() + + scanner := bufio.NewScanner(file) + + // Skip the header line + if !scanner.Scan() { + return false + } + + // Abstract sockets in /proc/net/unix are represented with @ prefix + // The socket path is the last field in each line + for scanner.Scan() { + line := scanner.Text() + fields := strings.Fields(line) + + // The path is the last field (8th field typically) + if len(fields) >= 8 { + path := fields[len(fields)-1] + if path == sockpath { + return true + } + } + } + + return false +} + func isIP4Addr(fl FieldLevel) bool { val := fl.Field().String() diff --git a/doc.go b/doc.go index cd6eefdc..1f588e6f 100644 --- a/doc.go +++ b/doc.go @@ -1263,6 +1263,15 @@ This validates that a string value contains a valid Unix Address. Usage: unix_addr +# Unix Domain Socket Exists + +This validates that a Unix domain socket file exists at the specified path. +It checks both filesystem-based sockets and Linux abstract sockets (prefixed with @). +For filesystem sockets, it verifies the path exists and is a socket file. +For abstract sockets on Linux, it checks /proc/net/unix. + + Usage: uds_exists + # Media Access Control Address MAC This validates that a string value contains a valid MAC Address. diff --git a/validator_test.go b/validator_test.go index 8e969d30..835a3a15 100644 --- a/validator_test.go +++ b/validator_test.go @@ -12,9 +12,11 @@ import ( "image" "image/jpeg" "image/png" + "net" "os" "path/filepath" "reflect" + "runtime" "strings" "testing" "time" @@ -2950,6 +2952,82 @@ func TestUnixAddrValidation(t *testing.T) { } } +func TestUnixDomainSocketExistsValidation(t *testing.T) { + validate := New() + + // Test with empty string - should fail + errs := validate.Var("", "uds_exists") + NotEqual(t, errs, nil) + AssertError(t, errs, "", "", "", "", "uds_exists") + + // Test with non-existent path - should fail + errs = validate.Var("/tmp/nonexistent.sock", "uds_exists") + NotEqual(t, errs, nil) + AssertError(t, errs, "", "", "", "", "uds_exists") + + // Test with a real socket file + // Create a temporary socket file for testing + // Use /tmp directly to avoid path length issues on macOS + sockPath := "/tmp/test_validator.sock" + + // Create a Unix domain socket + listener, err := net.Listen("unix", sockPath) + if err != nil { + t.Fatalf("Failed to create test socket: %v", err) + } + defer listener.Close() + defer os.Remove(sockPath) + + // Test with existing socket - should pass + errs = validate.Var(sockPath, "uds_exists") + Equal(t, errs, nil) + + // Test with regular file (not a socket) - should fail + regularFile := "/tmp/test_validator_regular.txt" + if err := os.WriteFile(regularFile, []byte("test"), 0644); err != nil { + t.Fatalf("Failed to create regular file: %v", err) + } + defer os.Remove(regularFile) + + errs = validate.Var(regularFile, "uds_exists") + NotEqual(t, errs, nil) + AssertError(t, errs, "", "", "", "", "uds_exists") + + // Test with directory - should fail + dirPath := "/tmp/test_validator_dir" + if err := os.Mkdir(dirPath, 0755); err != nil && !os.IsExist(err) { + t.Fatalf("Failed to create directory: %v", err) + } + defer os.RemoveAll(dirPath) + + errs = validate.Var(dirPath, "uds_exists") + NotEqual(t, errs, nil) + AssertError(t, errs, "", "", "", "", "uds_exists") + + // Linux-specific test for abstract sockets + if runtime.GOOS == "linux" { + // Create an abstract socket (prefixed with @) + abstractSockName := "@test_abstract_socket_" + fmt.Sprintf("%d", time.Now().UnixNano()) + + // In Go, abstract sockets are created by using a null byte prefix + // but for testing, we need to use the actual implementation + abstractListener, err := net.Listen("unix", "\x00"+abstractSockName[1:]) + if err != nil { + t.Fatalf("Failed to create abstract socket: %v", err) + } + defer abstractListener.Close() + + // Test with existing abstract socket - should pass + errs = validate.Var(abstractSockName, "uds_exists") + Equal(t, errs, nil) + + // Test with non-existent abstract socket - should fail + errs = validate.Var("@nonexistent_abstract_socket", "uds_exists") + NotEqual(t, errs, nil) + AssertError(t, errs, "", "", "", "", "uds_exists") + } +} + func TestSliceMapArrayChanFuncPtrInterfaceRequiredValidation(t *testing.T) { validate := New() From 7bffc5d766c3297529246314efe06f3782ec9a54 Mon Sep 17 00:00:00 2001 From: Barash Asenov Date: Thu, 30 Oct 2025 18:58:10 +0100 Subject: [PATCH 2/3] [fix] fix ci --- .golangci.yaml | 2 -- baked_in.go | 4 +++- validator_test.go | 38 ++++++++++++++++++++++++-------------- 3 files changed, 27 insertions(+), 17 deletions(-) diff --git a/.golangci.yaml b/.golangci.yaml index dd9c05cc..d3d8e390 100644 --- a/.golangci.yaml +++ b/.golangci.yaml @@ -2,8 +2,6 @@ version: "2" linters: default: all disable: - - noinlineerr - - wsl_v5 - copyloopvar - cyclop - depguard diff --git a/baked_in.go b/baked_in.go index 2b1087d2..c74aaa1a 100644 --- a/baked_in.go +++ b/baked_in.go @@ -2628,7 +2628,9 @@ func isAbstractSocketExists(sockpath string) bool { if err != nil { return false } - defer file.Close() + defer func() { + _ = file.Close() + }() scanner := bufio.NewScanner(file) diff --git a/validator_test.go b/validator_test.go index 835a3a15..244b26cb 100644 --- a/validator_test.go +++ b/validator_test.go @@ -792,7 +792,7 @@ func TestStructPartial(t *testing.T) { // the following should all return no errors as everything is valid in // the default state - errs := validate.StructPartialCtx(context.Background(), tPartial, p1...) + errs := validate.StructPartialCtx(t.Context(), tPartial, p1...) Equal(t, errs, nil) errs = validate.StructPartial(tPartial, p2...) @@ -802,7 +802,7 @@ func TestStructPartial(t *testing.T) { errs = validate.StructPartial(tPartial.SubSlice[0], p3...) Equal(t, errs, nil) - errs = validate.StructExceptCtx(context.Background(), tPartial, p1...) + errs = validate.StructExceptCtx(t.Context(), tPartial, p1...) Equal(t, errs, nil) errs = validate.StructExcept(tPartial, p2...) @@ -1040,7 +1040,7 @@ func TestCrossStructLteFieldValidation(t *testing.T) { AssertError(t, errs, "Test.Float", "Test.Float", "Float", "Float", "ltecsfield") AssertError(t, errs, "Test.Array", "Test.Array", "Array", "Array", "ltecsfield") - errs = validate.VarWithValueCtx(context.Background(), 1, "", "ltecsfield") + errs = validate.VarWithValueCtx(t.Context(), 1, "", "ltecsfield") NotEqual(t, errs, nil) AssertError(t, errs, "", "", "", "", "ltecsfield") @@ -2233,7 +2233,7 @@ func TestSQLValue2Validation(t *testing.T) { AssertError(t, errs, "", "", "", "", "required") val.Name = "Valid Name" - errs = validate.VarCtx(context.Background(), val, "required") + errs = validate.VarCtx(t.Context(), val, "required") Equal(t, errs, nil) val.Name = "errorme" @@ -2975,8 +2975,12 @@ func TestUnixDomainSocketExistsValidation(t *testing.T) { if err != nil { t.Fatalf("Failed to create test socket: %v", err) } - defer listener.Close() - defer os.Remove(sockPath) + defer func() { + _ = listener.Close() + }() + defer func() { + _ = os.Remove(sockPath) + }() // Test with existing socket - should pass errs = validate.Var(sockPath, "uds_exists") @@ -2987,7 +2991,9 @@ func TestUnixDomainSocketExistsValidation(t *testing.T) { if err := os.WriteFile(regularFile, []byte("test"), 0644); err != nil { t.Fatalf("Failed to create regular file: %v", err) } - defer os.Remove(regularFile) + defer func() { + _ = os.Remove(regularFile) + }() errs = validate.Var(regularFile, "uds_exists") NotEqual(t, errs, nil) @@ -2998,7 +3004,9 @@ func TestUnixDomainSocketExistsValidation(t *testing.T) { if err := os.Mkdir(dirPath, 0755); err != nil && !os.IsExist(err) { t.Fatalf("Failed to create directory: %v", err) } - defer os.RemoveAll(dirPath) + defer func() { + _ = os.RemoveAll(dirPath) + }() errs = validate.Var(dirPath, "uds_exists") NotEqual(t, errs, nil) @@ -3015,7 +3023,9 @@ func TestUnixDomainSocketExistsValidation(t *testing.T) { if err != nil { t.Fatalf("Failed to create abstract socket: %v", err) } - defer abstractListener.Close() + defer func() { + _ = abstractListener.Close() + }() // Test with existing abstract socket - should pass errs = validate.Var(abstractSockName, "uds_exists") @@ -9830,7 +9840,7 @@ func TestStructFiltered(t *testing.T) { // the following should all return no errors as everything is valid in // the default state - errs := validate.StructFilteredCtx(context.Background(), tPartial, p1) + errs := validate.StructFilteredCtx(t.Context(), tPartial, p1) Equal(t, errs, nil) errs = validate.StructFiltered(tPartial, p2) @@ -10243,7 +10253,7 @@ func TestValidateStructRegisterCtx(t *testing.T) { validate.RegisterStructValidationCtx(slFn, Test{}) - ctx := context.WithValue(context.Background(), &ctxVal, "testval") + ctx := context.WithValue(t.Context(), &ctxVal, "testval") ctx = context.WithValue(ctx, &ctxSlVal, "slVal") errs := validate.StructCtx(ctx, tst) Equal(t, errs, nil) @@ -13678,7 +13688,7 @@ func TestValidate_ValidateMapCtx(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { validate := New() - if got := validate.ValidateMapCtx(context.Background(), tt.args.data, tt.args.rules); len(got) != tt.want { + if got := validate.ValidateMapCtx(t.Context(), tt.args.data, tt.args.rules); len(got) != tt.want { t.Errorf("ValidateMapCtx() = %v, want %v", got, tt.want) } }) @@ -13749,7 +13759,7 @@ func TestValidate_ValidateMapCtxWithKeys(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { validate := New() - errs := validate.ValidateMapCtx(context.Background(), tt.args.data, tt.args.rules) + errs := validate.ValidateMapCtx(t.Context(), tt.args.data, tt.args.rules) NotEqual(t, errs, nil) Equal(t, len(errs), tt.want) for key, err := range errs { @@ -13771,7 +13781,7 @@ func TestValidate_VarWithKey(t *testing.T) { func TestValidate_VarWithKeyCtx(t *testing.T) { validate := New() - errs := validate.VarWithKeyCtx(context.Background(), "age", 15, "required,gt=16") + errs := validate.VarWithKeyCtx(t.Context(), "age", 15, "required,gt=16") NotEqual(t, errs, nil) AssertError(t, errs, "age", "age", "age", "age", "gt") From 4b86d5b3063408677f7c031e0ccf3d1212425b35 Mon Sep 17 00:00:00 2001 From: Barash Asenov Date: Thu, 30 Oct 2025 19:10:55 +0100 Subject: [PATCH 3/3] [fix] Fix linter errors && skip on windows --- .golangci.yaml | 3 +++ baked_in.go | 5 +++++ validator_test.go | 54 +++++++++++++++++------------------------------ 3 files changed, 27 insertions(+), 35 deletions(-) diff --git a/.golangci.yaml b/.golangci.yaml index d3d8e390..e89da03d 100644 --- a/.golangci.yaml +++ b/.golangci.yaml @@ -2,6 +2,8 @@ version: "2" linters: default: all disable: + - noinlineerr + - wsl_v5 - copyloopvar - cyclop - depguard @@ -50,3 +52,4 @@ linters: - varnamelen - wrapcheck - wsl + - modernize diff --git a/baked_in.go b/baked_in.go index c74aaa1a..34a2a3eb 100644 --- a/baked_in.go +++ b/baked_in.go @@ -2600,7 +2600,12 @@ func isUnixAddrResolvable(fl FieldLevel) bool { // isUnixDomainSocketExists is the validation function for validating if the field's value is an existing Unix domain socket. // It handles both filesystem-based sockets and Linux abstract sockets. +// It always returns false for Windows. func isUnixDomainSocketExists(fl FieldLevel) bool { + if runtime.GOOS == "windows" { + return false + } + sockpath := fl.Field().String() if sockpath == "" { diff --git a/validator_test.go b/validator_test.go index 244b26cb..b4b0f040 100644 --- a/validator_test.go +++ b/validator_test.go @@ -3,7 +3,9 @@ package validator import ( "bytes" "context" + "net" "database/sql" + "runtime" "database/sql/driver" "encoding/base64" "encoding/json" @@ -12,11 +14,9 @@ import ( "image" "image/jpeg" "image/png" - "net" "os" "path/filepath" "reflect" - "runtime" "strings" "testing" "time" @@ -792,7 +792,7 @@ func TestStructPartial(t *testing.T) { // the following should all return no errors as everything is valid in // the default state - errs := validate.StructPartialCtx(t.Context(), tPartial, p1...) + errs := validate.StructPartialCtx(context.Background(), tPartial, p1...) Equal(t, errs, nil) errs = validate.StructPartial(tPartial, p2...) @@ -802,7 +802,7 @@ func TestStructPartial(t *testing.T) { errs = validate.StructPartial(tPartial.SubSlice[0], p3...) Equal(t, errs, nil) - errs = validate.StructExceptCtx(t.Context(), tPartial, p1...) + errs = validate.StructExceptCtx(context.Background(), tPartial, p1...) Equal(t, errs, nil) errs = validate.StructExcept(tPartial, p2...) @@ -1040,7 +1040,7 @@ func TestCrossStructLteFieldValidation(t *testing.T) { AssertError(t, errs, "Test.Float", "Test.Float", "Float", "Float", "ltecsfield") AssertError(t, errs, "Test.Array", "Test.Array", "Array", "Array", "ltecsfield") - errs = validate.VarWithValueCtx(t.Context(), 1, "", "ltecsfield") + errs = validate.VarWithValueCtx(context.Background(), 1, "", "ltecsfield") NotEqual(t, errs, nil) AssertError(t, errs, "", "", "", "", "ltecsfield") @@ -2233,7 +2233,7 @@ func TestSQLValue2Validation(t *testing.T) { AssertError(t, errs, "", "", "", "", "required") val.Name = "Valid Name" - errs = validate.VarCtx(t.Context(), val, "required") + errs = validate.VarCtx(context.Background(), val, "required") Equal(t, errs, nil) val.Name = "errorme" @@ -2953,25 +2953,23 @@ func TestUnixAddrValidation(t *testing.T) { } func TestUnixDomainSocketExistsValidation(t *testing.T) { + if runtime.GOOS == "windows" { + t.Skip("Unix domain sockets are not supported on Windows") + } + validate := New() - // Test with empty string - should fail errs := validate.Var("", "uds_exists") NotEqual(t, errs, nil) AssertError(t, errs, "", "", "", "", "uds_exists") - // Test with non-existent path - should fail errs = validate.Var("/tmp/nonexistent.sock", "uds_exists") NotEqual(t, errs, nil) AssertError(t, errs, "", "", "", "", "uds_exists") - // Test with a real socket file - // Create a temporary socket file for testing - // Use /tmp directly to avoid path length issues on macOS sockPath := "/tmp/test_validator.sock" - - // Create a Unix domain socket - listener, err := net.Listen("unix", sockPath) + var lc net.ListenConfig + listener, err := lc.Listen(t.Context(), "unix", sockPath) if err != nil { t.Fatalf("Failed to create test socket: %v", err) } @@ -2981,12 +2979,9 @@ func TestUnixDomainSocketExistsValidation(t *testing.T) { defer func() { _ = os.Remove(sockPath) }() - - // Test with existing socket - should pass errs = validate.Var(sockPath, "uds_exists") Equal(t, errs, nil) - // Test with regular file (not a socket) - should fail regularFile := "/tmp/test_validator_regular.txt" if err := os.WriteFile(regularFile, []byte("test"), 0644); err != nil { t.Fatalf("Failed to create regular file: %v", err) @@ -2994,12 +2989,10 @@ func TestUnixDomainSocketExistsValidation(t *testing.T) { defer func() { _ = os.Remove(regularFile) }() - errs = validate.Var(regularFile, "uds_exists") NotEqual(t, errs, nil) AssertError(t, errs, "", "", "", "", "uds_exists") - // Test with directory - should fail dirPath := "/tmp/test_validator_dir" if err := os.Mkdir(dirPath, 0755); err != nil && !os.IsExist(err) { t.Fatalf("Failed to create directory: %v", err) @@ -3007,31 +3000,22 @@ func TestUnixDomainSocketExistsValidation(t *testing.T) { defer func() { _ = os.RemoveAll(dirPath) }() - errs = validate.Var(dirPath, "uds_exists") NotEqual(t, errs, nil) AssertError(t, errs, "", "", "", "", "uds_exists") - // Linux-specific test for abstract sockets if runtime.GOOS == "linux" { - // Create an abstract socket (prefixed with @) abstractSockName := "@test_abstract_socket_" + fmt.Sprintf("%d", time.Now().UnixNano()) - - // In Go, abstract sockets are created by using a null byte prefix - // but for testing, we need to use the actual implementation - abstractListener, err := net.Listen("unix", "\x00"+abstractSockName[1:]) + var lc net.ListenConfig + abstractListener, err := lc.Listen(t.Context(), "unix", "\x00"+abstractSockName[1:]) if err != nil { t.Fatalf("Failed to create abstract socket: %v", err) } defer func() { _ = abstractListener.Close() }() - - // Test with existing abstract socket - should pass errs = validate.Var(abstractSockName, "uds_exists") Equal(t, errs, nil) - - // Test with non-existent abstract socket - should fail errs = validate.Var("@nonexistent_abstract_socket", "uds_exists") NotEqual(t, errs, nil) AssertError(t, errs, "", "", "", "", "uds_exists") @@ -9840,7 +9824,7 @@ func TestStructFiltered(t *testing.T) { // the following should all return no errors as everything is valid in // the default state - errs := validate.StructFilteredCtx(t.Context(), tPartial, p1) + errs := validate.StructFilteredCtx(context.Background(), tPartial, p1) Equal(t, errs, nil) errs = validate.StructFiltered(tPartial, p2) @@ -10253,7 +10237,7 @@ func TestValidateStructRegisterCtx(t *testing.T) { validate.RegisterStructValidationCtx(slFn, Test{}) - ctx := context.WithValue(t.Context(), &ctxVal, "testval") + ctx := context.WithValue(context.Background(), &ctxVal, "testval") ctx = context.WithValue(ctx, &ctxSlVal, "slVal") errs := validate.StructCtx(ctx, tst) Equal(t, errs, nil) @@ -13688,7 +13672,7 @@ func TestValidate_ValidateMapCtx(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { validate := New() - if got := validate.ValidateMapCtx(t.Context(), tt.args.data, tt.args.rules); len(got) != tt.want { + if got := validate.ValidateMapCtx(context.Background(), tt.args.data, tt.args.rules); len(got) != tt.want { t.Errorf("ValidateMapCtx() = %v, want %v", got, tt.want) } }) @@ -13759,7 +13743,7 @@ func TestValidate_ValidateMapCtxWithKeys(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { validate := New() - errs := validate.ValidateMapCtx(t.Context(), tt.args.data, tt.args.rules) + errs := validate.ValidateMapCtx(context.Background(), tt.args.data, tt.args.rules) NotEqual(t, errs, nil) Equal(t, len(errs), tt.want) for key, err := range errs { @@ -13781,7 +13765,7 @@ func TestValidate_VarWithKey(t *testing.T) { func TestValidate_VarWithKeyCtx(t *testing.T) { validate := New() - errs := validate.VarWithKeyCtx(t.Context(), "age", 15, "required,gt=16") + errs := validate.VarWithKeyCtx(context.Background(), "age", 15, "required,gt=16") NotEqual(t, errs, nil) AssertError(t, errs, "age", "age", "age", "age", "gt")