Skip to content

Feat: add tag to skip namespace in embedded structures #1426

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

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.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions baked_in.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ var (
keysTag: {},
endKeysTag: {},
structOnlyTag: {},
skipNamespaceTag: {},
omitzero: {},
omitempty: {},
omitnil: {},
Expand Down
35 changes: 25 additions & 10 deletions cache.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ const (
typeEndKeys
typeOmitNil
typeOmitZero
typeSkipNamespace
)

const (
Expand Down Expand Up @@ -77,11 +78,12 @@ type cStruct struct {
}

type cField struct {
idx int
name string
altName string
namesEqual bool
cTags *cTag
idx int
name string
altName string
namesEqual bool
skipNamespace bool
cTags *cTag
}

type cTag struct {
Expand All @@ -100,6 +102,15 @@ type cTag struct {
runValidationWhenNil bool
}

func (ct *cTag) HasTagInChain(tag string) bool {
for n := ct; n != nil; n = n.next {
if n.aliasTag == tag {
return true
}
}
return false
}

func (v *Validate) extractStructCache(current reflect.Value, sName string) *cStruct {
v.structCache.lock.Lock()
defer v.structCache.lock.Unlock() // leave as defer! because if inner panics, it will never get unlocked otherwise!
Expand Down Expand Up @@ -161,11 +172,12 @@ func (v *Validate) extractStructCache(current reflect.Value, sName string) *cStr
}

cs.fields = append(cs.fields, &cField{
idx: i,
name: fld.Name,
altName: customName,
cTags: ctag,
namesEqual: fld.Name == customName,
idx: i,
name: fld.Name,
altName: customName,
cTags: ctag,
namesEqual: fld.Name == customName,
skipNamespace: ctag.HasTagInChain(skipNamespaceTag),
})
}
v.structCache.Set(typ, cs)
Expand Down Expand Up @@ -256,6 +268,9 @@ func (v *Validate) parseFieldTagsRecursive(tag string, fieldName string, alias s
case structOnlyTag:
current.typeof = typeStructOnly

case skipNamespaceTag:
current.typeof = typeSkipNamespace

case noStructLevelTag:
current.typeof = typeNoStructLevel

Expand Down
12 changes: 12 additions & 0 deletions doc.go
Original file line number Diff line number Diff line change
Expand Up @@ -186,6 +186,18 @@ Same as structonly tag except that any struct level validations will not run.

Usage: nostructlevel

# Skip Namespace

Instruct validator to skip name of embedded structure into field namespace.

Usage: skipns

Example #1

type Outer struct {
Embedded `validate:"skipns"`
}

# Omit Empty

Allows conditional validation, for example if a field is not set with
Expand Down
7 changes: 6 additions & 1 deletion validator.go
Original file line number Diff line number Diff line change
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 !cf.skipNamespace {
ns = append(append(ns, cf.altName...), '.')
}
structNs = append(append(structNs, cf.name...), '.')
}

Expand All @@ -205,6 +207,9 @@ OUTER:
case typeNoStructLevel:
return

case typeSkipNamespace:
ct = ct.next

case typeStructOnly:
if isNestedStruct {
// if len == 0 then validating using 'Var' or 'VarWithValue'
Expand Down
1 change: 1 addition & 0 deletions validator_instance.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ const (
tagKeySeparator = "="
structOnlyTag = "structonly"
noStructLevelTag = "nostructlevel"
skipNamespaceTag = "skipns"
omitzero = "omitzero"
omitempty = "omitempty"
omitnil = "omitnil"
Expand Down
57 changes: 57 additions & 0 deletions validator_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -420,6 +420,63 @@ func TestNameNamespace(t *testing.T) {
Equal(t, fe.StructNamespace(), "Namespace.Inner1.Inner2.String[1]")
}

func TestEmbeddingAndNamespace(t *testing.T) {
type Embedded struct {
EmbeddedString string `validate:"required" json:"JSONString"`
}

type OuterWithoutSkipTag struct {
Embedded
}

type OuterWithSkipTag struct {
Embedded `validate:"skipns"`
}

validate := New()
validate.RegisterTagNameFunc(func(fld reflect.StructField) string {
name := strings.SplitN(fld.Tag.Get("json"), ",", 2)[0]

if name == "-" {
return ""
}

return name
})

// Without skip tag
ts1 := &OuterWithoutSkipTag{
Embedded{EmbeddedString: "ok"},
}

errs := validate.Struct(ts1)
Equal(t, errs, nil)

ts1.EmbeddedString = ""

errs = validate.Struct(ts1)
NotEqual(t, errs, nil)
ve := errs.(ValidationErrors)
Equal(t, len(ve), 1)
AssertError(t, errs, "OuterWithoutSkipTag.Embedded.JSONString", "OuterWithoutSkipTag.Embedded.EmbeddedString", "JSONString", "EmbeddedString", "required")

// Without skip tag
ts2 := &OuterWithSkipTag{
Embedded{EmbeddedString: "ok"},
}

errs = validate.Struct(ts2)
Equal(t, errs, nil)

ts2.EmbeddedString = ""

errs = validate.Struct(ts2)
NotEqual(t, errs, nil)
ve = errs.(ValidationErrors)
Equal(t, len(ve), 1)
AssertError(t, errs, "OuterWithSkipTag.JSONString", "OuterWithSkipTag.Embedded.EmbeddedString", "JSONString", "EmbeddedString", "required")
}

func TestAnonymous(t *testing.T) {
validate := New()
validate.RegisterTagNameFunc(func(fld reflect.StructField) string {
Expand Down