Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: omit anonymous embedded struct #1211

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
7 changes: 7 additions & 0 deletions options.go
Expand Up @@ -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
}
}
4 changes: 1 addition & 3 deletions validator.go
Expand Up @@ -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())
Expand Down Expand Up @@ -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...), '.')
}
Expand Down Expand Up @@ -482,5 +481,4 @@ OUTER:
ct = ct.next
}
}

}
8 changes: 1 addition & 7 deletions validator_instance.go
Expand Up @@ -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.
Expand All @@ -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))

Expand All @@ -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,
Expand Down Expand Up @@ -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) {
Expand All @@ -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)
}
Expand Down Expand Up @@ -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)
}
Expand All @@ -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)
}
Expand Down Expand Up @@ -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

Expand Down
39 changes: 30 additions & 9 deletions validator_test.go
Expand Up @@ -746,11 +746,9 @@ func TestStructPartial(t *testing.T) {

SubSlice: []*SubTest{
{

Test: "Required",
},
{

Test: "Required",
},
},
Expand Down Expand Up @@ -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},
Expand Down Expand Up @@ -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},
Expand Down Expand Up @@ -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()

Expand Down Expand Up @@ -9518,11 +9515,9 @@ func TestStructFiltered(t *testing.T) {

SubSlice: []*SubTest{
{

Test: "Required",
},
{

Test: "Required",
},
},
Expand Down Expand Up @@ -12909,7 +12904,6 @@ func TestSemverFormatValidation(t *testing.T) {
}

func TestCveFormatValidation(t *testing.T) {

tests := []struct {
value string `validate:"cve"`
tag string
Expand Down Expand Up @@ -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{}
Expand Down Expand Up @@ -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)

Expand Down Expand Up @@ -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 {
Expand Down