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 {