diff --git a/builtins.go b/builtins.go index 3dd9d92..8d977bc 100644 --- a/builtins.go +++ b/builtins.go @@ -17,7 +17,6 @@ package validator import ( - "errors" "reflect" "regexp" "strconv" @@ -46,7 +45,7 @@ func nonzero(v interface{}, param string) error { case reflect.Invalid: valid = false // always invalid default: - panic("Unsupported type " + st.String()) + return ErrUnsupported } if !valid { @@ -63,17 +62,37 @@ func length(v interface{}, param string) error { valid := true switch st.Kind() { case reflect.String: - valid = int64(len(st.String())) == asInt(param) + p, err := asInt(param) + if err != nil { + return ErrBadParameter + } + valid = int64(len(st.String())) == p case reflect.Slice, reflect.Map, reflect.Array: - valid = int64(st.Len()) == asInt(param) + p, err := asInt(param) + if err != nil { + return ErrBadParameter + } + valid = int64(st.Len()) == p case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: - valid = st.Int() == asInt(param) + p, err := asInt(param) + if err != nil { + return ErrBadParameter + } + valid = st.Int() == p case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: - valid = st.Uint() == asUint(param) + p, err := asUint(param) + if err != nil { + return ErrBadParameter + } + valid = st.Uint() == p case reflect.Float32, reflect.Float64: - valid = st.Float() == asFloat(param) + p, err := asFloat(param) + if err != nil { + return ErrBadParameter + } + valid = st.Float() == p default: - panic("length is not a valid validation tag for type " + st.String()) + return ErrUnsupported } if !valid { return ErrLen @@ -90,17 +109,37 @@ func min(v interface{}, param string) error { invalid := false switch st.Kind() { case reflect.String: - invalid = int64(len(st.String())) < asInt(param) + p, err := asInt(param) + if err != nil { + return ErrBadParameter + } + invalid = int64(len(st.String())) < p case reflect.Slice, reflect.Map, reflect.Array: - invalid = int64(st.Len()) < asInt(param) + p, err := asInt(param) + if err != nil { + return ErrBadParameter + } + invalid = int64(st.Len()) < p case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: - invalid = st.Int() < asInt(param) + p, err := asInt(param) + if err != nil { + return ErrBadParameter + } + invalid = st.Int() < p case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: - invalid = st.Uint() < asUint(param) + p, err := asUint(param) + if err != nil { + return ErrBadParameter + } + invalid = st.Uint() < p case reflect.Float32, reflect.Float64: - invalid = st.Float() < asFloat(param) + p, err := asFloat(param) + if err != nil { + return ErrBadParameter + } + invalid = st.Float() < p default: - panic("min is not a valid validation tag for type " + st.String()) + return ErrUnsupported } if invalid { return ErrMin @@ -117,17 +156,37 @@ func max(v interface{}, param string) error { var invalid bool switch st.Kind() { case reflect.String: - invalid = int64(len(st.String())) > asInt(param) + p, err := asInt(param) + if err != nil { + return ErrBadParameter + } + invalid = int64(len(st.String())) > p case reflect.Slice, reflect.Map, reflect.Array: - invalid = int64(st.Len()) > asInt(param) + p, err := asInt(param) + if err != nil { + return ErrBadParameter + } + invalid = int64(st.Len()) > p case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: - invalid = st.Int() > asInt(param) + p, err := asInt(param) + if err != nil { + return ErrBadParameter + } + invalid = st.Int() > p case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: - invalid = st.Uint() > asUint(param) + p, err := asUint(param) + if err != nil { + return ErrBadParameter + } + invalid = st.Uint() > p case reflect.Float32, reflect.Float64: - invalid = st.Float() > asFloat(param) + p, err := asFloat(param) + if err != nil { + return ErrBadParameter + } + invalid = st.Float() > p default: - panic("max is not a valid validation tag for type " + st.String()) + return ErrUnsupported } if invalid { return ErrMax @@ -140,43 +199,46 @@ func max(v interface{}, param string) error { func regex(v interface{}, param string) error { s, ok := v.(string) if !ok { - panic("regexp requires a string") + return ErrUnsupported } - re := regexp.MustCompile(param) + re, err := regexp.Compile(param) + if err != nil { + return ErrBadParameter + } if !re.MatchString(s) { - return errors.New("regexp does not match") + return ErrRegexp } return nil } // asInt retuns the parameter as a int64 // or panics if it can't convert -func asInt(param string) int64 { +func asInt(param string) (int64, error) { i, err := strconv.ParseInt(param, 0, 64) if err != nil { - panic("Invalid param " + param + ", should be an integer") + return 0, ErrBadParameter } - return i + return i, nil } // asUint retuns the parameter as a uint64 // or panics if it can't convert -func asUint(param string) uint64 { +func asUint(param string) (uint64, error) { i, err := strconv.ParseUint(param, 0, 64) if err != nil { - panic("Invalid param " + param + ", should be an unsigned integer") + return 0, ErrBadParameter } - return i + return i, nil } // asFloat retuns the parameter as a float64 // or panics if it can't convert -func asFloat(param string) float64 { +func asFloat(param string) (float64, error) { i, err := strconv.ParseFloat(param, 64) if err != nil { - panic("Invalid param " + param + ", should be a float") + return 0.0, ErrBadParameter } - return i + return i, nil } diff --git a/doc.go b/doc.go index e1759f0..df2b55e 100644 --- a/doc.go +++ b/doc.go @@ -85,7 +85,7 @@ Note that there are no tests to prevent conflicting validator parameters. For instance, these fields will never be valid. ... - A int `validate:"max=0,nonzero"` + A int `validate:"max=0,min=1"` B string `validate:"len=10,regexp=^$" ... @@ -98,7 +98,7 @@ First, one needs to create a validation function. func notZZ(v interface{}, param string) error { st := reflect.ValueOf(v) if st.Kind() != reflect.String { - return errors.New("notZZ only validates strings") + return validate.ErrUnsupported } if st.String() == "ZZ" { return errors.New("value cannot be ZZ") @@ -127,7 +127,7 @@ To use parameters, it is very similar. func notSomething(v interface{}, param string) error { st := reflect.ValueOf(v) if st.Kind() != reflect.String { - return errors.New("notSomething only validates strings") + return validate.ErrUnsupported } if st.String() == param { return errors.New("value cannot be " + param) @@ -155,7 +155,8 @@ And you can delete a validation function by setting it to nil. validate.SetValidationFunc("notzz", nil) validate.SetValidationFunc("nonzero", nil) -Using a non-existing validation func in a field tag will panic. +Using a non-existing validation func in a field tag will always return +false and with error validate.ErrUnknownTag. Finally, package validator also provides a helper function that can be used to validate simple variables/values. diff --git a/examplevalidate_test.go b/examplevalidate_test.go index 8df81c8..2c04c07 100644 --- a/examplevalidate_test.go +++ b/examplevalidate_test.go @@ -70,7 +70,7 @@ func ExampleValidate() { // Street cannot be empty. // Invalid due to fields: // - Age ([less than min]) - // - Email ([regexp does not match]) + // - Email ([regular expression mismatch]) // - Address.Street ([zero value]) } diff --git a/validator.go b/validator.go index cc93d6c..f8e6623 100644 --- a/validator.go +++ b/validator.go @@ -35,9 +35,18 @@ var ( // ErrLen is the error returned when length is not equal to // param specified ErrLen = errors.New("invalid length") + // ErrRegexp is the error returned when the value does not + // match the provided regular expression parameter + ErrRegexp = errors.New("regular expression mismatch") // ErrUnsupported is the error error returned when a validation rule // is used with an unsupported variable type ErrUnsupported = errors.New("unsupported type") + // ErrBadParameter is the error returned when an invalid parameter + // is provided to a validation rule (e.g. a string where an int was + // expected (max=foo,len=bar) or missing a parameter when one is required (len=)) + ErrBadParameter = errors.New("bad parameter") + // ErrUnknownTag is the error returned when an unknown tag is found + ErrUnknownTag = errors.New("unknown tag") // ErrInvalid is the error returned when variable is invalid // (normally a nil pointer) ErrInvalid = errors.New("invalid value") @@ -219,7 +228,11 @@ func (mv *Validator) Valid(val interface{}, tags string) (bool, []error) { // validateVar validates one single variable func (mv *Validator) validateVar(v interface{}, tag string) []error { - tags := mv.parseTags(tag) + tags, err := mv.parseTags(tag) + if err != nil { + // unknown tag found, give up. + return []error{err} + } errs := make([]error, 0, len(tags)) for _, t := range tags { if err := t.Fn(v, t.Param); err != nil { @@ -237,8 +250,7 @@ type tag struct { } // parseTags parses all individual tags found within a struct tag. -// These usually are in the -func (mv *Validator) parseTags(t string) []tag { +func (mv *Validator) parseTags(t string) ([]tag, error) { tl := strings.Split(t, ",") tags := make([]tag, 0, len(tl)) for _, i := range tl { @@ -246,18 +258,17 @@ func (mv *Validator) parseTags(t string) []tag { v := strings.SplitN(i, "=", 2) tg.Name = strings.Trim(v[0], " ") if tg.Name == "" { - panic("empty tag") + return []tag{}, ErrUnknownTag } if len(v) > 1 { tg.Param = strings.Trim(v[1], " ") } var found bool if tg.Fn, found = mv.validationFuncs[tg.Name]; !found { - panic("invalid tag " + tg.Name) + return []tag{}, ErrUnknownTag } - tags = append(tags, tg) } - return tags + return tags, nil } diff --git a/validator_test.go b/validator_test.go index fa3a2fc..1871414 100644 --- a/validator_test.go +++ b/validator_test.go @@ -17,8 +17,8 @@ package validator_test import ( - . "gopkg.in/check.v1" "testing" + . "gopkg.in/check.v1" "gopkg.in/validator.v1" ) @@ -178,21 +178,44 @@ func (ms *MySuite) TestValidateStructVar(c *C) { c.Assert(errs, HasError, validator.ErrUnsupported) } -/* -func (ms *MySuite) TestValidateVar1(c *C) { - t1 := TestStruct{Number: 123, Text: "Blah"} +func (ms *MySuite) TestUnknownTag(c *C) { + type test struct { + A int `validate:"foo"` + } + t := test{} + valid, errs := validator.Validate(t) + c.Assert(valid, Equals, false) + c.Assert(errs, HasLen, 1) + c.Assert(errs["A"], HasError, validator.ErrUnknownTag) +} - //s := reflect.ValueOf(t).Elem() - s := reflect.ValueOf(t1) - st := s.Type() - for i := 0; i < s.NumField(); i++ { - f := s.Field(i) - fmt.Printf("%d: %s %s %s = %v\n", i, - st.Field(i).Name, f.Type(), st.Field(i).Tag.Get("validator"), f.Interface()) +func (ms *MySuite) TestUnsupported(c *C) { + type test struct { + A int `validate:"regexp=a.*b"` + B float64 `validate:"regexp=.*"` } + t := test{} + valid, errs := validator.Validate(t) + c.Assert(valid, Equals, false) + c.Assert(errs, HasLen, 2) + c.Assert(errs["A"], HasError, validator.ErrUnsupported) + c.Assert(errs["B"], HasError, validator.ErrUnsupported) +} +func (ms *MySuite) TestBadParameter(c *C) { + type test struct { + A string `validate:"min="` + B string `validate:"len=="` + C string `validate:"max=foo"` + } + t := test{} + valid, errs := validator.Validate(t) + c.Assert(valid, Equals, false) + c.Assert(errs, HasLen, 3) + c.Assert(errs["A"], HasError, validator.ErrBadParameter) + c.Assert(errs["B"], HasError, validator.ErrBadParameter) + c.Assert(errs["C"], HasError, validator.ErrBadParameter) } -*/ type hasErrorChecker struct { *CheckerInfo