Feature Description
There is currently no first-class way to construct gin's default binding validator with go-playground/validator Options — most importantly WithRequiredStructEnabled().
Why this matters
WithRequiredStructEnabled() is what makes the required tag apply to non-pointer struct fields. Without it, required is silently a no-op on any value-typed struct field — wrapper types such as a UUID or Decimal that are defined as type X struct{...}. So a request that omits such a field binds its zero value and passes validation, instead of failing with a 400:
type CreateReq struct {
ID MyUUID `json:"id" binding:"required"` // MyUUID is a struct
}
// {} binds successfully — `required` never fires.
Why it can't be opted into today
The option is constructor-only. In go-playground/validator it sets a private field with no setter, so it can only be enabled via validator.New(validator.WithRequiredStructEnabled()):
// validator_instance.go
requiredStructEnabled bool
// options.go
func WithRequiredStructEnabled() Option { return func(v *Validate) { v.requiredStructEnabled = true } }
But gin's defaultValidator.lazyinit() hardcodes validator.New() with no options:
// binding/default_validator.go
func (v *defaultValidator) lazyinit() {
v.once.Do(func() {
v.validate = validator.New()
v.validate.SetTagName("binding")
})
}
binding.Validator.Engine() returns the already-constructed *validator.Validate, which is too late — the option must be passed at construction, and there is no setter to flip it afterwards. Because defaultValidator (and its validate field) are unexported, the only way to enable the option is to replace binding.Validator entirely with a custom StructValidator, re-implementing ValidateStruct's pointer/slice/array dispatch just to pass one option:
type myValidator struct{ v *validator.Validate }
func (m *myValidator) ValidateStruct(obj any) error { /* copy of gin's ptr/slice/struct dispatch */ }
func (m *myValidator) Engine() any { return m.v }
// binding.Validator = &myValidator{v: validator.New(validator.WithRequiredStructEnabled())}
That's a lot of copied boilerplate for a common need.
Proposal
Provide a first-class way to construct the default validator with options. Any of:
- An exported constructor for the default validator, e.g.
binding.NewStructValidator(opts ...validator.Option) StructValidator, so users can do binding.Validator = binding.NewStructValidator(validator.WithRequiredStructEnabled()) without re-implementing ValidateStruct.
- A package-level setter such as
binding.SetValidatorOptions(opts ...validator.Option) applied on lazyinit.
- Exporting
defaultValidator (or accepting options on it) so the ptr/slice dispatch can be reused.
Option 1 seems the least invasive and keeps gin's existing ValidateStruct behavior intact.
Tested with gin v1.12.0 and go-playground/validator v10.30.2.
Feature Description
There is currently no first-class way to construct gin's default binding validator with go-playground/validator
Options — most importantlyWithRequiredStructEnabled().Why this matters
WithRequiredStructEnabled()is what makes therequiredtag apply to non-pointer struct fields. Without it,requiredis silently a no-op on any value-typed struct field — wrapper types such as aUUIDorDecimalthat are defined astype X struct{...}. So a request that omits such a field binds its zero value and passes validation, instead of failing with a 400:Why it can't be opted into today
The option is constructor-only. In go-playground/validator it sets a private field with no setter, so it can only be enabled via
validator.New(validator.WithRequiredStructEnabled()):But gin's
defaultValidator.lazyinit()hardcodesvalidator.New()with no options:binding.Validator.Engine()returns the already-constructed*validator.Validate, which is too late — the option must be passed at construction, and there is no setter to flip it afterwards. BecausedefaultValidator(and itsvalidatefield) are unexported, the only way to enable the option is to replacebinding.Validatorentirely with a customStructValidator, re-implementingValidateStruct's pointer/slice/array dispatch just to pass one option:That's a lot of copied boilerplate for a common need.
Proposal
Provide a first-class way to construct the default validator with options. Any of:
binding.NewStructValidator(opts ...validator.Option) StructValidator, so users can dobinding.Validator = binding.NewStructValidator(validator.WithRequiredStructEnabled())without re-implementingValidateStruct.binding.SetValidatorOptions(opts ...validator.Option)applied onlazyinit.defaultValidator(or accepting options on it) so the ptr/slice dispatch can be reused.Option 1 seems the least invasive and keeps gin's existing
ValidateStructbehavior intact.Tested with gin v1.12.0 and go-playground/validator v10.30.2.