Use generics with the RegisterValidation ?
- [x] I have looked at the documentation here first?
- [x] I have looked at the examples provided that may showcase my question here?
Package version eg. v9, v10:
v10
Issue, Question or Enhancement:
I am trying to implement an optional type for the API and integrate the validator with generics unfortunatly i do not see any way to use just generics on the validator. So far using RegisterCustomTypeFunc I was able to make it work by passing all the possible types of the generic.
Code sample, to showcase or reproduce:
import (
"encoding/json"
)
type Optional[T any] struct {
Value T
Null bool
Set bool
}
func (i *Optional[T]) UnmarshalJSON(data []byte) error {
// If this method was called, the value was set.
i.Set = true
if string(data) == "null" {
// The key was set to null
i.Null = true
return nil
}
// The key isn't set to null
var temp T
if err := json.Unmarshal(data, &temp); err != nil {
return err
}
i.Value = temp
i.Null = false
return nil
}
type Client struct {
Id Optional[string] `json:"id" validate:"required,min=10"`
Location Optional[Location] `json:"location" validate:"required,dive"`
}
type Location struct {
Type string `json:"type,omitempty" validate:"omitempty,enum"`
Coordinates string `json:"coordinates,omitempty" validate:"omitempty,enum"`
Address string `json:"address,omitempty" validate:"omitempty"`
}
Ideally I would love to be able to register in in the way like:
validate.RegisterCustomTypeFunc(customValuer, opt.Optional[any]{})
func customValuer(field reflect.Value) interface{} {
if valuer, ok := field.Interface().(Optional[any]); ok {
return valuer.Value
}
return nil
}
Or at least would be great to do something similar to RegisterValidation where I could just specify custom tag for the optional fields and return the value that would be validated instead of Boolean eg:
validate.RegisterSomethingCool("customOptional", func(f validator.FieldLevel) interface{} {
value := f.Field().Interface().(Optional[any])
return value.Value
})
Any solution for this?
I ran into the same issue.
Here is an example diff of a possible solution:
diff --git a/util.go b/util.go
index 1685159..51f3000 100644
--- a/util.go
+++ b/util.go
@@ -51,6 +51,13 @@ BEGIN:
}
}
+ if v.v.hasCustomMatcherFunc {
+ if nc := v.v.customMatcherFunc(current); nc != nil {
+ current = reflect.ValueOf(nc)
+ goto BEGIN
+ }
+ }
+
return current, current.Kind(), nullable
}
}
diff --git a/validator_instance.go b/validator_instance.go
index d5a7be1..8e7f5c1 100644
--- a/validator_instance.go
+++ b/validator_instance.go
@@ -85,6 +85,7 @@ type Validate struct {
tagNameFunc TagNameFunc
structLevelFuncs map[reflect.Type]StructLevelFuncCtx
customFuncs map[reflect.Type]CustomTypeFunc
+ customMatcherFunc CustomTypeFunc
aliases map[string]string
validations map[string]internalValidationFuncWrapper
transTagFunc map[ut.Translator]map[string]TranslationFunc // map[<locale>]map[<tag>]TranslationFunc
@@ -92,6 +93,7 @@ type Validate struct {
tagCache *tagCache
structCache *structCache
hasCustomFuncs bool
+ hasCustomMatcherFunc bool
hasTagNameFunc bool
requiredStructEnabled bool
}
@@ -337,6 +339,14 @@ func (v *Validate) RegisterCustomTypeFunc(fn CustomTypeFunc, types ...interface{
v.hasCustomFuncs = true
}
+// RegisterCustomMatcherFunc registers a CustomTypeFunc for manual conversion
+//
+// NOTE: this method is not thread-safe it is intended that these all be registered prior to any validation
+func (v *Validate) RegisterCustomMatcherFunc(fn CustomTypeFunc) {
+ v.customMatcherFunc = fn
+ v.hasCustomMatcherFunc = true
+}
+
// RegisterTranslation registers translations against the provided tag.
func (v *Validate) RegisterTranslation(tag string, trans ut.Translator, registerFn RegisterTranslationsFunc, translationFn TranslationFunc) (err error) {
Example usage:
validate.RegisterCustomMatcherFunc(DriverValuer)
func DriverValuer(field reflect.Value) interface{} {
if valuer, ok := field.Interface().(driver.Valuer); ok {
if val, err := valuer.Value(); err == nil {
return val
}
}
return nil
}
I have exactly the same problem as many I think who try to implement a PATCH API where you have to distinguish between null and undefined and therefore use such a generic based optional type.
Is there an ideomatic "official" solution for using it with validator?
There is a discussion here about adding the ability, haven’t had time to play around with potential solutions yet https://github.com/go-playground/validator/discussions/1232
Any solution?