Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add support to tag isvalid #1363

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -262,6 +262,8 @@ validate := validator.New(validator.WithRequiredStructEnabled())
| excluded_without | Excluded Without |
| excluded_without_all | Excluded Without All |
| unique | Unique |
| isvalid | Verify if the method `Validate() error` does not return an error |


#### Aliases:
| Tag | Description |
Expand Down
27 changes: 27 additions & 0 deletions baked_in.go
Original file line number Diff line number Diff line change
Expand Up @@ -241,6 +241,7 @@ var (
"mongodb_connection_string": isMongoDBConnectionString,
"cron": isCron,
"spicedb": isSpiceDB,
"isvalid": isValid,
}
)

Expand Down Expand Up @@ -3044,3 +3045,29 @@ func isCron(fl FieldLevel) bool {
cronString := fl.Field().String()
return cronRegex().MatchString(cronString)
}

func isValid(fl FieldLevel) bool {
if instance, ok := tryConvertFieldTo[interface{ Validate() error }](fl.Field()); ok {
return instance.Validate() == nil
}

return false
}

func tryConvertFieldTo[V any](field reflect.Value) (v V, ok bool) {
v, ok = convertFieldTo[V](field)
if !ok && field.CanAddr() {
v, ok = convertFieldTo[V](field.Addr())
}

return v, ok
}

func convertFieldTo[V any](field reflect.Value) (v V, ok bool) {
if v, ok = field.Interface().(V); ok {
return v, ok
}

var zero V
return zero, false
}
7 changes: 7 additions & 0 deletions doc.go
Original file line number Diff line number Diff line change
Expand Up @@ -756,6 +756,13 @@ in a field of the struct specified via a parameter.
// For slices of struct:
Usage: unique=field

# IsValid

This validates that an object respects the interface `Validate() error` and
the method `Validate` does not return an error.

Usage: isvalid

# Alpha Only

This validates that a string value contains ASCII alpha characters only
Expand Down
78 changes: 77 additions & 1 deletion validator_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"database/sql/driver"
"encoding/base64"
"encoding/json"
"errors"
"fmt"
"image"
"image/jpeg"
Expand Down Expand Up @@ -12080,7 +12081,7 @@ func TestExcludedIf(t *testing.T) {

test11 := struct {
Field1 bool
Field2 *string `validate:"excluded_if=Field1 false"`
Field2 *string `validate:"excluded_if=Field1 false"`
}{
Field1: false,
Field2: nil,
Expand Down Expand Up @@ -14123,3 +14124,78 @@ func TestPrivateFieldsStruct(t *testing.T) {
Equal(t, len(errs), tc.errorNum)
}
}

type NotRed struct {
Color string
}

func (r *NotRed) Validate() error {
if r != nil && r.Color == "red" {
return errors.New("should not be red")
}

return nil
}

func TestIsValid(t *testing.T) {
t.Run("using pointer", func(t *testing.T) {
validate := New()

type Test struct {
String string
Inner *NotRed `validate:"isvalid"`
}

var tt Test

errs := validate.Struct(tt)
NotEqual(t, errs, nil)

fe := errs.(ValidationErrors)[0]
Equal(t, fe.Field(), "Inner")
Equal(t, fe.Namespace(), "Test.Inner")
Equal(t, fe.Tag(), "isvalid")

tt.Inner = &NotRed{Color: "blue"}
errs = validate.Struct(tt)
Equal(t, errs, nil)

tt.Inner = &NotRed{Color: "red"}
errs = validate.Struct(tt)
NotEqual(t, errs, nil)

fe = errs.(ValidationErrors)[0]
Equal(t, fe.Field(), "Inner")
Equal(t, fe.Namespace(), "Test.Inner")
Equal(t, fe.Tag(), "isvalid")

})

t.Run("using struct", func(t *testing.T) {
validate := New()

type Test2 struct {
String string
Inner NotRed `validate:"isvalid"`
}

var tt2 Test2

errs := validate.Struct(&tt2)
Equal(t, errs, nil)

tt2.Inner = NotRed{Color: "blue"}

errs = validate.Struct(&tt2)
Equal(t, errs, nil)

tt2.Inner = NotRed{Color: "red"}
errs = validate.Struct(&tt2)
NotEqual(t, errs, nil)

fe := errs.(ValidationErrors)[0]
Equal(t, fe.Field(), "Inner")
Equal(t, fe.Namespace(), "Test2.Inner")
Equal(t, fe.Tag(), "isvalid")
})
}
Loading