v2 add cross field validation ability

pull/16/head v2.0
Dean Karn 10 years ago
parent 5a0fdabb75
commit 191b1ce01f
  1. 10
      README.md
  2. 46
      baked_in.go
  3. 30
      doc.go
  4. 121
      validator.go
  5. 26
      validator_test.go

@ -1,6 +1,6 @@
Package go-validate-yourself
================
[![Build Status](https://travis-ci.org/joeybloggs/go-validate-yourself.svg?branch=v2-development)](https://travis-ci.org/joeybloggs/go-validate-yourself)
[![Build Status](https://travis-ci.org/joeybloggs/go-validate-yourself.svg?branch=v2)](https://travis-ci.org/joeybloggs/go-validate-yourself)
Package validator implements value validations for structs and individual fields based on tags.
@ -9,20 +9,20 @@ Installation
Just use go get.
go get gopkg.in/joeybloggs/go-validate-yourself.v1
go get gopkg.in/joeybloggs/go-validate-yourself.v2
or to update
go get -u gopkg.in/joeybloggs/go-validate-yourself.v1
go get -u gopkg.in/joeybloggs/go-validate-yourself.v2
And then just import the package into your own code.
import "gopkg.in/joeybloggs/go-validate-yourself.v1"
import "gopkg.in/joeybloggs/go-validate-yourself.v2"
Usage
=====
Please see http://godoc.org/gopkg.in/joeybloggs/go-validate-yourself.v1 for detailed usage docs.
Please see http://godoc.org/gopkg.in/joeybloggs/go-validate-yourself.v2 for detailed usage docs.
Contributing
============

@ -33,7 +33,7 @@ var BakedInValidators = map[string]ValidationFunc{
"uri": isURI,
}
func isURI(field interface{}, param string) bool {
func isURI(val interface{}, field interface{}, param string) bool {
st := reflect.ValueOf(field)
@ -48,7 +48,7 @@ func isURI(field interface{}, param string) bool {
}
}
func isURL(field interface{}, param string) bool {
func isURL(val interface{}, field interface{}, param string) bool {
st := reflect.ValueOf(field)
@ -72,7 +72,7 @@ func isURL(field interface{}, param string) bool {
}
}
func isEmail(field interface{}, param string) bool {
func isEmail(val interface{}, field interface{}, param string) bool {
st := reflect.ValueOf(field)
@ -85,7 +85,7 @@ func isEmail(field interface{}, param string) bool {
}
}
func isHsla(field interface{}, param string) bool {
func isHsla(val interface{}, field interface{}, param string) bool {
st := reflect.ValueOf(field)
@ -98,7 +98,7 @@ func isHsla(field interface{}, param string) bool {
}
}
func isHsl(field interface{}, param string) bool {
func isHsl(val interface{}, field interface{}, param string) bool {
st := reflect.ValueOf(field)
@ -111,7 +111,7 @@ func isHsl(field interface{}, param string) bool {
}
}
func isRgba(field interface{}, param string) bool {
func isRgba(val interface{}, field interface{}, param string) bool {
st := reflect.ValueOf(field)
@ -124,7 +124,7 @@ func isRgba(field interface{}, param string) bool {
}
}
func isRgb(field interface{}, param string) bool {
func isRgb(val interface{}, field interface{}, param string) bool {
st := reflect.ValueOf(field)
@ -137,7 +137,7 @@ func isRgb(field interface{}, param string) bool {
}
}
func isHexcolor(field interface{}, param string) bool {
func isHexcolor(val interface{}, field interface{}, param string) bool {
st := reflect.ValueOf(field)
@ -150,7 +150,7 @@ func isHexcolor(field interface{}, param string) bool {
}
}
func isHexadecimal(field interface{}, param string) bool {
func isHexadecimal(val interface{}, field interface{}, param string) bool {
st := reflect.ValueOf(field)
@ -163,7 +163,7 @@ func isHexadecimal(field interface{}, param string) bool {
}
}
func isNumber(field interface{}, param string) bool {
func isNumber(val interface{}, field interface{}, param string) bool {
st := reflect.ValueOf(field)
@ -176,7 +176,7 @@ func isNumber(field interface{}, param string) bool {
}
}
func isNumeric(field interface{}, param string) bool {
func isNumeric(val interface{}, field interface{}, param string) bool {
st := reflect.ValueOf(field)
@ -189,7 +189,7 @@ func isNumeric(field interface{}, param string) bool {
}
}
func isAlphanum(field interface{}, param string) bool {
func isAlphanum(val interface{}, field interface{}, param string) bool {
st := reflect.ValueOf(field)
@ -202,7 +202,7 @@ func isAlphanum(field interface{}, param string) bool {
}
}
func isAlpha(field interface{}, param string) bool {
func isAlpha(val interface{}, field interface{}, param string) bool {
st := reflect.ValueOf(field)
@ -215,7 +215,7 @@ func isAlpha(field interface{}, param string) bool {
}
}
func hasValue(field interface{}, param string) bool {
func hasValue(val interface{}, field interface{}, param string) bool {
st := reflect.ValueOf(field)
@ -229,7 +229,7 @@ func hasValue(field interface{}, param string) bool {
}
}
func isGte(field interface{}, param string) bool {
func isGte(val interface{}, field interface{}, param string) bool {
st := reflect.ValueOf(field)
@ -265,7 +265,7 @@ func isGte(field interface{}, param string) bool {
}
}
func isGt(field interface{}, param string) bool {
func isGt(val interface{}, field interface{}, param string) bool {
st := reflect.ValueOf(field)
@ -304,7 +304,7 @@ func isGt(field interface{}, param string) bool {
// length tests whether a variable's length is equal to a given
// value. For strings it tests the number of characters whereas
// for maps and slices it tests the number of items.
func hasLengthOf(field interface{}, param string) bool {
func hasLengthOf(val interface{}, field interface{}, param string) bool {
st := reflect.ValueOf(field)
@ -344,12 +344,12 @@ func hasLengthOf(field interface{}, param string) bool {
// number. For number types, it's a simple lesser-than test; for
// strings it tests the number of characters whereas for maps
// and slices it tests the number of items.
func hasMinOf(field interface{}, param string) bool {
func hasMinOf(val interface{}, field interface{}, param string) bool {
return isGte(field, param)
return isGte(val, field, param)
}
func isLte(field interface{}, param string) bool {
func isLte(val interface{}, field interface{}, param string) bool {
st := reflect.ValueOf(field)
@ -385,7 +385,7 @@ func isLte(field interface{}, param string) bool {
}
}
func isLt(field interface{}, param string) bool {
func isLt(val interface{}, field interface{}, param string) bool {
st := reflect.ValueOf(field)
@ -425,9 +425,9 @@ func isLt(field interface{}, param string) bool {
// value. For numbers, it's a simple lesser-than test; for
// strings it tests the number of characters whereas for maps
// and slices it tests the number of items.
func hasMaxOf(field interface{}, param string) bool {
func hasMaxOf(val interface{}, field interface{}, param string) bool {
return isLte(field, param)
return isLte(val, field, param)
}
// asInt retuns the parameter as a int64

@ -82,7 +82,7 @@ Custom Functions
Custom functions can be added
//Structure
func customFunc(field interface{}, param string) bool {
func customFunc(val interface{}, field interface{}, param string) bool {
if whatever {
return false
@ -92,9 +92,35 @@ Custom functions can be added
}
validator.AddFunction("custom tag name", customFunc)
// NOTE: using the same tag name as an existing function
// NOTES: using the same tag name as an existing function
// will overwrite the existing one
Cross Field Validation
Cross Field Validation can be implemented, for example Start & End Date range validation
// NOTE: when calling validator.validateStruct(val) val will be the top level struct passed
// into the function
// when calling validator.ValidateFieldByTagAndValue(val, field, tag) val will be
// whatever you pass, struct, field...
// when calling validator.ValidateFieldByTag(field, tag) val will be nil
//
// Because of the specific requirements and field names within each persons project that
// uses this library it is unlikely that any baked in function for this type of validation
// would be added, but you can add your own custom ones and keep all your validation logic
// in one place.
func isDateRangeValid(val interface{}, field interface{}, param string) bool {
myStruct := val.(myStructType)
if myStruct.Start.After(field.(time.Time)) {
return false
}
return true
}
Custom Tag Name
A custom tag name can be set to avoid conficts, or just have a shorter name

@ -95,8 +95,8 @@ func (e *StructValidationErrors) Flatten() map[string]*FieldValidationError {
return errs
}
// ValidationFunc that accepts the value of a field and parameter for use in validation (parameter not always used or needed)
type ValidationFunc func(v interface{}, param string) bool
// ValidationFunc that accepts a value(optional usage), a field and parameter(optional usage) for use in validation
type ValidationFunc func(val interface{}, v interface{}, param string) bool
// Validator implements the Validator Struct
// NOTE: Fields within are not thread safe and that is on purpose
@ -167,6 +167,87 @@ func ValidateStruct(s interface{}) *StructValidationErrors {
// ValidateStruct validates a struct and returns a struct containing the errors
func (v *Validator) ValidateStruct(s interface{}) *StructValidationErrors {
structValue := reflect.ValueOf(s)
if structValue.Kind() == reflect.Ptr && !structValue.IsNil() {
return v.ValidateStruct(structValue.Elem().Interface())
}
return v.validateStructRecursive(s, s)
// structValue := reflect.ValueOf(s)
// structType := reflect.TypeOf(s)
// structName := structType.Name()
// validationErrors := &StructValidationErrors{
// Struct: structName,
// Errors: map[string]*FieldValidationError{},
// StructErrors: map[string]*StructValidationErrors{},
// }
// if structValue.Kind() == reflect.Ptr && !structValue.IsNil() {
// return v.ValidateStruct(structValue.Elem().Interface())
// }
// if structValue.Kind() != reflect.Struct && structValue.Kind() != reflect.Interface {
// panic("interface passed for validation is not a struct")
// }
// var numFields = structValue.NumField()
// for i := 0; i < numFields; i++ {
// valueField := structValue.Field(i)
// typeField := structType.Field(i)
// if valueField.Kind() == reflect.Ptr && !valueField.IsNil() {
// valueField = valueField.Elem()
// }
// tag := typeField.Tag.Get(v.tagName)
// if tag == "-" {
// continue
// }
// // if no validation and not a struct (which may containt fields for validation)
// if tag == "" && valueField.Kind() != reflect.Struct && valueField.Kind() != reflect.Interface {
// continue
// }
// switch valueField.Kind() {
// case reflect.Struct, reflect.Interface:
// if !unicode.IsUpper(rune(typeField.Name[0])) {
// continue
// }
// if structErrors := v.ValidateStruct(valueField.Interface()); structErrors != nil {
// validationErrors.StructErrors[typeField.Name] = structErrors
// // free up memory map no longer needed
// structErrors = nil
// }
// default:
// if fieldError := v.validateFieldByNameAndTag(valueField.Interface(), typeField.Name, tag); fieldError != nil {
// validationErrors.Errors[fieldError.Field] = fieldError
// // free up memory reference
// fieldError = nil
// }
// }
// }
// if len(validationErrors.Errors) == 0 && len(validationErrors.StructErrors) == 0 {
// return nil
// }
// return validationErrors
}
// validateStructRecursive validates a struct recursivly and passes the top level struct around for use in validator functions and returns a struct containing the errors
func (v *Validator) validateStructRecursive(top interface{}, s interface{}) *StructValidationErrors {
structValue := reflect.ValueOf(s)
structType := reflect.TypeOf(s)
structName := structType.Name()
@ -178,7 +259,7 @@ func (v *Validator) ValidateStruct(s interface{}) *StructValidationErrors {
}
if structValue.Kind() == reflect.Ptr && !structValue.IsNil() {
return v.ValidateStruct(structValue.Elem().Interface())
return v.validateStructRecursive(top, structValue.Elem().Interface())
}
if structValue.Kind() != reflect.Struct && structValue.Kind() != reflect.Interface {
@ -215,7 +296,7 @@ func (v *Validator) ValidateStruct(s interface{}) *StructValidationErrors {
continue
}
if structErrors := v.ValidateStruct(valueField.Interface()); structErrors != nil {
if structErrors := v.validateStructRecursive(top, valueField.Interface()); structErrors != nil {
validationErrors.StructErrors[typeField.Name] = structErrors
// free up memory map no longer needed
structErrors = nil
@ -223,7 +304,7 @@ func (v *Validator) ValidateStruct(s interface{}) *StructValidationErrors {
default:
if fieldError := v.validateFieldByNameAndTag(valueField.Interface(), typeField.Name, tag); fieldError != nil {
if fieldError := v.validateFieldByNameAndTagAndValue(top, valueField.Interface(), typeField.Name, tag); fieldError != nil {
validationErrors.Errors[fieldError.Field] = fieldError
// free up memory reference
fieldError = nil
@ -241,30 +322,42 @@ func (v *Validator) ValidateStruct(s interface{}) *StructValidationErrors {
// ValidateFieldByTag allows validation of a single field with the internal validator, still using tag style validation to check multiple errors
func ValidateFieldByTag(f interface{}, tag string) *FieldValidationError {
return internalValidator.validateFieldByNameAndTag(f, "", tag)
return internalValidator.ValidateFieldByTag(f, tag)
}
// 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 {
return v.validateFieldByNameAndTag(f, "", tag)
return v.ValidateFieldByTagAndValue(nil, f, tag)
}
// ValidateFieldByTagAndValue allows validation of a single field with the internal validator, still using tag style validation to check multiple errors
func ValidateFieldByTagAndValue(val interface{}, f interface{}, tag string) *FieldValidationError {
return internalValidator.ValidateFieldByTagAndValue(val, f, tag)
}
// ValidateFieldByTagAndValue allows validation of a single field, still using tag style validation to check multiple errors
func (v *Validator) ValidateFieldByTagAndValue(val interface{}, f interface{}, tag string) *FieldValidationError {
return v.validateFieldByNameAndTagAndValue(val, f, "", tag)
}
func (v *Validator) validateFieldByNameAndTag(f interface{}, name string, tag string) *FieldValidationError {
func (v *Validator) validateFieldByNameAndTagAndValue(val interface{}, f interface{}, name string, tag string) *FieldValidationError {
// This is a double check if coming from ValidateStruct but need to be here in case function is called directly
if tag == "-" {
return nil
}
if strings.Contains(tag, omitempty) && !hasValue(f, "") {
if strings.Contains(tag, omitempty) && !hasValue(val, f, "") {
return nil
}
valueField := reflect.ValueOf(f)
if valueField.Kind() == reflect.Ptr && !valueField.IsNil() {
return v.ValidateFieldByTag(valueField.Elem().Interface(), tag)
return v.validateFieldByNameAndTagAndValue(val, valueField.Elem().Interface(), name, tag)
}
switch valueField.Kind() {
@ -287,7 +380,7 @@ func (v *Validator) validateFieldByNameAndTag(f interface{}, name string, tag st
for _, val := range orVals {
valErr, err = v.validateFieldByNameAndSingleTag(f, name, val)
valErr, err = v.validateFieldByNameAndSingleTag(val, f, name, val)
if err == nil {
return nil
@ -305,7 +398,7 @@ func (v *Validator) validateFieldByNameAndTag(f interface{}, name string, tag st
return valErr
}
if valErr, err = v.validateFieldByNameAndSingleTag(f, name, valTag); err != nil {
if valErr, err = v.validateFieldByNameAndSingleTag(val, f, name, valTag); err != nil {
valErr.Kind = valueField.Kind()
return valErr
@ -315,7 +408,7 @@ func (v *Validator) validateFieldByNameAndTag(f interface{}, name string, tag st
return nil
}
func (v *Validator) validateFieldByNameAndSingleTag(f interface{}, name string, valTag string) (*FieldValidationError, error) {
func (v *Validator) validateFieldByNameAndSingleTag(val interface{}, f interface{}, name string, valTag string) (*FieldValidationError, error) {
vals := strings.Split(valTag, "=")
key := strings.Trim(vals[0], " ")
@ -346,7 +439,7 @@ func (v *Validator) validateFieldByNameAndSingleTag(f interface{}, name string,
param = strings.Trim(vals[1], " ")
}
if err := valFunc(f, param); !err {
if err := valFunc(val, f, param); !err {
valErr.Param = param
return valErr, errors.New(key)
}

@ -123,11 +123,35 @@ func AssertMapFieldError(s map[string]*validator.FieldValidationError, field str
c.Assert(val.ErrorTag, Equals, expectedTag)
}
func newValidatorFunc(field interface{}, param string) bool {
func newValidatorFunc(val interface{}, field interface{}, param string) bool {
return true
}
func isEqualFunc(val interface{}, field interface{}, param string) bool {
return val.(string) == field.(string)
}
func (ms *MySuite) TestValidateByTagAndValue(c *C) {
val := "test"
field := "test"
err := validator.ValidateFieldByTagAndValue(val, field, "required")
c.Assert(err, IsNil)
validator.AddFunction("isequaltestfunc", isEqualFunc)
err = validator.ValidateFieldByTagAndValue(val, field, "isequaltestfunc")
c.Assert(err, IsNil)
val = "unequal"
err = validator.ValidateFieldByTagAndValue(val, field, "isequaltestfunc")
c.Assert(err, NotNil)
c.Assert(err.ErrorTag, Equals, "isequaltestfunc")
}
func (ms *MySuite) TestAddFunctions(c *C) {
validate := validator.NewValidator("validateme", validator.BakedInValidators)

Loading…
Cancel
Save