go-playground / validator

:100:Go Struct and Field validation, including Cross Field, Cross Struct, Map, Slice and Array diving
MIT License
16.14k stars 1.29k forks source link

feat: add custom error message to struct field and to struct level validation #1183

Open navamedha opened 9 months ago

navamedha commented 9 months ago

Add custom error message to struct field and to struct level validation.

Enhances

struct field

We can now give a custom error message to a struct field by adding a struct tag and give that struct tag to validate.RegisterErrMsgFunc, like we give json struct tag to validate.RegisterTagNameFunc.

Custom error message can be accessed using err.Msg() as following.

type User struct {
  Email string `validate:"required,email" msg:"User email is invalid"`
}

v = validator.New()

v.RegisterErrMsgFunc(func(fld reflect.StructField) string {
  return fld.Tag.Get("msg")
})

err = v.Struct(&User{})

for _, err := range err.(validator.ValidationErrors) {
  // err.Field() --> Email
  // err.Msg() --> User email is invalid
  // err.Error() --> Key: 'User.e-mail' Error:Field validation for 'e-mail' failed on the 'required' tag
}

struct level

We can now add a custom error message while registering validation at struct level using validate.RegisterStructValidation.

Custom error message can be accessed using err.Msg() as following.

type User1 struct {
  Addr1 string
  Addr2 string
  Addr3 string
}

v1 = validator.New()

v1.RegisterStructValidation(User1StructLevelValidation, User1{})

err = v1.Struct(&User1{})

for _, err := range err.(validator.ValidationErrors) {
  // err.Field() --> Addr1
  // err.Msg() --> Any one of Addr1 or Addr2 or Addr3 must be provided
  // err.Error() --> Key: 'User1.Addr1' Error:Field validation for 'Addr1' failed on the 'addr1oraddr2oraddr3' tag
}

...

func User1StructLevelValidation(sl StructLevel) {
  st := sl.Current().Interface().(User1)
  if st.Addr1 == "" && st.Addr2 == "" && st.Addr3 == "" {
    sl.ReportErrorWithMsg(st.Addr1, "Addr1", "Addr1", "addr1oraddr2oraddr3", "",
      "Any one of Addr1 or Addr2 or Addr3 must be provided")

    sl.ReportErrorWithMsg(st.Addr2, "Addr2", "Addr2", "addr1oraddr2oraddr3", "",
      "Any one of Addr1 or Addr2 or Addr3 must be provided")

    // without custom error message
    sl.ReportError(st.Addr3, "Addr3", "Addr3", "addr1oraddr2oraddr3", "")
  }
}

Make sure that you've checked the boxes below before you submit PR:

Created a test --> TestCustomErrorMessages

@go-playground/validator-maintainers

coveralls commented 9 months ago

Coverage Status

coverage: 73.884% (+0.04%) from 73.849% when pulling b58bb0be3e79ca61c61e6ac269e76125b2d2ff41 on navamedha:custom-err-msg into 94a637ab9fbbb0bc0fe8a278f0352d0b14e2c365 on go-playground:master.

deankarn commented 8 months ago

@navamedha TY for the PR.

Now that validation tags run properly on structs themselves this is something I've been thinking about adding.

This is a variant, one of many, I've been thinking about implementing and need to give it some thought.

Some context is I'm still thinking about how to handle 2 things:

  1. Whether I should use reflection and add the reflect.Value or StructField to the validation failures and let it be extracted dynamically there which may benefit a could other future things.
  2. Still thinking about how to handle validations like so and whether to enforce a single message or allow separate for inter nested ones eg. map[int]string``validate:"gt=0,dive,keys,eq=1|eq=2,endkeys,required" <- three different errors can occur at 3 different levels
zx8 commented 8 months ago

Have been waiting for something like this for a while now!

I've currently pulled this PR in manually and am using it in my go.mod - it's working really well, so great job. 🎉

Of course, I'd love to move back to an official tag sooner rather than later, so am keen to see what @deankarn cooks up before this is eventually merged.

joseluisq commented 3 months ago

Any plan to move this forward? Also, does this PR consider placeholders in the message?

deankarn commented 3 months ago

Sorry all, not sure when I am going to be able to get to this and many other PR’s as I am swamped with other projects at the moment.

I could really use some help maintaining this package.

high level thoughts are I think this is a good stopgap solution for this version of the package. The next version and better option I would change the validations function signature to return error instead and let people customize their error return messaging as they see fit along with stripping out translation dependencies.

joseluisq commented 2 months ago

Thanks for the update. I guess, the PR is far from ready then. For example, It seems like does not support multiple messages per validation per struct field.

I would like to be able to do something like this.

type UserParams struct {
    UserId string `validate:"required(message = 'UserID is not provided'), uuid4(message = 'UserID is not a valid UUID4!')"`
}

In a similar way to how we do it in for example the Rust world. That one should be great to have.