Skip to content

Commit

Permalink
Resolving "Validating unexported fields go-playground#417" (go-playgr…
Browse files Browse the repository at this point in the history
…ound#1234)

go-playground#417

@go-playground/validator-maintainers

---------

Co-authored-by: nikolay <[email protected]>
  • Loading branch information
2 people authored and d1slike committed Apr 1, 2024
1 parent 9083c2d commit 2c7caca
Show file tree
Hide file tree
Showing 6 changed files with 156 additions and 20 deletions.
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -15,4 +15,4 @@ test:
bench:
$(GOCMD) test -run=NONE -bench=. -benchmem ./...

.PHONY: test lint linters-install
.PHONY: test lint linters-install
2 changes: 1 addition & 1 deletion cache.go
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,7 @@ func (v *Validate) extractStructCache(current reflect.Value, sName string) *cStr

fld = typ.Field(i)

if !fld.Anonymous && len(fld.PkgPath) > 0 {
if !v.privateFieldValidation && !fld.Anonymous && len(fld.PkgPath) > 0 {
continue
}

Expand Down
9 changes: 9 additions & 0 deletions options.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,3 +14,12 @@ func WithRequiredStructEnabled() Option {
v.requiredStructEnabled = true
}
}

// WithPrivateFieldValidation activates validation for unexported fields
//
// It's experimental feature that partially uses unsafe package
func WithPrivateFieldValidation() Option {
return func(v *Validate) {
v.privateFieldValidation = true
}
}
32 changes: 28 additions & 4 deletions validator.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"fmt"
"reflect"
"strconv"
"unsafe"
)

// per validate construct
Expand Down Expand Up @@ -156,7 +157,7 @@ func (v *validate) traverseField(ctx context.Context, parent reflect.Value, curr
structNs: v.str2,
fieldLen: uint8(len(cf.altName)),
structfieldLen: uint8(len(cf.name)),
value: current.Interface(),
value: getValue(current),
param: ct.param,
kind: kind,
typ: current.Type(),
Expand Down Expand Up @@ -416,7 +417,7 @@ OUTER:
structNs: v.str2,
fieldLen: uint8(len(cf.altName)),
structfieldLen: uint8(len(cf.name)),
value: current.Interface(),
value: getValue(current),
param: ct.param,
kind: kind,
typ: typ,
Expand All @@ -436,7 +437,7 @@ OUTER:
structNs: v.str2,
fieldLen: uint8(len(cf.altName)),
structfieldLen: uint8(len(cf.name)),
value: current.Interface(),
value: getValue(current),
param: ct.param,
kind: kind,
typ: typ,
Expand Down Expand Up @@ -476,7 +477,7 @@ OUTER:
structNs: v.str2,
fieldLen: uint8(len(cf.altName)),
structfieldLen: uint8(len(cf.name)),
value: current.Interface(),
value: getValue(current),
param: ct.param,
kind: kind,
typ: typ,
Expand All @@ -491,6 +492,29 @@ OUTER:

}

func getValue(val reflect.Value) interface{} {
if val.CanInterface() {
return val.Interface()
}

if val.CanAddr() {
return reflect.NewAt(val.Type(), unsafe.Pointer(val.UnsafeAddr())).Elem().Interface()
}

switch val.Kind() {
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
return val.Int()
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
return val.Uint()
case reflect.Complex64, reflect.Complex128:
return val.Complex()
case reflect.Float32, reflect.Float64:
return val.Float()
default:
return val.String()
}
}

func (v *validate) traverseSlice(ctx context.Context, parent reflect.Value, current reflect.Value, ns []byte, structNs []byte, cf *cField) {
var i64 int64
reusableCF := &cField{}
Expand Down
29 changes: 15 additions & 14 deletions validator_instance.go
Original file line number Diff line number Diff line change
Expand Up @@ -80,20 +80,21 @@ type internalValidationFuncWrapper struct {

// Validate contains the validator settings and cache
type Validate struct {
tagName string
pool *sync.Pool
tagNameFunc TagNameFunc
structLevelFuncs map[reflect.Type]StructLevelFuncCtx
customFuncs map[reflect.Type]CustomTypeFunc
aliases map[string]string
validations map[string]internalValidationFuncWrapper
transTagFunc map[ut.Translator]map[string]TranslationFunc // map[<locale>]map[<tag>]TranslationFunc
rules map[reflect.Type]map[string]string
tagCache *tagCache
structCache *structCache
hasCustomFuncs bool
hasTagNameFunc bool
requiredStructEnabled bool
tagName string
pool *sync.Pool
tagNameFunc TagNameFunc
structLevelFuncs map[reflect.Type]StructLevelFuncCtx
customFuncs map[reflect.Type]CustomTypeFunc
aliases map[string]string
validations map[string]internalValidationFuncWrapper
transTagFunc map[ut.Translator]map[string]TranslationFunc // map[<locale>]map[<tag>]TranslationFunc
rules map[reflect.Type]map[string]string
tagCache *tagCache
structCache *structCache
hasCustomFuncs bool
hasTagNameFunc bool
requiredStructEnabled bool
privateFieldValidation bool
}

// New returns a new instance of 'validate' with sane defaults.
Expand Down
102 changes: 102 additions & 0 deletions validator_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -13692,3 +13692,105 @@ func TestOmitNilAndRequired(t *testing.T) {
AssertError(t, err2, "OmitNil.Inner.Str", "OmitNil.Inner.Str", "Str", "Str", "required")
})
}

func TestPrivateFieldsStruct(t *testing.T) {
type tc struct {
stct interface{}
errorNum int
}

tcs := []tc{
{
stct: &struct {
f1 int8 `validate:"required"`
f2 int16 `validate:"required"`
f3 int32 `validate:"required"`
f4 int64 `validate:"required"`
}{},
errorNum: 4,
},
{
stct: &struct {
f1 uint8 `validate:"required"`
f2 uint16 `validate:"required"`
f3 uint32 `validate:"required"`
f4 uint64 `validate:"required"`
}{},
errorNum: 4,
},
{
stct: &struct {
f1 complex64 `validate:"required"`
f2 complex128 `validate:"required"`
}{},
errorNum: 2,
},
{
stct: &struct {
f1 float32 `validate:"required"`
f2 float64 `validate:"required"`
}{},
errorNum: 2,
},
{
stct: struct {
f1 int8 `validate:"required"`
f2 int16 `validate:"required"`
f3 int32 `validate:"required"`
f4 int64 `validate:"required"`
}{},
errorNum: 4,
},
{
stct: struct {
f1 uint8 `validate:"required"`
f2 uint16 `validate:"required"`
f3 uint32 `validate:"required"`
f4 uint64 `validate:"required"`
}{},
errorNum: 4,
},
{
stct: struct {
f1 complex64 `validate:"required"`
f2 complex128 `validate:"required"`
}{},
errorNum: 2,
},
{
stct: struct {
f1 float32 `validate:"required"`
f2 float64 `validate:"required"`
}{},
errorNum: 2,
},
{
stct: struct {
f1 *int `validate:"required"`
f2 struct {
f3 int `validate:"required"`
}
}{},
errorNum: 2,
},
{
stct: &struct {
f1 *int `validate:"required"`
f2 struct {
f3 int `validate:"required"`
}
}{},
errorNum: 2,
},
}

validate := New(WithPrivateFieldValidation())

for _, tc := range tcs {
err := validate.Struct(tc.stct)
NotEqual(t, err, nil)

errs := err.(ValidationErrors)
Equal(t, len(errs), tc.errorNum)
}
}

0 comments on commit 2c7caca

Please sign in to comment.