Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion cache.go
Original file line number Diff line number Diff line change
Expand Up @@ -144,7 +144,7 @@ func (v *Validate) extractStructCache(current reflect.Value, sName string) *cStr

if v.hasTagNameFunc {
name := v.tagNameFunc(fld)
if len(name) > 0 {
if len(name) > 0 || v.omitBlankFieldNames {
customName = name
}
}
Expand Down
14 changes: 14 additions & 0 deletions options.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,3 +24,17 @@ func WithPrivateFieldValidation() Option {
v.privateFieldValidation = true
}
}

// WithTagNameFuncBlankOmit makes a blank return from a RegisterTagNameFunc omit
// the field from the error namespace instead of silently falling back to the
// struct field name.
//
// This was made opt-in behaviour to maintain backward compatibility with
// existing callers that rely on the fallback for error namespaces.
//
// It is recommended you enable this as it will be the default behaviour in v11+.
func WithTagNameFuncBlankOmit() Option {
return func(v *Validate) {
v.omitBlankFieldNames = true
}
}
26 changes: 20 additions & 6 deletions validator.go
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,7 @@ func (v *validate) traverseField(ctx context.Context, parent reflect.Value, curr

if ct.hasTag {
if kind == reflect.Invalid {
v.str1 = string(append(ns, cf.altName...))
v.str1 = appendAltName(ns, cf.altName)
if v.v.hasTagNameFunc {
v.str2 = string(append(structNs, cf.name...))
} else {
Expand All @@ -138,7 +138,7 @@ func (v *validate) traverseField(ctx context.Context, parent reflect.Value, curr
return
}

v.str1 = string(append(ns, cf.altName...))
v.str1 = appendAltName(ns, cf.altName)
if v.v.hasTagNameFunc {
v.str2 = string(append(structNs, cf.name...))
} else {
Expand Down Expand Up @@ -192,7 +192,9 @@ OUTER:
// 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 {
ns = append(append(ns, cf.altName...), '.')
if len(cf.altName) > 0 {
ns = append(append(ns, cf.altName...), '.')
}
structNs = append(append(structNs, cf.name...), '.')
}

Expand All @@ -212,7 +214,9 @@ OUTER:
// 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 {
ns = append(append(ns, cf.altName...), '.')
if len(cf.altName) > 0 {
ns = append(append(ns, cf.altName...), '.')
}
structNs = append(append(structNs, cf.name...), '.')
}

Expand Down Expand Up @@ -409,7 +413,7 @@ OUTER:

if ct.isBlockEnd || ct.next == nil {
// if we get here, no valid 'or' value and no more tags
v.str1 = string(append(ns, cf.altName...))
v.str1 = appendAltName(ns, cf.altName)

if v.v.hasTagNameFunc {
v.str2 = string(append(structNs, cf.name...))
Expand Down Expand Up @@ -468,7 +472,7 @@ OUTER:
v.ct = ct

if !ct.fn(ctx, v) {
v.str1 = string(append(ns, cf.altName...))
v.str1 = appendAltName(ns, cf.altName)

if v.v.hasTagNameFunc {
v.str2 = string(append(structNs, cf.name...))
Expand Down Expand Up @@ -499,6 +503,16 @@ OUTER:
}
}

func appendAltName(ns []byte, altName string) string {
if len(altName) > 0 {
return string(append(ns, altName...))
}
if n := len(ns); n > 0 && ns[n-1] == '.' {
return string(ns[:n-1])
}
return string(ns)
}

func getValue(val reflect.Value) interface{} {
if val.CanInterface() {
return val.Interface()
Expand Down
1 change: 1 addition & 0 deletions validator_instance.go
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,7 @@ type Validate struct {
hasTagNameFunc bool
requiredStructEnabled bool
privateFieldValidation bool
omitBlankFieldNames bool
}

// New returns a new instance of 'validate' with sane defaults.
Expand Down
103 changes: 73 additions & 30 deletions validator_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -422,6 +422,49 @@ func TestNameNamespace(t *testing.T) {
Equal(t, fe.StructNamespace(), "Namespace.Inner1.Inner2.String[1]")
}

func TestBlankTagNameFuncOmitsNamespace(t *testing.T) {
validate := New(WithTagNameFuncBlankOmit())
validate.RegisterTagNameFunc(func(fld reflect.StructField) string {
name, _, _ := strings.Cut(fld.Tag.Get("json"), ",")
if name == "-" {
return ""
}
return name
})

type Inner struct {
Field string `validate:"required" json:"field"`
}

type Outer struct {
Hidden Inner `json:"-"`
Skipped string `validate:"required" json:"-"`
}

errs := validate.Struct(Outer{})
NotEqual(t, errs, nil)

ve := errs.(ValidationErrors)
Equal(t, len(ve), 2)

AssertError(t, errs, "Outer.field", "Outer.Hidden.Field", "field", "Field", "required")
AssertError(t, errs, "Outer", "Outer.Skipped", "", "Skipped", "required")

fe := getError(ve, "Outer.field", "Outer.Hidden.Field")
NotEqual(t, fe, nil)
Equal(t, fe.Field(), "field")
Equal(t, fe.StructField(), "Field")
Equal(t, fe.Namespace(), "Outer.field")
Equal(t, fe.StructNamespace(), "Outer.Hidden.Field")

fe = getError(ve, "Outer", "Outer.Skipped")
NotEqual(t, fe, nil)
Equal(t, fe.Field(), "")
Equal(t, fe.StructField(), "Skipped")
Equal(t, fe.Namespace(), "Outer")
Equal(t, fe.StructNamespace(), "Outer.Skipped")
}

func TestAnonymous(t *testing.T) {
validate := New()
validate.RegisterTagNameFunc(func(fld reflect.StructField) string {
Expand Down Expand Up @@ -6321,32 +6364,32 @@ func TestMIMETypeValidation(t *testing.T) {
}

tests := []struct {
title string
param string
tag string
expected bool
createFile func()
title string
param string
tag string
expected bool
createFile func()
}{
{
title: "empty path",
param: paths["empty"],
tag: "mimetype=image/png",
expected: false,
createFile: func() {},
title: "empty path",
param: paths["empty"],
tag: "mimetype=image/png",
expected: false,
createFile: func() {},
},
{
title: "directory, not a file",
param: paths["directory"],
tag: "mimetype=image/png",
expected: false,
createFile: func() {},
title: "directory, not a file",
param: paths["directory"],
tag: "mimetype=image/png",
expected: false,
createFile: func() {},
},
{
title: "missing file",
param: paths["missing"],
tag: "mimetype=image/png",
expected: false,
createFile: func() {},
title: "missing file",
param: paths["missing"],
tag: "mimetype=image/png",
expected: false,
createFile: func() {},
},
{
title: "exact png match",
Expand Down Expand Up @@ -6401,11 +6444,11 @@ func TestMIMETypeValidation(t *testing.T) {
},
},
{
title: "type mismatch",
param: paths["go"],
tag: "mimetype=image/*",
expected: false,
createFile: func() {},
title: "type mismatch",
param: paths["go"],
tag: "mimetype=image/*",
expected: false,
createFile: func() {},
},
{
title: "subtype mismatch",
Expand All @@ -6425,11 +6468,11 @@ func TestMIMETypeValidation(t *testing.T) {
},
},
{
title: "invalid validator param missing subtype",
param: paths["go"],
tag: "mimetype=image",
expected: false,
createFile: func() {},
title: "invalid validator param missing subtype",
param: paths["go"],
tag: "mimetype=image",
expected: false,
createFile: func() {},
},
}

Expand Down