Merge pull request #114 from joeybloggs/v6-development

V6 development
pull/115/head
Dean Karn 9 years ago
commit fbc15ff5c6
  1. 62
      README.md
  2. 577
      baked_in.go
  3. 16
      benchmarks_test.go
  4. 105
      doc.go
  5. 34
      examples/simple.go
  6. 47
      examples_test.go
  7. 5
      regexes.go
  8. 1055
      validator.go
  9. 2147
      validator_test.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

File diff suppressed because it is too large Load Diff

@ -2,13 +2,19 @@ package validator
import "testing" import "testing"
func BenchmarkValidateField(b *testing.B) { func BenchmarkField(b *testing.B) {
for n := 0; n < b.N; n++ { for n := 0; n < b.N; n++ {
validate.Field("1", "len=1") validate.Field("1", "len=1")
} }
} }
func BenchmarkValidateStructSimple(b *testing.B) { func BenchmarkFieldOrTag(b *testing.B) {
for n := 0; n < b.N; n++ {
validate.Field("rgba(0,0,0,1)", "rgb|rgba")
}
}
func BenchmarkStructSimple(b *testing.B) {
type Foo struct { type Foo struct {
StringValue string `validate:"min=5,max=10"` StringValue string `validate:"min=5,max=10"`
@ -24,7 +30,7 @@ func BenchmarkValidateStructSimple(b *testing.B) {
} }
} }
func BenchmarkTemplateParallelSimple(b *testing.B) { func BenchmarkStructSimpleParallel(b *testing.B) {
type Foo struct { type Foo struct {
StringValue string `validate:"min=5,max=10"` StringValue string `validate:"min=5,max=10"`
@ -42,7 +48,7 @@ func BenchmarkTemplateParallelSimple(b *testing.B) {
}) })
} }
func BenchmarkValidateStructLarge(b *testing.B) { func BenchmarkStructComplex(b *testing.B) {
tFail := &TestString{ tFail := &TestString{
Required: "", Required: "",
@ -101,7 +107,7 @@ func BenchmarkValidateStructLarge(b *testing.B) {
} }
} }
func BenchmarkTemplateParallelLarge(b *testing.B) { func BenchmarkStructComplexParallel(b *testing.B) {
tFail := &TestString{ tFail := &TestString{
Required: "", Required: "",

105
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

@ -7,36 +7,28 @@ import (
) )
func ExampleValidate_new() { func ExampleValidate_new() {
validator.New("validate", validator.BakedInValidators) config := validator.Config{
} TagName: "validate",
ValidationFuncs: 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) validator.New(config)
message := "hello"
err := validate.Field(message, "valueishello")
fmt.Println(err)
//Output:
//<nil>
} }
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
validate = validator.New("validate", validator.BakedInValidators) config := validator.Config{
TagName: "validate",
ValidationFuncs: validator.BakedInValidators,
}
validate = validator.New(config)
i := 0 i := 0
err := validate.Field(i, "gt=1,lte=10") errs := validate.Field(i, "gt=1,lte=10")
err := errs[""]
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
@ -56,7 +48,12 @@ func ExampleValidate_struct() {
// This should be stored somewhere globally // This should be stored somewhere globally
var validate *validator.Validate var validate *validator.Validate
validate = validator.New("validate", validator.BakedInValidators) config := validator.Config{
TagName: "validate",
ValidationFuncs: validator.BakedInValidators,
}
validate = validator.New(config)
type ContactInformation struct { type ContactInformation struct {
Phone string `validate:"required"` Phone string `validate:"required"`
@ -83,10 +80,10 @@ func ExampleValidate_struct() {
ContactInformation: []*ContactInformation{contactInfo}, ContactInformation: []*ContactInformation{contactInfo},
} }
structError := validate.Struct(user) errs := validate.Struct(user)
for _, fieldError := range structError.Errors { for _, v := range errs {
fmt.Println(fieldError.Field) // Phone fmt.Println(v.Field) // Phone
fmt.Println(fieldError.Tag) // required fmt.Println(v.Tag) // required
//... and so forth //... and so forth
//Output: //Output:
//Phone //Phone

@ -58,7 +58,6 @@ var (
sSNRegex = regexp.MustCompile(sSNRegexString) sSNRegex = regexp.MustCompile(sSNRegexString)
) )
func matchesRegex(regex *regexp.Regexp, field interface{}) bool { func matchesRegex(regex *regexp.Regexp, value string) bool {
fieldAsString := field.(string) //this will panic inherently return regex.MatchString(value)
return regex.MatchString(fieldAsString)
} }

File diff suppressed because it is too large Load Diff

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