diff --git a/README.md b/README.md index 36ac9da..7f5af15 100644 --- a/README.md +++ b/README.md @@ -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) -[![Build Status](https://travis-ci.org/bluesuncorp/validator.svg?branch=v5.1)](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) -[![GoDoc](https://godoc.org/gopkg.in/bluesuncorp/validator.v5?status.svg)](https://godoc.org/gopkg.in/bluesuncorp/validator.v5) +[![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=v6)](https://coveralls.io/r/bluesuncorp/validator?branch=v6) +[![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. @@ -19,20 +19,20 @@ Installation Use go get. - go get gopkg.in/bluesuncorp/validator.v5 + go get gopkg.in/bluesuncorp/validator.v6 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. - import "gopkg.in/bluesuncorp/validator.v5" + import "gopkg.in/bluesuncorp/validator.v6" 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: ```go @@ -41,7 +41,7 @@ package main import ( "fmt" - "gopkg.in/bluesuncorp/validator.v5" + "gopkg.in/bluesuncorp/validator.v6" ) // User contains user information @@ -66,7 +66,12 @@ var validate *validator.Validate func main() { - validate = validator.New("validate", validator.BakedInValidators) + config := validator.Config{ + TagName: "validate", + ValidationFuncs: validator.BakedInValidators, + } + + validate = validator.New(config) address := &Address{ Street: "Eavesdown Docks", @@ -83,31 +88,14 @@ func main() { Addresses: []*Address{address}, } - // returns nil or *StructErrors + // returns nil or ValidationErrors ( map[string]*FieldError ) errs := validate.Struct(user) if errs != nil { - // err will be of type *FieldError - err := errs.Errors["Age"] - fmt.Println(err.Error()) // output: Field validation for "Age" failed on the "lte" tag - 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(errs) // output: Key: "User.Age" Error:Field validation for "Age" failed on the "lte" tag + // Key: "User.Addresses[0].City" Error:Field validation for "City" failed on the "required" tag + err := errs["User.Addresses[0].City"] fmt.Println(err.Field) // output: City fmt.Println(err.Tag) // output: required fmt.Println(err.Kind) // output: string @@ -126,14 +114,18 @@ func main() { Benchmarks ------ ###### 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 test -cpu=4 -bench=. -benchmem=true PASS -BenchmarkValidateField-4 3000000 429 ns/op 192 B/op 2 allocs/op -BenchmarkValidateStructSimple-4 500000 2877 ns/op 657 B/op 10 allocs/op -BenchmarkTemplateParallelSimple-4 500000 3097 ns/op 657 B/op 10 allocs/op -BenchmarkValidateStructLarge-4 100000 15228 ns/op 4350 B/op 62 allocs/op -BenchmarkTemplateParallelLarge-4 100000 14257 ns/op 4354 B/op 62 allocs/op +BenchmarkField-4 5000000 314 ns/op 16 B/op 1 allocs/op +BenchmarkFieldOrTag-4 500000 2425 ns/op 20 B/op 2 allocs/op +BenchmarkStructSimple-4 500000 3117 ns/op 553 B/op 14 allocs/op +BenchmarkStructSimpleParallel-4 1000000 1149 ns/op 553 B/op 14 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 diff --git a/doc.go b/doc.go index f45ef34..df2db9f 100644 --- a/doc.go +++ b/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 - - 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 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 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" } -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 can be added - //Structure - func customFunc(top interface{}, current interface{}, field interface{}, param string) bool { + // Structure + func customFunc(topStruct reflect.Value, currentStruct reflect.Value, field reflect.Value, fieldType reflect.Type, fieldKind reflect.Kind, param string) bool { if whatever { return false @@ -89,7 +30,7 @@ Custom functions can be added return true } - validate.AddFunction("custom tag name", customFunc) + validate.RegisterValidation("custom tag name", customFunc) // NOTES: using the same tag name as an existing function // 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 - // 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 // when calling validate.FieldWithValue(val, field, tag) val will be // 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 // uses this library it is likely that custom functions will need to be created for your // Cross Field Validation needs, however there are some build in Generic Cross Field validations, - // see Baked In Validators 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 - } + // see Baked In Validators eqfield, nefield, gtfield, gtefield, ltfield, ltefield and Tags below Multiple Validators @@ -135,7 +65,7 @@ Bad Validator definitions are not handled by the library 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 @@ -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 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: - @@ -162,14 +97,14 @@ Here is a list of the current built in validators: structonly 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 - none of the nested struct fields will be validated. This is usefull if - inside of you program you know the struct will be valid, but need to - verify it has been assigned. + any validation on the nested struct will be run, but none of the nested + struct fields will be validated. This is usefull if inside of you program + you know the struct will be valid, but need to verify it has been assigned. + NOTE: only "required" and "omitempty" can be used on a struct itself. omitempty 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. (Usage: omitempty) diff --git a/examples/simple.go b/examples/simple.go index 59cb1a9..98dabed 100644 --- a/examples/simple.go +++ b/examples/simple.go @@ -3,7 +3,7 @@ package main import ( "fmt" - "gopkg.in/bluesuncorp/validator.v5" + "gopkg.in/bluesuncorp/validator.v6" ) // User contains user information @@ -28,7 +28,12 @@ var validate *validator.Validate func main() { - validate = validator.New("validate", validator.BakedInValidators) + config := validator.Config{ + TagName: "validate", + ValidationFuncs: validator.BakedInValidators, + } + + validate = validator.New(config) address := &Address{ Street: "Eavesdown Docks", @@ -45,31 +50,14 @@ func main() { Addresses: []*Address{address}, } - // returns nil or *StructErrors + // returns nil or ValidationErrors ( map[string]*FieldError ) errs := validate.Struct(user) if errs != nil { - // err will be of type *FieldError - err := errs.Errors["Age"] - fmt.Println(err.Error()) // output: Field validation for "Age" failed on the "lte" tag - 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(errs) // output: Key: "User.Age" Error:Field validation for "Age" failed on the "lte" tag + // Key: "User.Addresses[0].City" Error:Field validation for "City" failed on the "required" tag + err := errs["User.Addresses[0].City"] fmt.Println(err.Field) // output: City fmt.Println(err.Tag) // output: required fmt.Println(err.Kind) // output: string diff --git a/examples_test.go b/examples_test.go index a2aca52..9d01450 100644 --- a/examples_test.go +++ b/examples_test.go @@ -1,89 +1,92 @@ package validator_test -// func ExampleValidate_new() { -// validator.New("validate", validator.BakedInValidators) -// } - -// func ExampleValidate_addFunction() { -// // This should be stored somewhere globally -// var validate *validator.Validate - -// validate = validator.New("validate", validator.BakedInValidators) - -// fn := func(top interface{}, current interface{}, field interface{}, param string) bool { -// return field.(string) == "hello" -// } - -// validate.AddFunction("valueishello", fn) - -// message := "hello" -// err := validate.Field(message, "valueishello") -// fmt.Println(err) -// //Output: -// // -// } - -// func ExampleValidate_field() { -// // This should be stored somewhere globally -// var validate *validator.Validate - -// validate = validator.New("validate", validator.BakedInValidators) - -// i := 0 -// err := validate.Field(i, "gt=1,lte=10") -// fmt.Println(err.Field) -// 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.Type) -// fmt.Println(err.Param) -// fmt.Println(err.Value) -// //Output: -// // -// //gt -// //int -// //int -// //1 -// //0 -// } - -// func ExampleValidate_struct() { -// // This should be stored somewhere globally -// var validate *validator.Validate - -// validate = validator.New("validate", validator.BakedInValidators) - -// type ContactInformation struct { -// Phone string `validate:"required"` -// Street string `validate:"required"` -// City string `validate:"required"` -// } - -// type User struct { -// Name string `validate:"required,excludesall=!@#$%^&*()_+-=:;?/0x2C"` // 0x2C = comma (,) -// Age int8 `validate:"required,gt=0,lt=150"` -// Email string `validate:"email"` -// ContactInformation []*ContactInformation -// } - -// contactInfo := &ContactInformation{ -// Street: "26 Here Blvd.", -// City: "Paradeso", -// } - -// user := &User{ -// Name: "Joey Bloggs", -// Age: 31, -// Email: "joeybloggs@gmail.com", -// ContactInformation: []*ContactInformation{contactInfo}, -// } - -// structError := validate.Struct(user) -// for _, fieldError := range structError.Errors { -// fmt.Println(fieldError.Field) // Phone -// fmt.Println(fieldError.Tag) // required -// //... and so forth -// //Output: -// //Phone -// //required -// } -// } +import ( + "fmt" + + "../validator" +) + +func ExampleValidate_new() { + config := validator.Config{ + TagName: "validate", + ValidationFuncs: validator.BakedInValidators, + } + + validator.New(config) +} + +func ExampleValidate_field() { + // This should be stored somewhere globally + var validate *validator.Validate + + config := validator.Config{ + TagName: "validate", + ValidationFuncs: validator.BakedInValidators, + } + + validate = validator.New(config) + + i := 0 + errs := validate.Field(i, "gt=1,lte=10") + err := errs[""] + fmt.Println(err.Field) + 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.Type) + fmt.Println(err.Param) + fmt.Println(err.Value) + //Output: + // + //gt + //int + //int + //1 + //0 +} + +func ExampleValidate_struct() { + // This should be stored somewhere globally + var validate *validator.Validate + + config := validator.Config{ + TagName: "validate", + ValidationFuncs: validator.BakedInValidators, + } + + validate = validator.New(config) + + type ContactInformation struct { + Phone string `validate:"required"` + Street string `validate:"required"` + City string `validate:"required"` + } + + type User struct { + Name string `validate:"required,excludesall=!@#$%^&*()_+-=:;?/0x2C"` // 0x2C = comma (,) + Age int8 `validate:"required,gt=0,lt=150"` + Email string `validate:"email"` + ContactInformation []*ContactInformation + } + + contactInfo := &ContactInformation{ + Street: "26 Here Blvd.", + City: "Paradeso", + } + + user := &User{ + Name: "Joey Bloggs", + Age: 31, + Email: "joeybloggs@gmail.com", + ContactInformation: []*ContactInformation{contactInfo}, + } + + errs := validate.Struct(user) + for _, v := range errs { + fmt.Println(v.Field) // Phone + fmt.Println(v.Tag) // required + //... and so forth + //Output: + //Phone + //required + } +} diff --git a/regexes.go b/regexes.go index e3f420e..d061b03 100644 --- a/regexes.go +++ b/regexes.go @@ -59,6 +59,5 @@ var ( ) func matchesRegex(regex *regexp.Regexp, value string) bool { - // fieldAsString := field.(string) //this will panic inherently return regex.MatchString(value) } diff --git a/validator.go b/validator.go index db665d8..6c44f2b 100644 --- a/validator.go +++ b/validator.go @@ -48,16 +48,36 @@ func newValidationErrors() interface{} { return map[string]*FieldError{} } -// Validate implements the Validate Struct -// NOTE: Fields within are not thread safe and that is on purpose -// Functions and Tags should all be predifined before use, so subscribe to the philosiphy -// or make it thread safe on your end +type tagCache struct { + 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 +} + +// Validate contains the validator settings passed in using the Config struct type Validate struct { config Config } -// Config contains the options that Validator with use -// passed to the New function +// Config contains the options that a Validator instance will use. +// It is passed to the New() function type Config struct { TagName string 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 // ValidationErrors is a type of map[string]*FieldError -// it exists to allow for multiple errors passed from this library -// and yet still comply to the error interface +// 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 -// 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. // 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 { buff := bytes.NewBufferString("") for key, err := range ve { buff.WriteString(fmt.Sprintf(fieldErrMsg, key, err.Field, err.Tag)) + buff.WriteString("\n") } 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 -// 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 func (v *Validate) RegisterValidation(key string, f Func) error { @@ -124,7 +145,9 @@ func (v *Validate) RegisterValidation(key string, f Func) error { 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 { errs := errsPool.Get().(map[string]*FieldError) @@ -140,7 +163,9 @@ func (v *Validate) Field(field interface{}, tag string) ValidationErrors { 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 { errs := errsPool.Get().(map[string]*FieldError) @@ -156,10 +181,7 @@ func (v *Validate) FieldWithValue(val interface{}, field interface{}, tag string return errs } -// Struct validates a struct, even it's nested structs, and returns a struct containing the errors -// 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. +// Struct validates a structs exposed fields, and automatically validates nested structs, unless otherwise specified. func (v *Validate) Struct(current interface{}) ValidationErrors { errs := errsPool.Get().(map[string]*FieldError) @@ -175,6 +197,7 @@ func (v *Validate) Struct(current interface{}) ValidationErrors { 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) { if current.Kind() == reflect.Ptr && !current.IsNil() { @@ -206,29 +229,7 @@ func (v *Validate) tranverseStruct(topStruct reflect.Value, currentStruct reflec } } -type tagCache struct { - 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 -} - +// traverseField validates any field, be it a struct or single field, ensures it's validity and passes it along to be validated via it's tag options func (v *Validate) traverseField(topStruct reflect.Value, currentStruct reflect.Value, current reflect.Value, errPrefix string, errs ValidationErrors, isStructField bool, tag string, name string) { 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) { 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) { 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 -// 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 { +// 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, cTag *tagCache, name string) bool { if cTag.isOrVal {