Skip to content

Commit cec504f

Browse files
Resolving "Validating unexported fields go-playground#417" (go-playground#1234)
## Add supporting for validation private fields go-playground#417 @go-playground/validator-maintainers --------- Co-authored-by: nikolay <[email protected]>
1 parent b328f72 commit cec504f

File tree

6 files changed

+156
-20
lines changed

6 files changed

+156
-20
lines changed

Makefile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,4 +15,4 @@ test:
1515
bench:
1616
$(GOCMD) test -run=NONE -bench=. -benchmem ./...
1717

18-
.PHONY: test lint linters-install
18+
.PHONY: test lint linters-install

cache.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -126,7 +126,7 @@ func (v *Validate) extractStructCache(current reflect.Value, sName string) *cStr
126126

127127
fld = typ.Field(i)
128128

129-
if !fld.Anonymous && len(fld.PkgPath) > 0 {
129+
if !v.privateFieldValidation && !fld.Anonymous && len(fld.PkgPath) > 0 {
130130
continue
131131
}
132132

options.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,3 +14,12 @@ func WithRequiredStructEnabled() Option {
1414
v.requiredStructEnabled = true
1515
}
1616
}
17+
18+
// WithPrivateFieldValidation activates validation for unexported fields
19+
//
20+
// It's experimental feature that partially uses unsafe package
21+
func WithPrivateFieldValidation() Option {
22+
return func(v *Validate) {
23+
v.privateFieldValidation = true
24+
}
25+
}

validator.go

Lines changed: 28 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import (
55
"fmt"
66
"reflect"
77
"strconv"
8+
"unsafe"
89
)
910

1011
// per validate construct
@@ -156,7 +157,7 @@ func (v *validate) traverseField(ctx context.Context, parent reflect.Value, curr
156157
structNs: v.str2,
157158
fieldLen: uint8(len(cf.altName)),
158159
structfieldLen: uint8(len(cf.name)),
159-
value: current.Interface(),
160+
value: getValue(current),
160161
param: ct.param,
161162
kind: kind,
162163
typ: current.Type(),
@@ -410,7 +411,7 @@ OUTER:
410411
structNs: v.str2,
411412
fieldLen: uint8(len(cf.altName)),
412413
structfieldLen: uint8(len(cf.name)),
413-
value: current.Interface(),
414+
value: getValue(current),
414415
param: ct.param,
415416
kind: kind,
416417
typ: typ,
@@ -430,7 +431,7 @@ OUTER:
430431
structNs: v.str2,
431432
fieldLen: uint8(len(cf.altName)),
432433
structfieldLen: uint8(len(cf.name)),
433-
value: current.Interface(),
434+
value: getValue(current),
434435
param: ct.param,
435436
kind: kind,
436437
typ: typ,
@@ -470,7 +471,7 @@ OUTER:
470471
structNs: v.str2,
471472
fieldLen: uint8(len(cf.altName)),
472473
structfieldLen: uint8(len(cf.name)),
473-
value: current.Interface(),
474+
value: getValue(current),
474475
param: ct.param,
475476
kind: kind,
476477
typ: typ,
@@ -484,3 +485,26 @@ OUTER:
484485
}
485486

486487
}
488+
489+
func getValue(val reflect.Value) interface{} {
490+
if val.CanInterface() {
491+
return val.Interface()
492+
}
493+
494+
if val.CanAddr() {
495+
return reflect.NewAt(val.Type(), unsafe.Pointer(val.UnsafeAddr())).Elem().Interface()
496+
}
497+
498+
switch val.Kind() {
499+
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
500+
return val.Int()
501+
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
502+
return val.Uint()
503+
case reflect.Complex64, reflect.Complex128:
504+
return val.Complex()
505+
case reflect.Float32, reflect.Float64:
506+
return val.Float()
507+
default:
508+
return val.String()
509+
}
510+
}

validator_instance.go

