Merge pull request #115 from bluesuncorp/v6-development

V6 development
pull/116/head
Dean Karn 9 years ago
commit 2a2b70052e
  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)
[![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://semaphoreci.com/api/v1/projects/ec20115f-ef1b-4c7d-9393-cc76aba74eb4/487374/badge.svg)](https://semaphoreci.com/joeybloggs/validator)
[![Coverage Status](https://coveralls.io/repos/bluesuncorp/validator/badge.svg?branch=v6)](https://coveralls.io/r/bluesuncorp/validator?branch=v6)
[![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

File diff suppressed because it is too large Load Diff

@ -2,13 +2,19 @@ package validator
import "testing"
func BenchmarkValidateField(b *testing.B) {
func BenchmarkField(b *testing.B) {
for n := 0; n < b.N; n++ {
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 {
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 {
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{
Required: "",
@ -101,7 +107,7 @@ func BenchmarkValidateStructLarge(b *testing.B) {
}
}
func BenchmarkTemplateParallelLarge(b *testing.B) {
func BenchmarkStructComplexParallel(b *testing.B) {
tFail := &TestString{
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
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)

@ -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

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

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

File diff suppressed because it is too large Load Diff

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