Skip to content

feat: implement ValidatorValuer interface feature #1416

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

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
28 changes: 28 additions & 0 deletions util.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,13 @@ import (
"time"
)

// ValidatorValuer is an interface that allows you to expose a method on a type
// (including generic types) that returns a value that is supposed to be validated.
type ValidatorValuer interface {
// ValidatorValue returns the value that is supposed to be validated.
ValidatorValue() any
}

// extractTypeInternal gets the actual underlying type of field value.
// It will dive into pointers, customTypes and return you the
// underlying value and it's kind.
Expand All @@ -23,6 +30,13 @@ BEGIN:
return current, reflect.Ptr, nullable
}

if current.CanInterface() {
if v, ok := current.Interface().(ValidatorValuer); ok {
current = reflect.ValueOf(v.ValidatorValue())
goto BEGIN
}
}

current = current.Elem()
goto BEGIN

Expand All @@ -34,6 +48,13 @@ BEGIN:
return current, reflect.Interface, nullable
}

if current.CanInterface() {
if v, ok := current.Interface().(ValidatorValuer); ok {
current = reflect.ValueOf(v.ValidatorValue())
goto BEGIN
}
}

current = current.Elem()
goto BEGIN

Expand All @@ -42,6 +63,13 @@ BEGIN:

default:

if current.CanInterface() {
if v, ok := current.Interface().(ValidatorValuer); ok {
current = reflect.ValueOf(v.ValidatorValue())
goto BEGIN
}
}

if v.v.hasCustomFuncs {
if fn, ok := v.v.customFuncs[current.Type()]; ok {
current = reflect.ValueOf(fn(current))
Expand Down
218 changes: 218 additions & 0 deletions validator_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -14293,3 +14293,221 @@ func TestValidateFn(t *testing.T) {
Equal(t, fe.Tag(), "validateFn")
})
}

type ValidatorValuerTypeWithPointerReceiver[T any] struct {
Data T
}

func (t *ValidatorValuerTypeWithPointerReceiver[T]) ValidatorValue() any {
return t.Data
}

type ValidatorValuerTypeWithValueReceiver[T any] struct {
Data T
}

func (t ValidatorValuerTypeWithValueReceiver[T]) ValidatorValue() any {
return t.Data
}