Lines changed: 15 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -80,20 +80,21 @@ type internalValidationFuncWrapper struct {
8080

8181
// Validate contains the validator settings and cache
8282
type Validate struct {
83-
tagName string
84-
pool *sync.Pool
85-
tagNameFunc TagNameFunc
86-
structLevelFuncs map[reflect.Type]StructLevelFuncCtx
87-
customFuncs map[reflect.Type]CustomTypeFunc
88-
aliases map[string]string
89-
validations map[string]internalValidationFuncWrapper
90-
transTagFunc map[ut.Translator]map[string]TranslationFunc // map[<locale>]map[<tag>]TranslationFunc
91-
rules map[reflect.Type]map[string]string
92-
tagCache *tagCache
93-
structCache *structCache
94-
hasCustomFuncs bool
95-
hasTagNameFunc bool
96-
requiredStructEnabled bool
83+
tagName string
84+
pool *sync.Pool
85+
tagNameFunc TagNameFunc
86+
structLevelFuncs map[reflect.Type]StructLevelFuncCtx
87+
customFuncs map[reflect.Type]CustomTypeFunc
88+
aliases map[string]string
89+
validations map[string]internalValidationFuncWrapper
90+
transTagFunc map[ut.Translator]map[string]TranslationFunc // map[<locale>]map[<tag>]TranslationFunc
91+
rules map[reflect.Type]map[string]string
92+
tagCache *tagCache
93+
structCache *structCache
94+
hasCustomFuncs bool
95+
hasTagNameFunc bool
96+
requiredStructEnabled bool
97+
privateFieldValidation bool
9798
}
9899

99100
// New returns a new instance of 'validate' with sane defaults.

validator_test.go

Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13692,3 +13692,105 @@ func TestOmitNilAndRequired(t *testing.T) {
1369213692
AssertError(t, err2, "OmitNil.Inner.Str", "OmitNil.Inner.Str", "Str", "Str", "required")
1369313693
})
1369413694
}
13695+
13696+
func TestPrivateFieldsStruct(t *testing.T) {
13697+
type tc struct {
13698+
stct interface{}
13699+
errorNum int
13700+
}
13701+
13702+
tcs := []tc{
13703+
{
13704+
stct: &struct {
13705+
f1 int8 `validate:"required"`
13706+
f2 int16 `validate:"required"`
13707+
f3 int32 `validate:"required"`
13708+
f4 int64 `validate:"required"`
13709+
}{},
13710+
errorNum: 4,
13711+
},
13712+
{
13713+
stct: &struct {
13714+
f1 uint8 `validate:"required"`
13715+
f2 uint16 `validate:"required"`
13716+
f3 uint32 `validate:"required"`
13717+
f4 uint64 `validate:"required"`
13718+
}{},
13719+
errorNum: 4,
13720+
},
13721+
{
13722+
stct: &struct {
13723+
f1 complex64 `validate:"required"`
13724+
f2 complex128 `validate:"required"`
13725+
}{},
13726+
errorNum: 2,
13727+
},
13728+
{
13729+
stct: &struct {
13730+
f1 float32 `validate:"required"`
13731+
f2 float64 `validate:"required"`
13732+
}{},
13733+
errorNum: 2,
13734+
},
13735+
{
13736+
stct: struct {
13737+
f1 int8 `validate:"required"`
13738+
f2 int16 `validate:"required"`
13739+
f3 int32 `validate:"required"`
13740+
f4 int64 `validate:"required"`
13741+
}{},
13742+
errorNum: 4,
13743+
},
13744+
{
13745+
stct: struct {
13746+
f1 uint8 `validate:"required"`
13747+
f2 uint16 `validate:"required"`
13748+
f3 uint32 `validate:"required"`
13749+
f4 uint64 `validate:"required"`
13750+
}{},
13751+
errorNum: 4,
13752+
},
13753+
{
13754+
stct: struct {
13755+
f1 complex64 `validate:"required"`
13756+
f2 complex128 `validate:"required"`
13757+
}{},
13758+
errorNum: 2,
13759+
},
13760+
{
13761+
stct: struct {
13762+
f1 float32 `validate:"required"`
13763+
f2 float64 `validate:"required"`
13764+
}{},
13765+
errorNum: 2,
13766+
},
13767+
{
13768+
stct: struct {
13769+
f1 *int `validate:"required"`
13770+
f2 struct {
13771+
f3 int `validate:"required"`
13772+
}
13773+
}{},
13774+
errorNum: 2,
13775+
},
13776+
{
13777+
stct: &struct {
13778+
f1 *int `validate:"required"`
13779+
f2 struct {
13780+
f3 int `validate:"required"`
13781+
}
13782+
}{},
13783+
errorNum: 2,
13784+
},
13785+
}
13786+
13787+
validate := New(WithPrivateFieldValidation())
13788+
13789+
for _, tc := range tcs {
13790+
err := validate.Struct(tc.stct)
13791+
NotEqual(t, err, nil)
13792+
13793+
errs := err.(ValidationErrors)
13794+
Equal(t, len(errs), tc.errorNum)
13795+
}
13796+
}

0 commit comments

Comments
 (0)