diff --git a/options.go b/options.go index 1dea56fd7..a00613c35 100644 --- a/options.go +++ b/options.go @@ -14,3 +14,10 @@ func WithRequiredStructEnabled() Option { v.requiredStructEnabled = true } } + +// WithRequiredStructEnabled enables omitting the name of embedded anonymous structs from the namespace. +func WithOmitAnonymousName() Option { + return func(v *Validate) { + v.omitAnonymousName = true + } +} diff --git a/validator.go b/validator.go index a072d39ce..7dbd00c0e 100644 --- a/validator.go +++ b/validator.go @@ -31,7 +31,6 @@ type validate struct { // parent and current will be the same the first run of validateStruct func (v *validate) validateStruct(ctx context.Context, parent reflect.Value, current reflect.Value, typ reflect.Type, ns []byte, structNs []byte, ct *cTag) { - cs, ok := v.v.structCache.Get(typ) if !ok { cs = v.v.extractStructCache(current, typ.Name()) @@ -193,7 +192,7 @@ OUTER: // Var - doesn't make much sense to do it that way, should call 'Struct', but no harm... // VarWithField - this allows for validating against each field within the struct against a specific value // pretty handy in certain situations - if len(cf.name) > 0 { + if len(cf.name) > 0 && !(v.v.omitAnonymousName && parent.Type().Field(cf.idx).Anonymous) { ns = append(append(ns, cf.altName...), '.') structNs = append(append(structNs, cf.name...), '.') } @@ -482,5 +481,4 @@ OUTER: ct = ct.next } } - } diff --git a/validator_instance.go b/validator_instance.go index d5a7be1de..2e31511b7 100644 --- a/validator_instance.go +++ b/validator_instance.go @@ -94,6 +94,7 @@ type Validate struct { hasCustomFuncs bool hasTagNameFunc bool requiredStructEnabled bool + omitAnonymousName bool } // New returns a new instance of 'validate' with sane defaults. @@ -102,7 +103,6 @@ type Validate struct { // in essence only parsing your validation tags once per struct type. // Using multiple instances neglects the benefit of caching. func New(options ...Option) *Validate { - tc := new(tagCache) tc.m.Store(make(map[string]*cTag)) @@ -124,7 +124,6 @@ func New(options ...Option) *Validate { // must copy validators for separate validations to be used in each instance for k, val := range bakedInValidators { - switch k { // these require that even if the value is nil that the validation should run, omitempty still overrides this behaviour case requiredIfTag, requiredUnlessTag, requiredWithTag, requiredWithAllTag, requiredWithoutTag, requiredWithoutAllTag, @@ -254,7 +253,6 @@ func (v *Validate) registerValidation(tag string, fn FuncCtx, bakedIn bool, nilC // // NOTE: this function is not thread-safe it is intended that these all be registered prior to any validation func (v *Validate) RegisterAlias(alias, tags string) { - _, ok := restrictedTags[alias] if ok || strings.ContainsAny(alias, restrictedTagChars) { @@ -278,7 +276,6 @@ func (v *Validate) RegisterStructValidation(fn StructLevelFunc, types ...interfa // NOTE: // - this method is not thread-safe it is intended that these all be registered prior to any validation func (v *Validate) RegisterStructValidationCtx(fn StructLevelFuncCtx, types ...interface{}) { - if v.structLevelFuncs == nil { v.structLevelFuncs = make(map[reflect.Type]StructLevelFuncCtx) } @@ -325,7 +322,6 @@ func (v *Validate) RegisterStructValidationMapRules(rules map[string]string, typ // // NOTE: this method is not thread-safe it is intended that these all be registered prior to any validation func (v *Validate) RegisterCustomTypeFunc(fn CustomTypeFunc, types ...interface{}) { - if v.customFuncs == nil { v.customFuncs = make(map[reflect.Type]CustomTypeFunc) } @@ -339,7 +335,6 @@ func (v *Validate) RegisterCustomTypeFunc(fn CustomTypeFunc, types ...interface{ // RegisterTranslation registers translations against the provided tag. func (v *Validate) RegisterTranslation(tag string, trans ut.Translator, registerFn RegisterTranslationsFunc, translationFn TranslationFunc) (err error) { - if v.transTagFunc == nil { v.transTagFunc = make(map[ut.Translator]map[string]TranslationFunc) } @@ -373,7 +368,6 @@ func (v *Validate) Struct(s interface{}) error { // It returns InvalidValidationError for bad values passed in and nil or ValidationErrors as error otherwise. // You will need to assert the error if it's not nil eg. err.(validator.ValidationErrors) to access the array of errors. func (v *Validate) StructCtx(ctx context.Context, s interface{}) (err error) { - val := reflect.ValueOf(s) top := val diff --git a/validator_test.go b/validator_test.go index 2826ae70e..d1d11a93a 100644 --- a/validator_test.go +++ b/validator_test.go @@ -746,11 +746,9 @@ func TestStructPartial(t *testing.T) { SubSlice: []*SubTest{ { - Test: "Required", }, { - Test: "Required", }, }, @@ -4002,7 +4000,6 @@ func TestUUID5Validation(t *testing.T) { param string expected bool }{ - {"", false}, {"xxxa987fbc9-4bed-3078-cf07-9141ba07c9f3", false}, {"9c858901-8a57-4791-81fe-4c455b099bc9", false}, @@ -4190,7 +4187,6 @@ func TestUUID5RFC4122Validation(t *testing.T) { param string expected bool }{ - {"", false}, {"xxxa987Fbc9-4bed-3078-cf07-9141ba07c9f3", false}, {"9c858901-8a57-4791-81Fe-4c455b099bc9", false}, @@ -8793,6 +8789,7 @@ func TestNumeric(t *testing.T) { errs = validate.Var(i, "numeric") Equal(t, errs, nil) } + func TestBoolean(t *testing.T) { validate := New() @@ -9518,11 +9515,9 @@ func TestStructFiltered(t *testing.T) { SubSlice: []*SubTest{ { - Test: "Required", }, { - Test: "Required", }, }, @@ -12909,7 +12904,6 @@ func TestSemverFormatValidation(t *testing.T) { } func TestCveFormatValidation(t *testing.T) { - tests := []struct { value string `validate:"cve"` tag string @@ -13106,7 +13100,6 @@ func TestPostCodeByIso3166Alpha2Field_InvalidKind(t *testing.T) { } func TestValidate_ValidateMapCtx(t *testing.T) { - type args struct { data map[string]interface{} rules map[string]interface{} @@ -13588,7 +13581,7 @@ func TestNestedStructValidation(t *testing.T) { }, } - var evaluateTest = func(tt test, errs error) { + evaluateTest := func(tt test, errs error) { if tt.err != (testErr{}) && errs != nil { Equal(t, len(errs.(ValidationErrors)), 1) @@ -13626,6 +13619,34 @@ func TestNestedStructValidation(t *testing.T) { } } +func TestHideEmbeddedStructName(t *testing.T) { + validate := New(WithOmitAnonymousName()) + + type Inner struct { + Test string `validate:"required"` + } + + t.Run("anonymous", func(t *testing.T) { + type Anonymous struct { + Inner + } + + err := validate.Struct(Anonymous{}) + NotEqual(t, err, nil) + AssertError(t, err.(ValidationErrors), "Anonymous.Test", "Anonymous.Test", "Test", "Test", "required") + }) + + t.Run("named", func(t *testing.T) { + type Named struct { + Inner Inner + } + + err := validate.Struct(Named{}) + NotEqual(t, err, nil) + AssertError(t, err.(ValidationErrors), "Named.Inner.Test", "Named.Inner.Test", "Test", "Test", "required") + }) +} + func TestTimeRequired(t *testing.T) { validate := New() validate.RegisterTagNameFunc(func(fld reflect.StructField) string {