From 493dfb6209fcb493fcdbc2a0f14b882f2d53979f Mon Sep 17 00:00:00 2001 From: Dean Karn Date: Wed, 28 Sep 2016 10:56:49 -0400 Subject: [PATCH] correct required tag functionality for pointers, now works like old `exists` tag. --- README.md | 108 +++++++++++++++++++++--------------------- baked_in.go | 6 +++ validator.go | 14 +++--- validator_instance.go | 9 +++- validator_test.go | 93 ++++++++++++++++++++++++++++++++++++ 5 files changed, 167 insertions(+), 63 deletions(-) diff --git a/README.md b/README.md index 3841f1b..a52ceaf 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ Package validator ================ [![Join the chat at https://gitter.im/go-playground/validator](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/go-playground/validator?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) -![Project status](https://img.shields.io/badge/version-9.2.0-green.svg) +![Project status](https://img.shields.io/badge/version-9.2.1-green.svg) [![Build Status](https://semaphoreci.com/api/v1/joeybloggs/validator/branches/v9/badge.svg)](https://semaphoreci.com/joeybloggs/validator) [![Coverage Status](https://coveralls.io/repos/go-playground/validator/badge.svg?branch=v9&service=github)](https://coveralls.io/github/go-playground/validator?branch=v9) [![Go Report Card](https://goreportcard.com/badge/github.com/go-playground/validator)](https://goreportcard.com/report/github.com/go-playground/validator) @@ -66,60 +66,60 @@ Please see http://godoc.org/gopkg.in/go-playground/validator.v9 for detailed usa Benchmarks ------ -###### Run on MacBook Pro (Retina, 15-inch, Late 2013) 2.6 GHz Intel Core i7 16 GB 1600 MHz DDR3 using Go version go1.7 darwin/amd64 +###### Run on MacBook Pro (Retina, 15-inch, Late 2013) 2.6 GHz Intel Core i7 16 GB 1600 MHz DDR3 using Go version go1.7.1 darwin/amd64 ```go -BenchmarkFieldSuccess-8 20000000 104 ns/op 0 B/op 0 allocs/op -BenchmarkFieldSuccessParallel-8 50000000 34.5 ns/op 0 B/op 0 allocs/op -BenchmarkFieldFailure-8 5000000 335 ns/op 208 B/op 4 allocs/op -BenchmarkFieldFailureParallel-8 20000000 118 ns/op 208 B/op 4 allocs/op -BenchmarkFieldDiveSuccess-8 2000000 718 ns/op 201 B/op 11 allocs/op -BenchmarkFieldDiveSuccessParallel-8 10000000 234 ns/op 201 B/op 11 allocs/op -BenchmarkFieldDiveFailure-8 2000000 971 ns/op 412 B/op 16 allocs/op -BenchmarkFieldDiveFailureParallel-8 5000000 341 ns/op 413 B/op 16 allocs/op -BenchmarkFieldCustomTypeSuccess-8 5000000 268 ns/op 32 B/op 2 allocs/op -BenchmarkFieldCustomTypeSuccessParallel-8 20000000 82.3 ns/op 32 B/op 2 allocs/op -BenchmarkFieldCustomTypeFailure-8 5000000 331 ns/op 208 B/op 4 allocs/op -BenchmarkFieldCustomTypeFailureParallel-8 20000000 116 ns/op 208 B/op 4 allocs/op -BenchmarkFieldOrTagSuccess-8 2000000 872 ns/op 16 B/op 1 allocs/op -BenchmarkFieldOrTagSuccessParallel-8 5000000 389 ns/op 16 B/op 1 allocs/op -BenchmarkFieldOrTagFailure-8 3000000 569 ns/op 224 B/op 5 allocs/op -BenchmarkFieldOrTagFailureParallel-8 5000000 397 ns/op 224 B/op 5 allocs/op -BenchmarkStructLevelValidationSuccess-8 5000000 334 ns/op 32 B/op 2 allocs/op -BenchmarkStructLevelValidationSuccessParallel-8 20000000 111 ns/op 32 B/op 2 allocs/op -BenchmarkStructLevelValidationFailure-8 2000000 622 ns/op 304 B/op 8 allocs/op -BenchmarkStructLevelValidationFailureParallel-8 10000000 274 ns/op 304 B/op 8 allocs/op -BenchmarkStructSimpleCustomTypeSuccess-8 3000000 525 ns/op 32 B/op 2 allocs/op -BenchmarkStructSimpleCustomTypeSuccessParallel-8 10000000 165 ns/op 32 B/op 2 allocs/op -BenchmarkStructSimpleCustomTypeFailure-8 2000000 826 ns/op 424 B/op 9 allocs/op -BenchmarkStructSimpleCustomTypeFailureParallel-8 5000000 378 ns/op 440 B/op 10 allocs/op -BenchmarkStructFilteredSuccess-8 2000000 734 ns/op 288 B/op 9 allocs/op -BenchmarkStructFilteredSuccessParallel-8 5000000 313 ns/op 288 B/op 9 allocs/op -BenchmarkStructFilteredFailure-8 2000000 592 ns/op 256 B/op 7 allocs/op -BenchmarkStructFilteredFailureParallel-8 10000000 272 ns/op 256 B/op 7 allocs/op -BenchmarkStructPartialSuccess-8 2000000 682 ns/op 256 B/op 6 allocs/op -BenchmarkStructPartialSuccessParallel-8 10000000 279 ns/op 256 B/op 6 allocs/op -BenchmarkStructPartialFailure-8 2000000 938 ns/op 480 B/op 11 allocs/op -BenchmarkStructPartialFailureParallel-8 5000000 398 ns/op 480 B/op 11 allocs/op -BenchmarkStructExceptSuccess-8 1000000 1088 ns/op 496 B/op 12 allocs/op -BenchmarkStructExceptSuccessParallel-8 10000000 257 ns/op 240 B/op 5 allocs/op -BenchmarkStructExceptFailure-8 2000000 897 ns/op 464 B/op 10 allocs/op -BenchmarkStructExceptFailureParallel-8 5000000 394 ns/op 464 B/op 10 allocs/op -BenchmarkStructSimpleCrossFieldSuccess-8 3000000 535 ns/op 72 B/op 3 allocs/op -BenchmarkStructSimpleCrossFieldSuccessParallel-8 10000000 184 ns/op 72 B/op 3 allocs/op -BenchmarkStructSimpleCrossFieldFailure-8 2000000 789 ns/op 304 B/op 8 allocs/op -BenchmarkStructSimpleCrossFieldFailureParallel-8 5000000 386 ns/op 304 B/op 8 allocs/op -BenchmarkStructSimpleCrossStructCrossFieldSuccess-8 2000000 793 ns/op 80 B/op 4 allocs/op -BenchmarkStructSimpleCrossStructCrossFieldSuccessParallel-8 5000000 287 ns/op 80 B/op 4 allocs/op -BenchmarkStructSimpleCrossStructCrossFieldFailure-8 1000000 1065 ns/op 320 B/op 9 allocs/op -BenchmarkStructSimpleCrossStructCrossFieldFailureParallel-8 3000000 417 ns/op 320 B/op 9 allocs/op -BenchmarkStructSimpleSuccess-8 5000000 364 ns/op 0 B/op 0 allocs/op -BenchmarkStructSimpleSuccessParallel-8 20000000 112 ns/op 0 B/op 0 allocs/op -BenchmarkStructSimpleFailure-8 2000000 785 ns/op 424 B/op 9 allocs/op -BenchmarkStructSimpleFailureParallel-8 5000000 339 ns/op 424 B/op 9 allocs/op -BenchmarkStructComplexSuccess-8 1000000 2136 ns/op 128 B/op 8 allocs/op -BenchmarkStructComplexSuccessParallel-8 2000000 755 ns/op 128 B/op 8 allocs/op -BenchmarkStructComplexFailure-8 300000 5248 ns/op 3041 B/op 53 allocs/op -BenchmarkStructComplexFailureParallel-8 1000000 2363 ns/op 3041 B/op 53 allocs/op +BenchmarkFieldSuccess-8 20000000 106 ns/op +BenchmarkFieldSuccessParallel-8 50000000 33.7 ns/op +BenchmarkFieldFailure-8 5000000 346 ns/op +BenchmarkFieldFailureParallel-8 20000000 115 ns/op +BenchmarkFieldDiveSuccess-8 2000000 739 ns/op +BenchmarkFieldDiveSuccessParallel-8 10000000 246 ns/op +BenchmarkFieldDiveFailure-8 1000000 1043 ns/op +BenchmarkFieldDiveFailureParallel-8 5000000 381 ns/op +BenchmarkFieldCustomTypeSuccess-8 5000000 270 ns/op +BenchmarkFieldCustomTypeSuccessParallel-8 20000000 92.5 ns/op +BenchmarkFieldCustomTypeFailure-8 5000000 331 ns/op +BenchmarkFieldCustomTypeFailureParallel-8 20000000 132 ns/op +BenchmarkFieldOrTagSuccess-8 2000000 874 ns/op +BenchmarkFieldOrTagSuccessParallel-8 5000000 368 ns/op +BenchmarkFieldOrTagFailure-8 3000000 566 ns/op +BenchmarkFieldOrTagFailureParallel-8 5000000 427 ns/op +BenchmarkStructLevelValidationSuccess-8 5000000 335 ns/op +BenchmarkStructLevelValidationSuccessParallel-8 20000000 124 ns/op +BenchmarkStructLevelValidationFailure-8 2000000 630 ns/op +BenchmarkStructLevelValidationFailureParallel-8 10000000 298 ns/op +BenchmarkStructSimpleCustomTypeSuccess-8 3000000 535 ns/op +BenchmarkStructSimpleCustomTypeSuccessParallel-8 10000000 170 ns/op +BenchmarkStructSimpleCustomTypeFailure-8 2000000 821 ns/op +BenchmarkStructSimpleCustomTypeFailureParallel-8 5000000 379 ns/op +BenchmarkStructFilteredSuccess-8 2000000 769 ns/op +BenchmarkStructFilteredSuccessParallel-8 5000000 328 ns/op +BenchmarkStructFilteredFailure-8 2000000 594 ns/op +BenchmarkStructFilteredFailureParallel-8 10000000 244 ns/op +BenchmarkStructPartialSuccess-8 2000000 682 ns/op +BenchmarkStructPartialSuccessParallel-8 5000000 291 ns/op +BenchmarkStructPartialFailure-8 1000000 1034 ns/op +BenchmarkStructPartialFailureParallel-8 5000000 392 ns/op +BenchmarkStructExceptSuccess-8 1000000 1014 ns/op +BenchmarkStructExceptSuccessParallel-8 10000000 257 ns/op +BenchmarkStructExceptFailure-8 2000000 875 ns/op +BenchmarkStructExceptFailureParallel-8 5000000 405 ns/op +BenchmarkStructSimpleCrossFieldSuccess-8 3000000 545 ns/op +BenchmarkStructSimpleCrossFieldSuccessParallel-8 10000000 177 ns/op +BenchmarkStructSimpleCrossFieldFailure-8 2000000 787 ns/op +BenchmarkStructSimpleCrossFieldFailureParallel-8 5000000 341 ns/op +BenchmarkStructSimpleCrossStructCrossFieldSuccess-8 2000000 795 ns/op +BenchmarkStructSimpleCrossStructCrossFieldSuccessParallel-8 10000000 267 ns/op +BenchmarkStructSimpleCrossStructCrossFieldFailure-8 1000000 1119 ns/op +BenchmarkStructSimpleCrossStructCrossFieldFailureParallel-8 3000000 437 ns/op +BenchmarkStructSimpleSuccess-8 5000000 377 ns/op +BenchmarkStructSimpleSuccessParallel-8 20000000 110 ns/op +BenchmarkStructSimpleFailure-8 2000000 785 ns/op +BenchmarkStructSimpleFailureParallel-8 5000000 302 ns/op +BenchmarkStructComplexSuccess-8 1000000 2159 ns/op +BenchmarkStructComplexSuccessParallel-8 2000000 723 ns/op +BenchmarkStructComplexFailure-8 300000 5237 ns/op +BenchmarkStructComplexFailureParallel-8 1000000 2378 ns/op ``` Complimentary Software diff --git a/baked_in.go b/baked_in.go index f5dee2a..9a94aa5 100644 --- a/baked_in.go +++ b/baked_in.go @@ -26,6 +26,7 @@ var ( utf8HexComma: {}, utf8Pipe: {}, noStructLevelTag: {}, + requiredTag: {}, } // BakedInAliasValidators is a default mapping of a single validation tag that @@ -887,6 +888,11 @@ func hasValue(fl FieldLevel) bool { case reflect.Slice, reflect.Map, reflect.Ptr, reflect.Interface, reflect.Chan, reflect.Func: return !field.IsNil() default: + + if fl.(*validate).fldIsPointer && field.Interface() != nil { + return true + } + return field.IsValid() && field.Interface() != reflect.Zero(field.Type()).Interface() } } diff --git a/validator.go b/validator.go index 7ae76be..94d9e8d 100644 --- a/validator.go +++ b/validator.go @@ -20,10 +20,11 @@ type validate struct { ffn FilterFunc // StructLevel & FieldLevel fields - slflParent reflect.Value - slCurrent reflect.Value - flField reflect.Value - flParam string + slflParent reflect.Value + slCurrent reflect.Value + flField reflect.Value + flParam string + fldIsPointer bool // misc reusable values misc []byte @@ -95,9 +96,8 @@ func (v *validate) traverseField(parent reflect.Value, current reflect.Value, ns var typ reflect.Type var kind reflect.Kind - var nullable bool - current, kind, nullable = v.extractTypeInternal(current, nullable) + current, kind, v.fldIsPointer = v.extractTypeInternal(current, false) switch kind { case reflect.Ptr, reflect.Interface, reflect.Invalid: @@ -207,7 +207,7 @@ OUTER: v.flField = current v.flParam = "" - if !nullable && !hasValue(v) { + if !v.fldIsPointer && !hasValue(v) { return } diff --git a/validator_instance.go b/validator_instance.go index c50aa5f..5f2cd74 100644 --- a/validator_instance.go +++ b/validator_instance.go @@ -23,6 +23,7 @@ const ( omitempty = "omitempty" skipValidationTag = "-" diveTag = "dive" + requiredTag = "required" namespaceSeparator = "." leftBracket = "[" rightBracket = "]" @@ -92,7 +93,7 @@ func New() *Validate { for k, val := range bakedInValidators { // no need to error check here, baked in will alwaays be valid - v.RegisterValidation(k, val) + v.registerValidation(k, val, true) } v.pool = &sync.Pool{ @@ -127,6 +128,10 @@ func (v *Validate) RegisterTagNameFunc(fn TagNameFunc) { // - if the key already exists, the previous validation function will be replaced. // - this method is not thread-safe it is intended that these all be registered prior to any validation func (v *Validate) RegisterValidation(tag string, fn Func) error { + return v.registerValidation(tag, fn, false) +} + +func (v *Validate) registerValidation(tag string, fn Func, bakedIn bool) error { if len(tag) == 0 { return errors.New("Function Key cannot be empty") @@ -138,7 +143,7 @@ func (v *Validate) RegisterValidation(tag string, fn Func) error { _, ok := restrictedTags[tag] - if ok || strings.ContainsAny(tag, restrictedTagChars) { + if !bakedIn && (ok || strings.ContainsAny(tag, restrictedTagChars)) { panic(fmt.Sprintf(restrictedTagErr, tag)) } diff --git a/validator_test.go b/validator_test.go index 1b93214..82a83fb 100644 --- a/validator_test.go +++ b/validator_test.go @@ -6731,3 +6731,96 @@ func TestStructFiltered(t *testing.T) { NotEqual(t, err, nil) Equal(t, err.Error(), "validator: (nil *time.Time)") } + +func TestRequiredPtr(t *testing.T) { + + type Test struct { + Bool *bool `validate:"required"` + } + + validate := New() + + f := false + + test := Test{ + Bool: &f, + } + + err := validate.Struct(test) + Equal(t, err, nil) + + tr := true + + test.Bool = &tr + + err = validate.Struct(test) + Equal(t, err, nil) + + test.Bool = nil + + err = validate.Struct(test) + NotEqual(t, err, nil) + + errs, ok := err.(ValidationErrors) + Equal(t, ok, true) + Equal(t, len(errs), 1) + AssertError(t, errs, "Test.Bool", "Test.Bool", "Bool", "Bool", "required") + + type Test2 struct { + Bool bool `validate:"required"` + } + + var test2 Test2 + + err = validate.Struct(test2) + NotEqual(t, err, nil) + + errs, ok = err.(ValidationErrors) + Equal(t, ok, true) + Equal(t, len(errs), 1) + AssertError(t, errs, "Test2.Bool", "Test2.Bool", "Bool", "Bool", "required") + + test2.Bool = true + + err = validate.Struct(test2) + Equal(t, err, nil) + + type Test3 struct { + Arr []string `validate:"required"` + } + + var test3 Test3 + + err = validate.Struct(test3) + NotEqual(t, err, nil) + + errs, ok = err.(ValidationErrors) + Equal(t, ok, true) + Equal(t, len(errs), 1) + AssertError(t, errs, "Test3.Arr", "Test3.Arr", "Arr", "Arr", "required") + + test3.Arr = make([]string, 0) + + err = validate.Struct(test3) + Equal(t, err, nil) + + type Test4 struct { + Arr *[]string `validate:"required"` // I know I know pointer to array, just making sure validation works as expected... + } + + var test4 Test4 + + err = validate.Struct(test4) + NotEqual(t, err, nil) + + errs, ok = err.(ValidationErrors) + Equal(t, ok, true) + Equal(t, len(errs), 1) + AssertError(t, errs, "Test4.Arr", "Test4.Arr", "Arr", "Arr", "required") + + arr := make([]string, 0) + test4.Arr = &arr + + err = validate.Struct(test4) + Equal(t, err, nil) +}