Merge pull request #28 from joeybloggs/v5

changes in preparation for package rename to validator
pull/31/head
Dean Karn 9 years ago
commit 9d1ffaeb87
  1. 13
      README.md
  2. 2
      baked_in.go
  3. 46
      doc.go
  4. 150
      validator.go
  5. 560
      validator_test.go

@ -1,29 +1,30 @@
Package go-validate-yourself
================
[![Build Status](https://travis-ci.org/bluesuncorp/go-validate-yourself.svg?branch=v4)](https://travis-ci.org/bluesuncorp/go-validate-yourself)
[![GoDoc](https://godoc.org/gopkg.in/bluesuncorp/go-validate-yourself.v4?status.svg)](https://godoc.org/gopkg.in/bluesuncorp/go-validate-yourself.v4)
[![Build Status](https://travis-ci.org/bluesuncorp/go-validate-yourself.svg?branch=v5)](https://travis-ci.org/bluesuncorp/go-validate-yourself)
[![GoDoc](https://godoc.org/gopkg.in/bluesuncorp/go-validate-yourself.v5?status.svg)](https://godoc.org/gopkg.in/bluesuncorp/go-validate-yourself.v5)
Package validator implements value validations for structs and individual fields based on tags.
It is even capable of Cross Field and even Cross Field Cross Struct validation.
Installation
============
Just use go get.
go get gopkg.in/bluesuncorp/go-validate-yourself.v4
go get gopkg.in/bluesuncorp/go-validate-yourself.v5
or to update
go get -u gopkg.in/bluesuncorp/go-validate-yourself.v4
go get -u gopkg.in/bluesuncorp/go-validate-yourself.v5
And then just import the package into your own code.
import "gopkg.in/bluesuncorp/go-validate-yourself.v4"
import "gopkg.in/bluesuncorp/go-validate-yourself.v5"
Usage
=====
Please see http://godoc.org/gopkg.in/bluesuncorp/go-validate-yourself.v4 for detailed usage docs.
Please see http://godoc.org/gopkg.in/bluesuncorp/go-validate-yourself.v5 for detailed usage docs.
Contributing
============

@ -11,7 +11,7 @@ import (
// BakedInValidators is the default map of ValidationFunc
// you can add, remove or even replace items to suite your needs,
// or even disregard and use your own map if so desired.
var BakedInValidators = map[string]ValidationFunc{
var BakedInValidators = map[string]Func{
"required": hasValue,
"len": hasLengthOf,
"min": hasMinOf,

@ -1,12 +1,12 @@
/*
Package validator implements value validations for structs and individual fields based on tags. It can also handle Cross Field validation and even Cross Field Cross Struct validation for nested structs.
Built In Validator
Validate
myValidator = validator.NewValidator("validate", validator.BakedInValidators)
validate := validator.New("validate", validator.BakedInValidators)
errs := myValidator.ValidateStruct(//your struct)
valErr := myValidator.ValidateFieldByTag(field, "omitempty,min=1,max=10")
errs := validate.Struct(//your struct)
valErr := validate.Field(field, "omitempty,min=1,max=10")
A simple example usage:
@ -25,17 +25,17 @@ A simple example usage:
}
// errs will contain a hierarchical list of errors
// using the StructValidationErrors struct
// using the StructErrors struct
// or nil if no errors exist
errs := myValidator.ValidateStruct(user)
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 FieldValidationError
errs.Errors will have 1 error of type FieldError
NOTE: Anonymous Structs - they don't have names so expect the Struct name
within StructValidationErrors to be blank.
within StructErrors to be blank.
Error Handling
@ -45,7 +45,7 @@ The error can be used like so
fieldErr.Field // "Name"
fieldErr.ErrorTag // "required"
Both StructValidationErrors and FieldValidationError implement the Error interface but it's
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
@ -67,7 +67,7 @@ I needed to know the field and what validation failed so that I could provide an
}
The hierarchical error structure is hard to work with sometimes.. Agreed Flatten function to the rescue!
Flatten will return a map of FieldValidationError's but the field name will be namespaced.
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"
@ -89,7 +89,7 @@ Custom functions can be added
return true
}
myValidator.AddFunction("custom tag name", customFunc)
validate.AddFunction("custom tag name", customFunc)
// NOTES: using the same tag name as an existing function
// will overwrite the existing one
@ -97,11 +97,11 @@ Cross Field Validation
Cross Field Validation can be implemented, for example Start & End Date range validation
// NOTE: when calling myValidator.validateStruct(val) val will be the top level struct passed
// NOTE: when calling validate.Struct(val) val will be the top level struct passed
// into the function
// when calling myValidator.ValidateFieldByTagAndValue(val, field, tag) val will be
// when calling validate.FieldWithValue(val, field, tag) val will be
// whatever you pass, struct, field...
// when calling myValidator.ValidateFieldByTag(field, tag) val will be nil
// when calling validate.Field(field, tag) val will be nil
//
// 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
@ -225,29 +225,29 @@ Here is a list of the current built in validators:
Only valid for Numbers and time.Time types, this will validate the field value
against another fields value either within a struct or passed in field.
usage examples are for validation of a Start and End date:
Validation on End field using ValidateByStruct Usage(gtfield=Start)
Validating by field ValidateFieldByTagAndValue(start, end, "gtfield")
Validation on End field using validate.Struct Usage(gtfield=Start)
Validating by field validate.FieldWithValue(start, end, "gtfield")
gtefield
Only valid for Numbers and time.Time types, this will validate the field value
against another fields value either within a struct or passed in field.
usage examples are for validation of a Start and End date:
Validation on End field using ValidateByStruct Usage(gtefield=Start)
Validating by field ValidateFieldByTagAndValue(start, end, "gtefield")
Validation on End field using validate.Struct Usage(gtefield=Start)
Validating by field validate.FieldWithValue(start, end, "gtefield")
ltfield
Only valid for Numbers and time.Time types, this will validate the field value
against another fields value either within a struct or passed in field.
usage examples are for validation of a Start and End date:
Validation on End field using ValidateByStruct Usage(ltfield=Start)
Validating by field ValidateFieldByTagAndValue(start, end, "ltfield")
Validation on End field using validate.Struct Usage(ltfield=Start)
Validating by field validate.FieldWithValue(start, end, "ltfield")
ltefield
Only valid for Numbers and time.Time types, this will validate the field value
against another fields value either within a struct or passed in field.
usage examples are for validation of a Start and End date:
Validation on End field using ValidateByStruct Usage(ltefield=Start)
Validating by field ValidateFieldByTagAndValue(start, end, "ltefield")
Validation on End field using validate.Struct Usage(ltefield=Start)
Validating by field validate.FieldWithValue(start, end, "ltefield")
alpha
This validates that a strings value contains alpha characters only
@ -329,6 +329,6 @@ This package panics when bad input is provided, this is by design, bad code like
TestField: "Test"
}
myValidator.ValidateStruct(t) // this will panic
validate.Struct(t) // this will panic
*/
package validator

@ -2,7 +2,7 @@
* Package validator
*
* MISC:
* - anonymous structs - they don't have names so expect the Struct name within StructValidationErrors to be blank
* - anonymous structs - they don't have names so expect the Struct name within StructErrors to be blank
*
*/
@ -19,49 +19,49 @@ import (
)
const (
tagSeparator = ","
orSeparator = "|"
noValidationTag = "-"
tagKeySeparator = "="
structOnlyTag = "structonly"
omitempty = "omitempty"
validationFieldErrMsg = "Field validation for \"%s\" failed on the \"%s\" tag\n"
validationStructErrMsg = "Struct:%s\n"
tagSeparator = ","
orSeparator = "|"
noValidationTag = "-"
tagKeySeparator = "="
structOnlyTag = "structonly"
omitempty = "omitempty"
fieldErrMsg = "Field validation for \"%s\" failed on the \"%s\" tag\n"
structErrMsg = "Struct:%s\n"
)
// FieldValidationError contains a single field's validation error along
// FieldError contains a single field's validation error along
// with other properties that may be needed for error message creation
type FieldValidationError struct {
Field string
ErrorTag string
Kind reflect.Kind
Type reflect.Type
Param string
Value interface{}
type FieldError struct {
Field string
Tag string
Kind reflect.Kind
Type reflect.Type
Param string
Value interface{}
}
// This is intended for use in development + debugging and not intended to be a production error message.
// it also allows FieldValidationError to be used as an Error interface
func (e *FieldValidationError) Error() string {
return fmt.Sprintf(validationFieldErrMsg, e.Field, e.ErrorTag)
// it also allows FieldError to be used as an Error interface
func (e *FieldError) Error() string {
return fmt.Sprintf(fieldErrMsg, e.Field, e.Tag)
}
// StructValidationErrors is hierarchical list of field and struct validation errors
// for a non hierarchical representation please see the Flatten method for StructValidationErrors
type StructValidationErrors struct {
// StructErrors is hierarchical list of field and struct validation errors
// for a non hierarchical representation please see the Flatten method for StructErrors
type StructErrors struct {
// Name of the Struct
Struct string
// Struct Field Errors
Errors map[string]*FieldValidationError
Errors map[string]*FieldError
// Struct Fields of type struct and their errors
// key = Field Name of current struct, but internally Struct will be the actual struct name unless anonymous struct, it will be blank
StructErrors map[string]*StructValidationErrors
StructErrors map[string]*StructErrors
}
// This is intended for use in development + debugging and not intended to be a production error message.
// it also allows StructValidationErrors to be used as an Error interface
func (e *StructValidationErrors) Error() string {
buff := bytes.NewBufferString(fmt.Sprintf(validationStructErrMsg, e.Struct))
// it also allows StructErrors to be used as an Error interface
func (e *StructErrors) Error() string {
buff := bytes.NewBufferString(fmt.Sprintf(structErrMsg, e.Struct))
for _, err := range e.Errors {
buff.WriteString(err.Error())
@ -74,15 +74,15 @@ func (e *StructValidationErrors) Error() string {
return buff.String()
}
// Flatten flattens the StructValidationErrors hierarchical structure into a flat namespace style field name
// Flatten flattens the StructErrors hierarchical structure into a flat namespace style field name
// for those that want/need it
func (e *StructValidationErrors) Flatten() map[string]*FieldValidationError {
func (e *StructErrors) Flatten() map[string]*FieldError {
if e == nil {
return nil
}
errs := map[string]*FieldValidationError{}
errs := map[string]*FieldError{}
for _, f := range e.Errors {
@ -103,27 +103,27 @@ func (e *StructValidationErrors) Flatten() map[string]*FieldValidationError {
return errs
}
// ValidationFunc accepts all values needed for file and cross field validation
// Func accepts all values needed for file and cross field validation
// top = top level struct when validating by struct otherwise nil
// current = current level struct when validating by struct otherwise optional comparison value
// f = field value for validation
// param = parameter used in validation i.e. gt=0 param would be 0
type ValidationFunc func(top interface{}, current interface{}, f interface{}, param string) bool
type Func func(top interface{}, current interface{}, f interface{}, param string) bool
// Validator implements the Validator Struct
// 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 Validator struct {
type Validate struct {
// tagName being used.
tagName string
// validationFuncs is a map of validation functions and the tag keys
validationFuncs map[string]ValidationFunc
// validateFuncs is a map of validation functions and the tag keys
validationFuncs map[string]Func
}
// NewValidator creates a new Validator instance for use.
func NewValidator(tagName string, funcs map[string]ValidationFunc) *Validator {
return &Validator{
// New creates a new Validate instance for use.
func New(tagName string, funcs map[string]Func) *Validate {
return &Validate{
tagName: tagName,
validationFuncs: funcs,
}
@ -131,13 +131,13 @@ func NewValidator(tagName string, funcs map[string]ValidationFunc) *Validator {
// SetTag sets tagName of the Validator to one of your choosing after creation
// perhaps to dodge a tag name conflict in a specific section of code
func (v *Validator) SetTag(tagName string) {
func (v *Validate) SetTag(tagName string) {
v.tagName = tagName
}
// AddFunction adds a ValidationFunc to a Validator's map of validators denoted by the key
// AddFunction 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.
func (v *Validator) AddFunction(key string, f ValidationFunc) error {
func (v *Validate) AddFunction(key string, f Func) error {
if len(key) == 0 {
return errors.New("Function Key cannot be empty")
@ -152,34 +152,34 @@ func (v *Validator) AddFunction(key string, f ValidationFunc) error {
return nil
}
// ValidateStruct validates a struct, even it's nested structs, and returns a struct containing the errors
// 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.
func (v *Validator) ValidateStruct(s interface{}) *StructValidationErrors {
func (v *Validate) Struct(s interface{}) *StructErrors {
return v.validateStructRecursive(s, s, s)
return v.structRecursive(s, s, s)
}
// validateStructRecursive validates a struct recursivly and passes the top level and current struct around for use in validator functions and returns a struct containing the errors
func (v *Validator) validateStructRecursive(top interface{}, current interface{}, s interface{}) *StructValidationErrors {
// structRecursive validates a struct recursivly and passes the top level and current struct around for use in validator functions and returns a struct containing the errors
func (v *Validate) structRecursive(top interface{}, current interface{}, s interface{}) *StructErrors {
structValue := reflect.ValueOf(s)
structType := reflect.TypeOf(s)
structName := structType.Name()
if structValue.Kind() == reflect.Ptr && !structValue.IsNil() {
return v.validateStructRecursive(top, current, structValue.Elem().Interface())
return v.structRecursive(top, current, structValue.Elem().Interface())
}
if structValue.Kind() != reflect.Struct && structValue.Kind() != reflect.Interface {
panic("interface passed for validation is not a struct")
}
validationErrors := &StructValidationErrors{
validationErrors := &StructErrors{
Struct: structName,
Errors: map[string]*FieldValidationError{},
StructErrors: map[string]*StructValidationErrors{},
Errors: map[string]*FieldError{},
StructErrors: map[string]*StructErrors{},
}
var numFields = structValue.NumField()
@ -214,7 +214,7 @@ func (v *Validator) validateStructRecursive(top interface{}, current interface{}
if valueField.Type() == reflect.TypeOf(time.Time{}) {
if fieldError := v.validateFieldByNameAndTagAndValue(top, current, valueField.Interface(), typeField.Name, tag); fieldError != nil {
if fieldError := v.fieldWithNameAndValue(top, current, valueField.Interface(), typeField.Name, tag); fieldError != nil {
validationErrors.Errors[fieldError.Field] = fieldError
// free up memory reference
fieldError = nil
@ -226,7 +226,7 @@ func (v *Validator) validateStructRecursive(top interface{}, current interface{}
continue
}
if structErrors := v.validateStructRecursive(top, valueField.Interface(), valueField.Interface()); structErrors != nil {
if structErrors := v.structRecursive(top, valueField.Interface(), valueField.Interface()); structErrors != nil {
validationErrors.StructErrors[typeField.Name] = structErrors
// free up memory map no longer needed
structErrors = nil
@ -235,7 +235,7 @@ func (v *Validator) validateStructRecursive(top interface{}, current interface{}
default:
if fieldError := v.validateFieldByNameAndTagAndValue(top, current, valueField.Interface(), typeField.Name, tag); fieldError != nil {
if fieldError := v.fieldWithNameAndValue(top, current, valueField.Interface(), typeField.Name, tag); fieldError != nil {
validationErrors.Errors[fieldError.Field] = fieldError
// free up memory reference
fieldError = nil
@ -250,21 +250,21 @@ func (v *Validator) validateStructRecursive(top interface{}, current interface{}
return validationErrors
}
// ValidateFieldByTag allows validation of a single field, still using tag style validation to check multiple errors
func (v *Validator) ValidateFieldByTag(f interface{}, tag string) *FieldValidationError {
// Field allows validation of a single field, still using tag style validation to check multiple errors
func (v *Validate) Field(f interface{}, tag string) *FieldError {
return v.ValidateFieldByTagAndValue(nil, f, tag)
return v.FieldWithValue(nil, f, tag)
}
// ValidateFieldByTagAndValue allows validation of a single field, possibly even against another fields value, still using tag style validation to check multiple errors
func (v *Validator) ValidateFieldByTagAndValue(val interface{}, f interface{}, tag string) *FieldValidationError {
// FieldWithValue allows validation of a single field, possibly even against another fields value, still using tag style validation to check multiple errors
func (v *Validate) FieldWithValue(val interface{}, f interface{}, tag string) *FieldError {
return v.validateFieldByNameAndTagAndValue(nil, val, f, "", tag)
return v.fieldWithNameAndValue(nil, val, f, "", tag)
}
func (v *Validator) validateFieldByNameAndTagAndValue(val interface{}, current interface{}, f interface{}, name string, tag string) *FieldValidationError {
func (v *Validate) fieldWithNameAndValue(val interface{}, current interface{}, f interface{}, name string, tag string) *FieldError {
// This is a double check if coming from ValidateStruct but need to be here in case function is called directly
// This is a double check if coming from validate.Struct but need to be here in case function is called directly
if tag == noValidationTag {
return nil
}
@ -277,7 +277,7 @@ func (v *Validator) validateFieldByNameAndTagAndValue(val interface{}, current i
fieldKind := valueField.Kind()
if fieldKind == reflect.Ptr && !valueField.IsNil() {
return v.validateFieldByNameAndTagAndValue(val, current, valueField.Elem().Interface(), name, tag)
return v.fieldWithNameAndValue(val, current, valueField.Elem().Interface(), name, tag)
}
fieldType := valueField.Type()
@ -291,7 +291,7 @@ func (v *Validator) validateFieldByNameAndTagAndValue(val interface{}, current i
}
}
var valErr *FieldValidationError
var valErr *FieldError
var err error
valTags := strings.Split(tag, tagSeparator)
@ -305,25 +305,25 @@ func (v *Validator) validateFieldByNameAndTagAndValue(val interface{}, current i
for _, val := range orVals {
valErr, err = v.validateFieldByNameAndSingleTag(val, current, f, name, val)
valErr, err = v.fieldWithNameAndSingleTag(val, current, f, name, val)
if err == nil {
return nil
}
errTag += orSeparator + valErr.ErrorTag
errTag += orSeparator + valErr.Tag
}
errTag = strings.TrimLeft(errTag, orSeparator)
valErr.ErrorTag = errTag
valErr.Tag = errTag
valErr.Kind = fieldKind
return valErr
}
if valErr, err = v.validateFieldByNameAndSingleTag(val, current, f, name, valTag); err != nil {
if valErr, err = v.fieldWithNameAndSingleTag(val, current, f, name, valTag); err != nil {
valErr.Kind = valueField.Kind()
valErr.Type = fieldType
@ -335,7 +335,7 @@ func (v *Validator) validateFieldByNameAndTagAndValue(val interface{}, current i
return nil
}
func (v *Validator) validateFieldByNameAndSingleTag(val interface{}, current interface{}, f interface{}, name string, valTag string) (*FieldValidationError, error) {
func (v *Validate) fieldWithNameAndSingleTag(val interface{}, current interface{}, f interface{}, name string, valTag string) (*FieldError, error) {
vals := strings.Split(valTag, tagKeySeparator)
key := strings.Trim(vals[0], " ")
@ -344,11 +344,11 @@ func (v *Validator) validateFieldByNameAndSingleTag(val interface{}, current int
panic(fmt.Sprintf("Invalid validation tag on field %s", name))
}
valErr := &FieldValidationError{
Field: name,
ErrorTag: key,
Value: f,
Param: "",
valErr := &FieldError{
Field: name,
Tag: key,
Value: f,
Param: "",
}
// OK to continue because we checked it's existance before getting into this loop

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