Updated documentation for new v6

pull/114/head
joeybloggs 9 years ago
parent 933fe0b7a9
commit 41b4a43989
  1. 62
      README.md
  2. 103
      doc.go
  3. 34
      examples/simple.go
  4. 177
      examples_test.go
  5. 1
      regexes.go
  6. 86
      validator.go

@ -2,9 +2,9 @@ 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://travis-ci.org/bluesuncorp/validator.svg?branch=v5.1)](https://travis-ci.org/bluesuncorp/validator) [![Build Status](https://travis-ci.org/bluesuncorp/validator.svg?branch=v6)](https://travis-ci.org/bluesuncorp/validator)
[![Coverage Status](https://coveralls.io/repos/bluesuncorp/validator/badge.svg?branch=v5)](https://coveralls.io/r/bluesuncorp/validator?branch=v5) [![Coverage Status](https://coveralls.io/repos/bluesuncorp/validator/badge.svg?branch=v6)](https://coveralls.io/r/bluesuncorp/validator?branch=v6)
[![GoDoc](https://godoc.org/gopkg.in/bluesuncorp/validator.v5?status.svg)](https://godoc.org/gopkg.in/bluesuncorp/validator.v5) [![GoDoc](https://godoc.org/gopkg.in/bluesuncorp/validator.v6?status.svg)](https://godoc.org/gopkg.in/bluesuncorp/validator.v6)
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.
@ -19,20 +19,20 @@ Installation
Use go get. Use go get.
go get gopkg.in/bluesuncorp/validator.v5 go get gopkg.in/bluesuncorp/validator.v6
or to update or to update
go get -u gopkg.in/bluesuncorp/validator.v5 go get -u gopkg.in/bluesuncorp/validator.v6
Then import the validator package into your own code. Then import the validator package into your own code.
import "gopkg.in/bluesuncorp/validator.v5" import "gopkg.in/bluesuncorp/validator.v6"
Usage and documentation Usage and documentation
------ ------
Please see http://godoc.org/gopkg.in/bluesuncorp/validator.v5 for detailed usage docs. Please see http://godoc.org/gopkg.in/bluesuncorp/validator.v6 for detailed usage docs.
##### Example: ##### Example:
```go ```go
@ -41,7 +41,7 @@ package main
import ( import (
"fmt" "fmt"
"gopkg.in/bluesuncorp/validator.v5" "gopkg.in/bluesuncorp/validator.v6"
) )
// User contains user information // User contains user information
@ -66,7 +66,12 @@ var validate *validator.Validate
func main() { func main() {
validate = validator.New("validate", validator.BakedInValidators) config := validator.Config{
TagName: "validate",
ValidationFuncs: validator.BakedInValidators,
}
validate = validator.New(config)
address := &Address{ address := &Address{
Street: "Eavesdown Docks", Street: "Eavesdown Docks",
@ -83,31 +88,14 @@ func main() {
Addresses: []*Address{address}, Addresses: []*Address{address},
} }
// returns nil or *StructErrors // returns nil or ValidationErrors ( map[string]*FieldError )
errs := validate.Struct(user) errs := validate.Struct(user)
if errs != nil { if errs != nil {
// err will be of type *FieldError fmt.Println(errs) // output: Key: "User.Age" Error:Field validation for "Age" failed on the "lte" tag
err := errs.Errors["Age"] // Key: "User.Addresses[0].City" Error:Field validation for "City" failed on the "required" tag
fmt.Println(err.Error()) // output: Field validation for "Age" failed on the "lte" tag err := errs["User.Addresses[0].City"]
fmt.Println(err.Field) // output: Age
fmt.Println(err.Tag) // output: lte
fmt.Println(err.Kind) // output: uint8
fmt.Println(err.Type) // output: uint8
fmt.Println(err.Param) // output: 130
fmt.Println(err.Value) // output: 135
// or if you prefer you can use the Flatten function
// NOTE: I find this usefull when using a more hard static approach of checking field errors.
// The above, is best for passing to some generic code to say parse the errors. i.e. I pass errs
// to a routine which loops through the errors, creates and translates the error message into the
// users locale and returns a map of map[string]string // field and error which I then use
// within the HTML rendering.
flat := errs.Flatten()
fmt.Println(flat) // output: map[Age:Field validation for "Age" failed on the "lte" tag Addresses[0].Address.City:Field validation for "City" failed on the "required" tag]
err = flat["Addresses[0].Address.City"]
fmt.Println(err.Field) // output: City fmt.Println(err.Field) // output: City
fmt.Println(err.Tag) // output: required fmt.Println(err.Tag) // output: required
fmt.Println(err.Kind) // output: string fmt.Println(err.Kind) // output: string
@ -126,14 +114,18 @@ func main() {
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
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 $ go test -cpu=4 -bench=. -benchmem=true
PASS PASS
BenchmarkValidateField-4 3000000 429 ns/op 192 B/op 2 allocs/op BenchmarkField-4 5000000 314 ns/op 16 B/op 1 allocs/op
BenchmarkValidateStructSimple-4 500000 2877 ns/op 657 B/op 10 allocs/op BenchmarkFieldOrTag-4 500000 2425 ns/op 20 B/op 2 allocs/op
BenchmarkTemplateParallelSimple-4 500000 3097 ns/op 657 B/op 10 allocs/op BenchmarkStructSimple-4 500000 3117 ns/op 553 B/op 14 allocs/op
BenchmarkValidateStructLarge-4 100000 15228 ns/op 4350 B/op 62 allocs/op BenchmarkStructSimpleParallel-4 1000000 1149 ns/op 553 B/op 14 allocs/op
BenchmarkTemplateParallelLarge-4 100000 14257 ns/op 4354 B/op 62 allocs/op BenchmarkStructComplex-4 100000 19580 ns/op 3230 B/op 102 allocs/op
BenchmarkStructComplexParallel-4 200000 6686 ns/op 3232 B/op 102 allocs/op
``` ```
How to Contribute How to Contribute

103
doc.go

@ -1,59 +1,9 @@
/* /*
Package validator implements value validations for structs and individual fields based on tags. It can also handle Cross Field and Cross Struct validation for nested structs. Package validator implements value validations for structs and individual fields based on tags.
It can also handle Cross Field and Cross Struct validation for nested structs and has the ability
to dive into arrays and maps of any type.
Validate Why not a better error message? because this library intends for you to handle your own error messages.
validate := validator.New("validate", validator.BakedInValidators)
errs := validate.Struct(//your struct)
valErr := validate.Field(field, "omitempty,min=1,max=10")
A simple example usage:
type UserDetail struct {
Details string `validate:"-"`
}
type User struct {
Name string `validate:"required,max=60"`
PreferedName string `validate:"omitempty,max=60"`
Sub UserDetail
}
user := &User {
Name: "",
}
// errs will contain a hierarchical list of errors
// using the StructErrors struct
// or nil if no errors exist
errs := validate.Struct(user)
// in this case 1 error Name is required
errs.Struct will be "User"
errs.StructErrors will be empty <-- fields that were structs
errs.Errors will have 1 error of type FieldError
NOTE: Anonymous Structs - they don't have names so expect the Struct name
within StructErrors to be blank.
Error Handling
The error can be used like so
fieldErr, _ := errs["Name"]
fieldErr.Field // "Name"
fieldErr.ErrorTag // "required"
Both StructErrors and FieldError implement the Error interface but it's
intended use is for development + debugging, not a production error message.
fieldErr.Error() // Field validation for "Name" failed on the "required" tag
errs.Error()
// Struct: User
// Field validation for "Name" failed on the "required" tag
Why not a better error message? because this library intends for you to handle your own error messages
Why should I handle my own errors? Many reasons, for us building an internationalized application Why should I handle my own errors? Many reasons, for us building an internationalized application
I needed to know the field and what validation failed so that I could provide an error in the users specific language. I needed to know the field and what validation failed so that I could provide an error in the users specific language.
@ -66,21 +16,12 @@ I needed to know the field and what validation failed so that I could provide an
return "Translated string based on field" return "Translated string based on field"
} }
The hierarchical error structure is hard to work with sometimes.. Agreed Flatten function to the rescue!
Flatten will return a map of FieldError's but the field name will be namespaced.
// if UserDetail Details field failed validation
Field will be "Sub.Details"
// for Name
Field will be "Name"
Custom Functions Custom Functions
Custom functions can be added Custom functions can be added
// Structure // Structure
func customFunc(top interface{}, current interface{}, field interface{}, param string) bool { func customFunc(topStruct reflect.Value, currentStruct reflect.Value, field reflect.Value, fieldType reflect.Type, fieldKind reflect.Kind, param string) bool {
if whatever { if whatever {
return false return false
@ -89,7 +30,7 @@ Custom functions can be added
return true return true
} }
validate.AddFunction("custom tag name", customFunc) validate.RegisterValidation("custom tag name", customFunc)
// NOTES: using the same tag name as an existing function // NOTES: using the same tag name as an existing function
// will overwrite the existing one // will overwrite the existing one
@ -97,7 +38,7 @@ Cross Field Validation
Cross Field Validation can be implemented, for example Start & End Date range validation Cross Field Validation can be implemented, for example Start & End Date range validation
// NOTE: when calling validate.Struct(val) val 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...
@ -106,18 +47,7 @@ Cross Field Validation can be implemented, for example Start & End Date range va
// Because of the specific requirements and field names within each persons project that // 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 // 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, // Cross Field Validation needs, however there are some build in Generic Cross Field validations,
// see Baked In Validators and Tags below // see Baked In Validators eqfield, nefield, gtfield, gtefield, ltfield, ltefield and Tags below
func isDateRangeValid(val interface{}, field interface{}, param string) bool {
myStruct := val.(myStructType)
if myStruct.Start.After(field.(time.Time)) {
return false
}
return true
}
Multiple Validators Multiple Validators
@ -135,7 +65,7 @@ Bad Validator definitions are not handled by the library
Field `validate:"min=10,max=0"` Field `validate:"min=10,max=0"`
} }
// this definition of min max will never validate // this definition of min max will never succeed
Baked In Validators and Tags Baked In Validators and Tags
@ -148,6 +78,11 @@ included within the parameter i.e. excludesall=, you will need to use the UTF-8
representation 0x2C, which is replaced in the code as a comma, so the above will representation 0x2C, which is replaced in the code as a comma, so the above will
become excludesall=0x2C become excludesall=0x2C
NOTE3: pipe is the default separator of or validation tags, if you wish to have a pipe
included within the parameter i.e. excludesall=| you will need to use the UTF-8 hex
representation 0x7C, which is replaced in the code as a pipe, so the above will
become excludesall=0x7C
Here is a list of the current built in validators: Here is a list of the current built in validators:
- -
@ -162,14 +97,14 @@ Here is a list of the current built in validators:
structonly structonly
When a field that is a nest struct in encountered and contains this flag When a field that is a nest struct in encountered and contains this flag
any validation on the nested struct such as "required" will be run, but any validation on the nested struct will be run, but none of the nested
none of the nested struct fields will be validated. This is usefull if struct fields will be validated. This is usefull if inside of you program
inside of you program you know the struct will be valid, but need to you know the struct will be valid, but need to verify it has been assigned.
verify it has been assigned. NOTE: only "required" and "omitempty" can be used on a struct itself.
omitempty omitempty
Allows conditional validation, for example if a field is not set with Allows conditional validation, for example if a field is not set with
a value (Determined by the required validator) then other validation a value (Determined by the "required" validator) then other validation
such as min or max won't run, but if a value is set validation will run. such as min or max won't run, but if a value is set validation will run.
(Usage: omitempty) (Usage: omitempty)

@ -3,7 +3,7 @@ package main
import ( import (
"fmt" "fmt"
"gopkg.in/bluesuncorp/validator.v5" "gopkg.in/bluesuncorp/validator.v6"
) )
// User contains user information // User contains user information
@ -28,7 +28,12 @@ var validate *validator.Validate
func main() { func main() {
validate = validator.New("validate", validator.BakedInValidators) config := validator.Config{
TagName: "validate",
ValidationFuncs: validator.BakedInValidators,
}
validate = validator.New(config)
address := &Address{ address := &Address{
Street: "Eavesdown Docks", Street: "Eavesdown Docks",
@ -45,31 +50,14 @@ func main() {
Addresses: []*Address{address}, Addresses: []*Address{address},
} }
// returns nil or *StructErrors // returns nil or ValidationErrors ( map[string]*FieldError )
errs := validate.Struct(user) errs := validate.Struct(user)
if errs != nil { if errs != nil {
// err will be of type *FieldError fmt.Println(errs) // output: Key: "User.Age" Error:Field validation for "Age" failed on the "lte" tag
err := errs.Errors["Age"] // Key: "User.Addresses[0].City" Error:Field validation for "City" failed on the "required" tag
fmt.Println(err.Error()) // output: Field validation for "Age" failed on the "lte" tag err := errs["User.Addresses[0].City"]
fmt.Println(err.Field) // output: Age
fmt.Println(err.Tag) // output: lte
fmt.Println(err.Kind) // output: uint8
fmt.Println(err.Type) // output: uint8
fmt.Println(err.Param) // output: 130
fmt.Println(err.Value) // output: 135
// or if you prefer you can use the Flatten function
// NOTE: I find this usefull when using a more hard static approach of checking field errors.
// The above, is best for passing to some generic code to say parse the errors. i.e. I pass errs
// to a routine which loops through the errors, creates and translates the error message into the
// users locale and returns a map of map[string]string // field and error which I then use
// within the HTML rendering.
flat := errs.Flatten()
fmt.Println(flat) // output: map[Age:Field validation for "Age" failed on the "lte" tag Addresses[0].Address.City:Field validation for "City" failed on the "required" tag]
err = flat["Addresses[0].Address.City"]
fmt.Println(err.Field) // output: City fmt.Println(err.Field) // output: City
fmt.Println(err.Tag) // output: required fmt.Println(err.Tag) // output: required
fmt.Println(err.Kind) // output: string fmt.Println(err.Kind) // output: string

@ -1,89 +1,92 @@
package validator_test package validator_test
// func ExampleValidate_new() { import (
// validator.New("validate", validator.BakedInValidators) "fmt"
// }
"../validator"
// func ExampleValidate_addFunction() { )
// // This should be stored somewhere globally
// var validate *validator.Validate func ExampleValidate_new() {
config := validator.Config{
// validate = validator.New("validate", validator.BakedInValidators) TagName: "validate",
ValidationFuncs: validator.BakedInValidators,
// fn := func(top interface{}, current interface{}, field interface{}, param string) bool { }
// return field.(string) == "hello"
// } validator.New(config)
}
// validate.AddFunction("valueishello", fn)
func ExampleValidate_field() {
// message := "hello" // This should be stored somewhere globally
// err := validate.Field(message, "valueishello") var validate *validator.Validate
// fmt.Println(err)
// //Output: config := validator.Config{
// //<nil> TagName: "validate",
// } ValidationFuncs: validator.BakedInValidators,
}
// func ExampleValidate_field() {
// // This should be stored somewhere globally validate = validator.New(config)
// var validate *validator.Validate
i := 0
// validate = validator.New("validate", validator.BakedInValidators) errs := validate.Field(i, "gt=1,lte=10")
err := errs[""]
// i := 0 fmt.Println(err.Field)
// err := validate.Field(i, "gt=1,lte=10") fmt.Println(err.Tag)
// fmt.Println(err.Field) fmt.Println(err.Kind) // NOTE: Kind and Type can be different i.e. time Kind=struct and Type=time.Time
// fmt.Println(err.Tag) fmt.Println(err.Type)
// fmt.Println(err.Kind) // NOTE: Kind and Type can be different i.e. time Kind=struct and Type=time.Time fmt.Println(err.Param)
// fmt.Println(err.Type) fmt.Println(err.Value)
// fmt.Println(err.Param) //Output:
// fmt.Println(err.Value) //
// //Output: //gt
// // //int
// //gt //int
// //int //1
// //int //0
// //1 }
// //0
// } func ExampleValidate_struct() {
// This should be stored somewhere globally
// func ExampleValidate_struct() { var validate *validator.Validate
// // This should be stored somewhere globally
// var validate *validator.Validate config := validator.Config{
TagName: "validate",
// validate = validator.New("validate", validator.BakedInValidators) ValidationFuncs: validator.BakedInValidators,
}
// type ContactInformation struct {
// Phone string `validate:"required"` validate = validator.New(config)
// Street string `validate:"required"`
// City string `validate:"required"` type ContactInformation struct {
// } Phone string `validate:"required"`
Street string `validate:"required"`
// type User struct { City string `validate:"required"`
// Name string `validate:"required,excludesall=!@#$%^&*()_+-=:;?/0x2C"` // 0x2C = comma (,) }
// Age int8 `validate:"required,gt=0,lt=150"`
// Email string `validate:"email"` type User struct {
// ContactInformation []*ContactInformation Name string `validate:"required,excludesall=!@#$%^&*()_+-=:;?/0x2C"` // 0x2C = comma (,)
// } Age int8 `validate:"required,gt=0,lt=150"`
Email string `validate:"email"`
// contactInfo := &ContactInformation{ ContactInformation []*ContactInformation
// Street: "26 Here Blvd.", }
// City: "Paradeso",
// } contactInfo := &ContactInformation{
Street: "26 Here Blvd.",
// user := &User{ City: "Paradeso",
// Name: "Joey Bloggs", }
// Age: 31,
// Email: "joeybloggs@gmail.com", user := &User{
// ContactInformation: []*ContactInformation{contactInfo}, Name: "Joey Bloggs",
// } Age: 31,
Email: "joeybloggs@gmail.com",
// structError := validate.Struct(user) ContactInformation: []*ContactInformation{contactInfo},
// for _, fieldError := range structError.Errors { }
// fmt.Println(fieldError.Field) // Phone
// fmt.Println(fieldError.Tag) // required errs := validate.Struct(user)
// //... and so forth for _, v := range errs {
// //Output: fmt.Println(v.Field) // Phone
// //Phone fmt.Println(v.Tag) // required
// //required //... and so forth
// } //Output:
// } //Phone
//required
}
}

@ -59,6 +59,5 @@ var (
) )
func matchesRegex(regex *regexp.Regexp, value string) bool { func matchesRegex(regex *regexp.Regexp, value string) bool {
// fieldAsString := field.(string) //this will panic inherently
return regex.MatchString(value) return regex.MatchString(value)
} }

@ -48,16 +48,36 @@ func newValidationErrors() interface{} {
return map[string]*FieldError{} return map[string]*FieldError{}
} }
// Validate implements the Validate Struct type tagCache struct {
// NOTE: Fields within are not thread safe and that is on purpose tagVals [][]string
// Functions and Tags should all be predifined before use, so subscribe to the philosiphy isOrVal bool
// or make it thread safe on your end }
type tagCacheMap struct {
lock sync.RWMutex
m map[string][]*tagCache
}
func (s *tagCacheMap) Get(key string) ([]*tagCache, bool) {
s.lock.RLock()
defer s.lock.RUnlock()
value, ok := s.m[key]
return value, ok
}
func (s *tagCacheMap) Set(key string, value []*tagCache) {
s.lock.Lock()
defer s.lock.Unlock()
s.m[key] = value
}
// Validate contains the validator settings passed in using the Config struct
type Validate struct { type Validate struct {
config Config config Config
} }
// Config contains the options that Validator with use // Config contains the options that a Validator instance will use.
// passed to the New function // It is passed to the New() function
type Config struct { type Config struct {
TagName string TagName string
ValidationFuncs map[string]Func ValidationFuncs map[string]Func
@ -71,20 +91,21 @@ type Config struct {
type Func func(topStruct reflect.Value, currentStruct reflect.Value, field reflect.Value, fieldtype reflect.Type, fieldKind reflect.Kind, param string) bool type Func func(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 passed from this library // it exists to allow for multiple errors to be passed from this library
// and yet still comply to the error interface // and yet still subscribe to the error interface
type ValidationErrors map[string]*FieldError type ValidationErrors map[string]*FieldError
// This is intended for use in development + debugging and not intended to be a production error message. // 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. // It allows ValidationErrors to subscribe to the Error interface.
// All information to create an error message specific to your application is contained within // All information to create an error message specific to your application is contained within
// the FieldError found in the ValidationErrors // the FieldError found within the ValidationErrors map
func (ve ValidationErrors) Error() string { func (ve ValidationErrors) Error() string {
buff := bytes.NewBufferString("") buff := bytes.NewBufferString("")
for key, err := range ve { for key, err := range ve {
buff.WriteString(fmt.Sprintf(fieldErrMsg, key, err.Field, err.Tag)) buff.WriteString(fmt.Sprintf(fieldErrMsg, key, err.Field, err.Tag))
buff.WriteString("\n")
} }
return strings.TrimSpace(buff.String()) return strings.TrimSpace(buff.String())
@ -107,7 +128,7 @@ func New(config Config) *Validate {
} }
// RegisterValidation adds a validation Func to a Validate's map of validators denoted by the key // RegisterValidation adds a validation Func to a Validate's map of validators denoted by the key
// NOTE: if the key already exists, it will get replaced. // NOTE: if the key already exists, the previous validation function will be replaced.
// NOTE: this method is not thread-safe // NOTE: this method is not thread-safe
func (v *Validate) RegisterValidation(key string, f Func) error { func (v *Validate) RegisterValidation(key string, f Func) error {
@ -124,7 +145,9 @@ func (v *Validate) RegisterValidation(key string, f Func) error {
return nil return nil
} }
// Field allows validation of a single field, still using tag style validation to check multiple errors // Field validates a single field using tag style validation and returns ValidationErrors
// 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) ValidationErrors { func (v *Validate) Field(field interface{}, tag string) ValidationErrors {
errs := errsPool.Get().(map[string]*FieldError) errs := errsPool.Get().(map[string]*FieldError)
@ -140,7 +163,9 @@ func (v *Validate) Field(field interface{}, tag string) ValidationErrors {
return errs return errs
} }
// FieldWithValue allows validation of a single field, possibly even against another fields value, still using tag style validation to check multiple errors // FieldWithValue validates a single field, against another fields value using tag style validation and returns ValidationErrors
// 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) ValidationErrors { func (v *Validate) FieldWithValue(val interface{}, field interface{}, tag string) ValidationErrors {
errs := errsPool.Get().(map[string]*FieldError) errs := errsPool.Get().(map[string]*FieldError)
@ -156,10 +181,7 @@ func (v *Validate) FieldWithValue(val interface{}, field interface{}, tag string
return errs return errs
} }
// Struct validates a struct, even it's nested structs, and returns a struct containing the errors // Struct validates a structs exposed fields, and automatically validates nested structs, unless otherwise specified.
// NOTE: Nested Arrays, or Maps of structs do not get validated only the Array or Map itself; the reason is that there is no good
// way to represent or report which struct within the array has the error, besides can validate the struct prior to adding it to
// the Array or Map.
func (v *Validate) Struct(current interface{}) ValidationErrors { func (v *Validate) Struct(current interface{}) ValidationErrors {
errs := errsPool.Get().(map[string]*FieldError) errs := errsPool.Get().(map[string]*FieldError)
@ -175,6 +197,7 @@ func (v *Validate) Struct(current interface{}) ValidationErrors {
return errs return errs
} }
// 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) {
if current.Kind() == reflect.Ptr && !current.IsNil() { if current.Kind() == reflect.Ptr && !current.IsNil() {
@ -206,29 +229,7 @@ func (v *Validate) tranverseStruct(topStruct reflect.Value, currentStruct reflec
} }
} }
type tagCache struct { // 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
tagVals [][]string
isOrVal bool
}
type tagCacheMap struct {
lock sync.RWMutex
m map[string][]*tagCache
}
func (s *tagCacheMap) Get(key string) ([]*tagCache, bool) {
s.lock.RLock()
defer s.lock.RUnlock()
value, ok := s.m[key]
return value, ok
}
func (s *tagCacheMap) Set(key string, value []*tagCache) {
s.lock.Lock()
defer s.lock.Unlock()
s.m[key] = value
}
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) {
if tag == skipValidationTag { if tag == skipValidationTag {
@ -399,6 +400,7 @@ func (v *Validate) traverseField(topStruct reflect.Value, currentStruct reflect.
} }
} }
// 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) {
for i := 0; i < current.Len(); i++ { for i := 0; i < current.Len(); i++ {
@ -413,6 +415,7 @@ func (v *Validate) traverseSlice(topStruct reflect.Value, currentStruct reflect.
} }
} }
// 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) {
for _, key := range current.MapKeys() { for _, key := range current.MapKeys() {
@ -427,8 +430,7 @@ func (v *Validate) traverseMap(topStruct reflect.Value, currentStruct reflect.Va
} }
} }
// validateField validates a field based on the provided key tag and param and return true if there is an error false if all ok // validateField validates a field based on the provided tag's key and param values and returns true if there is an error or false if all ok
// func (v *Validate) validateField(topStruct reflect.Value, currentStruct reflect.Value, current reflect.Value, currentType reflect.Type, currentKind reflect.Kind, errPrefix string, errs ValidationErrors, key string, param string, name string) bool {
func (v *Validate) validateField(topStruct reflect.Value, currentStruct reflect.Value, current reflect.Value, currentType reflect.Type, currentKind reflect.Kind, errPrefix string, errs ValidationErrors, cTag *tagCache, name string) bool { func (v *Validate) validateField(topStruct reflect.Value, currentStruct reflect.Value, current reflect.Value, currentType reflect.Type, currentKind reflect.Kind, errPrefix string, errs ValidationErrors, cTag *tagCache, name string) bool {
if cTag.isOrVal { if cTag.isOrVal {

Loading…
Cancel
Save