Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
103 changes: 90 additions & 13 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@ It has the following **unique** features:

- Cross Field and Cross Struct validations.
- Slice, Array and Map diving, which allows any or all levels of a multidimensional field to be validated.
- Handles type interface by determining it's underlying type prior to validation.
- Handles type interface by determining it's underlying type prior to validation.
- Handles custom field types such as sql driver Valuer see [Valuer](https://golang.org/src/database/sql/driver/types.go?s=1210:1293#L29)

Installation
------------
Expand All @@ -35,6 +36,8 @@ Usage and documentation
Please see http://godoc.org/gopkg.in/bluesuncorp/validator.v6 for detailed usage docs.

##### Examples:

Struct & Field validation
```go
package main

Expand Down Expand Up @@ -130,6 +133,76 @@ func validateField() {
}
```

Custom Field Type
```go
package main

import (
"errors"
"fmt"
"reflect"

sql "database/sql/driver"

"gopkg.in/bluesuncorp/validator.v6"
)

var validate *validator.Validate

type valuer struct {
Name string
}

func (v valuer) Value() (sql.Value, error) {

if v.Name == "errorme" {
return nil, errors.New("some kind of error")
}

if v.Name == "blankme" {
return "", nil
}

if len(v.Name) == 0 {
return nil, nil
}

return v.Name, nil
}

func main() {

customTypes := map[reflect.Type]validator.CustomTypeFunc{}
customTypes[reflect.TypeOf((*sql.Valuer)(nil))] = ValidateValuerType
customTypes[reflect.TypeOf(valuer{})] = ValidateValuerType

config := validator.Config{
TagName: "validate",
ValidationFuncs: validator.BakedInValidators,
CustomTypeFuncs: customTypes,
}

validate = validator.New(config)

validateCustomFieldType()
}

func validateCustomFieldType() {
val := valuer{
Name: "blankme",
}

errs := validate.Field(val, "required")
if errs != nil {
fmt.Println(errs) // output: Key: "" Error:Field validation for "" failed on the "required" tag
return
}

// all ok
}

```

Benchmarks
------
###### Run on MacBook Pro (Retina, 15-inch, Late 2013) 2.6 GHz Intel Core i7 16 GB 1600 MHz DDR3
Expand All @@ -139,18 +212,22 @@ hurt parallel performance too much.
```go
$ go test -cpu=4 -bench=. -benchmem=true
PASS
BenchmarkFieldSuccess-4 5000000 326 ns/op 16 B/op 1 allocs/op
BenchmarkFieldFailure-4 5000000 327 ns/op 16 B/op 1 allocs/op
BenchmarkFieldOrTagSuccess-4 500000 2738 ns/op 20 B/op 2 allocs/op
BenchmarkFieldOrTagFailure-4 1000000 1341 ns/op 384 B/op 6 allocs/op
BenchmarkStructSimpleSuccess-4 1000000 1282 ns/op 24 B/op 3 allocs/op
BenchmarkStructSimpleFailure-4 1000000 1870 ns/op 529 B/op 11 allocs/op
BenchmarkStructSimpleSuccessParallel-4 5000000 348 ns/op 24 B/op 3 allocs/op
BenchmarkStructSimpleFailureParallel-4 2000000 807 ns/op 529 B/op 11 allocs/op
BenchmarkStructComplexSuccess-4 200000 8081 ns/op 368 B/op 30 allocs/op
BenchmarkStructComplexFailure-4 100000 12418 ns/op 2861 B/op 72 allocs/op
BenchmarkStructComplexSuccessParallel-4 500000 2249 ns/op 369 B/op 30 allocs/op
BenchmarkStructComplexFailureParallel-4 300000 5183 ns/op 2863 B/op 72 allocs/op
BenchmarkFieldSuccess-4 5000000 318 ns/op 16 B/op 1 allocs/op
BenchmarkFieldFailure-4 5000000 316 ns/op 16 B/op 1 allocs/op
BenchmarkFieldCustomTypeSuccess-4 3000000 492 ns/op 32 B/op 2 allocs/op
BenchmarkFieldCustomTypeFailure-4 2000000 843 ns/op 416 B/op 6 allocs/op
BenchmarkFieldOrTagSuccess-4 500000 2384 ns/op 20 B/op 2 allocs/op
BenchmarkFieldOrTagFailure-4 1000000 1295 ns/op 384 B/op 6 allocs/op
BenchmarkStructSimpleSuccess-4 1000000 1175 ns/op 24 B/op 3 allocs/op
BenchmarkStructSimpleFailure-4 1000000 1822 ns/op 529 B/op 11 allocs/op
BenchmarkStructSimpleCustomTypeSuccess-4 1000000 1302 ns/op 56 B/op 5 allocs/op
BenchmarkStructSimpleCustomTypeFailure-4 1000000 1847 ns/op 577 B/op 13 allocs/op
BenchmarkStructSimpleSuccessParallel-4 5000000 339 ns/op 24 B/op 3 allocs/op
BenchmarkStructSimpleFailureParallel-4 2000000 733 ns/op 529 B/op 11 allocs/op
BenchmarkStructComplexSuccess-4 200000 7104 ns/op 368 B/op 30 allocs/op
BenchmarkStructComplexFailure-4 100000 11996 ns/op 2861 B/op 72 allocs/op
BenchmarkStructComplexSuccessParallel-4 1000000 2252 ns/op 368 B/op 30 allocs/op
BenchmarkStructComplexFailureParallel-4 300000 4691 ns/op 2862 B/op 72 allocs/op
```

How to Contribute
Expand Down
84 changes: 83 additions & 1 deletion benchmarks_test.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
package validator

import "testing"
import (
sql "database/sql/driver"
"reflect"
"testing"
)

func BenchmarkFieldSuccess(b *testing.B) {
for n := 0; n < b.N; n++ {
Expand All @@ -14,6 +18,38 @@ func BenchmarkFieldFailure(b *testing.B) {
}
}

func BenchmarkFieldCustomTypeSuccess(b *testing.B) {

customTypes := map[reflect.Type]CustomTypeFunc{}
customTypes[reflect.TypeOf((*sql.Valuer)(nil))] = ValidateValuerType
customTypes[reflect.TypeOf(valuer{})] = ValidateValuerType

validate := New(Config{TagName: "validate", ValidationFuncs: BakedInValidators, CustomTypeFuncs: customTypes})

val := valuer{
Name: "1",
}

for n := 0; n < b.N; n++ {
validate.Field(val, "len=1")
}
}

func BenchmarkFieldCustomTypeFailure(b *testing.B) {

customTypes := map[reflect.Type]CustomTypeFunc{}
customTypes[reflect.TypeOf((*sql.Valuer)(nil))] = ValidateValuerType
customTypes[reflect.TypeOf(valuer{})] = ValidateValuerType

validate := New(Config{TagName: "validate", ValidationFuncs: BakedInValidators, CustomTypeFuncs: customTypes})

val := valuer{}

for n := 0; n < b.N; n++ {
validate.Field(val, "len=1")
}
}

func BenchmarkFieldOrTagSuccess(b *testing.B) {
for n := 0; n < b.N; n++ {
validate.Field("rgba(0,0,0,1)", "rgb|rgba")
Expand Down Expand Up @@ -54,6 +90,52 @@ func BenchmarkStructSimpleFailure(b *testing.B) {
}
}

func BenchmarkStructSimpleCustomTypeSuccess(b *testing.B) {

customTypes := map[reflect.Type]CustomTypeFunc{}
customTypes[reflect.TypeOf((*sql.Valuer)(nil))] = ValidateValuerType
customTypes[reflect.TypeOf(valuer{})] = ValidateValuerType

validate := New(Config{TagName: "validate", ValidationFuncs: BakedInValidators, CustomTypeFuncs: customTypes})

val := valuer{
Name: "1",
}

type Foo struct {
Valuer valuer `validate:"len=1"`
IntValue int `validate:"min=5,max=10"`
}

validFoo := &Foo{Valuer: val, IntValue: 7}

for n := 0; n < b.N; n++ {
validate.Struct(validFoo)
}
}

func BenchmarkStructSimpleCustomTypeFailure(b *testing.B) {

customTypes := map[reflect.Type]CustomTypeFunc{}
customTypes[reflect.TypeOf((*sql.Valuer)(nil))] = ValidateValuerType
customTypes[reflect.TypeOf(valuer{})] = ValidateValuerType

validate := New(Config{TagName: "validate", ValidationFuncs: BakedInValidators, CustomTypeFuncs: customTypes})

val := valuer{}

type Foo struct {
Valuer valuer `validate:"len=1"`
IntValue int `validate:"min=5,max=10"`
}

validFoo := &Foo{Valuer: val, IntValue: 3}

for n := 0; n < b.N; n++ {
validate.Struct(validFoo)
}
}

func BenchmarkStructSimpleSuccessParallel(b *testing.B) {

type Foo struct {
Expand Down
58 changes: 58 additions & 0 deletions examples/simple.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
package main

import (
"errors"
"fmt"
"reflect"

sql "database/sql/driver"

"gopkg.in/bluesuncorp/validator.v6"
)
Expand Down Expand Up @@ -90,3 +94,57 @@ func validateField() {

// email ok, move on
}

var validate2 *validator.Validate

type valuer struct {
Name string
}

func (v valuer) Value() (sql.Value, error) {

if v.Name == "errorme" {
return nil, errors.New("some kind of error")
}

if v.Name == "blankme" {
return "", nil
}

if len(v.Name) == 0 {
return nil, nil
}

return v.Name, nil
}

func main2() {

customTypes := map[reflect.Type]validator.CustomTypeFunc{}
customTypes[reflect.TypeOf((*sql.Valuer)(nil))] = ValidateValuerType
customTypes[reflect.TypeOf(valuer{})] = ValidateValuerType

config := validator.Config{
TagName: "validate",
ValidationFuncs: validator.BakedInValidators,
CustomTypeFuncs: customTypes,
}

validate2 = validator.New(config)

validateCustomFieldType()
}

func validateCustomFieldType() {
val := valuer{
Name: "blankme",
}

errs := validate2.Field(val, "required")
if errs != nil {
fmt.Println(errs) // output: Key: "" Error:Field validation for "" failed on the "required" tag
return
}

// all ok
}
34 changes: 30 additions & 4 deletions validator.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ var (

// returns new ValidationErrors to the pool
func newValidationErrors() interface{} {
return map[string]*FieldError{}
return ValidationErrors{}
}

type tagCache struct {
Expand Down Expand Up @@ -81,8 +81,15 @@ type Validate struct {
type Config struct {
TagName string
ValidationFuncs map[string]Func
CustomTypeFuncs map[reflect.Type]CustomTypeFunc
hasCustomFuncs bool
}

// CustomTypeFunc allows for overriding or adding custom field type handler functions
// field = field value of the type to return a value to be validated
// example Valuer from sql drive see https://golang.org/src/database/sql/driver/types.go?s=1210:1293#L29
type CustomTypeFunc func(field reflect.Value) interface{}

// Func accepts all values needed for file and cross field validation
// topStruct = top level struct when validating by struct otherwise nil
// currentStruct = current level struct when validating by struct otherwise optional comparison value
Expand Down Expand Up @@ -124,6 +131,11 @@ type FieldError struct {

// New creates a new Validate instance for use.
func New(config Config) *Validate {

if config.CustomTypeFuncs != nil && len(config.CustomTypeFuncs) > 0 {
config.hasCustomFuncs = true
}

return &Validate{config: config}
}

Expand All @@ -150,7 +162,7 @@ func (v *Validate) RegisterValidation(key string, f Func) error {
// 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)
errs := errsPool.Get().(ValidationErrors)
fieldVal := reflect.ValueOf(field)

v.traverseField(fieldVal, fieldVal, fieldVal, "", errs, false, tag, "")
Expand All @@ -168,7 +180,7 @@ func (v *Validate) Field(field interface{}, tag string) ValidationErrors {
// 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)
errs := errsPool.Get().(ValidationErrors)
topVal := reflect.ValueOf(val)

v.traverseField(topVal, topVal, reflect.ValueOf(field), "", errs, false, tag, "")
Expand All @@ -184,7 +196,7 @@ func (v *Validate) FieldWithValue(val interface{}, field interface{}, tag string
// 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)
errs := errsPool.Get().(ValidationErrors)
sv := reflect.ValueOf(current)

v.tranverseStruct(sv, sv, sv, "", errs, true)
Expand Down Expand Up @@ -316,6 +328,13 @@ func (v *Validate) traverseField(topStruct reflect.Value, currentStruct reflect.

if kind == reflect.Struct {

if v.config.hasCustomFuncs {
if fn, ok := v.config.CustomTypeFuncs[typ]; ok {
v.traverseField(topStruct, currentStruct, reflect.ValueOf(fn(current)), errPrefix, errs, isStructField, tag, name)
return
}
}

// required passed validation above so stop here
// if only validating the structs existance.
if strings.Contains(tag, structOnlyTag) {
Expand All @@ -334,6 +353,13 @@ func (v *Validate) traverseField(topStruct reflect.Value, currentStruct reflect.
}
}

if v.config.hasCustomFuncs {
if fn, ok := v.config.CustomTypeFuncs[typ]; ok {
v.traverseField(topStruct, currentStruct, reflect.ValueOf(fn(current)), errPrefix, errs, isStructField, tag, name)
return
}
}

tags, isCached := tagsCache.Get(tag)

if !isCached {
Expand Down
Loading