Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 21 additions & 6 deletions v.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand All @@ -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
Expand Down
114 changes: 101 additions & 13 deletions v_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"`
Expand All @@ -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)
Expand All @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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,
}})

Expand All @@ -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")
Expand All @@ -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)
}

Expand Down