Add support for custom validation rules

This commit is contained in:
Nise Void 2019-04-26 10:33:41 +02:00
parent 922297b368
commit f79b7c6649
Signed by: NiseVoid
GPG Key ID: FBA14AC83EA602F3
4 changed files with 54 additions and 20 deletions

View File

@ -6,15 +6,21 @@ import (
"strconv" "strconv"
) )
func inputInt(kind reflect.Kind, value string) interface{} { // InputFunc is a function that converts the parameter of a validation rule to the desired type
return int(inputSame(reflect.Int, value).(int64)) type InputFunc func(reflect.Kind, string) interface{}
// InputInt always returns an int
func InputInt(kind reflect.Kind, value string) interface{} {
return int(InputSame(reflect.Int, value).(int64))
} }
func inputRegexp(kind reflect.Kind, value string) interface{} { // InputRegexp always returns a compiled regular expression
func InputRegexp(kind reflect.Kind, value string) interface{} {
return regexp.MustCompile(value) return regexp.MustCompile(value)
} }
func inputSame(kind reflect.Kind, value string) interface{} { // InputSame returns the type matching the field
func InputSame(kind reflect.Kind, value string) interface{} {
var val interface{} var val interface{}
var err error var err error

View File

@ -6,17 +6,25 @@ import (
"strings" "strings"
) )
type listFunc func(reflect.Value, interface{}) bool // ValidationFunc is a function to validate a field
type ValidationFunc func(reflect.Value, interface{}) bool
// Kinds is a map with validation funcs for each reflect.Kind
type Kinds map[reflect.Kind]ValidationFunc
type listFuncInfo struct { type listFuncInfo struct {
inputFunc func(reflect.Kind, string) interface{} inputFunc InputFunc
kinds _kinds kinds Kinds
} }
type _kinds map[reflect.Kind]listFunc // AddRule adds a rule to the list of validation functions
func AddRule(name string, inputFunc InputFunc, kinds Kinds) {
funcs[name] = listFuncInfo{inputFunc, kinds}
}
// nolint: dupl // nolint: dupl
var funcs = map[string]listFuncInfo{ var funcs = map[string]listFuncInfo{
`required`: {nil, _kinds{ `required`: {nil, Kinds{
reflect.Ptr: func(rv reflect.Value, _ interface{}) bool { reflect.Ptr: func(rv reflect.Value, _ interface{}) bool {
return !rv.IsNil() return !rv.IsNil()
}, },
@ -44,29 +52,29 @@ var funcs = map[string]listFuncInfo{
}}, }},
// Strings // Strings
`prefix`: {inputSame, _kinds{ `prefix`: {InputSame, Kinds{
reflect.String: func(rv reflect.Value, val interface{}) bool { reflect.String: func(rv reflect.Value, val interface{}) bool {
return strings.HasPrefix(rv.String(), val.(string)) return strings.HasPrefix(rv.String(), val.(string))
}, },
}}, }},
`suffix`: {inputSame, _kinds{ `suffix`: {InputSame, Kinds{
reflect.String: func(rv reflect.Value, val interface{}) bool { reflect.String: func(rv reflect.Value, val interface{}) bool {
return strings.HasSuffix(rv.String(), val.(string)) return strings.HasSuffix(rv.String(), val.(string))
}, },
}}, }},
`contains`: {inputSame, _kinds{ `contains`: {InputSame, Kinds{
reflect.String: func(rv reflect.Value, val interface{}) bool { reflect.String: func(rv reflect.Value, val interface{}) bool {
return strings.Contains(rv.String(), val.(string)) return strings.Contains(rv.String(), val.(string))
}, },
}}, }},
`regexp`: {inputRegexp, _kinds{ `regexp`: {InputRegexp, Kinds{
reflect.String: func(rv reflect.Value, val interface{}) bool { reflect.String: func(rv reflect.Value, val interface{}) bool {
return val.(*regexp.Regexp).MatchString(rv.String()) return val.(*regexp.Regexp).MatchString(rv.String())
}, },
}}, }},
// Comparisons // Comparisons
`eq`: {inputSame, _kinds{ `eq`: {InputSame, Kinds{
reflect.String: func(rv reflect.Value, val interface{}) bool { reflect.String: func(rv reflect.Value, val interface{}) bool {
return rv.String() == val.(string) return rv.String() == val.(string)
}, },
@ -82,7 +90,7 @@ var funcs = map[string]listFuncInfo{
}}, }},
// Integers // Integers
`gt`: {inputSame, _kinds{ `gt`: {InputSame, Kinds{
reflect.Int: func(rv reflect.Value, val interface{}) bool { reflect.Int: func(rv reflect.Value, val interface{}) bool {
return rv.Int() > val.(int64) return rv.Int() > val.(int64)
}, },
@ -93,7 +101,7 @@ var funcs = map[string]listFuncInfo{
return rv.Float() > val.(float64) return rv.Float() > val.(float64)
}, },
}}, }},
`lt`: {inputSame, _kinds{ `lt`: {InputSame, Kinds{
reflect.Int: func(rv reflect.Value, val interface{}) bool { reflect.Int: func(rv reflect.Value, val interface{}) bool {
return rv.Int() < val.(int64) return rv.Int() < val.(int64)
}, },
@ -106,7 +114,7 @@ var funcs = map[string]listFuncInfo{
}}, }},
// Slices, maps & strings // Slices, maps & strings
`len`: {inputInt, _kinds{ `len`: {InputInt, Kinds{
reflect.Slice: func(rv reflect.Value, val interface{}) bool { reflect.Slice: func(rv reflect.Value, val interface{}) bool {
return rv.Len() == val.(int) return rv.Len() == val.(int)
}, },
@ -117,7 +125,7 @@ var funcs = map[string]listFuncInfo{
return rv.Len() == val.(int) return rv.Len() == val.(int)
}, },
}}, }},
`min`: {inputInt, _kinds{ `min`: {InputInt, Kinds{
reflect.Slice: func(rv reflect.Value, val interface{}) bool { reflect.Slice: func(rv reflect.Value, val interface{}) bool {
return rv.Len() >= val.(int) return rv.Len() >= val.(int)
}, },
@ -128,7 +136,7 @@ var funcs = map[string]listFuncInfo{
return rv.Len() >= val.(int) return rv.Len() >= val.(int)
}, },
}}, }},
`max`: {inputInt, _kinds{ `max`: {InputInt, Kinds{
reflect.Slice: func(rv reflect.Value, val interface{}) bool { reflect.Slice: func(rv reflect.Value, val interface{}) bool {
return rv.Len() <= val.(int) return rv.Len() <= val.(int)
}, },

View File

@ -1,9 +1,29 @@
package validate package validate
import ( import (
"reflect"
"testing" "testing"
) )
func TestAddRule(t *testing.T) {
type s struct {
A string `validate:"custom"`
}
AddRule(`custom`, nil, Kinds{
reflect.String: func(rv reflect.Value, _ interface{}) bool {
return rv.String() == `custom`
},
})
var pass = s{`custom`}
var fail = s{`somethingelse`}
check(t, pass, 0)
check(t, fail, 1)
}
func TestRuleRequired(t *testing.T) { func TestRuleRequired(t *testing.T) {
type s struct { type s struct {
A *string `validate:"required"` A *string `validate:"required"`

View File

@ -222,7 +222,7 @@ func getTagFuncs(i int, ft reflect.StructField, kind reflect.Kind, tags []string
return rules return rules
} }
func getTagFunc(tag, value string, kind reflect.Kind) (listFunc, interface{}) { func getTagFunc(tag, value string, kind reflect.Kind) (ValidationFunc, interface{}) {
tagInfo, ok := funcs[tag] tagInfo, ok := funcs[tag]
if !ok { if !ok {
panic(`Unknown validation ` + tag) panic(`Unknown validation ` + tag)