validator icon indicating copy to clipboard operation
validator copied to clipboard

Question: How to validate that an inner slice of structs conforms to custom validation logic

Open LeviMatus opened this issue 3 years ago • 3 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:

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 the User's Address validate 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.

LeviMatus avatar Mar 25 '22 13:03 LeviMatus

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"`
}

zxxf18 avatar Oct 09 '22 08:10 zxxf18

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

fabiante avatar Jan 19 '24 10:01 fabiante

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.

deankarn avatar Jan 19 '24 14:01 deankarn