validator icon indicating copy to clipboard operation
validator copied to clipboard

Use generics with the RegisterValidation ?

Open goriunov opened this issue 2 years ago • 5 comments

  • [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
})

goriunov avatar Jan 31 '23 23:01 goriunov

Any solution for this?

acomanescu avatar Oct 28 '23 14:10 acomanescu

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
}

dropwhile avatar Dec 24 '23 01:12 dropwhile

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?

stonymahony avatar Mar 14 '24 20:03 stonymahony

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

deankarn avatar Mar 15 '24 05:03 deankarn

Any solution?

renom avatar Aug 08 '24 17:08 renom