validator icon indicating copy to clipboard operation
validator copied to clipboard

Custom error message

Open 1379 opened this issue 5 years ago • 10 comments

The parameter of the RegisterValidation function is a validation function that returns a Boolean value. If we want to customize the error message, we have to register a translation function, which is too much trouble. Our verification function may return an error for many reasons, so the error message should also be different, but only based on the returned boolean value, we cannot determine what caused the verification failure, so the error message of the translation function is only One kind. Why can't you register a check function that returns error directly? Just like the following.

type Func=func(fl FieldLevel) error
func (v *Validate) RegisterValidation(tag string, fn Func, callValidationEvenIfNull ...bool) error {

}

1379 avatar Sep 28 '20 12:09 1379

Only based on the boolean value returned by the validation function, we cannot determine what caused the validation failure, so customizing the error message will be very troublesome

1379 avatar Sep 28 '20 12:09 1379

@1379 sounds like a great enhancement for the next version of the lib, but unfortunately right now is a breaking change.

deankarn avatar Oct 17 '20 15:10 deankarn

@deankarn I simply made some changes to the "validator", which is currently supported and used in my own project. My implementation is like this: Provides two RegisterValidation functions, the first parameter type is still "type Func=func(fl FieldLevel) boolean", The parameter type of the second function becomes "type Func=func(fl FieldLevel) error". When verifying, you only need to determine which type of verification function is.

1379 avatar Oct 17 '20 15:10 1379

Same need

pwli0755 avatar Oct 20 '20 01:10 pwli0755

The custom validation error messages from sl.ReportError() are a bit silly too:

// sl.ReportError(field interface{}, fieldName string, structFieldName string, tag string, param string)
sl.ReportError(hosts, "hosts", "this does nothing", "some error message here", "this does nothing")

==>

 Key: 'ClusterConfig.Spec.hosts' Error:Field validation for 'hosts' failed on the 'some error message here' tag

If it was not from a tag, but a custom StructValidation , it should say something like:

 Key: 'ClusterConfig.Spec.hosts' Error:Field validation for 'hosts' failed: some error message here

kke avatar Nov 18 '20 14:11 kke

here is a way to fix it #837

kevin2027 avatar Sep 18 '21 06:09 kevin2027

same need. especially for sl.ReportError() on struct validation to include custom error messages to user

pbikki avatar Apr 13 '23 16:04 pbikki

I think it's possible to implement this without a breaking change, though it'll be ugly with some bits of code purely dedicated to backwards compatibility. We'd have to put a new registration mechanism with new names next to the current one, and turn the current one into a proxy:

type FuncWithCustomError=func(fl FieldLevel) error
func (v *Validate) RegisterValidationWithCustomError(tag string, fn FuncWithCustomError, callValidationEvenIfNull ...bool) error {
    // Current implementation but for FuncWithCustomError instead of Func.
}

type Func=func(fl FieldLevel) bool
func (v *Validate) RegisterValidation(tag string, fn Func, callValidationEvenIfNull ...bool) error {
    fnWithCustomError := func(fl FieldLevel) error {
        if fn(fl) {
            return nil
        } else {
            return errors.New("")
        }
    }
    return RegisterValidationWithCustomError(tag, fnWithCustomError , callValidationEvenIfNull...)
}

Of course, it would still be a pretty big change to the deeper architecture of the library: all baked-in validators would need to be wrapped, all code which calls the validators needs to call them by the new signature, and I don't know the codebase well enough to tell if more things than that need to be changed.

Would the maintainer(s) of this project be on board with this solution? If yes, would you appreciate me familiarising myself with the codebase and cooking up a PR for this, or would you prefer to implement this yourself?

joostvdhoff avatar Sep 04 '24 08:09 joostvdhoff

Need this also.

To reiterate, because some comments here seem to think this is about purely customising the error message, but it is not. It's about being able to return an error message that gives deeper reasons why a custom validation failed. That reason might change according to the intricate and particular contents of the field.

Imagine for example a struct field that is a custom string type, and it is meant to conform to a particular grammar that I've created elsewhere in my code. What I want is to return specific, helpful error messages when that parsing fails. The message will change according to the specifics of the input string, so can't be solved with static custom message tags, or with the current "translation" workaround.

+1 on @joostvdhoff's proposal from me!

ezk84 avatar Oct 30 '24 17:10 ezk84

Here's a limited v10 workaround for single field validation. It doesn't work for cross field or cross struct validations, but might work if you just need to, say, validate syntax on a string field. It does require executing the custom validation twice: once to induce a FieldError and again to get an actual error back when processing FieldErrors.

This isn't production-ready code, but just to give the flavor.

func RealValidateFoo(foo string, param string) error {
   // perform syntax validation on foo
}

err = validate.RegisterValidation("foo", func(fl validator.FieldLevel) bool {
        return RealValidateFoo(fl.Field().String(), fl.Param()) == nil
})

// later on, when iterating over validation errors

switch err := err.(type) {
case validator.ValidationErrors:
        for _, fieldErr := range err {
                errMsg := fieldErr.Error()

                switch fieldErr.Tag() {
                case "foo":
                        customErr := RealValidateFoo(fmt.Sprint(fieldErr.Value()), fieldErr.Param())
                        if customErr != nil {
                                errMsg = customErr.Error()
                        }
                // check other custom validations
                }

                fmt.Println(errMsg)
        }
...
}

mbarnes avatar Mar 12 '25 19:03 mbarnes

Please also support forwarding parameters in aliases. For example,

validate.RegisterAlias("minChar", "min")
validate.RegisterAlias("maxChar", "max")

So, can define different messages for the same validator.

dumindu avatar Jun 25 '25 18:06 dumindu

i'm also interested on this. Currently was able to workaround this with

func (v *Validator) validateExpr(ctx context.Context, fl validator.FieldLevel) bool {
	expr := fl.Field().String()
	_, err := doThing(ctx, expr)
	if err != nil {
		type reporter interface {
			ReportError(field interface{}, fieldName, structFieldName string, tag, param string)
		}
		// 
		if r, ok := fl.(reporter); ok {
			r.ReportError(fl.Field(), fl.FieldName(), fl.StructFieldName(), fl.GetTag(), err.Error())
			// we don't return false here as we use alternative way to report the error
			return true
		}
		return false
	}
	return true
}

But the solution is hackish. Actually just an ability to set Param in validator.FieldLevel will be already good enough.

avasenin avatar Jul 07 '25 15:07 avasenin