Skip to content

Commit 42927a0

Browse files
authored
feat: implement ValidatorValuer interface feature (#1416)
## Problem In order to have custom type validations for generic types, every possible type has to be registered with `RegisterCustomTypeFunc` to return the underlying type. It is especially relevant for nullable types e.g. `sql.Null[T]`. Discussed here: #1232 ## Fixes Or Enhances * Adds a mechanism for validating generic types without using RegisterCustomTypeFunc by introducing `ValidatorValuer` interface. Example: ``` type Nullable[T any] struct { Data T } func (t Nullable[T]) ValidatorValue() any { return t.Data } type Config struct { Name string `validate:"required"` } type Record struct { Config Nullable[Config] `validate:"required"` } func TestValidatorValuerInterface2(t *testing.T) { s := Record{ Config: Nullable[Config]{}, } v := New() errs := v.Struct(s) t.Fatalf("Error: %v", errs.Error()) } // Key: 'Record.Config.Name' Error:Field validation for 'Name' failed on the 'required' tag // Notice: no Data in the Record.Config.Name ``` **Make sure that you've checked the boxes below before you submit PR:** - [x] Tests exist or have been written that cover this particular change. @go-playground/validator-maintainers
1 parent c254ece commit 42927a0

File tree

2 files changed

+246
-0
lines changed

2 files changed

+246
-0
lines changed

util.go

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,13 @@ import (
99
"time"
1010
)
1111

12+
// Valuer is an interface that allows you to expose a method on a type
13+
// (including generic types) that returns a value that is supposed to be validated.
14+
type Valuer interface {
15+
// ValidatorValue returns the value that is supposed to be validated.
16+
ValidatorValue() any
17+
}
18+
1219
// extractTypeInternal gets the actual underlying type of field value.
1320
// It will dive into pointers, customTypes and return you the
1421
// underlying value and it's kind.
@@ -23,6 +30,13 @@ BEGIN:
2330
return current, reflect.Ptr, nullable
2431
}
2532

33+
if current.CanInterface() {
34+
if v, ok := current.Interface().(Valuer); ok {
35+
current = reflect.ValueOf(v.ValidatorValue())
36+
goto BEGIN
37+
}
38+
}
39+
2640
current = current.Elem()
2741
goto BEGIN
2842

@@ -34,6 +48,13 @@ BEGIN:
3448
return current, reflect.Interface, nullable
3549
}
3650

51+
if current.CanInterface() {
52+
if v, ok := current.Interface().(Valuer); ok {
53+
current = reflect.ValueOf(v.ValidatorValue())
54+
goto BEGIN
55+
}
56+
}
57+
3758
current = current.Elem()
3859
goto BEGIN
3960

@@ -42,6 +63,13 @@ BEGIN:
4263

4364
default:
4465

