From 83c9883fe8b0faee623669a98865b780db9cc330 Mon Sep 17 00:00:00 2001 From: Dean Karn Date: Thu, 12 Feb 2015 14:47:22 -0500 Subject: [PATCH 1/5] add gopkg --- validator_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/validator_test.go b/validator_test.go index 9180a03..5f32192 100644 --- a/validator_test.go +++ b/validator_test.go @@ -4,7 +4,7 @@ import ( "fmt" "testing" - "github.com/joeybloggs/go-validate-yourself" + "gopkg.in/joeybloggs/go-validate-yourself.v0" ) type UserDetails struct { From aad5727a16ed87f286394fd7226656e747553630 Mon Sep 17 00:00:00 2001 From: Dean Karn Date: Thu, 12 Feb 2015 20:49:57 -0500 Subject: [PATCH 2/5] initial working commit --- baked_in.go | 18 ++++ validator.go | 209 ++++++++++++++++++++++++++++++++++++++++++---- validator_test.go | 33 +++++++- 3 files changed, 241 insertions(+), 19 deletions(-) create mode 100644 baked_in.go diff --git a/baked_in.go b/baked_in.go new file mode 100644 index 0000000..728a635 --- /dev/null +++ b/baked_in.go @@ -0,0 +1,18 @@ +package validator + +import ( + "log" + "reflect" +) + +var bakedInValidators = map[string]ValidationFunc{ + "required": isRequired, +} + +func isRequired(field interface{}, param string) bool { + + log.Printf("Required:%s Valid:%t\n", field, field != nil && field != reflect.Zero(reflect.TypeOf(field)).Interface()) + return field != nil && field != reflect.Zero(reflect.TypeOf(field)).Interface() + + // return true +} diff --git a/validator.go b/validator.go index 1b1d13f..272a5d9 100644 --- a/validator.go +++ b/validator.go @@ -2,9 +2,10 @@ package validator import ( "errors" - "fmt" "log" "reflect" + "strings" + "unicode" ) // FieldValidationError contains a single fields validation error @@ -17,17 +18,18 @@ type FieldValidationError 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 ArrayValidationErrors is created for each struct type StructValidationErrors struct { - Errors []FieldValidationError + Struct string + Errors []*FieldValidationError } -// ArrayStructValidationErrors is a struct that contains a 2D flattened list of struct specific StructValidationErrors -type ArrayStructValidationErrors struct { - // Key = Struct Name - Errors map[string][]StructValidationErrors -} +// // ArrayStructValidationErrors is a struct that contains a 2D flattened list of struct specific StructValidationErrors +// type ArrayStructValidationErrors struct { +// // Key = Struct Name +// Errors map[string][]StructValidationErrors +// } // 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) error +type ValidationFunc func(v interface{}, param string) bool // Validator implements the Validator Struct // NOTE: Fields within are not thread safe and that is on purpose @@ -40,7 +42,7 @@ type Validator struct { validationFuncs map[string]ValidationFunc } -var bakedInValidators = map[string]ValidationFunc{} +// var bakedInValidators = map[string]ValidationFunc{} var internalValidator = NewValidator("validate", bakedInValidators) @@ -89,17 +91,23 @@ func (v *Validator) AddFunction(key string, f ValidationFunc) error { return nil } -func ValidateStruct(s interface{}) ArrayStructValidationErrors { +// ValidateStruct validates a struct and returns a struct containing the errors +func ValidateStruct(s interface{}) []*StructValidationErrors { return internalValidator.ValidateStruct(s) } -func (v *Validator) ValidateStruct(s interface{}) ArrayStructValidationErrors { - - var errorStruct = ArrayStructValidationErrors{} +// ValidateStruct validates a struct and returns a struct containing the errors +func (v *Validator) ValidateStruct(s interface{}) []*StructValidationErrors { + errorArray := []*StructValidationErrors{} structValue := reflect.ValueOf(s) structType := reflect.TypeOf(s) + structName := structType.Name() + + var currentStructError = &StructValidationErrors{ + Struct: structName, + } if structValue.Kind() == reflect.Ptr && !structValue.IsNil() { return v.ValidateStruct(structValue.Elem().Interface()) @@ -120,8 +128,179 @@ func (v *Validator) ValidateStruct(s interface{}) ArrayStructValidationErrors { valueField = valueField.Elem() } - fmt.Println(typeField.Name) + 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 { + continue + } + + switch valueField.Kind() { + + case reflect.Struct: + + if !unicode.IsUpper(rune(typeField.Name[0])) { + continue + } + + if structErrors := v.ValidateStruct(valueField.Interface()); structErrors != nil { + errorArray = append(errorArray, structErrors...) + } + + default: + + if fieldError := v.validateStructFieldByTag(valueField.Interface(), typeField.Name, tag); fieldError != nil { + currentStructError.Errors = append(currentStructError.Errors, fieldError) + } + } + } + + if currentStructError.Errors != nil { + errorArray = append(errorArray, currentStructError) + } + + if len(errorArray) == 0 { + return nil + } + + return errorArray +} + +// ValidateFieldWithTag validates the given field by the given tag arguments +func (v *Validator) validateStructFieldByTag(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 + // } + // + // valueField := reflect.ValueOf(f) + // + // if valueField.Kind() == reflect.Ptr && !valueField.IsNil() { + // return v.validateStructFieldByTag(valueField.Elem().Interface(), name, tag) + // } + // + // // fmt.Println(typeField.Name) + // + // switch valueField.Kind() { + // + // case reflect.Struct, reflect.Invalid: + // log.Fatal("Invalid field passed to ValidateFieldWithTag") + // } + // + // // typeField := reflect.TypeOf(f) + // // name := "" + // valTags := strings.Split(tag, ",") + // + // for _, valTag := range valTags { + // + // vals := strings.Split(valTag, "=") + // key := strings.Trim(vals[0], " ") + // + // if len(key) == 0 { + // log.Fatalf("Invalid validation tag on field %s", name) + // } + // + // valFunc := v.validationFuncs[key] + // if valFunc == nil { + // log.Fatalf("Undefined validation function on field %s", name) + // } + // + // param := "" + // if len(vals) > 1 { + // param = strings.Trim(vals[1], " ") + // } + // + // if err := valFunc(f, param); !err { + // + // return &FieldValidationError{ + // Field: name, + // ErrorTag: key, + // } + // } + // + // } + // + // return nil + // + // + + if err := v.validateFieldByNameAndTag(f, name, tag); err != nil { + return &FieldValidationError{ + Field: name, + ErrorTag: err.Error(), + } + } + + return nil +} + +// 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) error { + + return internalValidator.validateFieldByNameAndTag(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) error { + + return v.validateFieldByNameAndTag(f, "", tag) +} + +func (v *Validator) validateFieldByNameAndTag(f interface{}, name string, tag string) error { + + // This is a double check if coming from ValidateStruct but need to be here in case function is called directly + if tag == "-" { + return nil + } + + valueField := reflect.ValueOf(f) + + if valueField.Kind() == reflect.Ptr && !valueField.IsNil() { + return v.ValidateFieldByTag(valueField.Elem().Interface(), tag) + } + + // fmt.Println(typeField.Name) + + switch valueField.Kind() { + + case reflect.Struct, reflect.Invalid: + log.Fatal("Invalid field passed to ValidateFieldWithTag") + } + + // typeField := reflect.TypeOf(f) + // name := "" + valTags := strings.Split(tag, ",") + + for _, valTag := range valTags { + + vals := strings.Split(valTag, "=") + key := strings.Trim(vals[0], " ") + + if len(key) == 0 { + log.Fatalf("Invalid validation tag on field %s", name) + } + + valFunc := v.validationFuncs[key] + if valFunc == nil { + log.Fatalf("Undefined validation function on field %s", name) + } + + param := "" + if len(vals) > 1 { + param = strings.Trim(vals[1], " ") + } + + if err := valFunc(f, param); !err { + + return errors.New(key) + } + } - return errorStruct + return nil } diff --git a/validator_test.go b/validator_test.go index 5f32192..097477e 100644 --- a/validator_test.go +++ b/validator_test.go @@ -4,7 +4,7 @@ import ( "fmt" "testing" - "gopkg.in/joeybloggs/go-validate-yourself.v0" + "github.com/joeybloggs/go-validate-yourself" ) type UserDetails struct { @@ -19,13 +19,38 @@ type User struct { func TestValidateStruct(t *testing.T) { u := &User{ - FirstName: "Dean Karn", + FirstName: "", Details: &UserDetails{ - "26 Here Blvd.", + "", }, } errors := validator.ValidateStruct(u) - fmt.Println(errors) + fmt.Println(errors == nil) + + 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) + } + } + } + +// 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) +// } From 1b249998b2f87af4776698a19ac5fa4b154a7b77 Mon Sep 17 00:00:00 2001 From: Dean Karn Date: Thu, 12 Feb 2015 21:42:24 -0500 Subject: [PATCH 3/5] code cleanup --- baked_in.go | 8 +---- validator.go | 94 +++++++++++----------------------------------------- 2 files changed, 20 insertions(+), 82 deletions(-) diff --git a/baked_in.go b/baked_in.go index 728a635..375a6eb 100644 --- a/baked_in.go +++ b/baked_in.go @@ -1,9 +1,6 @@ package validator -import ( - "log" - "reflect" -) +import "reflect" var bakedInValidators = map[string]ValidationFunc{ "required": isRequired, @@ -11,8 +8,5 @@ var bakedInValidators = map[string]ValidationFunc{ func isRequired(field interface{}, param string) bool { - log.Printf("Required:%s Valid:%t\n", field, field != nil && field != reflect.Zero(reflect.TypeOf(field)).Interface()) return field != nil && field != reflect.Zero(reflect.TypeOf(field)).Interface() - - // return true } diff --git a/validator.go b/validator.go index 272a5d9..5c45558 100644 --- a/validator.go +++ b/validator.go @@ -14,20 +14,15 @@ type FieldValidationError struct { ErrorTag string } -// StructValidationErrors is a slice of errors for struct fields ( Excluding struct fields) +// 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 ArrayValidationErrors is created for each struct +// 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 { Struct string - Errors []*FieldValidationError + Errors map[string]*FieldValidationError } -// // ArrayStructValidationErrors is a struct that contains a 2D flattened list of struct specific StructValidationErrors -// type ArrayStructValidationErrors struct { -// // Key = Struct Name -// Errors map[string][]StructValidationErrors -// } - // 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 @@ -92,21 +87,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{}) []*StructValidationErrors { +func ValidateStruct(s interface{}) map[string]*StructValidationErrors { return internalValidator.ValidateStruct(s) } // ValidateStruct validates a struct and returns a struct containing the errors -func (v *Validator) ValidateStruct(s interface{}) []*StructValidationErrors { +func (v *Validator) ValidateStruct(s interface{}) map[string]*StructValidationErrors { - errorArray := []*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{}, } if structValue.Kind() == reflect.Ptr && !structValue.IsNil() { @@ -148,19 +144,27 @@ func (v *Validator) ValidateStruct(s interface{}) []*StructValidationErrors { } if structErrors := v.ValidateStruct(valueField.Interface()); structErrors != nil { - errorArray = append(errorArray, structErrors...) + for key, val := range structErrors { + errorArray[key] = val + } + // free up memory map no longer needed + structErrors = nil } default: if fieldError := v.validateStructFieldByTag(valueField.Interface(), typeField.Name, tag); fieldError != nil { - currentStructError.Errors = append(currentStructError.Errors, fieldError) + currentStructError.Errors[fieldError.Field] = fieldError + // free up memory reference + fieldError = nil } } } if currentStructError.Errors != nil { - errorArray = append(errorArray, currentStructError) + errorArray[currentStructError.Struct] = currentStructError + // free up memory + currentStructError = nil } if len(errorArray) == 0 { @@ -173,62 +177,6 @@ func (v *Validator) ValidateStruct(s interface{}) []*StructValidationErrors { // ValidateFieldWithTag validates the given field by the given tag arguments func (v *Validator) validateStructFieldByTag(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 - // } - // - // valueField := reflect.ValueOf(f) - // - // if valueField.Kind() == reflect.Ptr && !valueField.IsNil() { - // return v.validateStructFieldByTag(valueField.Elem().Interface(), name, tag) - // } - // - // // fmt.Println(typeField.Name) - // - // switch valueField.Kind() { - // - // case reflect.Struct, reflect.Invalid: - // log.Fatal("Invalid field passed to ValidateFieldWithTag") - // } - // - // // typeField := reflect.TypeOf(f) - // // name := "" - // valTags := strings.Split(tag, ",") - // - // for _, valTag := range valTags { - // - // vals := strings.Split(valTag, "=") - // key := strings.Trim(vals[0], " ") - // - // if len(key) == 0 { - // log.Fatalf("Invalid validation tag on field %s", name) - // } - // - // valFunc := v.validationFuncs[key] - // if valFunc == nil { - // log.Fatalf("Undefined validation function on field %s", name) - // } - // - // param := "" - // if len(vals) > 1 { - // param = strings.Trim(vals[1], " ") - // } - // - // if err := valFunc(f, param); !err { - // - // return &FieldValidationError{ - // Field: name, - // ErrorTag: key, - // } - // } - // - // } - // - // return nil - // - // - if err := v.validateFieldByNameAndTag(f, name, tag); err != nil { return &FieldValidationError{ Field: name, @@ -264,16 +212,12 @@ func (v *Validator) validateFieldByNameAndTag(f interface{}, name string, tag st return v.ValidateFieldByTag(valueField.Elem().Interface(), tag) } - // fmt.Println(typeField.Name) - switch valueField.Kind() { case reflect.Struct, reflect.Invalid: log.Fatal("Invalid field passed to ValidateFieldWithTag") } - // typeField := reflect.TypeOf(f) - // name := "" valTags := strings.Split(tag, ",") for _, valTag := range valTags { From 05d2507495afff2a94d30fbf7605a34d4c9c1b43 Mon Sep 17 00:00:00 2001 From: Dean Karn Date: Thu, 12 Feb 2015 22:42:16 -0500 Subject: [PATCH 4/5] add omit empty check allow for optional value checking --- baked_in.go | 193 +++++++++++++++++++++++++++++++++++++++++++++- validator.go | 14 +++- validator_test.go | 2 +- 3 files changed, 206 insertions(+), 3 deletions(-) diff --git a/baked_in.go b/baked_in.go index 375a6eb..f96398a 100644 --- a/baked_in.go +++ b/baked_in.go @@ -1,12 +1,203 @@ package validator -import "reflect" +import ( + "log" + "reflect" + "regexp" + "strconv" +) var bakedInValidators = map[string]ValidationFunc{ "required": isRequired, + "length": length, + "min": min, + "max": max, + "regex": regex, } func isRequired(field interface{}, param string) bool { return field != nil && field != reflect.Zero(reflect.TypeOf(field)).Interface() } + +// 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 length(field interface{}, param string) bool { + + st := reflect.ValueOf(field) + + switch st.Kind() { + + case reflect.String: + p := asInt(param) + + return int64(len(st.String())) == p + + case reflect.Slice, reflect.Map, reflect.Array: + p := asInt(param) + + return int64(st.Len()) == p + + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + p := asInt(param) + + return st.Int() == p + + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: + p := asUint(param) + + return st.Uint() == p + + case reflect.Float32, reflect.Float64: + p := asFloat(param) + + return st.Float() == p + + default: + log.Fatalf("Bad field type for Input Param %s for %s\n", param, field) + return false + } +} + +// min tests whether a variable value is larger or equal to a given +// 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 min(field interface{}, param string) bool { + + st := reflect.ValueOf(field) + + switch st.Kind() { + + case reflect.String: + p := asInt(param) + + return int64(len(st.String())) >= p + + case reflect.Slice, reflect.Map, reflect.Array: + p := asInt(param) + + return int64(st.Len()) >= p + + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + p := asInt(param) + + return st.Int() >= p + + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: + p := asUint(param) + + return st.Uint() >= p + + case reflect.Float32, reflect.Float64: + p := asFloat(param) + + return st.Float() >= p + + default: + log.Fatalf("Bad field type for Input Param %s for %s\n", param, field) + return false + } +} + +// max tests whether a variable value is lesser than a given +// 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 max(field interface{}, param string) bool { + + st := reflect.ValueOf(field) + + switch st.Kind() { + + case reflect.String: + p := asInt(param) + + return int64(len(st.String())) <= p + + case reflect.Slice, reflect.Map, reflect.Array: + p := asInt(param) + + return int64(st.Len()) <= p + + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + p := asInt(param) + + return st.Int() <= p + + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: + p := asUint(param) + + return st.Uint() <= p + + case reflect.Float32, reflect.Float64: + p := asFloat(param) + + return st.Float() <= p + + default: + log.Fatalf("Bad field type for Input Param %s for %s\n", param, field) + return false + } +} + +// regex is the builtin validation function that checks +// whether the string variable matches a regular expression +func regex(field interface{}, param string) bool { + + s, ok := field.(string) + if !ok { + log.Fatalf("Bad field type %s\n", field) + } + + re, err := regexp.Compile(param) + if err != nil { + log.Fatalf("Bad regex param %s\n", param) + } + + if !re.MatchString(s) { + return false + } + + return true +} + +// asInt retuns the parameter as a int64 +// or panics if it can't convert +func asInt(param string) int64 { + + i, err := strconv.ParseInt(param, 0, 64) + + if err != nil { + log.Fatalf("Bad Input Param %s\n", param) + } + + return i +} + +// asUint returns the parameter as a uint64 +// or panics if it can't convert +func asUint(param string) uint64 { + + i, err := strconv.ParseUint(param, 0, 64) + + if err != nil { + log.Fatalf("Bad Input Param %s\n", param) + } + + return i +} + +// asFloat returns the parameter as a float64 +// or panics if it can't convert +func asFloat(param string) float64 { + + i, err := strconv.ParseFloat(param, 64) + + if err != nil { + log.Fatalf("Bad Input Param %s\n", param) + } + + return i +} diff --git a/validator.go b/validator.go index 5c45558..52a3dad 100644 --- a/validator.go +++ b/validator.go @@ -8,6 +8,10 @@ import ( "unicode" ) +const ( + omitempty string = "omitempty" +) + // FieldValidationError contains a single fields validation error type FieldValidationError struct { Field string @@ -161,7 +165,7 @@ func (v *Validator) ValidateStruct(s interface{}) map[string]*StructValidationEr } } - if currentStructError.Errors != nil { + if len(currentStructError.Errors) > 0 { errorArray[currentStructError.Struct] = currentStructError // free up memory currentStructError = nil @@ -206,6 +210,10 @@ func (v *Validator) validateFieldByNameAndTag(f interface{}, name string, tag st return nil } + if strings.Contains(tag, omitempty) && !isRequired(f, "") { + return nil + } + valueField := reflect.ValueOf(f) if valueField.Kind() == reflect.Ptr && !valueField.IsNil() { @@ -229,6 +237,10 @@ func (v *Validator) validateFieldByNameAndTag(f interface{}, name string, tag st log.Fatalf("Invalid validation tag on field %s", name) } + if key == omitempty { + continue + } + valFunc := v.validationFuncs[key] if valFunc == nil { log.Fatalf("Undefined validation function on field %s", name) diff --git a/validator_test.go b/validator_test.go index 097477e..5a4a344 100644 --- a/validator_test.go +++ b/validator_test.go @@ -8,7 +8,7 @@ import ( ) type UserDetails struct { - Address string `validate:"required"` + Address string `validate:"omitempty,length=6"` } type User struct { From cff7604ce76ed7eabbafb38d18f0979bc5e634fd Mon Sep 17 00:00:00 2001 From: Dean Karn Date: Thu, 12 Feb 2015 22:43:23 -0500 Subject: [PATCH 5/5] function rename --- baked_in.go | 4 ++-- validator.go | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/baked_in.go b/baked_in.go index f96398a..1fa65c2 100644 --- a/baked_in.go +++ b/baked_in.go @@ -8,14 +8,14 @@ import ( ) var bakedInValidators = map[string]ValidationFunc{ - "required": isRequired, + "required": required, "length": length, "min": min, "max": max, "regex": regex, } -func isRequired(field interface{}, param string) bool { +func required(field interface{}, param string) bool { return field != nil && field != reflect.Zero(reflect.TypeOf(field)).Interface() } diff --git a/validator.go b/validator.go index 52a3dad..e9f62b4 100644 --- a/validator.go +++ b/validator.go @@ -210,7 +210,7 @@ func (v *Validator) validateFieldByNameAndTag(f interface{}, name string, tag st return nil } - if strings.Contains(tag, omitempty) && !isRequired(f, "") { + if strings.Contains(tag, omitempty) && !required(f, "") { return nil }