Merge pull request #161 from joeybloggs/v7-development

Changes for v7
pull/163/head
Dean Karn 9 years ago
commit d423756cc0
  1. 1
      .gitignore
  2. 64
      README.md
  3. 675
      baked_in.go
  4. 228
      benchmarks_test.go
  5. 61
      doc.go
  6. 2
      examples_test.go
  7. 216
      util.go
  8. 218
      validator.go
  9. 1145
      validator_test.go

1
.gitignore vendored

@ -24,5 +24,6 @@ _testmain.go
*.prof *.prof
*.test *.test
*.out *.out
*.txt
cover.html cover.html
README.html README.html

@ -3,14 +3,14 @@ Package validator
[![Join the chat at https://gitter.im/bluesuncorp/validator](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/bluesuncorp/validator?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) [![Join the chat at https://gitter.im/bluesuncorp/validator](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/bluesuncorp/validator?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
[![Build Status](https://semaphoreci.com/api/v1/projects/ec20115f-ef1b-4c7d-9393-cc76aba74eb4/487374/badge.svg)](https://semaphoreci.com/joeybloggs/validator) [![Build Status](https://semaphoreci.com/api/v1/projects/ec20115f-ef1b-4c7d-9393-cc76aba74eb4/487374/badge.svg)](https://semaphoreci.com/joeybloggs/validator)
[![Coverage Status](https://coveralls.io/repos/bluesuncorp/validator/badge.svg?branch=v6)](https://coveralls.io/r/bluesuncorp/validator?branch=v6) [![Coverage Status](https://coveralls.io/repos/bluesuncorp/validator/badge.svg?branch=v7)](https://coveralls.io/r/bluesuncorp/validator?branch=v7)
[![GoDoc](https://godoc.org/gopkg.in/bluesuncorp/validator.v6?status.svg)](https://godoc.org/gopkg.in/bluesuncorp/validator.v6) [![GoDoc](https://godoc.org/gopkg.in/bluesuncorp/validator.v7?status.svg)](https://godoc.org/gopkg.in/bluesuncorp/validator.v7)
Package validator implements value validations for structs and individual fields based on tags. Package validator implements value validations for structs and individual fields based on tags.
It has the following **unique** features: It has the following **unique** features:
- Cross Field and Cross Struct validations. - Cross Field and Cross Struct validations by using validation tags or custom validators.
- Slice, Array and Map diving, which allows any or all levels of a multidimensional field to be validated. - Slice, Array and Map diving, which allows any or all levels of a multidimensional field to be validated.
- Handles type interface by determining it's underlying type prior to validation. - Handles type interface by determining it's underlying type prior to validation.
- Handles custom field types such as sql driver Valuer see [Valuer](https://golang.org/src/database/sql/driver/types.go?s=1210:1293#L29) - Handles custom field types such as sql driver Valuer see [Valuer](https://golang.org/src/database/sql/driver/types.go?s=1210:1293#L29)
@ -20,20 +20,20 @@ Installation
Use go get. Use go get.
go get gopkg.in/bluesuncorp/validator.v6 go get gopkg.in/bluesuncorp/validator.v7
or to update or to update
go get -u gopkg.in/bluesuncorp/validator.v6 go get -u gopkg.in/bluesuncorp/validator.v7
Then import the validator package into your own code. Then import the validator package into your own code.
import "gopkg.in/bluesuncorp/validator.v6" import "gopkg.in/bluesuncorp/validator.v7"
Usage and documentation Usage and documentation
------ ------
Please see http://godoc.org/gopkg.in/bluesuncorp/validator.v6 for detailed usage docs. Please see http://godoc.org/gopkg.in/bluesuncorp/validator.v7 for detailed usage docs.
##### Examples: ##### Examples:
@ -143,7 +143,7 @@ import (
"fmt" "fmt"
"reflect" "reflect"
"gopkg.in/bluesuncorp/validator.v6" "gopkg.in/bluesuncorp/validator.v7"
) )
// DbBackedUser User struct // DbBackedUser User struct
@ -187,29 +187,35 @@ func ValidateValuer(field reflect.Value) interface{} {
Benchmarks Benchmarks
------ ------
###### Run on MacBook Pro (Retina, 15-inch, Late 2013) 2.6 GHz Intel Core i7 16 GB 1600 MHz DDR3 ###### Run on MacBook Pro (Retina, 15-inch, Late 2013) 2.6 GHz Intel Core i7 16 GB 1600 MHz DDR3 using Go 1.5
NOTE: allocations for structs are up from v5, however ns/op for parallel operations are way down.
It was a decicion not to cache struct info because although it reduced allocation to v5 levels, it
hurt parallel performance too much.
```go ```go
$ go test -cpu=4 -bench=. -benchmem=true
PASS PASS
BenchmarkFieldSuccess-4 5000000 318 ns/op 16 B/op 1 allocs/op BenchmarkFieldSuccess-4 5000000 290 ns/op 16 B/op 1 allocs/op
BenchmarkFieldFailure-4 5000000 316 ns/op 16 B/op 1 allocs/op BenchmarkFieldFailure-4 5000000 286 ns/op 16 B/op 1 allocs/op
BenchmarkFieldCustomTypeSuccess-4 3000000 492 ns/op 32 B/op 2 allocs/op BenchmarkFieldDiveSuccess-4 500000 2497 ns/op 384 B/op 19 allocs/op
BenchmarkFieldCustomTypeFailure-4 2000000 843 ns/op 416 B/op 6 allocs/op BenchmarkFieldDiveFailure-4 500000 3022 ns/op 752 B/op 23 allocs/op
BenchmarkFieldOrTagSuccess-4 500000 2384 ns/op 20 B/op 2 allocs/op BenchmarkFieldCustomTypeSuccess-4 3000000 446 ns/op 32 B/op 2 allocs/op
BenchmarkFieldOrTagFailure-4 1000000 1295 ns/op 384 B/op 6 allocs/op BenchmarkFieldCustomTypeFailure-4 2000000 778 ns/op 416 B/op 6 allocs/op
BenchmarkStructSimpleSuccess-4 1000000 1175 ns/op 24 B/op 3 allocs/op BenchmarkFieldOrTagSuccess-4 1000000 1287 ns/op 32 B/op 2 allocs/op
BenchmarkStructSimpleFailure-4 1000000 1822 ns/op 529 B/op 11 allocs/op BenchmarkFieldOrTagFailure-4 1000000 1125 ns/op 400 B/op 6 allocs/op
BenchmarkStructSimpleCustomTypeSuccess-4 1000000 1302 ns/op 56 B/op 5 allocs/op BenchmarkStructSimpleCustomTypeSuccess-4 1000000 1225 ns/op 80 B/op 5 allocs/op
BenchmarkStructSimpleCustomTypeFailure-4 1000000 1847 ns/op 577 B/op 13 allocs/op BenchmarkStructSimpleCustomTypeFailure-4 1000000 1742 ns/op 608 B/op 13 allocs/op
BenchmarkStructSimpleSuccessParallel-4 5000000 339 ns/op 24 B/op 3 allocs/op BenchmarkStructPartialSuccess-4 1000000 1304 ns/op 400 B/op 11 allocs/op
BenchmarkStructSimpleFailureParallel-4 2000000 733 ns/op 529 B/op 11 allocs/op BenchmarkStructPartialFailure-4 1000000 1818 ns/op 784 B/op 16 allocs/op
BenchmarkStructComplexSuccess-4 200000 7104 ns/op 368 B/op 30 allocs/op BenchmarkStructExceptSuccess-4 2000000 869 ns/op 368 B/op 9 allocs/op
BenchmarkStructComplexFailure-4 100000 11996 ns/op 2861 B/op 72 allocs/op BenchmarkStructExceptFailure-4 1000000 1308 ns/op 400 B/op 11 allocs/op
BenchmarkStructComplexSuccessParallel-4 1000000 2252 ns/op 368 B/op 30 allocs/op BenchmarkStructSimpleCrossFieldSuccess-4 2000000 973 ns/op 128 B/op 6 allocs/op
BenchmarkStructComplexFailureParallel-4 300000 4691 ns/op 2862 B/op 72 allocs/op BenchmarkStructSimpleCrossFieldFailure-4 1000000 1519 ns/op 528 B/op 11 allocs/op
BenchmarkStructSimpleCrossStructCrossFieldSuccess-4 1000000 1382 ns/op 160 B/op 8 allocs/op
BenchmarkStructSimpleCrossStructCrossFieldFailure-4 1000000 1931 ns/op 560 B/op 13 allocs/op
BenchmarkStructSimpleSuccess-4 1000000 1132 ns/op 48 B/op 3 allocs/op
BenchmarkStructSimpleFailure-4 1000000 1735 ns/op 560 B/op 11 allocs/op
BenchmarkStructSimpleSuccessParallel-4 3000000 363 ns/op 48 B/op 3 allocs/op
BenchmarkStructSimpleFailureParallel-4 2000000 705 ns/op 560 B/op 11 allocs/op
BenchmarkStructComplexSuccess-4 200000 6935 ns/op 432 B/op 27 allocs/op
BenchmarkStructComplexFailure-4 200000 11059 ns/op 2920 B/op 69 allocs/op
BenchmarkStructComplexSuccessParallel-4 1000000 2220 ns/op 432 B/op 27 allocs/op
BenchmarkStructComplexFailureParallel-4 300000 4739 ns/op 2920 B/op 69 allocs/op
``` ```
How to Contribute How to Contribute

File diff suppressed because it is too large Load Diff

@ -4,6 +4,7 @@ import (
sql "database/sql/driver" sql "database/sql/driver"
"reflect" "reflect"
"testing" "testing"
"time"
) )
func BenchmarkFieldSuccess(b *testing.B) { func BenchmarkFieldSuccess(b *testing.B) {
@ -18,6 +19,18 @@ func BenchmarkFieldFailure(b *testing.B) {
} }
} }
func BenchmarkFieldDiveSuccess(b *testing.B) {
for n := 0; n < b.N; n++ {
validate.Field([]string{"val1", "val2", "val3"}, "required,dive,required")
}
}
func BenchmarkFieldDiveFailure(b *testing.B) {
for n := 0; n < b.N; n++ {
validate.Field([]string{"val1", "", "val3"}, "required,dive,required")
}
}
func BenchmarkFieldCustomTypeSuccess(b *testing.B) { func BenchmarkFieldCustomTypeSuccess(b *testing.B) {
customTypes := map[reflect.Type]CustomTypeFunc{} customTypes := map[reflect.Type]CustomTypeFunc{}
@ -62,34 +75,6 @@ func BenchmarkFieldOrTagFailure(b *testing.B) {
} }
} }
func BenchmarkStructSimpleSuccess(b *testing.B) {
type Foo struct {
StringValue string `validate:"min=5,max=10"`
IntValue int `validate:"min=5,max=10"`
}
validFoo := &Foo{StringValue: "Foobar", IntValue: 7}
for n := 0; n < b.N; n++ {
validate.Struct(validFoo)
}
}
func BenchmarkStructSimpleFailure(b *testing.B) {
type Foo struct {
StringValue string `validate:"min=5,max=10"`
IntValue int `validate:"min=5,max=10"`
}
invalidFoo := &Foo{StringValue: "Fo", IntValue: 3}
for n := 0; n < b.N; n++ {
validate.Struct(invalidFoo)
}
}
func BenchmarkStructSimpleCustomTypeSuccess(b *testing.B) { func BenchmarkStructSimpleCustomTypeSuccess(b *testing.B) {
customTypes := map[reflect.Type]CustomTypeFunc{} customTypes := map[reflect.Type]CustomTypeFunc{}
@ -136,6 +121,193 @@ func BenchmarkStructSimpleCustomTypeFailure(b *testing.B) {
} }
} }
func BenchmarkStructPartialSuccess(b *testing.B) {
type Test struct {
Name string `validate:"required"`
NickName string `validate:"required"`
}
test := &Test{
Name: "Joey Bloggs",
}
for n := 0; n < b.N; n++ {
validate.StructPartial(test, "Name")
}
}
func BenchmarkStructPartialFailure(b *testing.B) {
type Test struct {
Name string `validate:"required"`
NickName string `validate:"required"`
}
test := &Test{
Name: "Joey Bloggs",
}
for n := 0; n < b.N; n++ {
validate.StructPartial(test, "NickName")
}
}
func BenchmarkStructExceptSuccess(b *testing.B) {
type Test struct {
Name string `validate:"required"`
NickName string `validate:"required"`
}
test := &Test{
Name: "Joey Bloggs",
}
for n := 0; n < b.N; n++ {
validate.StructPartial(test, "Nickname")
}
}
func BenchmarkStructExceptFailure(b *testing.B) {
type Test struct {
Name string `validate:"required"`
NickName string `validate:"required"`
}
test := &Test{
Name: "Joey Bloggs",
}
for n := 0; n < b.N; n++ {
validate.StructPartial(test, "Name")
}
}
func BenchmarkStructSimpleCrossFieldSuccess(b *testing.B) {
type Test struct {
Start time.Time
End time.Time `validate:"gtfield=Start"`
}
now := time.Now().UTC()
then := now.Add(time.Hour * 5)
test := &Test{
Start: now,
End: then,
}
for n := 0; n < b.N; n++ {
validate.Struct(test)
}
}
func BenchmarkStructSimpleCrossFieldFailure(b *testing.B) {
type Test struct {
Start time.Time
End time.Time `validate:"gtfield=Start"`
}
now := time.Now().UTC()
then := now.Add(time.Hour * -5)
test := &Test{
Start: now,
End: then,
}
for n := 0; n < b.N; n++ {
validate.Struct(test)
}
}
func BenchmarkStructSimpleCrossStructCrossFieldSuccess(b *testing.B) {
type Inner struct {
Start time.Time
}
type Outer struct {
Inner *Inner
CreatedAt time.Time `validate:"eqcsfield=Inner.Start"`
}
now := time.Now().UTC()
inner := &Inner{
Start: now,
}
outer := &Outer{
Inner: inner,
CreatedAt: now,
}
for n := 0; n < b.N; n++ {
validate.Struct(outer)
}
}
func BenchmarkStructSimpleCrossStructCrossFieldFailure(b *testing.B) {
type Inner struct {
Start time.Time
}
type Outer struct {
Inner *Inner
CreatedAt time.Time `validate:"eqcsfield=Inner.Start"`
}
now := time.Now().UTC()
then := now.Add(time.Hour * 5)
inner := &Inner{
Start: then,
}
outer := &Outer{
Inner: inner,
CreatedAt: now,
}
for n := 0; n < b.N; n++ {
validate.Struct(outer)
}
}
func BenchmarkStructSimpleSuccess(b *testing.B) {
type Foo struct {
StringValue string `validate:"min=5,max=10"`
IntValue int `validate:"min=5,max=10"`
}
validFoo := &Foo{StringValue: "Foobar", IntValue: 7}
for n := 0; n < b.N; n++ {
validate.Struct(validFoo)
}
}
func BenchmarkStructSimpleFailure(b *testing.B) {
type Foo struct {
StringValue string `validate:"min=5,max=10"`
IntValue int `validate:"min=5,max=10"`
}
invalidFoo := &Foo{StringValue: "Fo", IntValue: 3}
for n := 0; n < b.N; n++ {
validate.Struct(invalidFoo)
}
}
func BenchmarkStructSimpleSuccessParallel(b *testing.B) { func BenchmarkStructSimpleSuccessParallel(b *testing.B) {
type Foo struct { type Foo struct {

@ -21,7 +21,7 @@ Custom Functions
Custom functions can be added Custom functions can be added
// Structure // Structure
func customFunc(topStruct reflect.Value, currentStruct reflect.Value, field reflect.Value, fieldType reflect.Type, fieldKind reflect.Kind, param string) bool { func customFunc(v *Validate, topStruct reflect.Value, currentStructOrField reflect.Value, field reflect.Value, fieldType reflect.Type, fieldKind reflect.Kind, param string) bool {
if whatever { if whatever {
return false return false
@ -36,18 +36,41 @@ Custom functions can be added
Cross Field Validation Cross Field Validation
Cross Field Validation can be implemented, for example Start & End Date range validation Cross Field Validation can be done via the following tags: eqfield, nefield, gtfield, gtefield,
ltfield, ltefield, eqcsfield, necsfield, gtcsfield, ftecsfield, ltcsfield and ltecsfield. If
however some custom cross field validation is required, it can be done using a custom validation.
Why not just have cross fields validation tags i.e. only eqcsfield and not eqfield; the reason is
efficiency, if you want to check a field within the same struct eqfield only has to find the field
on the same struct, 1 level; but if we used eqcsfield it could be multiple levels down.
type Inner struct {
StartDate time.Time
}
type Outer struct {
InnerStructField *Inner
CreatedAt time.Time `validate:"ltecsfield=InnerStructField.StartDate"`
}
now := time.Now()
inner := &Inner{
StartDate: now,
}
outer := &Outer{
InnerStructField: inner,
CreatedAt: now,
}
errs := validate.Struct(outer)
// NOTE: when calling validate.Struct(val) topStruct will be the top level struct passed // NOTE: when calling validate.Struct(val) topStruct will be the top level struct passed
// into the function // into the function
// when calling validate.FieldWithValue(val, field, tag) val will be // when calling validate.FieldWithValue(val, field, tag) val will be
// whatever you pass, struct, field... // whatever you pass, struct, field...
// when calling validate.Field(field, tag) val will be nil // when calling validate.Field(field, tag) val will be nil
//
// Because of the specific requirements and field names within each persons project that
// uses this library it is likely that custom functions will need to be created for your
// Cross Field Validation needs, however there are some build in Generic Cross Field validations,
// see Baked In Validators eqfield, nefield, gtfield, gtefield, ltfield, ltefield and Tags below
Multiple Validators Multiple Validators
@ -201,6 +224,10 @@ Here is a list of the current built in validators:
Validation on Password field using validate.Struct Usage(eqfield=ConfirmPassword) Validation on Password field using validate.Struct Usage(eqfield=ConfirmPassword)
Validating by field validate.FieldWithValue(password, confirmpassword, "eqfield") Validating by field validate.FieldWithValue(password, confirmpassword, "eqfield")
eqcsfield
This does the same as eqfield except that it validates the field provided relative
to the top level struct. (Usage: eqcsfield=InnerStructField.Field)
nefield nefield
This will validate the field value against another fields value either within This will validate the field value against another fields value either within
a struct or passed in field. a struct or passed in field.
@ -208,6 +235,10 @@ Here is a list of the current built in validators:
Validation on Color field using validate.Struct Usage(nefield=Color2) Validation on Color field using validate.Struct Usage(nefield=Color2)
Validating by field validate.FieldWithValue(color1, color2, "nefield") Validating by field validate.FieldWithValue(color1, color2, "nefield")
necsfield
This does the same as nefield except that it validates the field provided relative
to the top level struct. (Usage: necsfield=InnerStructField.Field)
gtfield gtfield
Only valid for Numbers and time.Time types, this will validate the field value Only valid for Numbers and time.Time types, this will validate the field value
against another fields value either within a struct or passed in field. against another fields value either within a struct or passed in field.
@ -215,6 +246,10 @@ Here is a list of the current built in validators:
Validation on End field using validate.Struct Usage(gtfield=Start) Validation on End field using validate.Struct Usage(gtfield=Start)
Validating by field validate.FieldWithValue(start, end, "gtfield") Validating by field validate.FieldWithValue(start, end, "gtfield")
gtcsfield
This does the same as gtfield except that it validates the field provided relative
to the top level struct. (Usage: gtcsfield=InnerStructField.Field)
gtefield gtefield
Only valid for Numbers and time.Time types, this will validate the field value Only valid for Numbers and time.Time types, this will validate the field value
against another fields value either within a struct or passed in field. against another fields value either within a struct or passed in field.
@ -222,6 +257,10 @@ Here is a list of the current built in validators:
Validation on End field using validate.Struct Usage(gtefield=Start) Validation on End field using validate.Struct Usage(gtefield=Start)
Validating by field validate.FieldWithValue(start, end, "gtefield") Validating by field validate.FieldWithValue(start, end, "gtefield")
gtecsfield
This does the same as gtefield except that it validates the field provided relative
to the top level struct. (Usage: gtecsfield=InnerStructField.Field)
ltfield ltfield
Only valid for Numbers and time.Time types, this will validate the field value Only valid for Numbers and time.Time types, this will validate the field value
against another fields value either within a struct or passed in field. against another fields value either within a struct or passed in field.
@ -229,6 +268,10 @@ Here is a list of the current built in validators:
Validation on End field using validate.Struct Usage(ltfield=Start) Validation on End field using validate.Struct Usage(ltfield=Start)
Validating by field validate.FieldWithValue(start, end, "ltfield") Validating by field validate.FieldWithValue(start, end, "ltfield")
ltcsfield
This does the same as ltfield except that it validates the field provided relative
to the top level struct. (Usage: ltcsfield=InnerStructField.Field)
ltefield ltefield
Only valid for Numbers and time.Time types, this will validate the field value Only valid for Numbers and time.Time types, this will validate the field value
against another fields value either within a struct or passed in field. against another fields value either within a struct or passed in field.
@ -236,6 +279,10 @@ Here is a list of the current built in validators:
Validation on End field using validate.Struct Usage(ltefield=Start) Validation on End field using validate.Struct Usage(ltefield=Start)
Validating by field validate.FieldWithValue(start, end, "ltefield") Validating by field validate.FieldWithValue(start, end, "ltefield")
ltecsfield
This does the same as ltefield except that it validates the field provided relative
to the top level struct. (Usage: ltecsfield=InnerStructField.Field)
alpha alpha
This validates that a string value contains alpha characters only This validates that a string value contains alpha characters only
(Usage: alpha) (Usage: alpha)

@ -3,7 +3,7 @@ package validator_test
import ( import (
"fmt" "fmt"
"../validator" "gopkg.in/bluesuncorp/validator.v6"
) )
func ExampleValidate_new() { func ExampleValidate_new() {

@ -0,0 +1,216 @@
package validator
import (
"reflect"
"strconv"
"strings"
)
const (
namespaceSeparator = "."
leftBracket = "["
rightBracket = "]"
)
func (v *Validate) extractType(current reflect.Value) (reflect.Value, reflect.Kind) {
switch current.Kind() {
case reflect.Ptr:
if current.IsNil() {
return current, reflect.Ptr
}
return v.extractType(current.Elem())
case reflect.Interface:
if current.IsNil() {
return current, reflect.Interface
}
return v.extractType(current.Elem())
case reflect.Invalid:
return current, reflect.Invalid
default:
if v.config.hasCustomFuncs {
if fn, ok := v.config.CustomTypeFuncs[current.Type()]; ok {
return v.extractType(reflect.ValueOf(fn(current)))
}
}
return current, current.Kind()
}
}
func (v *Validate) getStructFieldOK(current reflect.Value, namespace string) (reflect.Value, reflect.Kind, bool) {
current, kind := v.extractType(current)
if kind == reflect.Invalid {
return current, kind, false
}
if len(namespace) == 0 {
return current, kind, true
}
switch kind {
case reflect.Ptr, reflect.Interface:
return current, kind, false
case reflect.Struct:
typ := current.Type()
fld := namespace
ns := namespace
if typ != timeType && typ != timePtrType {
idx := strings.Index(namespace, namespaceSeparator)
if idx != -1 {
fld = namespace[:idx]
ns = namespace[idx+1:]
} else {
ns = ""
idx = len(namespace)
}
bracketIdx := strings.Index(fld, leftBracket)
if bracketIdx != -1 {
fld = fld[:bracketIdx]
ns = namespace[bracketIdx:]
}
current = current.FieldByName(fld)
return v.getStructFieldOK(current, ns)
}
case reflect.Array, reflect.Slice:
idx := strings.Index(namespace, leftBracket)
idx2 := strings.Index(namespace, rightBracket)
arrIdx, _ := strconv.Atoi(namespace[idx+1 : idx2])
if arrIdx >= current.Len() {
return current, kind, false
}
startIdx := idx2 + 1
if startIdx < len(namespace) {
if namespace[startIdx:startIdx+1] == namespaceSeparator {
startIdx++
}
}
return v.getStructFieldOK(current.Index(arrIdx), namespace[startIdx:])
case reflect.Map:
idx := strings.Index(namespace, leftBracket) + 1
idx2 := strings.Index(namespace, rightBracket)
endIdx := idx2
if endIdx+1 < len(namespace) {
if namespace[endIdx+1:endIdx+2] == namespaceSeparator {
endIdx++
}
}
key := namespace[idx:idx2]
switch current.Type().Key().Kind() {
case reflect.Int:
i, _ := strconv.Atoi(key)
return v.getStructFieldOK(current.MapIndex(reflect.ValueOf(i)), namespace[endIdx+1:])
case reflect.Int8:
i, _ := strconv.ParseInt(key, 10, 8)
return v.getStructFieldOK(current.MapIndex(reflect.ValueOf(int8(i))), namespace[endIdx+1:])
case reflect.Int16:
i, _ := strconv.ParseInt(key, 10, 16)
return v.getStructFieldOK(current.MapIndex(reflect.ValueOf(int16(i))), namespace[endIdx+1:])
case reflect.Int32:
i, _ := strconv.ParseInt(key, 10, 32)
return v.getStructFieldOK(current.MapIndex(reflect.ValueOf(int32(i))), namespace[endIdx+1:])
case reflect.Int64:
i, _ := strconv.ParseInt(key, 10, 64)
return v.getStructFieldOK(current.MapIndex(reflect.ValueOf(i)), namespace[endIdx+1:])
case reflect.Uint:
i, _ := strconv.ParseUint(key, 10, 0)
return v.getStructFieldOK(current.MapIndex(reflect.ValueOf(uint(i))), namespace[endIdx+1:])
case reflect.Uint8:
i, _ := strconv.ParseUint(key, 10, 8)
return v.getStructFieldOK(current.MapIndex(reflect.ValueOf(uint8(i))), namespace[endIdx+1:])
case reflect.Uint16:
i, _ := strconv.ParseUint(key, 10, 16)
return v.getStructFieldOK(current.MapIndex(reflect.ValueOf(uint16(i))), namespace[endIdx+1:])
case reflect.Uint32:
i, _ := strconv.ParseUint(key, 10, 32)
return v.getStructFieldOK(current.MapIndex(reflect.ValueOf(uint32(i))), namespace[endIdx+1:])
case reflect.Uint64:
i, _ := strconv.ParseUint(key, 10, 64)
return v.getStructFieldOK(current.MapIndex(reflect.ValueOf(i)), namespace[endIdx+1:])
case reflect.Float32:
f, _ := strconv.ParseFloat(key, 32)
return v.getStructFieldOK(current.MapIndex(reflect.ValueOf(float32(f))), namespace[endIdx+1:])
case reflect.Float64:
f, _ := strconv.ParseFloat(key, 64)
return v.getStructFieldOK(current.MapIndex(reflect.ValueOf(f)), namespace[endIdx+1:])
case reflect.Bool:
b, _ := strconv.ParseBool(key)
return v.getStructFieldOK(current.MapIndex(reflect.ValueOf(b)), namespace[endIdx+1:])
// reflect.Type = string
default:
return v.getStructFieldOK(current.MapIndex(reflect.ValueOf(key)), namespace[endIdx+1:])
}
}
// if got here there was more namespace, cannot go any deeper
panic("Invalid field namespace")
}
// asInt retuns the parameter as a int64
// or panics if it can't convert
func asInt(param string) int64 {
i, err := strconv.ParseInt(param, 0, 64)
panicIf(err)
return i
}
// asUint returns the parameter as a uint64
// or panics if it can't convert
func asUint(param string) uint64 {
i, err := strconv.ParseUint(param, 0, 64)
panicIf(err)
return i
}
// asFloat returns the parameter as a float64
// or panics if it can't convert
func asFloat(param string) float64 {
i, err := strconv.ParseFloat(param, 64)
panicIf(err)
return i
}
func panicIf(err error) {
if err != nil {
panic(err.Error())
}
}

@ -31,17 +31,18 @@ const (
diveTag = "dive" diveTag = "dive"
existsTag = "exists" existsTag = "exists"
fieldErrMsg = "Key: \"%s\" Error:Field validation for \"%s\" failed on the \"%s\" tag" fieldErrMsg = "Key: \"%s\" Error:Field validation for \"%s\" failed on the \"%s\" tag"
arrayIndexFieldName = "%s[%d]" arrayIndexFieldName = "%s" + leftBracket + "%d" + rightBracket
mapIndexFieldName = "%s[%v]" mapIndexFieldName = "%s" + leftBracket + "%v" + rightBracket
invalidValidation = "Invalid validation tag on field %s" invalidValidation = "Invalid validation tag on field %s"
undefinedValidation = "Undefined validation function on field %s" undefinedValidation = "Undefined validation function on field %s"
) )
var ( var (
timeType = reflect.TypeOf(time.Time{}) timeType = reflect.TypeOf(time.Time{})
timePtrType = reflect.TypeOf(&time.Time{}) timePtrType = reflect.TypeOf(&time.Time{})
errsPool = &sync.Pool{New: newValidationErrors} errsPool = &sync.Pool{New: newValidationErrors}
tagsCache = &tagCacheMap{m: map[string][]*tagCache{}} tagsCache = &tagCacheMap{m: map[string][]*tagCache{}}
emptyStructPtr = new(struct{})
) )
// returns new ValidationErrors to the pool // returns new ValidationErrors to the pool
@ -92,11 +93,12 @@ type Config struct {
type CustomTypeFunc func(field reflect.Value) interface{} type CustomTypeFunc func(field reflect.Value) interface{}
// Func accepts all values needed for file and cross field validation // Func accepts all values needed for file and cross field validation
// v = validator instance, needed but some built in functions for it's custom types
// topStruct = top level struct when validating by struct otherwise nil // topStruct = top level struct when validating by struct otherwise nil
// currentStruct = current level struct when validating by struct otherwise optional comparison value // currentStruct = current level struct when validating by struct otherwise optional comparison value
// field = field value for validation // field = field value for validation
// param = parameter used in validation i.e. gt=0 param would be 0 // param = parameter used in validation i.e. gt=0 param would be 0
type Func func(topStruct reflect.Value, currentStruct reflect.Value, field reflect.Value, fieldtype reflect.Type, fieldKind reflect.Kind, param string) bool type Func func(v *Validate, topStruct reflect.Value, currentStruct reflect.Value, field reflect.Value, fieldtype reflect.Type, fieldKind reflect.Kind, param string) bool
// ValidationErrors is a type of map[string]*FieldError // ValidationErrors is a type of map[string]*FieldError
// it exists to allow for multiple errors to be passed from this library // it exists to allow for multiple errors to be passed from this library
@ -180,7 +182,7 @@ func (v *Validate) Field(field interface{}, tag string) ValidationErrors {
errs := errsPool.Get().(ValidationErrors) errs := errsPool.Get().(ValidationErrors)
fieldVal := reflect.ValueOf(field) fieldVal := reflect.ValueOf(field)
v.traverseField(fieldVal, fieldVal, fieldVal, "", errs, false, tag, "") v.traverseField(fieldVal, fieldVal, fieldVal, "", errs, false, tag, "", false, false, nil)
if len(errs) == 0 { if len(errs) == 0 {
errsPool.Put(errs) errsPool.Put(errs)
@ -198,7 +200,92 @@ func (v *Validate) FieldWithValue(val interface{}, field interface{}, tag string
errs := errsPool.Get().(ValidationErrors) errs := errsPool.Get().(ValidationErrors)
topVal := reflect.ValueOf(val) topVal := reflect.ValueOf(val)
v.traverseField(topVal, topVal, reflect.ValueOf(field), "", errs, false, tag, "") v.traverseField(topVal, topVal, reflect.ValueOf(field), "", errs, false, tag, "", false, false, nil)
if len(errs) == 0 {
errsPool.Put(errs)
return nil
}
return errs
}
// StructPartial validates the fields passed in only, ignoring all others.
// Fields may be provided in a namespaced fashion relative to the struct provided
// i.e. NestedStruct.Field or NestedArrayField[0].Struct.Name
// NOTE: This is normally not needed, however in some specific cases such as: tied to a
// legacy data structure, it will be useful
func (v *Validate) StructPartial(current interface{}, fields ...string) ValidationErrors {
sv, _ := v.extractType(reflect.ValueOf(current))
name := sv.Type().Name()
m := map[string]*struct{}{}
if fields != nil {
for _, k := range fields {
flds := strings.Split(k, namespaceSeparator)
if len(flds) > 0 {
key := name + namespaceSeparator
for _, s := range flds {
idx := strings.Index(s, leftBracket)
if idx != -1 {
for idx != -1 {
key += s[:idx]
m[key] = emptyStructPtr
idx2 := strings.Index(s, rightBracket)
idx2++
key += s[idx:idx2]
m[key] = emptyStructPtr
s = s[idx2:]
idx = strings.Index(s, leftBracket)
}
} else {
key += s
m[key] = emptyStructPtr
}
key += namespaceSeparator
}
}
}
}
errs := errsPool.Get().(ValidationErrors)
v.tranverseStruct(sv, sv, sv, "", errs, true, len(m) != 0, false, m)
if len(errs) == 0 {
errsPool.Put(errs)
return nil
}
return errs
}
// StructExcept validates all fields except the ones passed in.
// Fields may be provided in a namespaced fashion relative to the struct provided
// i.e. NestedStruct.Field or NestedArrayField[0].Struct.Name
// NOTE: This is normally not needed, however in some specific cases such as: tied to a
// legacy data structure, it will be useful
func (v *Validate) StructExcept(current interface{}, fields ...string) ValidationErrors {
sv, _ := v.extractType(reflect.ValueOf(current))
name := sv.Type().Name()
m := map[string]*struct{}{}
for _, key := range fields {
m[name+"."+key] = emptyStructPtr
}
errs := errsPool.Get().(ValidationErrors)
v.tranverseStruct(sv, sv, sv, "", errs, true, len(m) != 0, true, m)
if len(errs) == 0 { if len(errs) == 0 {
errsPool.Put(errs) errsPool.Put(errs)
@ -214,7 +301,7 @@ func (v *Validate) Struct(current interface{}) ValidationErrors {
errs := errsPool.Get().(ValidationErrors) errs := errsPool.Get().(ValidationErrors)
sv := reflect.ValueOf(current) sv := reflect.ValueOf(current)
v.tranverseStruct(sv, sv, sv, "", errs, true) v.tranverseStruct(sv, sv, sv, "", errs, true, false, false, nil)
if len(errs) == 0 { if len(errs) == 0 {
errsPool.Put(errs) errsPool.Put(errs)
@ -225,7 +312,7 @@ func (v *Validate) Struct(current interface{}) ValidationErrors {
} }
// tranverseStruct traverses a structs fields and then passes them to be validated by traverseField // tranverseStruct traverses a structs fields and then passes them to be validated by traverseField
func (v *Validate) tranverseStruct(topStruct reflect.Value, currentStruct reflect.Value, current reflect.Value, errPrefix string, errs ValidationErrors, useStructName bool) { func (v *Validate) tranverseStruct(topStruct reflect.Value, currentStruct reflect.Value, current reflect.Value, errPrefix string, errs ValidationErrors, useStructName bool, partial bool, exclude bool, includeExclude map[string]*struct{}) {
if current.Kind() == reflect.Ptr && !current.IsNil() { if current.Kind() == reflect.Ptr && !current.IsNil() {
current = current.Elem() current = current.Elem()
@ -235,6 +322,7 @@ func (v *Validate) tranverseStruct(topStruct reflect.Value, currentStruct reflec
panic("value passed for validation is not a struct") panic("value passed for validation is not a struct")
} }
var ok bool
typ := current.Type() typ := current.Type()
if useStructName { if useStructName {
@ -252,29 +340,31 @@ func (v *Validate) tranverseStruct(topStruct reflect.Value, currentStruct reflec
continue continue
} }
v.traverseField(topStruct, currentStruct, current.Field(i), errPrefix, errs, true, fld.Tag.Get(v.config.TagName), fld.Name) if partial {
_, ok = includeExclude[errPrefix+fld.Name]
if (ok && exclude) || (!ok && !exclude) {
continue
}
}
v.traverseField(topStruct, currentStruct, current.Field(i), errPrefix, errs, true, fld.Tag.Get(v.config.TagName), fld.Name, partial, exclude, includeExclude)
} }
} }
// traverseField validates any field, be it a struct or single field, ensures it's validity and passes it along to be validated via it's tag options // traverseField validates any field, be it a struct or single field, ensures it's validity and passes it along to be validated via it's tag options
func (v *Validate) traverseField(topStruct reflect.Value, currentStruct reflect.Value, current reflect.Value, errPrefix string, errs ValidationErrors, isStructField bool, tag string, name string) { func (v *Validate) traverseField(topStruct reflect.Value, currentStruct reflect.Value, current reflect.Value, errPrefix string, errs ValidationErrors, isStructField bool, tag string, name string, partial bool, exclude bool, includeExclude map[string]*struct{}) {
if tag == skipValidationTag { if tag == skipValidationTag {
return return
} }
kind := current.Kind() current, kind := v.extractType(current)
var typ reflect.Type
if kind == reflect.Ptr && !current.IsNil() {
current = current.Elem()
kind = current.Kind()
}
// this also allows for tags 'required' and 'omitempty' to be used on
// nested struct fields because when len(tags) > 0 below and the value is nil
// then required failes and we check for omitempty just before that
if ((kind == reflect.Ptr || kind == reflect.Interface) && current.IsNil()) || kind == reflect.Invalid {
switch kind {
case reflect.Ptr, reflect.Interface, reflect.Invalid:
if strings.Contains(tag, omitempty) { if strings.Contains(tag, omitempty) {
return return
} }
@ -314,67 +404,29 @@ func (v *Validate) traverseField(topStruct reflect.Value, currentStruct reflect.
if kind == reflect.Invalid { if kind == reflect.Invalid {
return return
} }
}
typ := current.Type()
switch kind {
case reflect.Struct, reflect.Interface:
if kind == reflect.Interface {
current = current.Elem()
kind = current.Kind()
if kind == reflect.Ptr && !current.IsNil() {
current = current.Elem()
kind = current.Kind()
}
// changed current, so have to get inner type again
typ = current.Type()
if kind != reflect.Struct {
goto FALLTHROUGH
}
}
if typ != timeType && typ != timePtrType { case reflect.Struct:
typ = current.Type()
if kind == reflect.Struct { if typ != timeType {
if v.config.hasCustomFuncs { // required passed validation above so stop here
if fn, ok := v.config.CustomTypeFuncs[typ]; ok { // if only validating the structs existance.
v.traverseField(topStruct, currentStruct, reflect.ValueOf(fn(current)), errPrefix, errs, isStructField, tag, name) if strings.Contains(tag, structOnlyTag) {
return
}
}
// required passed validation above so stop here
// if only validating the structs existance.
if strings.Contains(tag, structOnlyTag) {
return
}
v.tranverseStruct(topStruct, current, current, errPrefix+name+".", errs, false)
return return
} }
}
FALLTHROUGH: v.tranverseStruct(topStruct, current, current, errPrefix+name+".", errs, false, partial, exclude, includeExclude)
fallthrough
default:
if len(tag) == 0 {
return return
} }
} }
if v.config.hasCustomFuncs { if len(tag) == 0 {
if fn, ok := v.config.CustomTypeFuncs[typ]; ok { return
v.traverseField(topStruct, currentStruct, reflect.ValueOf(fn(current)), errPrefix, errs, isStructField, tag, name)
return
}
} }
typ = current.Type()
tags, isCached := tagsCache.Get(tag) tags, isCached := tagsCache.Get(tag)
if !isCached { if !isCached {
@ -431,7 +483,7 @@ func (v *Validate) traverseField(topStruct reflect.Value, currentStruct reflect.
if cTag.tagVals[0][0] == omitempty { if cTag.tagVals[0][0] == omitempty {
if !hasValue(topStruct, currentStruct, current, typ, kind, "") { if !hasValue(v, topStruct, currentStruct, current, typ, kind, "") {
return return
} }
continue continue
@ -447,9 +499,9 @@ func (v *Validate) traverseField(topStruct reflect.Value, currentStruct reflect.
// or panic ;) // or panic ;)
switch kind { switch kind {
case reflect.Slice, reflect.Array: case reflect.Slice, reflect.Array:
v.traverseSlice(topStruct, currentStruct, current, errPrefix, errs, diveSubTag, name) v.traverseSlice(topStruct, currentStruct, current, errPrefix, errs, diveSubTag, name, partial, exclude, includeExclude)
case reflect.Map: case reflect.Map:
v.traverseMap(topStruct, currentStruct, current, errPrefix, errs, diveSubTag, name) v.traverseMap(topStruct, currentStruct, current, errPrefix, errs, diveSubTag, name, partial, exclude, includeExclude)
default: default:
// throw error, if not a slice or map then should not have gotten here // throw error, if not a slice or map then should not have gotten here
// bad dive tag // bad dive tag
@ -459,18 +511,18 @@ func (v *Validate) traverseField(topStruct reflect.Value, currentStruct reflect.
} }
// traverseSlice traverses a Slice or Array's elements and passes them to traverseField for validation // traverseSlice traverses a Slice or Array's elements and passes them to traverseField for validation
func (v *Validate) traverseSlice(topStruct reflect.Value, currentStruct reflect.Value, current reflect.Value, errPrefix string, errs ValidationErrors, tag string, name string) { func (v *Validate) traverseSlice(topStruct reflect.Value, currentStruct reflect.Value, current reflect.Value, errPrefix string, errs ValidationErrors, tag string, name string, partial bool, exclude bool, includeExclude map[string]*struct{}) {
for i := 0; i < current.Len(); i++ { for i := 0; i < current.Len(); i++ {
v.traverseField(topStruct, currentStruct, current.Index(i), errPrefix, errs, false, tag, fmt.Sprintf(arrayIndexFieldName, name, i)) v.traverseField(topStruct, currentStruct, current.Index(i), errPrefix, errs, false, tag, fmt.Sprintf(arrayIndexFieldName, name, i), partial, exclude, includeExclude)
} }
} }
// traverseMap traverses a map's elements and passes them to traverseField for validation // traverseMap traverses a map's elements and passes them to traverseField for validation
func (v *Validate) traverseMap(topStruct reflect.Value, currentStruct reflect.Value, current reflect.Value, errPrefix string, errs ValidationErrors, tag string, name string) { func (v *Validate) traverseMap(topStruct reflect.Value, currentStruct reflect.Value, current reflect.Value, errPrefix string, errs ValidationErrors, tag string, name string, partial bool, exclude bool, includeExclude map[string]*struct{}) {
for _, key := range current.MapKeys() { for _, key := range current.MapKeys() {
v.traverseField(topStruct, currentStruct, current.MapIndex(key), errPrefix, errs, false, tag, fmt.Sprintf(mapIndexFieldName, name, key.Interface())) v.traverseField(topStruct, currentStruct, current.MapIndex(key), errPrefix, errs, false, tag, fmt.Sprintf(mapIndexFieldName, name, key.Interface()), partial, exclude, includeExclude)
} }
} }
@ -491,7 +543,7 @@ func (v *Validate) validateField(topStruct reflect.Value, currentStruct reflect.
panic(strings.TrimSpace(fmt.Sprintf(undefinedValidation, name))) panic(strings.TrimSpace(fmt.Sprintf(undefinedValidation, name)))
} }
if valFunc(topStruct, currentStruct, current, currentType, currentKind, val[1]) { if valFunc(v, topStruct, currentStruct, current, currentType, currentKind, val[1]) {
return false return false
} }
@ -514,7 +566,7 @@ func (v *Validate) validateField(topStruct reflect.Value, currentStruct reflect.
panic(strings.TrimSpace(fmt.Sprintf(undefinedValidation, name))) panic(strings.TrimSpace(fmt.Sprintf(undefinedValidation, name)))
} }
if valFunc(topStruct, currentStruct, current, currentType, currentKind, cTag.tagVals[0][1]) { if valFunc(v, topStruct, currentStruct, current, currentType, currentKind, cTag.tagVals[0][1]) {
return false return false
} }

File diff suppressed because it is too large Load Diff
Loading…
Cancel
Save