working instance

pull/256/head
joeybloggs 8 years ago
parent 5f57d2222a
commit e0e1af6a61
  1. 32
      README.md
  2. 765
      baked_in.go
  3. 122
      benchmarks_test.go
  4. 56
      cache.go
  5. 11
      doc.go
  6. 202
      errors.go
  7. 162
      examples_test.go
  8. 50
      field_level.go
  9. 166
      struct_level.go
  10. 100
      util.go
  11. 756
      validator.go
  12. 442
      validator_instance.go
  13. 10827
      validator_test.go

@ -1,12 +1,12 @@
Package validator Package validator
================ ================
<img align="right" src="https://raw.githubusercontent.com/go-playground/validator/v8/logo.png"> <img align="right" src="https://raw.githubusercontent.com/go-playground/validator/v9/logo.png">
[![Join the chat at https://gitter.im/bluesuncorp/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) [![Join the chat at https://gitter.im/bluesuncorp/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-8.18.1-green.svg) ![Project status](https://img.shields.io/badge/alpha-9.0.0-red.svg)
[![Build Status](https://semaphoreci.com/api/v1/projects/ec20115f-ef1b-4c7d-9393-cc76aba74eb4/530054/badge.svg)](https://semaphoreci.com/joeybloggs/validator) [![Build Status](https://semaphoreci.com/api/v1/projects/ec20115f-ef1b-4c7d-9393-cc76aba74eb4/530054/badge.svg)](https://semaphoreci.com/joeybloggs/validator)
[![Coverage Status](https://coveralls.io/repos/go-playground/validator/badge.svg?branch=v8&service=github)](https://coveralls.io/github/go-playground/validator?branch=v8) [![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) [![Go Report Card](https://goreportcard.com/badge/github.com/go-playground/validator)](https://goreportcard.com/report/github.com/go-playground/validator)
[![GoDoc](https://godoc.org/gopkg.in/go-playground/validator.v8?status.svg)](https://godoc.org/gopkg.in/go-playground/validator.v8) [![GoDoc](https://godoc.org/gopkg.in/go-playground/validator.v9?status.svg)](https://godoc.org/gopkg.in/go-playground/validator.v8)
![License](https://img.shields.io/dub/l/vibe-d.svg) ![License](https://img.shields.io/dub/l/vibe-d.svg)
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.
@ -25,15 +25,15 @@ Installation
Use go get. Use go get.
go get gopkg.in/go-playground/validator.v8 go get gopkg.in/go-playground/validator.v9
or to update or to update
go get -u gopkg.in/go-playground/validator.v8 go get -u gopkg.in/go-playground/validator.v9
Then import the validator package into your own code. Then import the validator package into your own code.
import "gopkg.in/go-playground/validator.v8" import "gopkg.in/go-playground/validator.v9"
Error Return Value Error Return Value
------- -------
@ -57,7 +57,7 @@ validationErrors := err.(validator.ValidationErrors)
Usage and documentation Usage and documentation
------ ------
Please see http://godoc.org/gopkg.in/go-playground/validator.v8 for detailed usage docs. Please see http://godoc.org/gopkg.in/go-playground/validator.v9 for detailed usage docs.
##### Examples: ##### Examples:
@ -68,7 +68,7 @@ package main
import ( import (
"fmt" "fmt"
"gopkg.in/go-playground/validator.v8" "gopkg.in/go-playground/validator.v9"
) )
// User contains user information // User contains user information
@ -211,7 +211,7 @@ import (
"fmt" "fmt"
"reflect" "reflect"
"gopkg.in/go-playground/validator.v8" "gopkg.in/go-playground/validator.v9"
) )
// User contains user information // User contains user information
@ -346,21 +346,13 @@ Complimentary Software
Here is a list of software that compliments using this library either pre or post validation. Here is a list of software that compliments using this library either pre or post validation.
* [Gorilla Schema](https://github.com/gorilla/schema) - Package gorilla/schema fills a struct with form values. * [form](https://github.com/go-playground/form) - Decodes url.Values into Go value(s) and Encodes Go value(s) into url.Values. Dual Array and Full map support.
* [Conform](https://github.com/leebenson/conform) - Trims, sanitizes & scrubs data based on struct tags. * [Conform](https://github.com/leebenson/conform) - Trims, sanitizes & scrubs data based on struct tags.
How to Contribute How to Contribute
------ ------
There will always be a development branch for each version i.e. `v1-development`. In order to contribute, Make a pull request...
please make your pull requests against those branches.
If the changes being proposed or requested are breaking changes, please create an issue, for discussion
or create a pull request against the highest development branch for example this package has a
v1 and v1-development branch however, there will also be a v2-development branch even though v2 doesn't exist yet.
I strongly encourage everyone whom creates a custom validation function to contribute them and
help make this package even better.
License License
------ ------

File diff suppressed because it is too large Load Diff

@ -8,28 +8,38 @@ import (
func BenchmarkFieldSuccess(b *testing.B) { func BenchmarkFieldSuccess(b *testing.B) {
validate := New()
var s *string var s *string
tmp := "1" tmp := "1"
s = &tmp s = &tmp
b.ReportAllocs()
for n := 0; n < b.N; n++ { for n := 0; n < b.N; n++ {
validate.Field(s, "len=1") validate.Var(s, "len=1")
} }
} }
func BenchmarkFieldFailure(b *testing.B) { func BenchmarkFieldFailure(b *testing.B) {
validate := New()
var s *string var s *string
tmp := "12" tmp := "12"
s = &tmp s = &tmp
b.ReportAllocs()
for n := 0; n < b.N; n++ { for n := 0; n < b.N; n++ {
validate.Field(s, "len=1") validate.Var(s, "len=1")
} }
} }
func BenchmarkFieldDiveSuccess(b *testing.B) { func BenchmarkFieldDiveSuccess(b *testing.B) {
validate := New()
m := make([]*string, 3) m := make([]*string, 3)
t1 := "val1" t1 := "val1"
t2 := "val2" t2 := "val2"
@ -39,13 +49,17 @@ func BenchmarkFieldDiveSuccess(b *testing.B) {
m[1] = &t2 m[1] = &t2
m[2] = &t3 m[2] = &t3
b.ReportAllocs()
for n := 0; n < b.N; n++ { for n := 0; n < b.N; n++ {
validate.Field(m, "required,dive,required") validate.Var(m, "required,dive,required")
} }
} }
func BenchmarkFieldDiveFailure(b *testing.B) { func BenchmarkFieldDiveFailure(b *testing.B) {
validate := New()
m := make([]*string, 3) m := make([]*string, 3)
t1 := "val1" t1 := "val1"
t2 := "" t2 := ""
@ -55,65 +69,84 @@ func BenchmarkFieldDiveFailure(b *testing.B) {
m[1] = &t2 m[1] = &t2
m[2] = &t3 m[2] = &t3
b.ReportAllocs()
for n := 0; n < b.N; n++ { for n := 0; n < b.N; n++ {
validate.Field(m, "required,dive,required") validate.Var(m, "required,dive,required")
} }
} }
func BenchmarkFieldCustomTypeSuccess(b *testing.B) { func BenchmarkFieldCustomTypeSuccess(b *testing.B) {
validate := New()
validate.RegisterCustomTypeFunc(ValidateValuerType, (*sql.Valuer)(nil), valuer{}) validate.RegisterCustomTypeFunc(ValidateValuerType, (*sql.Valuer)(nil), valuer{})
val := valuer{ val := valuer{
Name: "1", Name: "1",
} }
b.ReportAllocs()
for n := 0; n < b.N; n++ { for n := 0; n < b.N; n++ {
validate.Field(val, "len=1") validate.Var(val, "len=1")
} }
} }
func BenchmarkFieldCustomTypeFailure(b *testing.B) { func BenchmarkFieldCustomTypeFailure(b *testing.B) {
validate := New()
validate.RegisterCustomTypeFunc(ValidateValuerType, (*sql.Valuer)(nil), valuer{}) validate.RegisterCustomTypeFunc(ValidateValuerType, (*sql.Valuer)(nil), valuer{})
val := valuer{} val := valuer{}
b.ReportAllocs()
for n := 0; n < b.N; n++ { for n := 0; n < b.N; n++ {
validate.Field(val, "len=1") validate.Var(val, "len=1")
} }
} }
func BenchmarkFieldOrTagSuccess(b *testing.B) { func BenchmarkFieldOrTagSuccess(b *testing.B) {
validate := New()
var s *string var s *string
tmp := "rgba(0,0,0,1)" tmp := "rgba(0,0,0,1)"
s = &tmp s = &tmp
b.ReportAllocs()
for n := 0; n < b.N; n++ { for n := 0; n < b.N; n++ {
validate.Field(s, "rgb|rgba") validate.Var(s, "rgb|rgba")
} }
} }
func BenchmarkFieldOrTagFailure(b *testing.B) { func BenchmarkFieldOrTagFailure(b *testing.B) {
validate := New()
var s *string var s *string
tmp := "#000" tmp := "#000"
s = &tmp s = &tmp
b.ReportAllocs()
for n := 0; n < b.N; n++ { for n := 0; n < b.N; n++ {
validate.Field(s, "rgb|rgba") validate.Var(s, "rgb|rgba")
} }
} }
func BenchmarkStructLevelValidationSuccess(b *testing.B) { func BenchmarkStructLevelValidationSuccess(b *testing.B) {
validate := New()
validate.RegisterStructValidation(StructValidationTestStructSuccess, TestStruct{}) validate.RegisterStructValidation(StructValidationTestStructSuccess, TestStruct{})
tst := &TestStruct{ tst := &TestStruct{
String: "good value", String: "good value",
} }
b.ReportAllocs()
for n := 0; n < b.N; n++ { for n := 0; n < b.N; n++ {
validate.Struct(tst) validate.Struct(tst)
} }
@ -121,12 +154,15 @@ func BenchmarkStructLevelValidationSuccess(b *testing.B) {
func BenchmarkStructLevelValidationFailure(b *testing.B) { func BenchmarkStructLevelValidationFailure(b *testing.B) {
validate := New()
validate.RegisterStructValidation(StructValidationTestStruct, TestStruct{}) validate.RegisterStructValidation(StructValidationTestStruct, TestStruct{})
tst := &TestStruct{ tst := &TestStruct{
String: "good value", String: "good value",
} }
b.ReportAllocs()
for n := 0; n < b.N; n++ { for n := 0; n < b.N; n++ {
validate.Struct(tst) validate.Struct(tst)
} }
@ -134,6 +170,7 @@ func BenchmarkStructLevelValidationFailure(b *testing.B) {
func BenchmarkStructSimpleCustomTypeSuccess(b *testing.B) { func BenchmarkStructSimpleCustomTypeSuccess(b *testing.B) {
validate := New()
validate.RegisterCustomTypeFunc(ValidateValuerType, (*sql.Valuer)(nil), valuer{}) validate.RegisterCustomTypeFunc(ValidateValuerType, (*sql.Valuer)(nil), valuer{})
val := valuer{ val := valuer{
@ -147,6 +184,8 @@ func BenchmarkStructSimpleCustomTypeSuccess(b *testing.B) {
validFoo := &Foo{Valuer: val, IntValue: 7} validFoo := &Foo{Valuer: val, IntValue: 7}
b.ReportAllocs()
for n := 0; n < b.N; n++ { for n := 0; n < b.N; n++ {
validate.Struct(validFoo) validate.Struct(validFoo)
} }
@ -154,6 +193,7 @@ func BenchmarkStructSimpleCustomTypeSuccess(b *testing.B) {
func BenchmarkStructSimpleCustomTypeFailure(b *testing.B) { func BenchmarkStructSimpleCustomTypeFailure(b *testing.B) {
validate := New()
validate.RegisterCustomTypeFunc(ValidateValuerType, (*sql.Valuer)(nil), valuer{}) validate.RegisterCustomTypeFunc(ValidateValuerType, (*sql.Valuer)(nil), valuer{})
val := valuer{} val := valuer{}
@ -165,6 +205,8 @@ func BenchmarkStructSimpleCustomTypeFailure(b *testing.B) {
validFoo := &Foo{Valuer: val, IntValue: 3} validFoo := &Foo{Valuer: val, IntValue: 3}
b.ReportAllocs()
for n := 0; n < b.N; n++ { for n := 0; n < b.N; n++ {
validate.Struct(validFoo) validate.Struct(validFoo)
} }
@ -172,6 +214,8 @@ func BenchmarkStructSimpleCustomTypeFailure(b *testing.B) {
func BenchmarkStructPartialSuccess(b *testing.B) { func BenchmarkStructPartialSuccess(b *testing.B) {
validate := New()
type Test struct { type Test struct {
Name string `validate:"required"` Name string `validate:"required"`
NickName string `validate:"required"` NickName string `validate:"required"`
@ -181,6 +225,8 @@ func BenchmarkStructPartialSuccess(b *testing.B) {
Name: "Joey Bloggs", Name: "Joey Bloggs",
} }
b.ReportAllocs()
for n := 0; n < b.N; n++ { for n := 0; n < b.N; n++ {
validate.StructPartial(test, "Name") validate.StructPartial(test, "Name")
} }
@ -188,6 +234,8 @@ func BenchmarkStructPartialSuccess(b *testing.B) {
func BenchmarkStructPartialFailure(b *testing.B) { func BenchmarkStructPartialFailure(b *testing.B) {
validate := New()
type Test struct { type Test struct {
Name string `validate:"required"` Name string `validate:"required"`
NickName string `validate:"required"` NickName string `validate:"required"`
@ -197,6 +245,8 @@ func BenchmarkStructPartialFailure(b *testing.B) {
Name: "Joey Bloggs", Name: "Joey Bloggs",
} }
b.ReportAllocs()
for n := 0; n < b.N; n++ { for n := 0; n < b.N; n++ {
validate.StructPartial(test, "NickName") validate.StructPartial(test, "NickName")
} }
@ -204,6 +254,8 @@ func BenchmarkStructPartialFailure(b *testing.B) {
func BenchmarkStructExceptSuccess(b *testing.B) { func BenchmarkStructExceptSuccess(b *testing.B) {
validate := New()
type Test struct { type Test struct {
Name string `validate:"required"` Name string `validate:"required"`
NickName string `validate:"required"` NickName string `validate:"required"`
@ -213,6 +265,8 @@ func BenchmarkStructExceptSuccess(b *testing.B) {
Name: "Joey Bloggs", Name: "Joey Bloggs",
} }
b.ReportAllocs()
for n := 0; n < b.N; n++ { for n := 0; n < b.N; n++ {
validate.StructPartial(test, "Nickname") validate.StructPartial(test, "Nickname")
} }
@ -220,6 +274,8 @@ func BenchmarkStructExceptSuccess(b *testing.B) {
func BenchmarkStructExceptFailure(b *testing.B) { func BenchmarkStructExceptFailure(b *testing.B) {
validate := New()
type Test struct { type Test struct {
Name string `validate:"required"` Name string `validate:"required"`
NickName string `validate:"required"` NickName string `validate:"required"`
@ -229,6 +285,8 @@ func BenchmarkStructExceptFailure(b *testing.B) {
Name: "Joey Bloggs", Name: "Joey Bloggs",
} }
b.ReportAllocs()
for n := 0; n < b.N; n++ { for n := 0; n < b.N; n++ {
validate.StructPartial(test, "Name") validate.StructPartial(test, "Name")
} }
@ -236,6 +294,8 @@ func BenchmarkStructExceptFailure(b *testing.B) {
func BenchmarkStructSimpleCrossFieldSuccess(b *testing.B) { func BenchmarkStructSimpleCrossFieldSuccess(b *testing.B) {
validate := New()
type Test struct { type Test struct {
Start time.Time Start time.Time
End time.Time `validate:"gtfield=Start"` End time.Time `validate:"gtfield=Start"`
@ -249,6 +309,8 @@ func BenchmarkStructSimpleCrossFieldSuccess(b *testing.B) {
End: then, End: then,
} }
b.ReportAllocs()
for n := 0; n < b.N; n++ { for n := 0; n < b.N; n++ {
validate.Struct(test) validate.Struct(test)
} }
@ -256,6 +318,8 @@ func BenchmarkStructSimpleCrossFieldSuccess(b *testing.B) {
func BenchmarkStructSimpleCrossFieldFailure(b *testing.B) { func BenchmarkStructSimpleCrossFieldFailure(b *testing.B) {
validate := New()
type Test struct { type Test struct {
Start time.Time Start time.Time
End time.Time `validate:"gtfield=Start"` End time.Time `validate:"gtfield=Start"`
@ -269,6 +333,8 @@ func BenchmarkStructSimpleCrossFieldFailure(b *testing.B) {
End: then, End: then,
} }
b.ReportAllocs()
for n := 0; n < b.N; n++ { for n := 0; n < b.N; n++ {
validate.Struct(test) validate.Struct(test)
} }
@ -276,6 +342,8 @@ func BenchmarkStructSimpleCrossFieldFailure(b *testing.B) {
func BenchmarkStructSimpleCrossStructCrossFieldSuccess(b *testing.B) { func BenchmarkStructSimpleCrossStructCrossFieldSuccess(b *testing.B) {
validate := New()
type Inner struct { type Inner struct {
Start time.Time Start time.Time
} }
@ -296,6 +364,8 @@ func BenchmarkStructSimpleCrossStructCrossFieldSuccess(b *testing.B) {
CreatedAt: now, CreatedAt: now,
} }
b.ReportAllocs()
for n := 0; n < b.N; n++ { for n := 0; n < b.N; n++ {
validate.Struct(outer) validate.Struct(outer)
} }
@ -303,6 +373,8 @@ func BenchmarkStructSimpleCrossStructCrossFieldSuccess(b *testing.B) {
func BenchmarkStructSimpleCrossStructCrossFieldFailure(b *testing.B) { func BenchmarkStructSimpleCrossStructCrossFieldFailure(b *testing.B) {
validate := New()
type Inner struct { type Inner struct {
Start time.Time Start time.Time
} }
@ -324,6 +396,8 @@ func BenchmarkStructSimpleCrossStructCrossFieldFailure(b *testing.B) {
CreatedAt: now, CreatedAt: now,
} }
b.ReportAllocs()
for n := 0; n < b.N; n++ { for n := 0; n < b.N; n++ {
validate.Struct(outer) validate.Struct(outer)
} }
@ -331,6 +405,8 @@ func BenchmarkStructSimpleCrossStructCrossFieldFailure(b *testing.B) {
func BenchmarkStructSimpleSuccess(b *testing.B) { func BenchmarkStructSimpleSuccess(b *testing.B) {
validate := New()
type Foo struct { type Foo struct {
StringValue string `validate:"min=5,max=10"` StringValue string `validate:"min=5,max=10"`
IntValue int `validate:"min=5,max=10"` IntValue int `validate:"min=5,max=10"`
@ -338,6 +414,8 @@ func BenchmarkStructSimpleSuccess(b *testing.B) {
validFoo := &Foo{StringValue: "Foobar", IntValue: 7} validFoo := &Foo{StringValue: "Foobar", IntValue: 7}
b.ReportAllocs()
for n := 0; n < b.N; n++ { for n := 0; n < b.N; n++ {
validate.Struct(validFoo) validate.Struct(validFoo)
} }
@ -345,6 +423,8 @@ func BenchmarkStructSimpleSuccess(b *testing.B) {
func BenchmarkStructSimpleFailure(b *testing.B) { func BenchmarkStructSimpleFailure(b *testing.B) {
validate := New()
type Foo struct { type Foo struct {
StringValue string `validate:"min=5,max=10"` StringValue string `validate:"min=5,max=10"`
IntValue int `validate:"min=5,max=10"` IntValue int `validate:"min=5,max=10"`
@ -352,6 +432,8 @@ func BenchmarkStructSimpleFailure(b *testing.B) {
invalidFoo := &Foo{StringValue: "Fo", IntValue: 3} invalidFoo := &Foo{StringValue: "Fo", IntValue: 3}
b.ReportAllocs()
for n := 0; n < b.N; n++ { for n := 0; n < b.N; n++ {
validate.Struct(invalidFoo) validate.Struct(invalidFoo)
} }
@ -359,6 +441,8 @@ func BenchmarkStructSimpleFailure(b *testing.B) {
func BenchmarkStructSimpleSuccessParallel(b *testing.B) { func BenchmarkStructSimpleSuccessParallel(b *testing.B) {
validate := New()
type Foo struct { type Foo struct {
StringValue string `validate:"min=5,max=10"` StringValue string `validate:"min=5,max=10"`
IntValue int `validate:"min=5,max=10"` IntValue int `validate:"min=5,max=10"`
@ -366,6 +450,8 @@ func BenchmarkStructSimpleSuccessParallel(b *testing.B) {
validFoo := &Foo{StringValue: "Foobar", IntValue: 7} validFoo := &Foo{StringValue: "Foobar", IntValue: 7}
b.ReportAllocs()
b.RunParallel(func(pb *testing.PB) { b.RunParallel(func(pb *testing.PB) {
for pb.Next() { for pb.Next() {
validate.Struct(validFoo) validate.Struct(validFoo)
@ -375,6 +461,8 @@ func BenchmarkStructSimpleSuccessParallel(b *testing.B) {
func BenchmarkStructSimpleFailureParallel(b *testing.B) { func BenchmarkStructSimpleFailureParallel(b *testing.B) {
validate := New()
type Foo struct { type Foo struct {
StringValue string `validate:"min=5,max=10"` StringValue string `validate:"min=5,max=10"`
IntValue int `validate:"min=5,max=10"` IntValue int `validate:"min=5,max=10"`
@ -382,6 +470,8 @@ func BenchmarkStructSimpleFailureParallel(b *testing.B) {
invalidFoo := &Foo{StringValue: "Fo", IntValue: 3} invalidFoo := &Foo{StringValue: "Fo", IntValue: 3}
b.ReportAllocs()
b.RunParallel(func(pb *testing.PB) { b.RunParallel(func(pb *testing.PB) {
for pb.Next() { for pb.Next() {
validate.Struct(invalidFoo) validate.Struct(invalidFoo)
@ -391,6 +481,8 @@ func BenchmarkStructSimpleFailureParallel(b *testing.B) {
func BenchmarkStructComplexSuccess(b *testing.B) { func BenchmarkStructComplexSuccess(b *testing.B) {
validate := New()
tSuccess := &TestString{ tSuccess := &TestString{
Required: "Required", Required: "Required",
Len: "length==10", Len: "length==10",
@ -418,6 +510,8 @@ func BenchmarkStructComplexSuccess(b *testing.B) {
}, },
} }
b.ReportAllocs()
for n := 0; n < b.N; n++ { for n := 0; n < b.N; n++ {
validate.Struct(tSuccess) validate.Struct(tSuccess)
} }
@ -425,6 +519,8 @@ func BenchmarkStructComplexSuccess(b *testing.B) {
func BenchmarkStructComplexFailure(b *testing.B) { func BenchmarkStructComplexFailure(b *testing.B) {
validate := New()
tFail := &TestString{ tFail := &TestString{
Required: "", Required: "",
Len: "", Len: "",
@ -449,6 +545,8 @@ func BenchmarkStructComplexFailure(b *testing.B) {
}, },
} }
b.ReportAllocs()
for n := 0; n < b.N; n++ { for n := 0; n < b.N; n++ {
validate.Struct(tFail) validate.Struct(tFail)
} }
@ -456,6 +554,8 @@ func BenchmarkStructComplexFailure(b *testing.B) {
func BenchmarkStructComplexSuccessParallel(b *testing.B) { func BenchmarkStructComplexSuccessParallel(b *testing.B) {
validate := New()
tSuccess := &TestString{ tSuccess := &TestString{
Required: "Required", Required: "Required",
Len: "length==10", Len: "length==10",
@ -483,6 +583,8 @@ func BenchmarkStructComplexSuccessParallel(b *testing.B) {
}, },
} }
b.ReportAllocs()
b.RunParallel(func(pb *testing.PB) { b.RunParallel(func(pb *testing.PB) {
for pb.Next() { for pb.Next() {
validate.Struct(tSuccess) validate.Struct(tSuccess)
@ -492,6 +594,8 @@ func BenchmarkStructComplexSuccessParallel(b *testing.B) {
func BenchmarkStructComplexFailureParallel(b *testing.B) { func BenchmarkStructComplexFailureParallel(b *testing.B) {
validate := New()
tFail := &TestString{ tFail := &TestString{
Required: "", Required: "",
Len: "", Len: "",
@ -516,6 +620,8 @@ func BenchmarkStructComplexFailureParallel(b *testing.B) {
}, },
} }
b.ReportAllocs()
b.RunParallel(func(pb *testing.PB) { b.RunParallel(func(pb *testing.PB) {
for pb.Next() { for pb.Next() {
validate.Struct(tFail) validate.Struct(tFail)

@ -2,6 +2,7 @@ package validator
import ( import (
"fmt" "fmt"
"log"
"reflect" "reflect"
"strings" "strings"
"sync" "sync"
@ -20,6 +21,15 @@ const (
typeExists typeExists
) )
const (
invalidValidation = "Invalid validation tag on field %s"
undefinedValidation = "Undefined validation function on field %s"
)
// var (
// validatable = reflect.ValueOf((*Validatable)(nil)).Elem()
// )
type structCache struct { type structCache struct {
lock sync.Mutex lock sync.Mutex
m atomic.Value // map[reflect.Type]*cStruct m atomic.Value // map[reflect.Type]*cStruct
@ -105,6 +115,15 @@ func (v *Validate) extractStructCache(current reflect.Value, sName string) *cStr
cs = &cStruct{Name: sName, fields: make(map[int]*cField), fn: v.structLevelFuncs[typ]} cs = &cStruct{Name: sName, fields: make(map[int]*cField), fn: v.structLevelFuncs[typ]}
if vable, ok := reflect.PtrTo(typ).(Validatable); ok {
if cs.fn != nil {
log.Println("warning: struct level validation overriding 'Validatabe' interface function")
} else {
cs.fn = vable.Validate
}
}
numFields := current.NumField() numFields := current.NumField()
var ctag *cTag var ctag *cTag
@ -116,7 +135,7 @@ func (v *Validate) extractStructCache(current reflect.Value, sName string) *cStr
fld = typ.Field(i) fld = typ.Field(i)
if !fld.Anonymous && fld.PkgPath != blank { if !fld.Anonymous && len(fld.PkgPath) > 0 {
continue continue
} }
@ -128,12 +147,11 @@ func (v *Validate) extractStructCache(current reflect.Value, sName string) *cStr
customName = fld.Name customName = fld.Name
if v.fieldNameTag != blank { if v.hasTagNameFunc {
name := strings.SplitN(fld.Tag.Get(v.fieldNameTag), ",", 2)[0] name := v.tagNameFunc(fld)
// dash check is for json "-" (aka skipValidationTag) means don't output in json if len(name) > 0 {
if name != "" && name != skipValidationTag {
customName = name customName = name
} }
} }
@ -142,7 +160,7 @@ func (v *Validate) extractStructCache(current reflect.Value, sName string) *cStr
// and so only struct level caching can be used instead of combined with Field tag caching // and so only struct level caching can be used instead of combined with Field tag caching
if len(tag) > 0 { if len(tag) > 0 {
ctag, _ = v.parseFieldTagsRecursive(tag, fld.Name, blank, false) ctag, _ = v.parseFieldTagsRecursive(tag, fld.Name, "", false)
} else { } else {
// even if field doesn't have validations need cTag for traversing to potential inner/nested // even if field doesn't have validations need cTag for traversing to potential inner/nested
// elements of the field. // elements of the field.
@ -172,20 +190,18 @@ func (v *Validate) parseFieldTagsRecursive(tag string, fieldName string, alias s
alias = t alias = t
} }
if v.hasAliasValidators { // check map for alias and process new tags, otherwise process as usual
// check map for alias and process new tags, otherwise process as usual if tagsVal, found := v.aliases[t]; found {
if tagsVal, found := v.aliasValidators[t]; found {
if i == 0 {
firstCtag, current = v.parseFieldTagsRecursive(tagsVal, fieldName, t, true)
} else {
next, curr := v.parseFieldTagsRecursive(tagsVal, fieldName, t, true)
current.next, current = next, curr
} if i == 0 {
firstCtag, current = v.parseFieldTagsRecursive(tagsVal, fieldName, t, true)
} else {
next, curr := v.parseFieldTagsRecursive(tagsVal, fieldName, t, true)
current.next, current = next, curr
continue
} }
continue
} }
if i == 0 { if i == 0 {
@ -214,10 +230,6 @@ func (v *Validate) parseFieldTagsRecursive(tag string, fieldName string, alias s
current.typeof = typeNoStructLevel current.typeof = typeNoStructLevel
continue continue
case existsTag:
current.typeof = typeExists
continue
default: default:
// if a pipe character is needed within the param you must use the utf8Pipe representation "0x7C" // if a pipe character is needed within the param you must use the utf8Pipe representation "0x7C"
@ -244,7 +256,7 @@ func (v *Validate) parseFieldTagsRecursive(tag string, fieldName string, alias s
panic(strings.TrimSpace(fmt.Sprintf(invalidValidation, fieldName))) panic(strings.TrimSpace(fmt.Sprintf(invalidValidation, fieldName)))
} }
if current.fn, ok = v.validationFuncs[current.tag]; !ok { if current.fn, ok = v.validations[current.tag]; !ok {
panic(strings.TrimSpace(fmt.Sprintf(undefinedValidation, fieldName))) panic(strings.TrimSpace(fmt.Sprintf(undefinedValidation, fieldName)))
} }

@ -192,17 +192,6 @@ Same as structonly tag except that any struct level validations will not run.
Usage: nostructlevel Usage: nostructlevel
Exists
Is a special tag without a validation function attached. It is used when a field
is a Pointer, Interface or Invalid and you wish to validate that it exists.
Example: want to ensure a bool exists if you define the bool as a pointer and
use exists it will ensure there is a value; couldn't use required as it would
fail when the bool was false. exists will fail is the value is a Pointer, Interface
or Invalid and is nil.
Usage: exists
Omit Empty Omit Empty
Allows conditional validation, for example if a field is not set with Allows conditional validation, for example if a field is not set with

@ -0,0 +1,202 @@
package validator
import (
"bytes"
"fmt"
"reflect"
"strings"
)
const (
fieldErrMsg = "Key: '%s' Error:Field validation for '%s' failed on the '%s' tag"
)
// InvalidValidationError describes an invalid argument passed to
// `Struct`, `StructExcept`, StructPartial` or `Field`
type InvalidValidationError struct {
Type reflect.Type
}
// Error returns InvalidValidationError message
func (e *InvalidValidationError) Error() string {
if e.Type == nil {
return "validator: (nil)"
}
return "validator: (nil " + e.Type.String() + ")"
}
// ValidationErrors is an array of FieldError's
// for use in custom error messages post validation.
type ValidationErrors []FieldError
// Error is intended for use in development + debugging and not intended to be a production error message.
// It allows ValidationErrors to subscribe to the Error interface.
// All information to create an error message specific to your application is contained within
// the FieldError found within the ValidationErrors array
func (ve ValidationErrors) Error() string {
buff := bytes.NewBufferString("")
var err *fieldError
for i := 0; i < len(ve); i++ {
err = ve[i].(*fieldError)
buff.WriteString(err.Error())
buff.WriteString("\n")
}
return strings.TrimSpace(buff.String())
}
// FieldError contains all functions to get error details
type FieldError interface {
// returns the validation tag that failed. if the
// validation was an alias, this will return the
// alias name and not the underlying tag that failed.
//
// eg. alias "iscolor": "hexcolor|rgb|rgba|hsl|hsla"
// will return "iscolor"
Tag() string
// returns the validation tag that failed, even if an
// alias the actual tag within the alias will be returned.
// If an 'or' validation fails the entire or will be returned.
//
// eg. alias "iscolor": "hexcolor|rgb|rgba|hsl|hsla"
// will return "hexcolor|rgb|rgba|hsl|hsla"
ActualTag() string
// returns the namespace for the field error, with the tag
// name taking precedence over the fields actual name.
//
// eq. JSON name "User.fname" see ActualNamespace for comparison
//
// NOTE: this field can be blank when validating a single primitive field
// using validate.Field(...) as there is no way to extract it's name
Namespace() string
// returns the namespace for the field error, with the fields
// actual name.
//
// eq. "User.FirstName" see Namespace for comparison
//
// NOTE: this field can be blank when validating a single primitive field
// using validate.Field(...) as there is no way to extract it's name
ActualNamespace() string
// returns the fields name with the tag name taking precedence over the
// fields actual name.
//
// eq. JSON name "fname"
// see ActualField for comparison
Field() string
// returns the fields actual name.
//
// eq. "FirstName"
// see Field for comparison
ActualField() string
// returns the actual fields value in case needed for creating the error
// message
Value() interface{}
// returns the param value, already converted into the fields type for
// comparison; this will also help with generating an error message
Param() interface{}
// Kind returns the Field's reflect Kind
//
// eg. time.Time's kind is a struct
Kind() reflect.Kind
// Type returns the Field's reflect Type
//
// // eg. time.Time's type is time.Time
Type() reflect.Type
}
// compile time interface checks
var _ FieldError = new(fieldError)
var _ error = new(fieldError)
// fieldError contains a single field's validation error along
// with other properties that may be needed for error message creation
// it complies with the FieldError interface
type fieldError struct {
tag string
actualTag string
ns string
actualNs string
field string
actualField string
value interface{}
param interface{}
kind reflect.Kind
typ reflect.Type
}
// Tag returns the validation tag that failed.
func (fe *fieldError) Tag() string {
return fe.tag
}
// ActualTag returns the validation tag that failed, even if an
// alias the actual tag within the alias will be returned.
func (fe *fieldError) ActualTag() string {
return fe.actualTag
}
// Namespace returns the namespace for the field error, with the tag
// name taking precedence over the fields actual name.
func (fe *fieldError) Namespace() string {
return fe.ns
}
// ActualNamespace returns the namespace for the field error, with the fields
// actual name.
func (fe *fieldError) ActualNamespace() string {
return fe.actualNs
}
// Field returns the fields name with the tag name taking precedence over the
// fields actual name.
func (fe *fieldError) Field() string {
return fe.field
}
// ActualField returns the fields actual name.
func (fe *fieldError) ActualField() string {
return fe.actualField
}
// Value returns the actual fields value in case needed for creating the error
// message
func (fe *fieldError) Value() interface{} {
return fe.value
}
// Param returns the param value, already converted into the fields type for
// comparison; this will also help with generating an error message
func (fe *fieldError) Param() interface{} {
return fe.param
}
// Kind returns the Field's reflect Kind
func (fe *fieldError) Kind() reflect.Kind {
return fe.kind
}
// Type returns the Field's reflect Type
func (fe *fieldError) Type() reflect.Type {
return fe.typ
}
// Error returns the fieldError's error message
func (fe *fieldError) Error() string {
return fmt.Sprintf(fieldErrMsg, fe.ns, fe.field, fe.tag)
}

@ -1,83 +1,83 @@
package validator_test package validator_test
import ( // import (
"fmt" // "fmt"
"gopkg.in/go-playground/validator.v8" // "gopkg.in/go-playground/validator.v8"
) // )
func ExampleValidate_new() { // func ExampleValidate_new() {
config := &validator.Config{TagName: "validate"} // config := &validator.Config{TagName: "validate"}
validator.New(config) // validator.New(config)
} // }
func ExampleValidate_field() { // func ExampleValidate_field() {
// This should be stored somewhere globally // // This should be stored somewhere globally
var validate *validator.Validate // var validate *validator.Validate
config := &validator.Config{TagName: "validate"} // config := &validator.Config{TagName: "validate"}
validate = validator.New(config) // validate = validator.New(config)
i := 0 // i := 0
errs := validate.Field(i, "gt=1,lte=10") // errs := validate.Field(i, "gt=1,lte=10")
err := errs.(validator.ValidationErrors)[""] // err := errs.(validator.ValidationErrors)[""]
fmt.Println(err.Field) // fmt.Println(err.Field)
fmt.Println(err.Tag) // fmt.Println(err.Tag)
fmt.Println(err.Kind) // NOTE: Kind and Type can be different i.e. time Kind=struct and Type=time.Time // fmt.Println(err.Kind) // NOTE: Kind and Type can be different i.e. time Kind=struct and Type=time.Time
fmt.Println(err.Type) // fmt.Println(err.Type)
fmt.Println(err.Param) // fmt.Println(err.Param)
fmt.Println(err.Value) // fmt.Println(err.Value)
//Output: // //Output:
// // //
//gt // //gt
//int // //int
//int // //int
//1 // //1
//0 // //0
} // }
func ExampleValidate_struct() { // func ExampleValidate_struct() {
// This should be stored somewhere globally // // This should be stored somewhere globally
var validate *validator.Validate // var validate *validator.Validate
config := &validator.Config{TagName: "validate"} // config := &validator.Config{TagName: "validate"}
validate = validator.New(config) // validate = validator.New(config)
type ContactInformation struct { // type ContactInformation struct {
Phone string `validate:"required"` // Phone string `validate:"required"`
Street string `validate:"required"` // Street string `validate:"required"`
City string `validate:"required"` // City string `validate:"required"`
} // }
type User struct { // type User struct {
Name string `validate:"required,excludesall=!@#$%^&*()_+-=:;?/0x2C"` // 0x2C = comma (,) // Name string `validate:"required,excludesall=!@#$%^&*()_+-=:;?/0x2C"` // 0x2C = comma (,)
Age int8 `validate:"required,gt=0,lt=150"` // Age int8 `validate:"required,gt=0,lt=150"`
Email string `validate:"email"` // Email string `validate:"email"`
ContactInformation []*ContactInformation // ContactInformation []*ContactInformation
} // }
contactInfo := &ContactInformation{ // contactInfo := &ContactInformation{
Street: "26 Here Blvd.", // Street: "26 Here Blvd.",
City: "Paradeso", // City: "Paradeso",
} // }
user := &User{ // user := &User{
Name: "Joey Bloggs", // Name: "Joey Bloggs",
Age: 31, // Age: 31,
Email: "joeybloggs@gmail.com", // Email: "joeybloggs@gmail.com",
ContactInformation: []*ContactInformation{contactInfo}, // ContactInformation: []*ContactInformation{contactInfo},
} // }
errs := validate.Struct(user) // errs := validate.Struct(user)
for _, v := range errs.(validator.ValidationErrors) { // for _, v := range errs.(validator.ValidationErrors) {
fmt.Println(v.Field) // Phone // fmt.Println(v.Field) // Phone
fmt.Println(v.Tag) // required // fmt.Println(v.Tag) // required
//... and so forth // //... and so forth
//Output: // //Output:
//Phone // //Phone
//required // //required
} // }
} // }

@ -0,0 +1,50 @@
package validator
import "reflect"
// FieldLevel contains all the information and helper functions
// to validate a field
type FieldLevel interface {
// returns the top level struct, if any
Top() reflect.Value
// returns the current fields parent struct, if any
Parent() reflect.Value
// returns current field for validation
Field() reflect.Value
// returns param for validation against current field
Param() string
// ExtractType gets the actual underlying type of field value.
// It will dive into pointers, customTypes and return you the
// underlying value and it's kind.
ExtractType(field reflect.Value) (value reflect.Value, kind reflect.Kind, nullable bool)
// traverses the parent struct to retrieve a specific field denoted by the provided namespace
// in the param and returns the field, field kind and whether is was successful in retrieving
// the field at all.
//
// NOTE: when not successful ok will be false, this can happen when a nested struct is nil and so the field
// could not be retrieved because it didn't exist.
GetStructFieldOK() (reflect.Value, reflect.Kind, bool)
}
var _ FieldLevel = new(validate)
// Field returns current field for validation
func (v *validate) Field() reflect.Value {
return v.flField
}
// Param returns param for validation against current field
func (v *validate) Param() string {
return v.flParam
}
// GetStructFieldOK returns Param returns param for validation against current field
func (v *validate) GetStructFieldOK() (reflect.Value, reflect.Kind, bool) {
return v.getStructFieldOKInternal(v.slflParent, v.flParam)
}

@ -0,0 +1,166 @@
package validator
import "reflect"
// StructLevelFunc accepts all values needed for struct level validation
type StructLevelFunc func(sl StructLevel)
// StructLevel contains all the information and helper functions
// to validate a struct
type StructLevel interface {
// returns the main validation object, in case one want to call validations internally.
Validator() *Validate
// returns the top level struct, if any
Top() reflect.Value
// returns the current fields parent struct, if any
Parent() reflect.Value
// returns the current struct.
// this is not needed when implementing 'Validatable' interface,
// only when a StructLevel is registered
Current() reflect.Value
// ExtractType gets the actual underlying type of field value.
// It will dive into pointers, customTypes and return you the
// underlying value and it's kind.
ExtractType(field reflect.Value) (value reflect.Value, kind reflect.Kind, nullable bool)
// reports an error just by passing the field and tag information
//
// NOTES:
//
// fieldName and altName get appended to the existing namespace that
// validator is on. eg. pass 'FirstName' or 'Names[0]' depending
// on the nesting
//
// tag can be an existing validation tag or just something you make up
// and process on the flip side it's up to you.
ReportError(field interface{}, fieldName, altName, tag string)
// reports an error just by passing ValidationErrors
//
// NOTES:
//
// relativeNamespace and relativeActualNamespace get appended to the
// existing namespace that validator is on.
// eg. pass 'User.FirstName' or 'Users[0].FirstName' depending
// on the nesting. most of the time they will be blank, unless you validate
// at a level lower the the current field depth
//
// tag can be an existing validation tag or just something you make up
// and process on the flip side it's up to you.
ReportValidationErrors(relativeNamespace, relativeActualNamespace string, errs ValidationErrors)
}
var _ StructLevel = new(validate)
// Top returns the top level struct
//
// NOTE: this can be the same as the current struct being validated
// if not is a nested struct.
//
// this is only called when within Struct and Field Level validation and
// should not be relied upon for an acurate value otherwise.
func (v *validate) Top() reflect.Value {
return v.top
}
// Parent returns the current structs parent
//
// NOTE: this can be the same as the current struct being validated
// if not is a nested struct.
//
// this is only called when within Struct and Field Level validation and
// should not be relied upon for an acurate value otherwise.
func (v *validate) Parent() reflect.Value {
return v.slflParent
}
// Current returns the current struct.
func (v *validate) Current() reflect.Value {
return v.slCurrent
}
// Validator returns the main validation object, in case one want to call validations internally.
func (v *validate) Validator() *Validate {
return v.v
}
// ExtractType gets the actual underlying type of field value.
func (v *validate) ExtractType(field reflect.Value) (reflect.Value, reflect.Kind, bool) {
return v.extractTypeInternal(field, false)
}
// ReportError reports an error just by passing the field and tag information
func (v *validate) ReportError(field interface{}, fieldName, altName, tag string) {
fv, kind, _ := v.extractTypeInternal(reflect.ValueOf(field), false)
if len(altName) == 0 {
altName = fieldName
}
ns := append(v.slNs, fieldName...)
nsActual := append(v.slActualNs, altName...)
switch kind {
case reflect.Invalid:
v.errs = append(v.errs,
&fieldError{
tag: tag,
actualTag: tag,
ns: string(ns),
actualNs: string(nsActual),
field: fieldName,
actualField: altName,
param: "",
kind: kind,
},
)
default:
v.errs = append(v.errs,
&fieldError{
tag: tag,
actualTag: tag,
ns: string(ns),
actualNs: string(nsActual),
field: fieldName,
actualField: altName,
value: fv.Interface(),
param: "",
kind: kind,
typ: fv.Type(),
},
)
}
}
// ReportValidationErrors reports ValidationErrors obtained from running validations within the Struct Level validation.
//
// NOTE: this function prepends the current namespace to the relative ones.
func (v *validate) ReportValidationErrors(relativeNamespace, relativeActualNamespace string, errs ValidationErrors) {
var err *fieldError
for i := 0; i < len(errs); i++ {
err = errs[i].(*fieldError)
err.ns = string(append(append(v.slNs, err.ns...), err.field...))
err.actualNs = string(append(append(v.slActualNs, err.actualNs...), err.actualField...))
v.errs = append(v.errs, err)
}
}
// Validatable is the interface a struct can implement and
// be validated just like registering a StructLevel validation
// (they actually have the exact same signature.)
type Validatable interface {
Validate(sl StructLevel)
}

@ -6,41 +6,26 @@ import (
"strings" "strings"
) )
const ( // import (
blank = "" // "reflect"
namespaceSeparator = "." // "strconv"
leftBracket = "[" // "strings"
rightBracket = "]" // )
restrictedTagChars = ".[],|=+()`~!@#$%^&*\\\"/?<>{}"
restrictedAliasErr = "Alias '%s' either contains restricted characters or is the same as a restricted tag needed for normal operation" // const (
restrictedTagErr = "Tag '%s' either contains restricted characters or is the same as a restricted tag needed for normal operation" // blank = ""
) // namespaceSeparator = "."
// leftBracket = "["
var ( // rightBracket = "]"
restrictedTags = map[string]struct{}{ // restrictedTagChars = ".[],|=+()`~!@#$%^&*\\\"/?<>{}"
diveTag: {}, // restrictedAliasErr = "Alias '%s' either contains restricted characters or is the same as a restricted tag needed for normal operation"
existsTag: {}, // restrictedTagErr = "Tag '%s' either contains restricted characters or is the same as a restricted tag needed for normal operation"
structOnlyTag: {}, // )
omitempty: {},
skipValidationTag: {}, // extractTypeInternal gets the actual underlying type of field value.
utf8HexComma: {},
utf8Pipe: {},
noStructLevelTag: {},
}
)
// ExtractType gets the actual underlying type of field value.
// It will dive into pointers, customTypes and return you the // It will dive into pointers, customTypes and return you the
// underlying value and it's kind. // underlying value and it's kind.
// it is exposed for use within you Custom Functions func (v *validate) extractTypeInternal(current reflect.Value, nullable bool) (reflect.Value, reflect.Kind, bool) {
func (v *Validate) ExtractType(current reflect.Value) (reflect.Value, reflect.Kind) {
val, k, _ := v.extractTypeInternal(current, false)
return val, k
}
// only exists to not break backward compatibility, needed to return the third param for a bug fix internally
func (v *Validate) extractTypeInternal(current reflect.Value, nullable bool) (reflect.Value, reflect.Kind, bool) {
switch current.Kind() { switch current.Kind() {
case reflect.Ptr: case reflect.Ptr:
@ -68,9 +53,9 @@ func (v *Validate) extractTypeInternal(current reflect.Value, nullable bool) (re
default: default:
if v.hasCustomFuncs { if v.v.hasCustomFuncs {
if fn, ok := v.customTypeFuncs[current.Type()]; ok { if fn, ok := v.v.customFuncs[current.Type()]; ok {
return v.extractTypeInternal(reflect.ValueOf(fn(current)), nullable) return v.extractTypeInternal(reflect.ValueOf(fn(current)), nullable)
} }
} }
@ -79,19 +64,20 @@ func (v *Validate) extractTypeInternal(current reflect.Value, nullable bool) (re
} }
} }
// GetStructFieldOK traverses a struct to retrieve a specific field denoted by the provided namespace and // getStructFieldOKInternal traverses a struct to retrieve a specific field denoted by the provided namespace and
// returns the field, field kind and whether is was successful in retrieving the field at all. // returns the field, field kind and whether is was successful in retrieving the field at all.
//
// NOTE: when not successful ok will be false, this can happen when a nested struct is nil and so the field // NOTE: when not successful ok will be false, this can happen when a nested struct is nil and so the field
// could not be retrieved because it didn't exist. // could not be retrieved because it didn't exist.
func (v *Validate) GetStructFieldOK(current reflect.Value, namespace string) (reflect.Value, reflect.Kind, bool) { func (v *validate) getStructFieldOKInternal(current reflect.Value, namespace string) (reflect.Value, reflect.Kind, bool) {
current, kind := v.ExtractType(current) current, kind, _ := v.ExtractType(current)
if kind == reflect.Invalid { if kind == reflect.Invalid {
return current, kind, false return current, kind, false
} }
if namespace == blank { if namespace == "" {
return current, kind, true return current, kind, true
} }
@ -107,7 +93,7 @@ func (v *Validate) GetStructFieldOK(current reflect.Value, namespace string) (re
fld := namespace fld := namespace
ns := namespace ns := namespace
if typ != timeType && typ != timePtrType { if typ != timeType {
idx := strings.Index(namespace, namespaceSeparator) idx := strings.Index(namespace, namespaceSeparator)
@ -115,7 +101,7 @@ func (v *Validate) GetStructFieldOK(current reflect.Value, namespace string) (re
fld = namespace[:idx] fld = namespace[:idx]
ns = namespace[idx+1:] ns = namespace[idx+1:]
} else { } else {
ns = blank ns = ""
} }
bracketIdx := strings.Index(fld, leftBracket) bracketIdx := strings.Index(fld, leftBracket)
@ -127,7 +113,7 @@ func (v *Validate) GetStructFieldOK(current reflect.Value, namespace string) (re
current = current.FieldByName(fld) current = current.FieldByName(fld)
return v.GetStructFieldOK(current, ns) return v.getStructFieldOKInternal(current, ns)
} }
case reflect.Array, reflect.Slice: case reflect.Array, reflect.Slice:
@ -148,7 +134,7 @@ func (v *Validate) GetStructFieldOK(current reflect.Value, namespace string) (re
} }
} }
return v.GetStructFieldOK(current.Index(arrIdx), namespace[startIdx:]) return v.getStructFieldOKInternal(current.Index(arrIdx), namespace[startIdx:])
case reflect.Map: case reflect.Map:
idx := strings.Index(namespace, leftBracket) + 1 idx := strings.Index(namespace, leftBracket) + 1
@ -167,47 +153,47 @@ func (v *Validate) GetStructFieldOK(current reflect.Value, namespace string) (re
switch current.Type().Key().Kind() { switch current.Type().Key().Kind() {
case reflect.Int: case reflect.Int:
i, _ := strconv.Atoi(key) i, _ := strconv.Atoi(key)
return v.GetStructFieldOK(current.MapIndex(reflect.ValueOf(i)), namespace[endIdx+1:]) return v.getStructFieldOKInternal(current.MapIndex(reflect.ValueOf(i)), namespace[endIdx+1:])
case reflect.Int8: case reflect.Int8:
i, _ := strconv.ParseInt(key, 10, 8) i, _ := strconv.ParseInt(key, 10, 8)
return v.GetStructFieldOK(current.MapIndex(reflect.ValueOf(int8(i))), namespace[endIdx+1:]) return v.getStructFieldOKInternal(current.MapIndex(reflect.ValueOf(int8(i))), namespace[endIdx+1:])
case reflect.Int16: case reflect.Int16:
i, _ := strconv.ParseInt(key, 10, 16) i, _ := strconv.ParseInt(key, 10, 16)
return v.GetStructFieldOK(current.MapIndex(reflect.ValueOf(int16(i))), namespace[endIdx+1:]) return v.getStructFieldOKInternal(current.MapIndex(reflect.ValueOf(int16(i))), namespace[endIdx+1:])
case reflect.Int32: case reflect.Int32:
i, _ := strconv.ParseInt(key, 10, 32) i, _ := strconv.ParseInt(key, 10, 32)
return v.GetStructFieldOK(current.MapIndex(reflect.ValueOf(int32(i))), namespace[endIdx+1:]) return v.getStructFieldOKInternal(current.MapIndex(reflect.ValueOf(int32(i))), namespace[endIdx+1:])
case reflect.Int64: case reflect.Int64:
i, _ := strconv.ParseInt(key, 10, 64) i, _ := strconv.ParseInt(key, 10, 64)
return v.GetStructFieldOK(current.MapIndex(reflect.ValueOf(i)), namespace[endIdx+1:]) return v.getStructFieldOKInternal(current.MapIndex(reflect.ValueOf(i)), namespace[endIdx+1:])
case reflect.Uint: case reflect.Uint:
i, _ := strconv.ParseUint(key, 10, 0) i, _ := strconv.ParseUint(key, 10, 0)
return v.GetStructFieldOK(current.MapIndex(reflect.ValueOf(uint(i))), namespace[endIdx+1:]) return v.getStructFieldOKInternal(current.MapIndex(reflect.ValueOf(uint(i))), namespace[endIdx+1:])
case reflect.Uint8: case reflect.Uint8:
i, _ := strconv.ParseUint(key, 10, 8) i, _ := strconv.ParseUint(key, 10, 8)
return v.GetStructFieldOK(current.MapIndex(reflect.ValueOf(uint8(i))), namespace[endIdx+1:]) return v.getStructFieldOKInternal(current.MapIndex(reflect.ValueOf(uint8(i))), namespace[endIdx+1:])
case reflect.Uint16: case reflect.Uint16:
i, _ := strconv.ParseUint(key, 10, 16) i, _ := strconv.ParseUint(key, 10, 16)
return v.GetStructFieldOK(current.MapIndex(reflect.ValueOf(uint16(i))), namespace[endIdx+1:]) return v.getStructFieldOKInternal(current.MapIndex(reflect.ValueOf(uint16(i))), namespace[endIdx+1:])
case reflect.Uint32: case reflect.Uint32:
i, _ := strconv.ParseUint(key, 10, 32) i, _ := strconv.ParseUint(key, 10, 32)
return v.GetStructFieldOK(current.MapIndex(reflect.ValueOf(uint32(i))), namespace[endIdx+1:]) return v.getStructFieldOKInternal(current.MapIndex(reflect.ValueOf(uint32(i))), namespace[endIdx+1:])
case reflect.Uint64: case reflect.Uint64:
i, _ := strconv.ParseUint(key, 10, 64) i, _ := strconv.ParseUint(key, 10, 64)
return v.GetStructFieldOK(current.MapIndex(reflect.ValueOf(i)), namespace[endIdx+1:]) return v.getStructFieldOKInternal(current.MapIndex(reflect.ValueOf(i)), namespace[endIdx+1:])
case reflect.Float32: case reflect.Float32:
f, _ := strconv.ParseFloat(key, 32) f, _ := strconv.ParseFloat(key, 32)
return v.GetStructFieldOK(current.MapIndex(reflect.ValueOf(float32(f))), namespace[endIdx+1:]) return v.getStructFieldOKInternal(current.MapIndex(reflect.ValueOf(float32(f))), namespace[endIdx+1:])
case reflect.Float64: case reflect.Float64:
f, _ := strconv.ParseFloat(key, 64) f, _ := strconv.ParseFloat(key, 64)
return v.GetStructFieldOK(current.MapIndex(reflect.ValueOf(f)), namespace[endIdx+1:]) return v.getStructFieldOKInternal(current.MapIndex(reflect.ValueOf(f)), namespace[endIdx+1:])
case reflect.Bool: case reflect.Bool:
b, _ := strconv.ParseBool(key) b, _ := strconv.ParseBool(key)
return v.GetStructFieldOK(current.MapIndex(reflect.ValueOf(b)), namespace[endIdx+1:]) return v.getStructFieldOKInternal(current.MapIndex(reflect.ValueOf(b)), namespace[endIdx+1:])
// reflect.Type = string // reflect.Type = string
default: default:
return v.GetStructFieldOK(current.MapIndex(reflect.ValueOf(key)), namespace[endIdx+1:]) return v.getStructFieldOKInternal(current.MapIndex(reflect.ValueOf(key)), namespace[endIdx+1:])
} }
} }

@ -1,580 +1,95 @@
/**
* Package validator
*
* MISC:
* - anonymous structs - they don't have names so expect the Struct name within StructErrors to be blank
*
*/
package validator package validator
import ( import (
"bytes"
"errors"
"fmt" "fmt"
"reflect" "reflect"
"strings"
"sync"
"time"
) )
const ( const (
utf8HexComma = "0x2C" arrayIndexFieldName = "%s" + leftBracket + "%d" + rightBracket
utf8Pipe = "0x7C" mapIndexFieldName = "%s" + leftBracket + "%v" + rightBracket
tagSeparator = ","
orSeparator = "|"
tagKeySeparator = "="
structOnlyTag = "structonly"
noStructLevelTag = "nostructlevel"
omitempty = "omitempty"
skipValidationTag = "-"
diveTag = "dive"
existsTag = "exists"
fieldErrMsg = "Key: '%s' Error:Field validation for '%s' failed on the '%s' tag"
arrayIndexFieldName = "%s" + leftBracket + "%d" + rightBracket
mapIndexFieldName = "%s" + leftBracket + "%v" + rightBracket
invalidValidation = "Invalid validation tag on field %s"
undefinedValidation = "Undefined validation function on field %s"
validatorNotInitialized = "Validator instance not initialized"
fieldNameRequired = "Field Name Required"
tagRequired = "Tag Required"
)
var (
timeType = reflect.TypeOf(time.Time{})
timePtrType = reflect.TypeOf(&time.Time{})
defaultCField = new(cField)
) )
// StructLevel contains all of the information and helper methods // per validate contruct
// for reporting errors during struct level validation type validate struct {
type StructLevel struct { v *Validate
TopStruct reflect.Value top reflect.Value
CurrentStruct reflect.Value ns []byte
errPrefix string actualNs []byte
nsPrefix string errs ValidationErrors
errs ValidationErrors isPartial bool
v *Validate hasExcludes bool
} includeExclude map[string]struct{} // reset only if StructPartial or StructExcept are called, no need otherwise
// ReportValidationErrors accepts the key relative to the top level struct and validatin errors. // StructLevel & FieldLevel fields
// Example: had a triple nested struct User, ContactInfo, Country and ran errs := validate.Struct(country) slflParent reflect.Value
// from within a User struct level validation would call this method like so: slCurrent reflect.Value
// ReportValidationErrors("ContactInfo.", errs) slNs []byte
// NOTE: relativeKey can contain both the Field Relative and Custom name relative paths slActualNs []byte
// i.e. ReportValidationErrors("ContactInfo.|cInfo", errs) where cInfo represents say the JSON name of flField reflect.Value
// the relative path; this will be split into 2 variables in the next valiator version. flParam string
func (sl *StructLevel) ReportValidationErrors(relativeKey string, errs ValidationErrors) {
for _, e := range errs {
idx := strings.Index(relativeKey, "|")
var rel string
var cRel string
if idx != -1 {
rel = relativeKey[:idx]
cRel = relativeKey[idx+1:]
} else {
rel = relativeKey
}
key := sl.errPrefix + rel + e.Field
e.FieldNamespace = key
e.NameNamespace = sl.nsPrefix + cRel + e.Name
sl.errs[key] = e
}
}
// ReportError reports an error just by passing the field and tag information
// NOTE: tag can be an existing validation tag or just something you make up
// and precess on the flip side it's up to you.
func (sl *StructLevel) ReportError(field reflect.Value, fieldName string, customName string, tag string) {
field, kind := sl.v.ExtractType(field)
if fieldName == blank {
panic(fieldNameRequired)
}
if customName == blank {
customName = fieldName
}
if tag == blank {
panic(tagRequired)
}
ns := sl.errPrefix + fieldName
switch kind {
case reflect.Invalid:
sl.errs[ns] = &FieldError{
FieldNamespace: ns,
NameNamespace: sl.nsPrefix + customName,
Name: customName,
Field: fieldName,
Tag: tag,
ActualTag: tag,
Param: blank,
Kind: kind,
}
default:
sl.errs[ns] = &FieldError{
FieldNamespace: ns,
NameNamespace: sl.nsPrefix + customName,
Name: customName,
Field: fieldName,
Tag: tag,
ActualTag: tag,
Param: blank,
Value: field.Interface(),
Kind: kind,
Type: field.Type(),
}
}
}
// Validate contains the validator settings passed in using the Config struct
type Validate struct {
tagName string
fieldNameTag string
validationFuncs map[string]Func
structLevelFuncs map[reflect.Type]StructLevelFunc
customTypeFuncs map[reflect.Type]CustomTypeFunc
aliasValidators map[string]string
hasCustomFuncs bool
hasAliasValidators bool
hasStructLevelFuncs bool
tagCache *tagCache
structCache *structCache
errsPool *sync.Pool
}
func (v *Validate) initCheck() {
if v == nil {
panic(validatorNotInitialized)
}
}
// Config contains the options that a Validator instance will use.
// It is passed to the New() function
type Config struct {
TagName string
FieldNameTag string
}
// CustomTypeFunc allows for overriding or adding custom field type handler functions
// field = field value of the type to return a value to be validated
// example Valuer from sql drive see https://golang.org/src/database/sql/driver/types.go?s=1210:1293#L29
type CustomTypeFunc func(field reflect.Value) interface{}
// 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
// currentStruct = current level struct when validating by struct otherwise optional comparison value
// field = field value for validation
// param = parameter used in validation i.e. gt=0 param would be 0
type Func func(v *Validate, topStruct reflect.Value, currentStruct reflect.Value, field reflect.Value, fieldtype reflect.Type, fieldKind reflect.Kind, param string) bool
// StructLevelFunc accepts all values needed for struct level validation
type StructLevelFunc func(v *Validate, structLevel *StructLevel)
// ValidationErrors is a type of map[string]*FieldError
// it exists to allow for multiple errors to be passed from this library
// and yet still subscribe to the error interface
type ValidationErrors map[string]*FieldError
// Error is intended for use in development + debugging and not intended to be a production error message.
// It allows ValidationErrors to subscribe to the Error interface.
// All information to create an error message specific to your application is contained within
// the FieldError found within the ValidationErrors map
func (ve ValidationErrors) Error() string {
buff := bytes.NewBufferString(blank)
for key, err := range ve {
buff.WriteString(fmt.Sprintf(fieldErrMsg, key, err.Field, err.Tag))
buff.WriteString("\n")
}
return strings.TrimSpace(buff.String())
}
// FieldError contains a single field's validation error along
// with other properties that may be needed for error message creation
type FieldError struct {
FieldNamespace string
NameNamespace string
Field string
Name string
Tag string
ActualTag string
Kind reflect.Kind
Type reflect.Type
Param string
Value interface{}
}
// New creates a new Validate instance for use.
func New(config *Config) *Validate {
tc := new(tagCache)
tc.m.Store(make(map[string]*cTag))
sc := new(structCache)
sc.m.Store(make(map[reflect.Type]*cStruct))
v := &Validate{
tagName: config.TagName,
fieldNameTag: config.FieldNameTag,
tagCache: tc,
structCache: sc,
errsPool: &sync.Pool{New: func() interface{} {
return ValidationErrors{}
}}}
if len(v.aliasValidators) == 0 {
// must copy alias validators for separate validations to be used in each validator instance
v.aliasValidators = map[string]string{}
for k, val := range bakedInAliasValidators {
v.RegisterAliasValidation(k, val)
}
}
if len(v.validationFuncs) == 0 {
// must copy validators for separate validations to be used in each instance
v.validationFuncs = map[string]Func{}
for k, val := range bakedInValidators {
v.RegisterValidation(k, val)
}
}
return v
}
// RegisterStructValidation registers a StructLevelFunc against a number of types
// NOTE: this method is not thread-safe it is intended that these all be registered prior to any validation
func (v *Validate) RegisterStructValidation(fn StructLevelFunc, types ...interface{}) {
v.initCheck()
if v.structLevelFuncs == nil {
v.structLevelFuncs = map[reflect.Type]StructLevelFunc{}
}
for _, t := range types {
v.structLevelFuncs[reflect.TypeOf(t)] = fn
}
v.hasStructLevelFuncs = true
}
// RegisterValidation adds a validation Func to a Validate's map of validators denoted by the key
// NOTE: if the key already exists, the previous validation function will be replaced.
// NOTE: this method is not thread-safe it is intended that these all be registered prior to any validation
func (v *Validate) RegisterValidation(key string, fn Func) error {
v.initCheck()
if key == blank {
return errors.New("Function Key cannot be empty")
}
if fn == nil {
return errors.New("Function cannot be empty")
}
_, ok := restrictedTags[key]
if ok || strings.ContainsAny(key, restrictedTagChars) {
panic(fmt.Sprintf(restrictedTagErr, key))
}
v.validationFuncs[key] = fn
return nil
}
// RegisterCustomTypeFunc registers a CustomTypeFunc against a number of types
// NOTE: this method is not thread-safe it is intended that these all be registered prior to any validation
func (v *Validate) RegisterCustomTypeFunc(fn CustomTypeFunc, types ...interface{}) {
v.initCheck()
if v.customTypeFuncs == nil {
v.customTypeFuncs = map[reflect.Type]CustomTypeFunc{}
}
for _, t := range types {
v.customTypeFuncs[reflect.TypeOf(t)] = fn
}
v.hasCustomFuncs = true
}
// RegisterAliasValidation registers a mapping of a single validationstag that
// defines a common or complex set of validation(s) to simplify adding validation
// to structs. NOTE: when returning an error the tag returned in FieldError will be
// the alias tag unless the dive tag is part of the alias; everything after the
// dive tag is not reported as the alias tag. Also the ActualTag in the before case
// will be the actual tag within the alias that failed.
// NOTE: this method is not thread-safe it is intended that these all be registered prior to any validation
func (v *Validate) RegisterAliasValidation(alias, tags string) {
v.initCheck()
_, ok := restrictedTags[alias]
if ok || strings.ContainsAny(alias, restrictedTagChars) {
panic(fmt.Sprintf(restrictedAliasErr, alias))
}
v.aliasValidators[alias] = tags
v.hasAliasValidators = true
}
// Field validates a single field using tag style validation and returns nil or ValidationErrors as type error.
// You will need to assert the error if it's not nil i.e. err.(validator.ValidationErrors) to access the map of errors.
// NOTE: it returns ValidationErrors instead of a single FieldError because this can also
// validate Array, Slice and maps fields which may contain more than one error
func (v *Validate) Field(field interface{}, tag string) error {
v.initCheck()
if len(tag) == 0 || tag == skipValidationTag {
return nil
}
errs := v.errsPool.Get().(ValidationErrors)
fieldVal := reflect.ValueOf(field)
ctag, ok := v.tagCache.Get(tag)
if !ok {
v.tagCache.lock.Lock()
defer v.tagCache.lock.Unlock()
// could have been multiple trying to access, but once first is done this ensures tag
// isn't parsed again.
ctag, ok = v.tagCache.Get(tag)
if !ok {
ctag, _ = v.parseFieldTagsRecursive(tag, blank, blank, false)
v.tagCache.Set(tag, ctag)
}
}
v.traverseField(fieldVal, fieldVal, fieldVal, blank, blank, errs, false, false, nil, nil, defaultCField, ctag)
if len(errs) == 0 {
v.errsPool.Put(errs)
return nil
}
return errs
} }
// FieldWithValue validates a single field, against another fields value using tag style validation and returns nil or ValidationErrors. // parent and current will be the same the first run of validateStruct
// You will need to assert the error if it's not nil i.e. err.(validator.ValidationErrors) to access the map of errors. func (v *validate) validateStruct(parent reflect.Value, current reflect.Value, typ reflect.Type, ns []byte, actualNs []byte, ct *cTag) {
// NOTE: it returns ValidationErrors instead of a single FieldError because this can also
// validate Array, Slice and maps fields which may contain more than one error
func (v *Validate) FieldWithValue(val interface{}, field interface{}, tag string) error {
v.initCheck()
if len(tag) == 0 || tag == skipValidationTag { first := len(ns) == 0
return nil
}
errs := v.errsPool.Get().(ValidationErrors) cs, ok := v.v.structCache.Get(typ)
topVal := reflect.ValueOf(val)
ctag, ok := v.tagCache.Get(tag)
if !ok { if !ok {
v.tagCache.lock.Lock() cs = v.v.extractStructCache(current, typ.Name())
defer v.tagCache.lock.Unlock()
// could have been multiple trying to access, but once first is done this ensures tag
// isn't parsed again.
ctag, ok = v.tagCache.Get(tag)
if !ok {
ctag, _ = v.parseFieldTagsRecursive(tag, blank, blank, false)
v.tagCache.Set(tag, ctag)
}
}
v.traverseField(topVal, topVal, reflect.ValueOf(field), blank, blank, errs, false, false, nil, nil, defaultCField, ctag)
if len(errs) == 0 {
v.errsPool.Put(errs)
return nil
} }
return errs if first {
}
// 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 and returns nil or ValidationErrors as error
// You will need to assert the error if it's not nil i.e. err.(validator.ValidationErrors) to access the map of errors.
func (v *Validate) StructPartial(current interface{}, fields ...string) error {
v.initCheck()
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) ns = append(ns, cs.Name...)
ns = append(ns, '.')
if idx != -1 { actualNs = append(actualNs, cs.Name...)
for idx != -1 { actualNs = append(actualNs, '.')
key += s[:idx]
m[key] = struct{}{}
idx2 := strings.Index(s, rightBracket)
idx2++
key += s[idx:idx2]
m[key] = struct{}{}
s = s[idx2:]
idx = strings.Index(s, leftBracket)
}
} else {
key += s
m[key] = struct{}{}
}
key += namespaceSeparator
}
}
}
} }
errs := v.errsPool.Get().(ValidationErrors) // ct is nil on top level struct, and structs as fields that have no tag info
// so if nil or if not nil and the structonly tag isn't present
v.ensureValidStruct(sv, sv, sv, blank, blank, errs, true, len(m) != 0, false, m, false) if ct == nil || ct.typeof != typeStructOnly {
if len(errs) == 0 {
v.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 and returns nil or ValidationErrors as error
// You will need to assert the error if it's not nil i.e. err.(validator.ValidationErrors) to access the map of errors.
func (v *Validate) StructExcept(current interface{}, fields ...string) error {
v.initCheck()
sv, _ := v.ExtractType(reflect.ValueOf(current))
name := sv.Type().Name()
m := map[string]struct{}{}
for _, key := range fields {
m[name+namespaceSeparator+key] = struct{}{}
}
errs := v.errsPool.Get().(ValidationErrors)
v.ensureValidStruct(sv, sv, sv, blank, blank, errs, true, len(m) != 0, true, m, false)
if len(errs) == 0 {
v.errsPool.Put(errs)
return nil
}
return errs
}
// Struct validates a structs exposed fields, and automatically validates nested structs, unless otherwise specified.
// it returns nil or ValidationErrors as error.
// You will need to assert the error if it's not nil i.e. err.(validator.ValidationErrors) to access the map of errors.
func (v *Validate) Struct(current interface{}) error {
v.initCheck()
errs := v.errsPool.Get().(ValidationErrors)
sv := reflect.ValueOf(current)
v.ensureValidStruct(sv, sv, sv, blank, blank, errs, true, false, false, nil, false)
if len(errs) == 0 {
v.errsPool.Put(errs)
return nil
}
return errs
}
func (v *Validate) ensureValidStruct(topStruct reflect.Value, currentStruct reflect.Value, current reflect.Value, errPrefix string, nsPrefix string, errs ValidationErrors, useStructName bool, partial bool, exclude bool, includeExclude map[string]struct{}, isStructOnly bool) {
if current.Kind() == reflect.Ptr && !current.IsNil() {
current = current.Elem()
}
if current.Kind() != reflect.Struct && current.Kind() != reflect.Interface {
panic("value passed for validation is not a struct")
}
v.tranverseStruct(topStruct, currentStruct, current, errPrefix, nsPrefix, errs, useStructName, partial, exclude, includeExclude, nil, nil)
}
// 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, nsPrefix string, errs ValidationErrors, useStructName bool, partial bool, exclude bool, includeExclude map[string]struct{}, cs *cStruct, ct *cTag) {
var ok bool
first := len(nsPrefix) == 0
typ := current.Type()
cs, ok = v.structCache.Get(typ)
if !ok {
cs = v.extractStructCache(current, typ.Name())
}
if useStructName {
errPrefix += cs.Name + namespaceSeparator
if len(v.fieldNameTag) != 0 {
nsPrefix += cs.Name + namespaceSeparator
}
}
// structonly tag present don't tranverseFields
// but must still check and run below struct level validation
// if present
if first || ct == nil || ct.typeof != typeStructOnly {
for _, f := range cs.fields { for _, f := range cs.fields {
if partial { if v.isPartial {
_, ok = includeExclude[errPrefix+f.Name] _, ok = v.includeExclude[string(append(ns, f.Name...))]
if (ok && exclude) || (!ok && !exclude) { if (ok && v.hasExcludes) || (!ok && !v.hasExcludes) {
continue continue
} }
} }
v.traverseField(topStruct, currentStruct, current.Field(f.Idx), errPrefix, nsPrefix, errs, partial, exclude, includeExclude, cs, f, f.cTags) v.traverseField(parent, current.Field(f.Idx), ns, actualNs, f, f.cTags)
} }
} }
// check if any struct level validations, after all field validations already checked. // check if any struct level validations, after all field validations already checked.
// first iteration will have no info about nostructlevel tag, and is checked prior to
// calling the next iteration of validateStruct called from traverseField.
if cs.fn != nil { if cs.fn != nil {
cs.fn(v, &StructLevel{v: v, TopStruct: topStruct, CurrentStruct: current, errPrefix: errPrefix, nsPrefix: nsPrefix, errs: errs})
v.slflParent = parent
v.slCurrent = current
v.slNs = ns
v.slActualNs = actualNs
cs.fn(v)
} }
} }
// 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, nsPrefix string, errs ValidationErrors, partial bool, exclude bool, includeExclude map[string]struct{}, cs *cStruct, cf *cField, ct *cTag) { func (v *validate) traverseField(parent reflect.Value, current reflect.Value, ns []byte, actualNs []byte, cf *cField, ct *cTag) {
current, kind, nullable := v.extractTypeInternal(current, false)
var typ reflect.Type var typ reflect.Type
var kind reflect.Kind
var nullable bool
current, kind, nullable = v.extractTypeInternal(current, nullable)
switch kind { switch kind {
case reflect.Ptr, reflect.Interface, reflect.Invalid: case reflect.Ptr, reflect.Interface, reflect.Invalid:
@ -589,39 +104,44 @@ func (v *Validate) traverseField(topStruct reflect.Value, currentStruct reflect.
if ct.hasTag { if ct.hasTag {
ns := errPrefix + cf.Name
if kind == reflect.Invalid { if kind == reflect.Invalid {
errs[ns] = &FieldError{
FieldNamespace: ns, v.errs = append(v.errs,
NameNamespace: nsPrefix + cf.AltName, &fieldError{
Name: cf.AltName, tag: ct.aliasTag,
Field: cf.Name, actualTag: ct.tag,
Tag: ct.aliasTag, ns: string(append(ns, cf.Name...)),
ActualTag: ct.tag, actualNs: string(append(actualNs, cf.AltName...)),
Param: ct.param, field: cf.AltName,
Kind: kind, actualField: cf.Name,
} param: ct.param,
kind: kind,
},
)
return return
} }
errs[ns] = &FieldError{ v.errs = append(v.errs,
FieldNamespace: ns, &fieldError{
NameNamespace: nsPrefix + cf.AltName, tag: ct.aliasTag,
Name: cf.AltName, actualTag: ct.tag,
Field: cf.Name, ns: string(append(ns, cf.Name...)),
Tag: ct.aliasTag, actualNs: string(append(actualNs, cf.AltName...)),
ActualTag: ct.tag, field: cf.AltName,
Param: ct.param, actualField: cf.Name,
Value: current.Interface(), value: current.Interface(),
Kind: kind, param: ct.param,
Type: current.Type(), kind: kind,
} typ: current.Type(),
},
)
return return
} }
case reflect.Struct: case reflect.Struct:
typ = current.Type() typ = current.Type()
if typ != timeType { if typ != timeType {
@ -634,7 +154,7 @@ func (v *Validate) traverseField(topStruct reflect.Value, currentStruct reflect.
return return
} }
v.tranverseStruct(topStruct, current, current, errPrefix+cf.Name+namespaceSeparator, nsPrefix+cf.AltName+namespaceSeparator, errs, false, partial, exclude, includeExclude, cs, ct) v.validateStruct(current, current, typ, append(append(ns, cf.Name...), '.'), append(append(actualNs, cf.AltName...), '.'), ct)
return return
} }
} }
@ -659,7 +179,12 @@ OUTER:
case typeOmitEmpty: case typeOmitEmpty:
if !nullable && !HasValue(v, topStruct, currentStruct, current, typ, kind, blank) { // set Field Level fields
v.slflParent = parent
v.flField = current
v.flParam = ""
if !nullable && !hasValue(v) {
return return
} }
@ -676,12 +201,12 @@ OUTER:
case reflect.Slice, reflect.Array: case reflect.Slice, reflect.Array:
for i := 0; i < current.Len(); i++ { for i := 0; i < current.Len(); i++ {
v.traverseField(topStruct, currentStruct, current.Index(i), errPrefix, nsPrefix, errs, partial, exclude, includeExclude, cs, &cField{Name: fmt.Sprintf(arrayIndexFieldName, cf.Name, i), AltName: fmt.Sprintf(arrayIndexFieldName, cf.AltName, i)}, ct) v.traverseField(parent, current.Index(i), ns, actualNs, &cField{Name: fmt.Sprintf(arrayIndexFieldName, cf.Name, i), AltName: fmt.Sprintf(arrayIndexFieldName, cf.AltName, i)}, ct)
} }
case reflect.Map: case reflect.Map:
for _, key := range current.MapKeys() { for _, key := range current.MapKeys() {
v.traverseField(topStruct, currentStruct, current.MapIndex(key), errPrefix, nsPrefix, errs, partial, exclude, includeExclude, cs, &cField{Name: fmt.Sprintf(mapIndexFieldName, cf.Name, key.Interface()), AltName: fmt.Sprintf(mapIndexFieldName, cf.AltName, key.Interface())}, ct) v.traverseField(parent, current.MapIndex(key), ns, actualNs, &cField{Name: fmt.Sprintf(mapIndexFieldName, cf.Name, key.Interface()), AltName: fmt.Sprintf(mapIndexFieldName, cf.AltName, key.Interface())}, ct)
} }
default: default:
@ -694,11 +219,16 @@ OUTER:
case typeOr: case typeOr:
errTag := blank errTag := ""
for { for {
if ct.fn(v, topStruct, currentStruct, current, typ, kind, ct.param) { // set Field Level fields
v.slflParent = parent
v.flField = current
v.flParam = ct.param
if ct.fn(v) {
// drain rest of the 'or' values, then continue or leave // drain rest of the 'or' values, then continue or leave
for { for {
@ -720,32 +250,39 @@ OUTER:
if ct.next == nil { if ct.next == nil {
// if we get here, no valid 'or' value and no more tags // if we get here, no valid 'or' value and no more tags
ns := errPrefix + cf.Name
if ct.hasAlias { if ct.hasAlias {
errs[ns] = &FieldError{
FieldNamespace: ns, v.errs = append(v.errs,
NameNamespace: nsPrefix + cf.AltName, &fieldError{
Name: cf.AltName, tag: ct.aliasTag,
Field: cf.Name, actualTag: ct.actualAliasTag,
Tag: ct.aliasTag, ns: string(append(ns, cf.Name...)),
ActualTag: ct.actualAliasTag, actualNs: string(append(actualNs, cf.AltName...)),
Value: current.Interface(), field: cf.AltName,
Type: typ, actualField: cf.Name,
Kind: kind, value: current.Interface(),
} param: ct.param,
kind: kind,
typ: typ,
},
)
} else { } else {
errs[errPrefix+cf.Name] = &FieldError{
FieldNamespace: ns, v.errs = append(v.errs,
NameNamespace: nsPrefix + cf.AltName, &fieldError{
Name: cf.AltName, tag: errTag[1:],
Field: cf.Name, actualTag: errTag[1:],
Tag: errTag[1:], ns: string(append(ns, cf.Name...)),
ActualTag: errTag[1:], actualNs: string(append(actualNs, cf.AltName...)),
Value: current.Interface(), field: cf.AltName,
Type: typ, actualField: cf.Name,
Kind: kind, value: current.Interface(),
} param: ct.param,
kind: kind,
typ: typ,
},
)
} }
return return
@ -755,22 +292,28 @@ OUTER:
} }
default: default:
if !ct.fn(v, topStruct, currentStruct, current, typ, kind, ct.param) {
// set Field Level fields
ns := errPrefix + cf.Name v.slflParent = parent
v.flField = current
errs[ns] = &FieldError{ v.flParam = ct.param
FieldNamespace: ns,
NameNamespace: nsPrefix + cf.AltName, if !ct.fn(v) {
Name: cf.AltName,
Field: cf.Name, v.errs = append(v.errs,
Tag: ct.aliasTag, &fieldError{
ActualTag: ct.tag, tag: ct.aliasTag,
Value: current.Interface(), actualTag: ct.tag,
Param: ct.param, ns: string(append(ns, cf.Name...)),
Type: typ, actualNs: string(append(actualNs, cf.AltName...)),
Kind: kind, field: cf.AltName,
} actualField: cf.Name,
value: current.Interface(),
param: ct.param,
kind: kind,
typ: typ,
},
)
return return
@ -779,4 +322,5 @@ OUTER:
ct = ct.next ct = ct.next
} }
} }
} }

@ -0,0 +1,442 @@
package validator
import (
"errors"
"fmt"
"reflect"
"strings"
"sync"
"time"
)
const (
defaultTagName = "validate"
utf8HexComma = "0x2C"
utf8Pipe = "0x7C"
tagSeparator = ","
orSeparator = "|"
tagKeySeparator = "="
structOnlyTag = "structonly"
noStructLevelTag = "nostructlevel"
omitempty = "omitempty"
skipValidationTag = "-"
diveTag = "dive"
namespaceSeparator = "."
leftBracket = "["
rightBracket = "]"
restrictedTagChars = ".[],|=+()`~!@#$%^&*\\\"/?<>{}"
restrictedAliasErr = "Alias '%s' either contains restricted characters or is the same as a restricted tag needed for normal operation"
restrictedTagErr = "Tag '%s' either contains restricted characters or is the same as a restricted tag needed for normal operation"
)
var (
timeType = reflect.TypeOf(time.Time{})
defaultCField = new(cField)
)
// CustomTypeFunc allows for overriding or adding custom field type handler functions
// field = field value of the type to return a value to be validated
// example Valuer from sql drive see https://golang.org/src/database/sql/driver/types.go?s=1210:1293#L29
type CustomTypeFunc func(field reflect.Value) interface{}
// TagNameFunc allows for adding of a custom tag name parser
type TagNameFunc func(field reflect.StructField) string
// Validate contains the validator settings and cache
type Validate struct {
tagName string
pool *sync.Pool
hasCustomFuncs bool
hasTagNameFunc bool
tagNameFunc TagNameFunc
structLevelFuncs map[reflect.Type]StructLevelFunc
customFuncs map[reflect.Type]CustomTypeFunc
aliases map[string]string
validations map[string]Func
tagCache *tagCache
structCache *structCache
}
// New returns a new instacne of 'validate' with sane defaults.
func New() *Validate {
tc := new(tagCache)
tc.m.Store(make(map[string]*cTag))
sc := new(structCache)
sc.m.Store(make(map[reflect.Type]*cStruct))
v := &Validate{
tagName: defaultTagName,
aliases: make(map[string]string, len(bakedInAliases)),
validations: make(map[string]Func, len(bakedInValidators)),
tagCache: tc,
structCache: sc,
}
// must copy alias validators for separate validations to be used in each validator instance
for k, val := range bakedInAliases {
v.RegisterAlias(k, val)
}
// must copy validators for separate validations to be used in each instance
for k, val := range bakedInValidators {
// no need to error check here, baked in will alwaays be valid
v.RegisterValidation(k, val)
}
v.pool = &sync.Pool{
New: func() interface{} {
return &validate{
v: v,
}
},
}
return v
}
// SetTagName allows for changing of the default tag name of 'validate'
func (v *Validate) SetTagName(name string) {
v.tagName = name
}
// RegisterTagNameFunc registers a function to get another name from the
// StructField eg. the JSON name
func (v *Validate) RegisterTagNameFunc(fn TagNameFunc) {
v.tagNameFunc = fn
v.hasTagNameFunc = true
}
// RegisterValidation adds a validation with the given tag
//
// NOTES:
// - 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 {
if len(tag) == 0 {
return errors.New("Function Key cannot be empty")
}
if fn == nil {
return errors.New("Function cannot be empty")
}
_, ok := restrictedTags[tag]
if ok || strings.ContainsAny(tag, restrictedTagChars) {
panic(fmt.Sprintf(restrictedTagErr, tag))
}
v.validations[tag] = fn
return nil
}
// RegisterAlias registers a mapping of a single validation tag that
// defines a common or complex set of validation(s) to simplify adding validation
// to structs.
//
// NOTE: this function is not thread-safe it is intended that these all be registered prior to any validation
func (v *Validate) RegisterAlias(alias, tags string) {
_, ok := restrictedTags[alias]
if ok || strings.ContainsAny(alias, restrictedTagChars) {
panic(fmt.Sprintf(restrictedAliasErr, alias))
}
v.aliases[alias] = tags
}
// RegisterStructValidation registers a StructLevelFunc against a number of types.
// This is akin to implementing the 'Validatable' interface, but for structs for which
// you may not have access or rights to change.
//
// NOTES:
// - if this and the 'Validatable' interface are implemented the Struct Level takes precedence as to enable
// a struct out of your control's validation to be overridden
// - this method is not thread-safe it is intended that these all be registered prior to any validation
func (v *Validate) RegisterStructValidation(fn StructLevelFunc, types ...interface{}) {
if v.structLevelFuncs == nil {
v.structLevelFuncs = make(map[reflect.Type]StructLevelFunc)
}
for _, t := range types {
v.structLevelFuncs[reflect.TypeOf(t)] = fn
}
}
// RegisterCustomTypeFunc registers a CustomTypeFunc against a number of types
//
// NOTE: this method is not thread-safe it is intended that these all be registered prior to any validation
func (v *Validate) RegisterCustomTypeFunc(fn CustomTypeFunc, types ...interface{}) {
if v.customFuncs == nil {
v.customFuncs = make(map[reflect.Type]CustomTypeFunc)
}
for _, t := range types {
v.customFuncs[reflect.TypeOf(t)] = fn
}
}
// Struct validates a structs exposed fields, and automatically validates nested structs, unless otherwise specified.
//
// It returns InvalidValidationError for bad values passed in and nil or ValidationErrors as error otherwise.
// You will need to assert the error if it's not nil eg. err.(validator.ValidationErrors) to access the array of errors.
func (v *Validate) Struct(s interface{}) (err error) {
val := reflect.ValueOf(s)
top := val
if val.Kind() == reflect.Ptr && !val.IsNil() {
val = val.Elem()
}
typ := val.Type()
if val.Kind() != reflect.Struct || typ == timeType {
return &InvalidValidationError{Type: typ}
}
// good to validate
vd := v.pool.Get().(*validate)
vd.top = top
vd.isPartial = false
// vd.hasExcludes = false // only need to reset in StructPartial and StructExcept
vd.validateStruct(top, val, typ, vd.ns[0:0], vd.actualNs[0:0], nil)
if len(vd.errs) > 0 {
err = vd.errs
vd.errs = nil
}
v.pool.Put(vd)
return
}
// StructPartial validates the fields passed in only, ignoring all others.
// Fields may be provided in a namespaced fashion relative to the struct provided
// eg. NestedStruct.Field or NestedArrayField[0].Struct.Name
//
// It returns InvalidValidationError for bad values passed in and nil or ValidationErrors as error otherwise.
// You will need to assert the error if it's not nil eg. err.(validator.ValidationErrors) to access the array of errors.
func (v *Validate) StructPartial(s interface{}, fields ...string) (err error) {
val := reflect.ValueOf(s)
top := val
if val.Kind() == reflect.Ptr && !val.IsNil() {
val = val.Elem()
}
typ := val.Type()
if val.Kind() != reflect.Struct || typ == timeType {
return &InvalidValidationError{Type: typ}
}
// good to validate
vd := v.pool.Get().(*validate)
vd.top = top
vd.isPartial = true
vd.hasExcludes = false
vd.includeExclude = make(map[string]struct{})
name := typ.Name()
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]
vd.includeExclude[key] = struct{}{}
idx2 := strings.Index(s, rightBracket)
idx2++
key += s[idx:idx2]
vd.includeExclude[key] = struct{}{}
s = s[idx2:]
idx = strings.Index(s, leftBracket)
}
} else {
key += s
vd.includeExclude[key] = struct{}{}
}
key += namespaceSeparator
}
}
}
}
vd.validateStruct(top, val, typ, vd.ns[0:0], vd.actualNs[0:0], nil)
if len(vd.errs) > 0 {
err = vd.errs
vd.errs = nil
}
v.pool.Put(vd)
return
}
// 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
//
// It returns InvalidValidationError for bad values passed in and nil or ValidationErrors as error otherwise.
// You will need to assert the error if it's not nil eg. err.(validator.ValidationErrors) to access the array of errors.
func (v *Validate) StructExcept(s interface{}, fields ...string) (err error) {
val := reflect.ValueOf(s)
top := val
if val.Kind() == reflect.Ptr && !val.IsNil() {
val = val.Elem()
}
typ := val.Type()
if val.Kind() != reflect.Struct || typ == timeType {
return &InvalidValidationError{Type: typ}
}
// good to validate
vd := v.pool.Get().(*validate)
vd.top = top
vd.isPartial = true
vd.hasExcludes = true
vd.includeExclude = make(map[string]struct{})
name := typ.Name()
for _, key := range fields {
vd.includeExclude[name+namespaceSeparator+key] = struct{}{}
}
vd.validateStruct(top, val, typ, vd.ns[0:0], vd.actualNs[0:0], nil)
if len(vd.errs) > 0 {
err = vd.errs
vd.errs = nil
}
v.pool.Put(vd)
return
}
// func (v *validate) traverseField(parent reflect.Value, current reflect.Value, ns []byte, actualNs []byte, cf *cField, ct *cTag) {
// Var validates a single variable using tag style validation.
// eg.
// var i int
// validate.Var(i, "gt=1,lt=10")
//
// It returns InvalidValidationError for bad values passed in and nil or ValidationErrors as error otherwise.
// You will need to assert the error if it's not nil eg. err.(validator.ValidationErrors) to access the array of errors.
// validate Array, Slice and maps fields which may contain more than one error
func (v *Validate) Var(field interface{}, tag string) (err error) {
if len(tag) == 0 || tag == skipValidationTag {
return nil
}
// find cached tag
ctag, ok := v.tagCache.Get(tag)
if !ok {
v.tagCache.lock.Lock()
defer v.tagCache.lock.Unlock()
// could have been multiple trying to access, but once first is done this ensures tag
// isn't parsed again.
ctag, ok = v.tagCache.Get(tag)
if !ok {
ctag, _ = v.parseFieldTagsRecursive(tag, "", "", false)
v.tagCache.Set(tag, ctag)
}
}
val := reflect.ValueOf(field)
vd := v.pool.Get().(*validate)
vd.top = val
vd.isPartial = false
vd.traverseField(val, val, vd.ns[0:0], vd.actualNs[0:0], defaultCField, ctag)
if len(vd.errs) > 0 {
err = vd.errs
vd.errs = nil
}
v.pool.Put(vd)
return
}
// VarWithValue validates a single variable, against another variable/field's value using tag style validation
// eg.
// s1 := "abcd"
// s2 := "abcd"
// validate.VarWithValue(s1, s2, "eqcsfield") // returns true
//
// It returns InvalidValidationError for bad values passed in and nil or ValidationErrors as error otherwise.
// You will need to assert the error if it's not nil eg. err.(validator.ValidationErrors) to access the array of errors.
// validate Array, Slice and maps fields which may contain more than one error
func (v *Validate) VarWithValue(field interface{}, other interface{}, tag string) (err error) {
if len(tag) == 0 || tag == skipValidationTag {
return nil
}
// find cached tag
ctag, ok := v.tagCache.Get(tag)
if !ok {
v.tagCache.lock.Lock()
defer v.tagCache.lock.Unlock()
// could have been multiple trying to access, but once first is done this ensures tag
// isn't parsed again.
ctag, ok = v.tagCache.Get(tag)
if !ok {
ctag, _ = v.parseFieldTagsRecursive(tag, "", "", false)
v.tagCache.Set(tag, ctag)
}
}
otherVal := reflect.ValueOf(other)
vd := v.pool.Get().(*validate)
vd.top = otherVal
vd.isPartial = false
vd.traverseField(otherVal, reflect.ValueOf(field), vd.ns[0:0], vd.actualNs[0:0], defaultCField, ctag)
if len(vd.errs) > 0 {
err = vd.errs
vd.errs = nil
}
v.pool.Put(vd)
return
}

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