From fe79700b958c9d409ad38ce21b0a42aba8bd98bd Mon Sep 17 00:00:00 2001 From: Dean Karn Date: Fri, 13 Feb 2015 10:40:48 -0500 Subject: [PATCH 1/2] issue-#3 unintended error redesign to heierarchical --- validator.go | 66 ++++++++++++++++++++++++++--------------------- validator_test.go | 57 +++++++++++++++++++++++++++++++++------- 2 files changed, 84 insertions(+), 39 deletions(-) diff --git a/validator.go b/validator.go index adbfeca..c85584d 100644 --- a/validator.go +++ b/validator.go @@ -1,3 +1,12 @@ +/** + * Package validator + * + * NOTES: + * + * anonymous structs - they don't have names so expect the Struct name within StructValidationErrors to be blank + * + */ + package validator import ( @@ -10,9 +19,10 @@ import ( ) const ( - defaultTagName = "validate" - omitempty string = "omitempty" - validationErrMsg = "Field validation for \"%s\" failed on the \"%s\" tag\n" + defaultTagName = "validate" + omitempty = "omitempty" + validationFieldErrMsg = "Field validation for \"%s\" failed on the \"%s\" tag\n" + validationStructErrMsg = "Struct:%s\n" ) // FieldValidationError contains a single fields validation error @@ -24,29 +34,35 @@ type FieldValidationError struct { // 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(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 ) -// 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 +// StructValidationErrors is hierarchical list of field and struct errors type StructValidationErrors struct { + // Name of the Struct Struct string + // Struct Field Errors 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. // it also allows StructValidationErrors to be used as an Error interface func (e StructValidationErrors) Error() string { - s := "" + s := fmt.Sprintf(validationStructErrMsg, e.Struct) 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) @@ -113,22 +129,22 @@ func (v *Validator) AddFunction(key string, f ValidationFunc) error { } // 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) } // 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) structType := reflect.TypeOf(s) structName := structType.Name() - var currentStructError = &StructValidationErrors{ - Struct: structName, - Errors: map[string]*FieldValidationError{}, + validationErrors := &StructValidationErrors{ + Struct: structName, + Errors: map[string]*FieldValidationError{}, + StructErrors: map[string]*StructValidationErrors{}, } if structValue.Kind() == reflect.Ptr && !structValue.IsNil() { @@ -170,9 +186,7 @@ func (v *Validator) ValidateStruct(s interface{}) map[string]*StructValidationEr } if structErrors := v.ValidateStruct(valueField.Interface()); structErrors != nil { - for key, val := range structErrors { - errorArray[key] = val - } + validationErrors.StructErrors[typeField.Name] = structErrors // free up memory map no longer needed structErrors = nil } @@ -180,24 +194,18 @@ func (v *Validator) ValidateStruct(s interface{}) map[string]*StructValidationEr default: 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 fieldError = nil } } } - if len(currentStructError.Errors) > 0 { - errorArray[currentStructError.Struct] = currentStructError - // free up memory - currentStructError = nil - } - - if len(errorArray) == 0 { + if len(validationErrors.Errors) == 0 && len(validationErrors.StructErrors) == 0 { return nil } - return errorArray + return validationErrors } // ValidateFieldWithTag validates the given field by the given tag arguments diff --git a/validator_test.go b/validator_test.go index 1010506..ed12593 100644 --- a/validator_test.go +++ b/validator_test.go @@ -2,13 +2,24 @@ package validator_test import ( "fmt" + "log" "testing" "github.com/joeybloggs/go-validate-yourself" ) +// type UserDetails struct { +// Address string `validate:"omitempty,length=6"` +// Sub struct { +// A string `validate:"required"` +// } +// } + type UserDetails struct { Address string `validate:"omitempty,length=6"` + Sub struct { + A string `validate:"required"` + } } type User struct { @@ -16,28 +27,54 @@ type User struct { Details *UserDetails } +// func Test(t *testing.T) { TestingT(t) } +// +// type MySuite struct{} +// +// var _ = Suite(&MySuite{}) +// +// func (s *MySuite) SetUpTest(c *C) { +// s.dir = c.MkDir() +// // Use s.dir to prepare some data. +// } + +// func RecursiveErrorReporter(e *validator.StructValidationErrors) { +// +// // log.Printf("Error within Struct:%s\n", e.Struct) +// +// // for _, f := range e.Errors { +// // log.Println(f.Error()) +// // } +// } + func TestValidateStruct(t *testing.T) { u := &User{ FirstName: "", Details: &UserDetails{ - "", + Address: "", + Sub: struct { + A string `validate:"required"` + }{ + A: "", + }, }, } errors := validator.ValidateStruct(u) fmt.Println(errors == nil) + log.Println(errors.Error()) - for _, i := range errors { - fmt.Printf("Error Struct:%s\n", i.Struct) - - for _, j := range i.Errors { - - fmt.Printf("Error Field:%s Error Tag:%s\n", j.Field, j.ErrorTag) - fmt.Println(j.Error()) - } - } + // for _, i := range errors { + // fmt.Printf("Error Struct:%s\n", i.Struct) + // + // for _, j := range i.Errors { + // + // fmt.Printf("Error Field:%s Error Tag:%s\n", j.Field, j.ErrorTag) + // fmt.Println(j.Error()) + // } + // } } From b2f3d55b2cdbe15da4ce097a6ff2e4256b6b893b Mon Sep 17 00:00:00 2001 From: Dean Karn Date: Fri, 13 Feb 2015 14:23:21 -0500 Subject: [PATCH 2/2] issue-#3 add tests for existing functions. --- baked_in.go | 13 +- validator.go | 14 +- validator_test.go | 404 +++++++++++++++++++++++++++++++++++++--------- 3 files changed, 344 insertions(+), 87 deletions(-) diff --git a/baked_in.go b/baked_in.go index 1fa65c2..464969c 100644 --- a/baked_in.go +++ b/baked_in.go @@ -9,7 +9,7 @@ import ( var bakedInValidators = map[string]ValidationFunc{ "required": required, - "length": length, + "len": length, "min": min, "max": max, "regex": regex, @@ -17,7 +17,16 @@ var bakedInValidators = map[string]ValidationFunc{ func required(field interface{}, param string) bool { - return field != nil && field != reflect.Zero(reflect.TypeOf(field)).Interface() + 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() + } } // length tests whether a variable's length is equal to a given diff --git a/validator.go b/validator.go index c85584d..2664663 100644 --- a/validator.go +++ b/validator.go @@ -1,9 +1,8 @@ /** * Package validator * - * NOTES: - * - * anonymous structs - they don't have names so expect the Struct name within StructValidationErrors to be blank + * MISC: + * - anonymous structs - they don't have names so expect the Struct name within StructValidationErrors to be blank * */ @@ -12,7 +11,6 @@ package validator import ( "errors" "fmt" - "log" "reflect" "strings" "unicode" @@ -152,7 +150,7 @@ func (v *Validator) ValidateStruct(s interface{}) *StructValidationErrors { } 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() @@ -253,7 +251,7 @@ func (v *Validator) validateFieldByNameAndTag(f interface{}, name string, tag st switch valueField.Kind() { case reflect.Struct, reflect.Invalid: - log.Fatal("Invalid field passed to ValidateFieldWithTag") + panic("Invalid field passed to ValidateFieldWithTag") } valTags := strings.Split(tag, ",") @@ -264,7 +262,7 @@ func (v *Validator) validateFieldByNameAndTag(f interface{}, name string, tag st key := strings.Trim(vals[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 @@ -274,7 +272,7 @@ func (v *Validator) validateFieldByNameAndTag(f interface{}, name string, tag st valFunc := v.validationFuncs[key] if valFunc == nil { - log.Fatalf("Undefined validation function on field %s", name) + panic(fmt.Sprintf("Undefined validation function on field %s", name)) } param := "" diff --git a/validator_test.go b/validator_test.go index ed12593..58375b4 100644 --- a/validator_test.go +++ b/validator_test.go @@ -2,93 +2,343 @@ package validator_test import ( "fmt" - "log" "testing" "github.com/joeybloggs/go-validate-yourself" + . "gopkg.in/check.v1" ) -// type UserDetails struct { -// Address string `validate:"omitempty,length=6"` -// Sub struct { -// A string `validate:"required"` -// } -// } +type SubTest struct { + Test string `validate:"required"` +} -type UserDetails struct { - Address string `validate:"omitempty,length=6"` - Sub struct { +type TestString struct { + Required string `validate:"required"` + 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 User struct { - FirstName string `validate:"required"` - Details *UserDetails -} - -// func Test(t *testing.T) { TestingT(t) } -// -// type MySuite struct{} -// -// var _ = Suite(&MySuite{}) -// -// func (s *MySuite) SetUpTest(c *C) { -// s.dir = c.MkDir() -// // Use s.dir to prepare some data. -// } - -// func RecursiveErrorReporter(e *validator.StructValidationErrors) { -// -// // log.Printf("Error within Struct:%s\n", e.Struct) -// -// // for _, f := range e.Errors { -// // log.Println(f.Error()) -// // } -// } - -func TestValidateStruct(t *testing.T) { - - u := &User{ - FirstName: "", - Details: &UserDetails{ - Address: "", - Sub: struct { - A string `validate:"required"` - }{ - A: "", - }, +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 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", }, } - errors := validator.ValidateStruct(u) - - fmt.Println(errors == nil) - log.Println(errors.Error()) - - // for _, i := range errors { - // fmt.Printf("Error Struct:%s\n", i.Struct) - // - // for _, j := range i.Errors { - // - // fmt.Printf("Error Field:%s Error Tag:%s\n", j.Field, j.ErrorTag) - // fmt.Println(j.Error()) - // } - // } - -} - -// func TestValidateField(t *testing.T) { -// -// u := &User{ -// FirstName: "Dean Karn", -// Details: &UserDetails{ -// "26 Here Blvd.", -// }, -// } -// -// err := validator.ValidateFieldByTag(u.FirstName, "required") -// -// fmt.Println(err == nil) -// fmt.Println(err) -// } + err := validator.ValidateStruct(tSuccess) + c.Assert(err, IsNil) + + tFail := &TestString{ + Required: "", + Len: "", + Min: "", + Max: "12345678901", + MinMax: "", + OmitEmpty: "12345678901", + Sub: &SubTest{ + Test: "", + }, + Anonymous: struct { + A string `validate:"required"` + }{ + A: "", + }, + } + + 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) + + // Assert Anonymous embedded struct + AssetStruct(err, "Anonymous", "", c) + + // Assert SubTest embedded 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) +} + +func (ms *MySuite) TestStructInt32Validation(c *C) { + + tSuccess := &TestInt32{ + 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 (ms *MySuite) TestStructUint64Validation(c *C) { + + tSuccess := &TestUint64{ + Required: 1, + Len: 10, + Min: 1, + Max: 10, + MinMax: 5, + OmitEmpty: 0, + } + + err := validator.ValidateStruct(tSuccess) + 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", "")) +}