Skip to content
Merged
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 |
| validateFn | 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 @@ -244,6 +244,7 @@ var (
"cron": isCron,
"spicedb": isSpiceDB,
"ein": isEIN,
"validateFn": isValidateFn,
}
)

Expand Down Expand Up @@ -3046,3 +3047,29 @@ func isEIN(fl FieldLevel) bool {

return einRegex().MatchString(field.String())
}

func isValidateFn(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

# ValidateFn

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

Usage: validateFn

# Alpha Only

This validates that a string value contains ASCII alpha characters only
Expand Down
76 changes: 76 additions & 0 deletions validator_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
"database/sql/driver"
"encoding/base64"
"encoding/json"
"errors"
"fmt"
"image"
"image/jpeg"
Expand Down Expand Up @@ -14148,3 +14149,78 @@
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:"validateFn"`
}

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(), "validateFn")

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(), "validateFn")

})

Check failure on line 14197 in validator_test.go

View workflow job for this annotation

GitHub Actions / lint

unnecessary trailing newline (whitespace)

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

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

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(), "validateFn")
})
}
Loading