66+
if current.CanInterface() {
67+
if v, ok := current.Interface().(Valuer); ok {
68+
current = reflect.ValueOf(v.ValidatorValue())
69+
goto BEGIN
70+
}
71+
}
72+
4573
if v.v.hasCustomFuncs {
4674
if fn, ok := v.v.customFuncs[current.Type()]; ok {
4775
current = reflect.ValueOf(fn(current))

validator_test.go

Lines changed: 218 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15841,3 +15841,221 @@ func TestRequiredIfWithArrays(t *testing.T) {
1584115841
Equal(t, err, nil) // No error - Text has value
1584215842
})
1584315843
}
15844+
15845+
type ValuerTypeWithPointerReceiver[T any] struct {
15846+
Data T
15847+
}
15848+
15849+
func (t *ValuerTypeWithPointerReceiver[T]) ValidatorValue() any {
15850+
return t.Data
15851+
}
15852+
15853+
type ValuerTypeWithValueReceiver[T any] struct {
15854+
Data T
15855+
}
15856+
15857+
func (t ValuerTypeWithValueReceiver[T]) ValidatorValue() any {
15858+
return t.Data
15859+
}
15860+
15861+
func TestValuerInterface(t *testing.T) {
15862+
t.Run("parent as Valuer (not called)", func(t *testing.T) {
15863+
errs := New().Struct(&ValuerTypeWithPointerReceiver[SubTest]{})
15864+
AssertError(t, errs,
15865+
"ValuerTypeWithPointerReceiver[github.com/go-playground/validator/v10.SubTest].Data.Test",
15866+
"ValuerTypeWithPointerReceiver[github.com/go-playground/validator/v10.SubTest].Data.Test",
15867+
"Test", "Test", "required")
15868+
})
15869+
t.Run("pointer parent, pointer nested, pointer receiver (called)", func(t *testing.T) {
15870+
type Parent struct {
15871+
Nested *ValuerTypeWithPointerReceiver[SubTest] `validate:"required"`
15872+
}
15873+
15874+
errs := New().Struct(&Parent{})
15875+
AssertError(t, errs, "Parent.Nested", "Parent.Nested", "Nested", "Nested", "required")
15876+
15877+
errs = New().Struct(&Parent{
15878+
Nested: &ValuerTypeWithPointerReceiver[SubTest]{},
15879+
})
15880+
AssertError(t, errs, "Parent.Nested.Test", "Parent.Nested.Test", "Test", "Test", "required")
15881+
15882+
errs = New().Struct(&Parent{
15883+
Nested: &ValuerTypeWithPointerReceiver[SubTest]{
15884+
Data: SubTest{
15885+
Test: "Test",
15886+
},
15887+
},
15888+
})
15889+
if errs != nil {
15890+
t.Fatalf("Expected no error, got: %v", errs)
15891+
}
15892+
})
15893+
t.Run("pointer parent, pointer nested, value receiver (called)", func(t *testing.T) {
15894+
type Parent struct {
15895+
Nested *ValuerTypeWithValueReceiver[SubTest] `validate:"required"`
15896+
}
15897+
15898+
errs := New().Struct(&Parent{})
15899+
AssertError(t, errs, "Parent.Nested", "Parent.Nested", "Nested", "Nested", "required")
15900+
15901+
errs = New().Struct(&Parent{
15902+
Nested: &ValuerTypeWithValueReceiver[SubTest]{},
15903+
})
15904+
AssertError(t, errs, "Parent.Nested.Test", "Parent.Nested.Test", "Test", "Test", "required")
15905+
15906+
errs = New().Struct(&Parent{
15907+
Nested: &ValuerTypeWithValueReceiver[SubTest]{
15908+
Data: SubTest{
15909+
Test: "Test",
15910+
},
15911+
},
15912+
})
15913+
if errs != nil {
15914+
t.Fatalf("Expected no error, got: %v", errs)
15915+
}
15916+
})
15917+
t.Run("pointer parent, value nested, pointer receiver (not called)", func(t *testing.T) {
15918+
type Parent struct {
15919+
Nested ValuerTypeWithPointerReceiver[SubTest] `validate:"required"`
15920+
}
15921+
15922+
errs := New().Struct(&Parent{})
15923+
AssertError(t, errs, "Parent.Nested.Data.Test", "Parent.Nested.Data.Test", "Test", "Test", "required")
15924+
15925+
errs = New().Struct(&Parent{
15926+
Nested: ValuerTypeWithPointerReceiver[SubTest]{},
15927+
})
15928+
AssertError(t, errs, "Parent.Nested.Data.Test", "Parent.Nested.Data.Test", "Test", "Test", "required")
15929+
15930+
errs = New().Struct(&Parent{
15931+
Nested: ValuerTypeWithPointerReceiver[SubTest]{
15932+
Data: SubTest{
15933+
Test: "Test",
15934+
},
15935+
},
15936+
})
15937+
if errs != nil {
15938+
t.Fatalf("Expected no error, got: %v", errs)
15939+
}
15940+
})
15941+
t.Run("pointer parent, value nested, value receiver (called)", func(t *testing.T) {
15942+
type Parent struct {
15943+
Nested ValuerTypeWithValueReceiver[SubTest] `validate:"required"`
15944+
}
15945+
15946+
errs := New().Struct(&Parent{})
15947+
AssertError(t, errs, "Parent.Nested.Test", "Parent.Nested.Test", "Test", "Test", "required")
15948+
15949+
errs = New().Struct(&Parent{
15950+
Nested: ValuerTypeWithValueReceiver[SubTest]{},
15951+
})
15952+
AssertError(t, errs, "Parent.Nested.Test", "Parent.Nested.Test", "Test", "Test", "required")
15953+
15954+
errs = New().Struct(&Parent{
15955+
Nested: ValuerTypeWithValueReceiver[SubTest]{
15956+
Data: SubTest{
15957+
Test: "Test",
15958+
},
15959+
},
15960+
})
15961+
if errs != nil {
15962+
t.Fatalf("Expected no error, got: %v", errs)
15963+
}
15964+
})
15965+
t.Run("value parent, pointer nested, pointer receiver (called)", func(t *testing.T) {
15966+
type Parent struct {
15967+
Nested *ValuerTypeWithPointerReceiver[SubTest] `validate:"required"`
15968+
}
15969+
15970+
errs := New().Struct(Parent{})
15971+
AssertError(t, errs, "Parent.Nested", "Parent.Nested", "Nested", "Nested", "required")
15972+
15973+
errs = New().Struct(Parent{
15974+
Nested: &ValuerTypeWithPointerReceiver[SubTest]{},
15975+
})
15976+
AssertError(t, errs, "Parent.Nested.Test", "Parent.Nested.Test", "Test", "Test", "required")
15977+
15978+
errs = New().Struct(Parent{
15979+
Nested: &ValuerTypeWithPointerReceiver[SubTest]{
15980+
Data: SubTest{
15981+
Test: "Test",
15982+
},
15983+
},
15984+
})
15985+
if errs != nil {
15986+
t.Fatalf("Expected no error, got: %v", errs)
15987+
}
15988+
})
15989+
t.Run("value parent, pointer nested, value receiver (called)", func(t *testing.T) {
15990+
type Parent struct {
15991+
Nested *ValuerTypeWithValueReceiver[SubTest] `validate:"required"`
15992+
}
15993+
15994+
errs := New().Struct(Parent{})
15995+
AssertError(t, errs, "Parent.Nested", "Parent.Nested", "Nested", "Nested", "required")
15996+
15997+
errs = New().Struct(Parent{
15998+
Nested: &ValuerTypeWithValueReceiver[SubTest]{},
15999+
})
16000+
AssertError(t, errs, "Parent.Nested.Test", "Parent.Nested.Test", "Test", "Test", "required")
16001+
16002+
errs = New().Struct(Parent{
16003+
Nested: &ValuerTypeWithValueReceiver[SubTest]{
16004+
Data: SubTest{
16005+
Test: "Test",
16006+
},
16007+
},
16008+
})
16009+
if errs != nil {
16010+
t.Fatalf("Expected no error, got: %v", errs)
16011+
}
16012+
})
16013+
t.Run("value parent, value nested, pointer receiver (not called)", func(t *testing.T) {
16014+
type Parent struct {
16015+
Nested ValuerTypeWithPointerReceiver[SubTest] `validate:"required"`
16016+
}
16017+
16018+
errs := New().Struct(Parent{})
16019+
AssertError(t, errs, "Parent.Nested.Data.Test", "Parent.Nested.Data.Test", "Test", "Test", "required")
16020+
16021+
errs = New().Struct(Parent{
16022+
Nested: ValuerTypeWithPointerReceiver[SubTest]{},
16023+
})
16024+
AssertError(t, errs, "Parent.Nested.Data.Test", "Parent.Nested.Data.Test", "Test", "Test", "required")
16025+
16026+
errs = New().Struct(Parent{
16027+
Nested: ValuerTypeWithPointerReceiver[SubTest]{
16028+
Data: SubTest{
16029+
Test: "Test",
16030+
},
16031+
},
16032+
})
16033+
if errs != nil {
16034+
t.Fatalf("Expected no error, got: %v", errs)
16035+
}
16036+
})
16037+
t.Run("value parent, value nested, value receiver (called)", func(t *testing.T) {
16038+
type Parent struct {
16039+
Nested ValuerTypeWithValueReceiver[SubTest] `validate:"required"`
16040+
}
16041+
16042+
errs := New().Struct(Parent{})
16043+
AssertError(t, errs, "Parent.Nested.Test", "Parent.Nested.Test", "Test", "Test", "required")
16044+
16045+
errs = New().Struct(Parent{
16046+
Nested: ValuerTypeWithValueReceiver[SubTest]{},
16047+
})
16048+
AssertError(t, errs, "Parent.Nested.Test", "Parent.Nested.Test", "Test", "Test", "required")
16049+
16050+
errs = New().Struct(Parent{
16051+
Nested: ValuerTypeWithValueReceiver[SubTest]{
16052+
Data: SubTest{
16053+
Test: "Test",
16054+
},
16055+
},
16056+
})
16057+
if errs != nil {
16058+
t.Fatalf("Expected no error, got: %v", errs)
16059+
}
16060+
})
16061+
}

0 commit comments

Comments
 (0)