Merge branch 'v0'

pull/16/head
Dean Karn 10 years ago
commit b9c9ddef92
  1. 203
      baked_in.go
  2. 171
      validator.go
  3. 33
      validator_test.go

@ -0,0 +1,203 @@
package validator
import (
"log"
"reflect"
"regexp"
"strconv"
)
var bakedInValidators = map[string]ValidationFunc{
"required": required,
"length": length,
"min": min,
"max": max,
"regex": regex,
}
func required(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
}

@ -2,9 +2,14 @@ package validator
import ( import (
"errors" "errors"
"fmt"
"log" "log"
"reflect" "reflect"
"strings"
"unicode"
)
const (
omitempty string = "omitempty"
) )
// FieldValidationError contains a single fields validation error // FieldValidationError contains a single fields validation error
@ -13,21 +18,17 @@ type FieldValidationError struct {
ErrorTag string 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 // 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 { type StructValidationErrors struct {
Errors []FieldValidationError Struct string
} 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) // 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 // Validator implements the Validator Struct
// NOTE: Fields within are not thread safe and that is on purpose // NOTE: Fields within are not thread safe and that is on purpose
@ -40,7 +41,7 @@ type Validator struct {
validationFuncs map[string]ValidationFunc validationFuncs map[string]ValidationFunc
} }
var bakedInValidators = map[string]ValidationFunc{} // var bakedInValidators = map[string]ValidationFunc{}
var internalValidator = NewValidator("validate", bakedInValidators) var internalValidator = NewValidator("validate", bakedInValidators)
@ -89,17 +90,24 @@ func (v *Validator) AddFunction(key string, f ValidationFunc) error {
return nil return nil
} }
func ValidateStruct(s interface{}) ArrayStructValidationErrors { // ValidateStruct validates a struct and returns a struct containing the errors
func ValidateStruct(s interface{}) map[string]*StructValidationErrors {
return internalValidator.ValidateStruct(s) return internalValidator.ValidateStruct(s)
} }
func (v *Validator) ValidateStruct(s interface{}) ArrayStructValidationErrors { // ValidateStruct validates a struct and returns a struct containing the errors
func (v *Validator) ValidateStruct(s interface{}) map[string]*StructValidationErrors {
var errorStruct = ArrayStructValidationErrors{}
errorArray := map[string]*StructValidationErrors{}
structValue := reflect.ValueOf(s) structValue := reflect.ValueOf(s)
structType := reflect.TypeOf(s) structType := reflect.TypeOf(s)
structName := structType.Name()
var currentStructError = &StructValidationErrors{
Struct: structName,
Errors: map[string]*FieldValidationError{},
}
if structValue.Kind() == reflect.Ptr && !structValue.IsNil() { if structValue.Kind() == reflect.Ptr && !structValue.IsNil() {
return v.ValidateStruct(structValue.Elem().Interface()) return v.ValidateStruct(structValue.Elem().Interface())
@ -120,8 +128,135 @@ func (v *Validator) ValidateStruct(s interface{}) ArrayStructValidationErrors {
valueField = valueField.Elem() 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 {
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[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 {
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 {
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
}
if strings.Contains(tag, omitempty) && !required(f, "") {
return nil
}
valueField := reflect.ValueOf(f)
if valueField.Kind() == reflect.Ptr && !valueField.IsNil() {
return v.ValidateFieldByTag(valueField.Elem().Interface(), tag)
}
switch valueField.Kind() {
case reflect.Struct, reflect.Invalid:
log.Fatal("Invalid field passed to ValidateFieldWithTag")
} }
return errorStruct 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)
}
if key == omitempty {
continue
}
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 nil
} }

@ -8,7 +8,7 @@ import (
) )
type UserDetails struct { type UserDetails struct {
Address string `validate:"required"` Address string `validate:"omitempty,length=6"`
} }
type User struct { type User struct {
@ -19,13 +19,38 @@ type User struct {
func TestValidateStruct(t *testing.T) { func TestValidateStruct(t *testing.T) {
u := &User{ u := &User{
FirstName: "Dean Karn", FirstName: "",
Details: &UserDetails{ Details: &UserDetails{
"26 Here Blvd.", "",
}, },
} }
errors := validator.ValidateStruct(u) 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)
// }

Loading…
Cancel
Save