Question: How to validate that an inner slice of structs conforms to custom validation logic
- [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:
Given two struct types, one of which holds a slice of the other as one of its members, how do I ensure that only one element of the slice has its value set to a target value (lets assume boolean field set to true). See the code sample for what I hope is a clearer illustration of what I'm asking here.
Example Scenario: Assume there's a need for a User to have a slice of Addresses. User's may have one Address with an IsMailing flag set to true. If two of such flags are set to true, then validation must fail. User's may have an arbitrary amount of IsMailing flags set to false.
I've laid out a few of the approaches I've tried. If there's a clean way of doing what I'm asking with reasonable control over errors, then please let me know.
Code sample, to showcase or reproduce:
type User struct {
FirstName string
LastName string
Address []Address `validate:"required"`
}
type Address struct {
Street string `validate:"required"`
City string `validate:"required"`
Planet string `validate:"required"`
Phone string `validate:"required"`
IsMailing bool `validate:"required"`
}
Moreover, []Address may need to be validated with other structs, so I want to be able to validate these rules independently from the User struct.
I've tried to accomplish this using:
-
RegisterStructValidation(func(sl validator.StructLevel), []Address{})The provided function never is called. I've tried adding the"dive"tag to the end of theUser'sAddressvalidate tags.There is a way to get around this and make it work, but its very ugly in my opinion:
type User struct {
FirstName string
LastName string
Address AddressSliceWrapper
}
type Address struct {
IsMailing bool `validate:"required"`
}
type Addresses []Address
// AddressSliceWrapper embeds the alias type for the sake of embedding a slice.
// This is very heavy handed.
type AddressSliceWrapper struct {
Addresses
}
RegisterCustomTypeFunc(func(field reflect.Value) interface{}), []Address{})This worked, but I found that I had no control over the errors being reported when this should fail, making it impractical.
func main() {
validate := validator.New()
validate.RegisterCustomTypeFunc(validateMailingAddress, []Address{})
// .... setup user and validate
}
func validateMailingAddress(field reflect.Value) interface{} {
addresses, ok := field.Interface().([]Address)
if !ok {
return nil
}
var hasMailing bool
for _, addr := range addresses {
if addr.IsMailing && hasMailing {
// for brevity. imagine a type which
// implements validator.FieldError is used
// so that validator.ValidationErrors may be returned.
return errors.New("already has mailing address")
}
hasMailing = addr.IsMailing
}
return addresses
}
This would work if the error were honored, but when the error is returned, its treated as a success.
validate.Var(...,...)I've tried a few variations of this, none of which have worked.
add "dive"
type User struct {
FirstName string
LastName string
Address []Address `validate:"required,dive"`
}
type Address struct {
Street string `validate:"required"`
City string `validate:"required"`
Planet string `validate:"required"`
Phone string `validate:"required"`
IsMailing bool `validate:"required"`
}
dive fixes my similar problem.
Is there explicit documentation on what dive does? The readme has no explaination on what is does besides: Ability to dive into both map keys and values for validation
Yes @fabiante there is documentation here in the Go documentation https://pkg.go.dev/github.com/go-playground/validator/v10#hdr-Dive
Reminder to everyone the README for projects is not documentation, but rather a quick reference to information.