diff --git a/v.go b/v.go index 7b3dcb3..4a25024 100644 --- a/v.go +++ b/v.go @@ -42,37 +42,42 @@ import ( ) // V is a map of tag names to validators. -type V map[string]func(interface{}) error +type V map[string]func(interface{}, []string) error // BadField is an error type containing a field name and associated error. // This is the type returned from Validate. type BadField struct { Field string - Err error + Err error } func (b BadField) Error() string { return fmt.Sprintf("field %s is invalid: %v", b.Field, b.Err) } +func NotCovered() { + fmt.Println("This will not be covered") +} + // Validate accepts a struct and returns a list of errors for all // fields that are invalid. If all fields are valid, or s is not a struct type, // Validate returns nil. // // Fields that are not tagged or cannot be interfaced via reflection // are skipped. -func (v V) Validate(s interface{}) []error { +func (v V) Validate(s interface{}) []BadField { t := reflect.TypeOf(s) if t == nil || t.Kind() != reflect.Struct { return nil } val := reflect.ValueOf(s) - var errs []error + var errs []BadField for i := 0; i < t.NumField(); i++ { f := t.Field(i) fv := val.Field(i) + if !fv.CanInterface() { continue } @@ -92,18 +97,28 @@ func (v V) Validate(s interface{}) []error { continue } + // Check for params (ie. "max_length[10]") + params := []string{} + b := strings.Index(vt, "[") + if b != -1 { + params = strings.Split(vt[b+1:len(vt)-1], "|") + vt = vt[0:b] + } + vf := v[vt] if vf == nil { errs = append(errs, BadField{ Field: f.Name, - Err: fmt.Errorf("undefined validator: %q", vt), + Err: fmt.Errorf("undefined validator: %q", vt), }) continue } - if err := vf(val); err != nil { + + if err := vf(val, params); err != nil { errs = append(errs, BadField{f.Name, err}) } } + } return errs diff --git a/v_test.go b/v_test.go index b374a97..71b9b6d 100644 --- a/v_test.go +++ b/v_test.go @@ -2,9 +2,97 @@ package validate import ( "fmt" + "strconv" "testing" ) +func ExampleVWithTwoParams_Validate() { + type X struct { + A string `validate:"between[5|12]"` + B string `validate:"between[4|7]"` + C string + D string + } + + vd := make(V) + + vd["between"] = func(i interface{}, p []string) error { + s := i.(string) + + switch len(p) { + case 1: + l, err := strconv.Atoi(p[0]) + + if err != nil { + panic(err) //"Param needs to be an int") + } + + if len(s) != l { + return fmt.Errorf("%q needs to be %d in length", s, l) + } + case 2: + b, err := strconv.Atoi(p[0]) + if err != nil { + panic(err) //"Param needs to be an int") + } + e, err := strconv.Atoi(p[1]) + if err != nil { + panic(err) //"Param needs to be an int") + } + if len(s) < b || len(s) > e { + return fmt.Errorf("%q needs to be between %d and %d in length", s, b, e) + } + } + + return nil + } + + fmt.Println(vd.Validate(X{ + A: "hello there", + B: "hi", + C: "help me", + D: "I am not validated", + })) + + // Output: [field B is invalid: "hi" needs to be between 4 and 7 in length] +} + +func ExampleVWithOneParam_Validate() { + type X struct { + A string + B string + C string `validate:"max_length[10]"` + D string `validate:"max_length[10]"` + } + + vd := make(V) + + vd["max_length"] = func(i interface{}, p []string) error { + s := i.(string) + + l, err := strconv.Atoi(p[0]) + + if err != nil { + panic(err) //"Param needs to be an int") + } + + if len(s) > l { + return fmt.Errorf("%q needs to be less than %d in length", s, l) + } + + return nil + } + + fmt.Println(vd.Validate(X{ + A: "hello there", + B: "hi", + C: "help me", + D: "I am not validated", + })) + + // Output: [field D is invalid: "I am not validated" needs to be less than 10 in length] +} + func ExampleV_Validate() { type X struct { A string `validate:"long"` @@ -14,14 +102,14 @@ func ExampleV_Validate() { } vd := make(V) - vd["long"] = func(i interface{}) error { + vd["long"] = func(i interface{}, p []string) error { s := i.(string) if len(s) < 5 { return fmt.Errorf("%q is too short", s) } return nil } - vd["short"] = func(i interface{}) error { + vd["short"] = func(i interface{}, p []string) error { s := i.(string) if len(s) >= 5 { return fmt.Errorf("%q is too long", s) @@ -45,9 +133,9 @@ func TestV_Validate_allgood(t *testing.T) { } vd := make(V) - vd["odd"] = func(i interface{}) error { + vd["odd"] = func(i interface{}, p []string) error { n := i.(int) - if n & 1 == 0 { + if n&1 == 0 { return fmt.Errorf("%d is not odd", n) } return nil @@ -90,16 +178,16 @@ func TestV_Validate_multi(t *testing.T) { } vd := make(V) - vd["nonzero"] = func(i interface{}) error { + vd["nonzero"] = func(i interface{}, p []string) error { n := i.(int) if n == 0 { return fmt.Errorf("should be nonzero") } return nil } - vd["odd"] = func(i interface{}) error { + vd["odd"] = func(i interface{}, p []string) error { n := i.(int) - if n & 1 == 0 { + if n&1 == 0 { return fmt.Errorf("%d is not odd", n) } return nil @@ -129,22 +217,22 @@ func ExampleV_Validate_struct() { } vd := make(V) - vd["nonzero"] = func(i interface{}) error { + vd["nonzero"] = func(i interface{}, p []string) error { n := i.(int) if n == 0 { return fmt.Errorf("should be nonzero") } return nil } - vd["odd"] = func(i interface{}) error { + vd["odd"] = func(i interface{}, p []string) error { x := i.(X) - if x.A & 1 == 0 { + if x.A&1 == 0 { return fmt.Errorf("%d is not odd", x.A) } return nil } - errs := vd.Validate(Y{ X{ + errs := vd.Validate(Y{X{ A: 0, }}) @@ -162,7 +250,7 @@ func TestV_Validate_uninterfaceable(t *testing.T) { } vd := make(V) - vd["nonzero"] = func(i interface{}) error { + vd["nonzero"] = func(i interface{}, p []string) error { n := i.(int) if n == 0 { return fmt.Errorf("should be nonzero") @@ -181,7 +269,7 @@ func TestV_Validate_uninterfaceable(t *testing.T) { func TestV_Validate_nonstruct(t *testing.T) { vd := make(V) - vd["wrong"] = func(i interface{}) error { + vd["wrong"] = func(i interface{}, p []string) error { return fmt.Errorf("WRONG: %v", i) }