Skip to content

Commit f881394

Browse files
committed
Feat: add tag to skip namespace in embedded structures
1 parent 20f7df6 commit f881394

File tree

6 files changed

+102
-11
lines changed

6 files changed

+102
-11
lines changed

baked_in.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ var (
5353
keysTag: {},
5454
endKeysTag: {},
5555
structOnlyTag: {},
56+
skipNamespaceTag: {},
5657
omitzero: {},
5758
omitempty: {},
5859
omitnil: {},

cache.go

Lines changed: 25 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ const (
2222
typeEndKeys
2323
typeOmitNil
2424
typeOmitZero
25+
typeSkipNamespace
2526
)
2627

2728
const (
@@ -77,11 +78,12 @@ type cStruct struct {
7778
}
7879

7980
type cField struct {
80-
idx int
81-
name string
82-
altName string
83-
namesEqual bool
84-
cTags *cTag
81+
idx int
82+
name string
83+
altName string
84+
namesEqual bool
85+
skipNamespace bool
86+
cTags *cTag
8587
}
8688

8789
type cTag struct {
@@ -100,6 +102,15 @@ type cTag struct {
100102
runValidationWhenNil bool
101103
}
102104

105+
func (ct *cTag) HasTagInChain(tag string) bool {
106+
for n := ct; n != nil; n = n.next {
107+
if n.aliasTag == tag {
108+
return true
109+
}
110+
}
111+
return false
112+
}
113+
103114
func (v *Validate) extractStructCache(current reflect.Value, sName string) *cStruct {
104115
v.structCache.lock.Lock()
105116
defer v.structCache.lock.Unlock() // leave as defer! because if inner panics, it will never get unlocked otherwise!
@@ -161,11 +172,12 @@ func (v *Validate) extractStructCache(current reflect.Value, sName string) *cStr
161172
}
162173

163174
cs.fields = append(cs.fields, &cField{
164-
idx: i,
165-
name: fld.Name,
166-
altName: customName,
167-
cTags: ctag,
168-
namesEqual: fld.Name == customName,
175+
idx: i,
176+
name: fld.Name,
177+
altName: customName,
178+
cTags: ctag,
179+
namesEqual: fld.Name == customName,
180+
skipNamespace: ctag.HasTagInChain(skipNamespaceTag),
169181
})
170182
}
171183
v.structCache.Set(typ, cs)
@@ -256,6 +268,9 @@ func (v *Validate) parseFieldTagsRecursive(tag string, fieldName string, alias s
256268
case structOnlyTag:
257269
current.typeof = typeStructOnly
258270

271+
case skipNamespaceTag:
272+
current.typeof = typeSkipNamespace
273+
259274
case noStructLevelTag:
260275
current.typeof = typeNoStructLevel
261276

doc.go

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -186,6 +186,18 @@ Same as structonly tag except that any struct level validations will not run.
186186
187187
Usage: nostructlevel
188188
189+
# Skip Namespace
190+
191+
Instruct validator to skip name of embedded structure into field namespace.
192+
193+
Usage: skipns
194+
195+
Example #1
196+
197+
type Outer struct {
198+
Embedded `validate:"skipns"`
199+
}
200+
189201
# Omit Empty
190202
191203
Allows conditional validation, for example if a field is not set with

validator.go

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -192,7 +192,9 @@ OUTER:
192192
// VarWithField - this allows for validating against each field within the struct against a specific value
193193
// pretty handy in certain situations
194194
if len(cf.name) > 0 {
195-
ns = append(append(ns, cf.altName...), '.')
195+
if !cf.skipNamespace {
196+
ns = append(append(ns, cf.altName...), '.')
197+
}
196198
structNs = append(append(structNs, cf.name...), '.')
197199
}
198200

@@ -205,6 +207,9 @@ OUTER:
205207
case typeNoStructLevel:
206208
return
207209

210+
case typeSkipNamespace:
211+
ct = ct.next
212+
208213
case typeStructOnly:
209214
if isNestedStruct {
210215
// if len == 0 then validating using 'Var' or 'VarWithValue'

validator_instance.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ const (
2121
tagKeySeparator = "="
2222
structOnlyTag = "structonly"
2323
noStructLevelTag = "nostructlevel"
24+
skipNamespaceTag = "skipns"
2425
omitzero = "omitzero"
2526
omitempty = "omitempty"
2627
omitnil = "omitnil"

validator_test.go

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -420,6 +420,63 @@ func TestNameNamespace(t *testing.T) {
420420
Equal(t, fe.StructNamespace(), "Namespace.Inner1.Inner2.String[1]")
421421
}
422422

423+
func TestEmbeddingAndNamespace(t *testing.T) {
424+
type Embedded struct {
425+
EmbeddedString string `validate:"required" json:"JSONString"`
426+
}
427+
428+
type OuterWithoutSkipTag struct {
429+
Embedded
430+
}
431+
432+
type OuterWithSkipTag struct {
433+
Embedded `validate:"skipns"`
434+
}
435+
436+
validate := New()
437+
validate.RegisterTagNameFunc(func(fld reflect.StructField) string {
438+
name := strings.SplitN(fld.Tag.Get("json"), ",", 2)[0]
439+
440+
if name == "-" {
441+
return ""
442+
}
443+
444+
return name
445+
})
446+
447+
// Without skip tag
448+
ts1 := &OuterWithoutSkipTag{
449+
Embedded{EmbeddedString: "ok"},
450+
}
451+
452+
errs := validate.Struct(ts1)
453+
Equal(t, errs, nil)
454+
455+
ts1.EmbeddedString = ""
456+
457+
errs = validate.Struct(ts1)
458+
NotEqual(t, errs, nil)
459+
ve := errs.(ValidationErrors)
460+
Equal(t, len(ve), 1)
461+
AssertError(t, errs, "OuterWithoutSkipTag.Embedded.JSONString", "OuterWithoutSkipTag.Embedded.EmbeddedString", "JSONString", "EmbeddedString", "required")
462+
463+
// Without skip tag
464+
ts2 := &OuterWithSkipTag{
465+
Embedded{EmbeddedString: "ok"},
466+
}
467+
468+
errs = validate.Struct(ts2)
469+
Equal(t, errs, nil)
470+
471+
ts2.EmbeddedString = ""
472+
473+
errs = validate.Struct(ts2)
474+
NotEqual(t, errs, nil)
475+
ve = errs.(ValidationErrors)
476+
Equal(t, len(ve), 1)
477+
AssertError(t, errs, "OuterWithSkipTag.JSONString", "OuterWithSkipTag.Embedded.EmbeddedString", "JSONString", "EmbeddedString", "required")
478+
}
479+
423480
func TestAnonymous(t *testing.T) {
424481
validate := New()
425482
validate.RegisterTagNameFunc(func(fld reflect.StructField) string {

0 commit comments

Comments
 (0)