func TestValidatorValuerInterface(t *testing.T) {
t.Run("parent as ValidatorValuer (not called)", func(t *testing.T) {
errs := New().Struct(&ValidatorValuerTypeWithPointerReceiver[SubTest]{})
AssertError(t, errs,
"ValidatorValuerTypeWithPointerReceiver[github.com/go-playground/validator/v10.SubTest].Data.Test",
"ValidatorValuerTypeWithPointerReceiver[github.com/go-playground/validator/v10.SubTest].Data.Test",
"Test", "Test", "required")
})
t.Run("pointer parent, pointer nested, pointer receiver (called)", func(t *testing.T) {
type Parent struct {
Nested *ValidatorValuerTypeWithPointerReceiver[SubTest] `validate:"required"`
}

errs := New().Struct(&Parent{})
AssertError(t, errs, "Parent.Nested", "Parent.Nested", "Nested", "Nested", "required")

errs = New().Struct(&Parent{
Nested: &ValidatorValuerTypeWithPointerReceiver[SubTest]{},
})
AssertError(t, errs, "Parent.Nested.Test", "Parent.Nested.Test", "Test", "Test", "required")

errs = New().Struct(&Parent{
Nested: &ValidatorValuerTypeWithPointerReceiver[SubTest]{
Data: SubTest{
Test: "Test",
},
},
})
if errs != nil {
t.Fatalf("Expected no error, got: %v", errs)
}
})
t.Run("pointer parent, pointer nested, value receiver (called)", func(t *testing.T) {
type Parent struct {
Nested *ValidatorValuerTypeWithValueReceiver[SubTest] `validate:"required"`
}

errs := New().Struct(&Parent{})
AssertError(t, errs, "Parent.Nested", "Parent.Nested", "Nested", "Nested", "required")

errs = New().Struct(&Parent{
Nested: &ValidatorValuerTypeWithValueReceiver[SubTest]{},
})
AssertError(t, errs, "Parent.Nested.Test", "Parent.Nested.Test", "Test", "Test", "required")

errs = New().Struct(&Parent{
Nested: &ValidatorValuerTypeWithValueReceiver[SubTest]{
Data: SubTest{
Test: "Test",
},
},
})
if errs != nil {
t.Fatalf("Expected no error, got: %v", errs)
}
})
t.Run("pointer parent, value nested, pointer receiver (not called)", func(t *testing.T) {
type Parent struct {
Nested ValidatorValuerTypeWithPointerReceiver[SubTest] `validate:"required"`
}

errs := New().Struct(&Parent{})
AssertError(t, errs, "Parent.Nested.Data.Test", "Parent.Nested.Data.Test", "Test", "Test", "required")

errs = New().Struct(&Parent{
Nested: ValidatorValuerTypeWithPointerReceiver[SubTest]{},
})
AssertError(t, errs, "Parent.Nested.Data.Test", "Parent.Nested.Data.Test", "Test", "Test", "required")

errs = New().Struct(&Parent{
Nested: ValidatorValuerTypeWithPointerReceiver[SubTest]{
Data: SubTest{
Test: "Test",
},
},
})
if errs != nil {
t.Fatalf("Expected no error, got: %v", errs)
}
})
t.Run("pointer parent, value nested, value receiver (called)", func(t *testing.T) {
type Parent struct {
Nested ValidatorValuerTypeWithValueReceiver[SubTest] `validate:"required"`
}

errs := New().Struct(&Parent{})
AssertError(t, errs, "Parent.Nested.Test", "Parent.Nested.Test", "Test", "Test", "required")

errs = New().Struct(&Parent{
Nested: ValidatorValuerTypeWithValueReceiver[SubTest]{},
})
AssertError(t, errs, "Parent.Nested.Test", "Parent.Nested.Test", "Test", "Test", "required")

errs = New().Struct(&Parent{
Nested: ValidatorValuerTypeWithValueReceiver[SubTest]{
Data: SubTest{
Test: "Test",
},
},
})
if errs != nil {
t.Fatalf("Expected no error, got: %v", errs)
}
})
t.Run("value parent, pointer nested, pointer receiver (called)", func(t *testing.T) {
type Parent struct {
Nested *ValidatorValuerTypeWithPointerReceiver[SubTest] `validate:"required"`
}

errs := New().Struct(Parent{})
AssertError(t, errs, "Parent.Nested", "Parent.Nested", "Nested", "Nested", "required")

errs = New().Struct(Parent{
Nested: &ValidatorValuerTypeWithPointerReceiver[SubTest]{},
})
AssertError(t, errs, "Parent.Nested.Test", "Parent.Nested.Test", "Test", "Test", "required")

errs = New().Struct(Parent{
Nested: &ValidatorValuerTypeWithPointerReceiver[SubTest]{
Data: SubTest{
Test: "Test",
},
},
})
if errs != nil {
t.Fatalf("Expected no error, got: %v", errs)
}
})
t.Run("value parent, pointer nested, value receiver (called)", func(t *testing.T) {
type Parent struct {
Nested *ValidatorValuerTypeWithValueReceiver[SubTest] `validate:"required"`
}

errs := New().Struct(Parent{})
AssertError(t, errs, "Parent.Nested", "Parent.Nested", "Nested", "Nested", "required")

errs = New().Struct(Parent{
Nested: &ValidatorValuerTypeWithValueReceiver[SubTest]{},
})
AssertError(t, errs, "Parent.Nested.Test", "Parent.Nested.Test", "Test", "Test", "required")

errs = New().Struct(Parent{
Nested: &ValidatorValuerTypeWithValueReceiver[SubTest]{
Data: SubTest{
Test: "Test",
},
},
})
if errs != nil {
t.Fatalf("Expected no error, got: %v", errs)
}
})
t.Run("value parent, value nested, pointer receiver (not called)", func(t *testing.T) {
type Parent struct {
Nested ValidatorValuerTypeWithPointerReceiver[SubTest] `validate:"required"`
}

errs := New().Struct(Parent{})
AssertError(t, errs, "Parent.Nested.Data.Test", "Parent.Nested.Data.Test", "Test", "Test", "required")

errs = New().Struct(Parent{
Nested: ValidatorValuerTypeWithPointerReceiver[SubTest]{},
})
AssertError(t, errs, "Parent.Nested.Data.Test", "Parent.Nested.Data.Test", "Test", "Test", "required")

errs = New().Struct(Parent{
Nested: ValidatorValuerTypeWithPointerReceiver[SubTest]{
Data: SubTest{
Test: "Test",
},
},
})
if errs != nil {
t.Fatalf("Expected no error, got: %v", errs)
}
})
t.Run("value parent, value nested, value receiver (called)", func(t *testing.T) {
type Parent struct {
Nested ValidatorValuerTypeWithValueReceiver[SubTest] `validate:"required"`
}

errs := New().Struct(Parent{})
AssertError(t, errs, "Parent.Nested.Test", "Parent.Nested.Test", "Test", "Test", "required")

errs = New().Struct(Parent{
Nested: ValidatorValuerTypeWithValueReceiver[SubTest]{},
})
AssertError(t, errs, "Parent.Nested.Test", "Parent.Nested.Test", "Test", "Test", "required")

errs = New().Struct(Parent{
Nested: ValidatorValuerTypeWithValueReceiver[SubTest]{
Data: SubTest{
Test: "Test",
},
},
})
if errs != nil {
t.Fatalf("Expected no error, got: %v", errs)
}
})
}