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 adbfeca..2664663 100644 --- a/validator.go +++ b/validator.go @@ -1,18 +1,26 @@ +/** + * Package validator + * + * MISC: + * - anonymous structs - they don't have names so expect the Struct name within StructValidationErrors to be blank + * + */ + package validator import ( "errors" "fmt" - "log" "reflect" "strings" "unicode" ) 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 +32,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 +127,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() { @@ -136,7 +150,7 @@ func (v *Validator) ValidateStruct(s interface{}) map[string]*StructValidationEr } 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() @@ -170,9 +184,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 +192,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 @@ -245,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, ",") @@ -256,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 @@ -266,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 1010506..58375b4 100644 --- a/validator_test.go +++ b/validator_test.go @@ -5,53 +5,340 @@ import ( "testing" "github.com/joeybloggs/go-validate-yourself" + . "gopkg.in/check.v1" ) -type UserDetails struct { - Address string `validate:"omitempty,length=6"` +type SubTest struct { + Test string `validate:"required"` } -type User struct { - FirstName string `validate:"required"` - Details *UserDetails +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 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{ - FirstName: "", - Details: &UserDetails{ - "", + 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{}, } - errors := validator.ValidateStruct(u) + 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) - fmt.Println(errors == nil) + // 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") +} - for _, i := range errors { - fmt.Printf("Error Struct:%s\n", i.Struct) +func (ms *MySuite) TestInvalidField(c *C) { + s := &SubTest{ + Test: "1", + } - for _, j := range i.Errors { + c.Assert(func() { validator.ValidateFieldByTag(s, "required") }, PanicMatches, "Invalid field passed to ValidateFieldWithTag") +} - fmt.Printf("Error Field:%s Error Tag:%s\n", j.Field, j.ErrorTag) - fmt.Println(j.Error()) - } +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 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) -// } +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", "")) +}