Merge branch 'v1-development' into v1

pull/16/head
Dean Karn 10 years ago
commit 677d6810c5
  1. 11
      baked_in.go
  2. 68
      validator.go
  3. 349
      validator_test.go

@ -9,7 +9,7 @@ import (
var bakedInValidators = map[string]ValidationFunc{ var bakedInValidators = map[string]ValidationFunc{
"required": required, "required": required,
"length": length, "len": length,
"min": min, "min": min,
"max": max, "max": max,
"regex": regex, "regex": regex,
@ -17,7 +17,16 @@ var bakedInValidators = map[string]ValidationFunc{
func required(field interface{}, param string) bool { func required(field interface{}, param string) bool {
st := reflect.ValueOf(field)
switch st.Kind() {
case reflect.Slice, reflect.Map, reflect.Array:
return field != nil && int64(st.Len()) > 0
default:
return field != nil && field != reflect.Zero(reflect.TypeOf(field)).Interface() return field != nil && field != reflect.Zero(reflect.TypeOf(field)).Interface()
}
} }
// length tests whether a variable's length is equal to a given // length tests whether a variable's length is equal to a given

@ -1,9 +1,16 @@
/**
* Package validator
*
* MISC:
* - anonymous structs - they don't have names so expect the Struct name within StructValidationErrors to be blank
*
*/
package validator package validator
import ( import (
"errors" "errors"
"fmt" "fmt"
"log"
"reflect" "reflect"
"strings" "strings"
"unicode" "unicode"
@ -11,8 +18,9 @@ import (
const ( const (
defaultTagName = "validate" defaultTagName = "validate"
omitempty string = "omitempty" omitempty = "omitempty"
validationErrMsg = "Field validation for \"%s\" failed on the \"%s\" tag\n" validationFieldErrMsg = "Field validation for \"%s\" failed on the \"%s\" tag\n"
validationStructErrMsg = "Struct:%s\n"
) )
// FieldValidationError contains a single fields validation error // FieldValidationError contains a single fields validation error
@ -24,29 +32,35 @@ type FieldValidationError struct {
// This is intended for use in development + debugging and not intended to be a production error message. // 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 // it also allows FieldValidationError to be used as an Error interface
func (e FieldValidationError) Error() string { func (e FieldValidationError) Error() string {
return fmt.Sprintf(validationErrMsg, e.Field, e.ErrorTag) return fmt.Sprintf(validationFieldErrMsg, e.Field, e.ErrorTag)
} }
// StructValidationErrors is a struct of errors for struct fields ( Excluding fields of type struct ) // StructValidationErrors is hierarchical list of field and struct errors
// NOTE: if a field within a struct is a struct it's errors will not be contained within the current
// StructValidationErrors but rather a new StructValidationErrors is created for each struct resulting in
// a neat & tidy 2D flattened list of structs validation errors
type StructValidationErrors struct { type StructValidationErrors struct {
// Name of the Struct
Struct string Struct string
// Struct Field Errors
Errors map[string]*FieldValidationError Errors map[string]*FieldValidationError
// 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
} }
// This is intended for use in development + debugging and not intended to be a production error message. // 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 // it also allows StructValidationErrors to be used as an Error interface
func (e StructValidationErrors) Error() string { func (e StructValidationErrors) Error() string {
s := "" s := fmt.Sprintf(validationStructErrMsg, e.Struct)
for _, err := range e.Errors { for _, err := range e.Errors {
s += fmt.Sprintf(validationErrMsg, err.Field, err.ErrorTag) s += err.Error()
}
for _, sErr := range e.StructErrors {
s += sErr.Error()
} }
return s return fmt.Sprintf("%s\n\n", s)
} }
// ValidationFunc that accepts the value of a field and parameter for use in validation (parameter not always used or needed) // ValidationFunc that accepts the value of a field and parameter for use in validation (parameter not always used or needed)
@ -113,22 +127,22 @@ func (v *Validator) AddFunction(key string, f ValidationFunc) error {
} }
// ValidateStruct validates a struct and returns a struct containing the errors // ValidateStruct validates a struct and returns a struct containing the errors
func ValidateStruct(s interface{}) map[string]*StructValidationErrors { func ValidateStruct(s interface{}) *StructValidationErrors {
return internalValidator.ValidateStruct(s) return internalValidator.ValidateStruct(s)
} }
// ValidateStruct validates a struct and returns a struct containing the errors // ValidateStruct validates a struct and returns a struct containing the errors
func (v *Validator) ValidateStruct(s interface{}) map[string]*StructValidationErrors { func (v *Validator) ValidateStruct(s interface{}) *StructValidationErrors {
errorArray := map[string]*StructValidationErrors{}
structValue := reflect.ValueOf(s) structValue := reflect.ValueOf(s)
structType := reflect.TypeOf(s) structType := reflect.TypeOf(s)
structName := structType.Name() structName := structType.Name()
var currentStructError = &StructValidationErrors{ validationErrors := &StructValidationErrors{
Struct: structName, Struct: structName,
Errors: map[string]*FieldValidationError{}, Errors: map[string]*FieldValidationError{},
StructErrors: map[string]*StructValidationErrors{},
} }
if structValue.Kind() == reflect.Ptr && !structValue.IsNil() { if structValue.Kind() == reflect.Ptr && !structValue.IsNil() {
@ -136,7 +150,7 @@ func (v *Validator) ValidateStruct(s interface{}) map[string]*StructValidationEr
} }
if structValue.Kind() != reflect.Struct { if structValue.Kind() != reflect.Struct {
log.Fatal("interface passed for validation is not a struct") panic("interface passed for validation is not a struct")
} }
var numFields = structValue.NumField() var numFields = structValue.NumField()
@ -170,9 +184,7 @@ func (v *Validator) ValidateStruct(s interface{}) map[string]*StructValidationEr
} }
if structErrors := v.ValidateStruct(valueField.Interface()); structErrors != nil { if structErrors := v.ValidateStruct(valueField.Interface()); structErrors != nil {
for key, val := range structErrors { validationErrors.StructErrors[typeField.Name] = structErrors
errorArray[key] = val
}
// free up memory map no longer needed // free up memory map no longer needed
structErrors = nil structErrors = nil
} }
@ -180,24 +192,18 @@ func (v *Validator) ValidateStruct(s interface{}) map[string]*StructValidationEr
default: default:
if fieldError := v.validateStructFieldByTag(valueField.Interface(), typeField.Name, tag); fieldError != nil { if fieldError := v.validateStructFieldByTag(valueField.Interface(), typeField.Name, tag); fieldError != nil {
currentStructError.Errors[fieldError.Field] = fieldError validationErrors.Errors[fieldError.Field] = fieldError
// free up memory reference // free up memory reference
fieldError = nil fieldError = nil
} }
} }
} }
if len(currentStructError.Errors) > 0 { if len(validationErrors.Errors) == 0 && len(validationErrors.StructErrors) == 0 {
errorArray[currentStructError.Struct] = currentStructError
// free up memory
currentStructError = nil
}
if len(errorArray) == 0 {
return nil return nil
} }
return errorArray return validationErrors
} }
// ValidateFieldWithTag validates the given field by the given tag arguments // ValidateFieldWithTag validates the given field by the given tag arguments
@ -245,7 +251,7 @@ func (v *Validator) validateFieldByNameAndTag(f interface{}, name string, tag st
switch valueField.Kind() { switch valueField.Kind() {
case reflect.Struct, reflect.Invalid: case reflect.Struct, reflect.Invalid:
log.Fatal("Invalid field passed to ValidateFieldWithTag") panic("Invalid field passed to ValidateFieldWithTag")
} }
valTags := strings.Split(tag, ",") valTags := strings.Split(tag, ",")
@ -256,7 +262,7 @@ func (v *Validator) validateFieldByNameAndTag(f interface{}, name string, tag st
key := strings.Trim(vals[0], " ") key := strings.Trim(vals[0], " ")
if len(key) == 0 { if len(key) == 0 {
log.Fatalf("Invalid validation tag on field %s", name) panic(fmt.Sprintf("Invalid validation tag on field %s", name))
} }
// OK to continue because we checked it's existance before getting into this loop // OK to continue because we checked it's existance before getting into this loop
@ -266,7 +272,7 @@ func (v *Validator) validateFieldByNameAndTag(f interface{}, name string, tag st
valFunc := v.validationFuncs[key] valFunc := v.validationFuncs[key]
if valFunc == nil { if valFunc == nil {
log.Fatalf("Undefined validation function on field %s", name) panic(fmt.Sprintf("Undefined validation function on field %s", name))
} }
param := "" param := ""

@ -5,53 +5,340 @@ import (
"testing" "testing"
"github.com/joeybloggs/go-validate-yourself" "github.com/joeybloggs/go-validate-yourself"
. "gopkg.in/check.v1"
) )
type UserDetails struct { type SubTest struct {
Address string `validate:"omitempty,length=6"` Test string `validate:"required"`
} }
type User struct { type TestString struct {
FirstName string `validate:"required"` Required string `validate:"required"`
Details *UserDetails Len string `validate:"len=10"`
Min string `validate:"min=1"`
Max string `validate:"max=10"`
MinMax string `validate:"min=1,max=10"`
OmitEmpty string `validate:"omitempty,min=1,max=10"`
Sub *SubTest
SubIgnore *SubTest `validate:"-"`
Anonymous struct {
A string `validate:"required"`
}
}
type TestInt32 struct {
Required int `validate:"required"`
Len int `validate:"len=10"`
Min int `validate:"min=1"`
Max int `validate:"max=10"`
MinMax int `validate:"min=1,max=10"`
OmitEmpty int `validate:"omitempty,min=1,max=10"`
}
type TestUint64 struct {
Required uint64 `validate:"required"`
Len uint64 `validate:"len=10"`
Min uint64 `validate:"min=1"`
Max uint64 `validate:"max=10"`
MinMax uint64 `validate:"min=1,max=10"`
OmitEmpty uint64 `validate:"omitempty,min=1,max=10"`
}
type TestFloat64 struct {
Required int64 `validate:"required"`
Len int64 `validate:"len=10"`
Min int64 `validate:"min=1"`
Max int64 `validate:"max=10"`
MinMax int64 `validate:"min=1,max=10"`
OmitEmpty int64 `validate:"omitempty,min=1,max=10"`
}
type TestSlice struct {
Required []int `validate:"required"`
Len []int `validate:"len=10"`
Min []int `validate:"min=1"`
Max []int `validate:"max=10"`
MinMax []int `validate:"min=1,max=10"`
OmitEmpty []int `validate:"omitempty,min=1,max=10"`
}
func Test(t *testing.T) { TestingT(t) }
type MySuite struct{}
var _ = Suite(&MySuite{})
func AssetStruct(s *validator.StructValidationErrors, structFieldName string, expectedStructName string, c *C) *validator.StructValidationErrors {
val, ok := s.StructErrors[structFieldName]
c.Assert(ok, Equals, true)
c.Assert(val, NotNil)
c.Assert(val.Struct, Equals, expectedStructName)
return val
} }
func TestValidateStruct(t *testing.T) { func AssertFieldError(s *validator.StructValidationErrors, field string, expectedTag string, c *C) {
val, ok := s.Errors[field]
c.Assert(ok, Equals, true)
c.Assert(val, NotNil)
c.Assert(val.Field, Equals, field)
c.Assert(val.ErrorTag, Equals, expectedTag)
}
func (ms *MySuite) TestStructStringValidation(c *C) {
tSuccess := &TestString{
Required: "Required",
Len: "length==10",
Min: "min=1",
Max: "1234567890",
MinMax: "12345",
OmitEmpty: "",
Sub: &SubTest{
Test: "1",
},
SubIgnore: &SubTest{
Test: "",
},
Anonymous: struct {
A string `validate:"required"`
}{
A: "1",
},
}
err := validator.ValidateStruct(tSuccess)
c.Assert(err, IsNil)
u := &User{ tFail := &TestString{
FirstName: "", Required: "",
Details: &UserDetails{ Len: "",
"", Min: "",
Max: "12345678901",
MinMax: "",
OmitEmpty: "12345678901",
Sub: &SubTest{
Test: "",
},
Anonymous: struct {
A string `validate:"required"`
}{
A: "",
}, },
} }
errors := validator.ValidateStruct(u) err = validator.ValidateStruct(tFail)
// Assert Top Level
c.Assert(err.Struct, Equals, "TestString")
c.Assert(len(err.Errors), Equals, 6)
c.Assert(len(err.StructErrors), Equals, 2)
// Assert Fields
AssertFieldError(err, "Required", "required", c)
AssertFieldError(err, "Len", "len", c)
AssertFieldError(err, "Min", "min", c)
AssertFieldError(err, "Max", "max", c)
AssertFieldError(err, "MinMax", "min", c)
AssertFieldError(err, "OmitEmpty", "max", c)
fmt.Println(errors == nil) // Assert Anonymous embedded struct
AssetStruct(err, "Anonymous", "", c)
for _, i := range errors { // Assert SubTest embedded struct
fmt.Printf("Error Struct:%s\n", i.Struct) val := AssetStruct(err, "Sub", "SubTest", c)
c.Assert(len(val.Errors), Equals, 1)
c.Assert(len(val.StructErrors), Equals, 0)
AssertFieldError(val, "Test", "required", c)
}
for _, j := range i.Errors { func (ms *MySuite) TestStructInt32Validation(c *C) {
fmt.Printf("Error Field:%s Error Tag:%s\n", j.Field, j.ErrorTag) tSuccess := &TestInt32{
fmt.Println(j.Error()) Required: 1,
Len: 10,
Min: 1,
Max: 10,
MinMax: 5,
OmitEmpty: 0,
} }
err := validator.ValidateStruct(tSuccess)
c.Assert(err, IsNil)
tFail := &TestInt32{
Required: 0,
Len: 11,
Min: -1,
Max: 11,
MinMax: -1,
OmitEmpty: 11,
} }
err = validator.ValidateStruct(tFail)
// Assert Top Level
c.Assert(err.Struct, Equals, "TestInt32")
c.Assert(len(err.Errors), Equals, 6)
c.Assert(len(err.StructErrors), Equals, 0)
// Assert Fields
AssertFieldError(err, "Required", "required", c)
AssertFieldError(err, "Len", "len", c)
AssertFieldError(err, "Min", "min", c)
AssertFieldError(err, "Max", "max", c)
AssertFieldError(err, "MinMax", "min", c)
AssertFieldError(err, "OmitEmpty", "max", c)
} }
// func TestValidateField(t *testing.T) { func (ms *MySuite) TestStructUint64Validation(c *C) {
//
// u := &User{ tSuccess := &TestUint64{
// FirstName: "Dean Karn", Required: 1,
// Details: &UserDetails{ Len: 10,
// "26 Here Blvd.", Min: 1,
// }, Max: 10,
// } MinMax: 5,
// OmitEmpty: 0,
// err := validator.ValidateFieldByTag(u.FirstName, "required") }
//
// fmt.Println(err == nil) err := validator.ValidateStruct(tSuccess)
// fmt.Println(err) c.Assert(err, IsNil)
// }
tFail := &TestUint64{
Required: 0,
Len: 11,
Min: 0,
Max: 11,
MinMax: 0,
OmitEmpty: 11,
}
err = validator.ValidateStruct(tFail)
// Assert Top Level
c.Assert(err.Struct, Equals, "TestUint64")
c.Assert(len(err.Errors), Equals, 6)
c.Assert(len(err.StructErrors), Equals, 0)
// Assert Fields
AssertFieldError(err, "Required", "required", c)
AssertFieldError(err, "Len", "len", c)
AssertFieldError(err, "Min", "min", c)
AssertFieldError(err, "Max", "max", c)
AssertFieldError(err, "MinMax", "min", c)
AssertFieldError(err, "OmitEmpty", "max", c)
}
func (ms *MySuite) TestStructFloat64Validation(c *C) {
tSuccess := &TestFloat64{
Required: 1,
Len: 10,
Min: 1,
Max: 10,
MinMax: 5,
OmitEmpty: 0,
}
err := validator.ValidateStruct(tSuccess)
c.Assert(err, IsNil)
tFail := &TestFloat64{
Required: 0,
Len: 11,
Min: 0,
Max: 11,
MinMax: 0,
OmitEmpty: 11,
}
err = validator.ValidateStruct(tFail)
// Assert Top Level
c.Assert(err.Struct, Equals, "TestFloat64")
c.Assert(len(err.Errors), Equals, 6)
c.Assert(len(err.StructErrors), Equals, 0)
// Assert Fields
AssertFieldError(err, "Required", "required", c)
AssertFieldError(err, "Len", "len", c)
AssertFieldError(err, "Min", "min", c)
AssertFieldError(err, "Max", "max", c)
AssertFieldError(err, "MinMax", "min", c)
AssertFieldError(err, "OmitEmpty", "max", c)
}
func (ms *MySuite) TestStructSliceValidation(c *C) {
tSuccess := &TestSlice{
Required: []int{1},
Len: []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 0},
Min: []int{1, 2},
Max: []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 0},
MinMax: []int{1, 2, 3, 4, 5},
OmitEmpty: []int{},
}
err := validator.ValidateStruct(tSuccess)
c.Assert(err, IsNil)
tFail := &TestSlice{
Required: []int{},
Len: []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1},
Min: []int{},
Max: []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1},
MinMax: []int{},
OmitEmpty: []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1},
}
err = validator.ValidateStruct(tFail)
// Assert Top Level
c.Assert(err.Struct, Equals, "TestSlice")
c.Assert(len(err.Errors), Equals, 6)
c.Assert(len(err.StructErrors), Equals, 0)
// Assert Fields
AssertFieldError(err, "Required", "required", c)
AssertFieldError(err, "Len", "len", c)
AssertFieldError(err, "Min", "min", c)
AssertFieldError(err, "Max", "max", c)
AssertFieldError(err, "MinMax", "min", c)
AssertFieldError(err, "OmitEmpty", "max", c)
}
func (ms *MySuite) TestInvalidStruct(c *C) {
s := &SubTest{
Test: "1",
}
c.Assert(func() { validator.ValidateStruct(s.Test) }, PanicMatches, "interface passed for validation is not a struct")
}
func (ms *MySuite) TestInvalidField(c *C) {
s := &SubTest{
Test: "1",
}
c.Assert(func() { validator.ValidateFieldByTag(s, "required") }, PanicMatches, "Invalid field passed to ValidateFieldWithTag")
}
func (ms *MySuite) TestInvalidTagField(c *C) {
s := &SubTest{
Test: "1",
}
c.Assert(func() { validator.ValidateFieldByTag(s.Test, "") }, PanicMatches, fmt.Sprintf("Invalid validation tag on field %s", ""))
}
func (ms *MySuite) TestInvalidValidatorFunction(c *C) {
s := &SubTest{
Test: "1",
}
c.Assert(func() { validator.ValidateFieldByTag(s.Test, "zzxxBadFunction") }, PanicMatches, fmt.Sprintf("Undefined validation function on field %s", ""))
}

Loading…
Cancel
Save