merge changes from Pull Request #24 from @manucorporat

pull/25/head
Dean Karn 9 years ago
parent 6856240ff0
commit 1e1442d02c
  1. 144
      baked_in.go
  2. 18
      doc.go
  3. 7
      regexes.go
  4. 61
      validator.go
  5. 22
      validator_test.go

@ -8,8 +8,9 @@ import (
"time"
)
// BakedInValidators is the map of ValidationFunc used internally
// but can be used with any new Validator if desired
// BakedInValidators is the default map of ValidationFunc
// you can add, remove or even replace items to suite your needs,
// or even disregard and use your own map if so desired.
var BakedInValidators = map[string]ValidationFunc{
"required": hasValue,
"len": hasLengthOf,
@ -54,7 +55,6 @@ func isURI(top interface{}, current interface{}, field interface{}, param string
}
func isURL(top interface{}, current interface{}, field interface{}, param string) bool {
st := reflect.ValueOf(field)
switch st.Kind() {
@ -77,146 +77,47 @@ func isURL(top interface{}, current interface{}, field interface{}, param string
}
func isEmail(top interface{}, current interface{}, field interface{}, param string) bool {
st := reflect.ValueOf(field)
switch st.Kind() {
case reflect.String:
return emailRegex.MatchString(field.(string))
}
panic(fmt.Sprintf("Bad field type %T", field))
return matchesRegex(emailRegex, field)
}
func isHsla(top interface{}, current interface{}, field interface{}, param string) bool {
st := reflect.ValueOf(field)
switch st.Kind() {
case reflect.String:
return hslaRegex.MatchString(field.(string))
}
panic(fmt.Sprintf("Bad field type %T", field))
return matchesRegex(hslaRegex, field)
}
func isHsl(top interface{}, current interface{}, field interface{}, param string) bool {
st := reflect.ValueOf(field)
switch st.Kind() {
case reflect.String:
return hslRegex.MatchString(field.(string))
}
panic(fmt.Sprintf("Bad field type %T", field))
return matchesRegex(hslRegex, field)
}
func isRgba(top interface{}, current interface{}, field interface{}, param string) bool {
st := reflect.ValueOf(field)
switch st.Kind() {
case reflect.String:
return rgbaRegex.MatchString(field.(string))
}
panic(fmt.Sprintf("Bad field type %T", field))
return matchesRegex(rgbaRegex, field)
}
func isRgb(top interface{}, current interface{}, field interface{}, param string) bool {
st := reflect.ValueOf(field)
switch st.Kind() {
case reflect.String:
return rgbRegex.MatchString(field.(string))
}
panic(fmt.Sprintf("Bad field type %T", field))
return matchesRegex(rgbRegex, field)
}
func isHexcolor(top interface{}, current interface{}, field interface{}, param string) bool {
st := reflect.ValueOf(field)
switch st.Kind() {
case reflect.String:
return hexcolorRegex.MatchString(field.(string))
}
panic(fmt.Sprintf("Bad field type %T", field))
return matchesRegex(hexcolorRegex, field)
}
func isHexadecimal(top interface{}, current interface{}, field interface{}, param string) bool {
st := reflect.ValueOf(field)
switch st.Kind() {
case reflect.String:
return hexadecimalRegex.MatchString(field.(string))
}
panic(fmt.Sprintf("Bad field type %T", field))
return matchesRegex(hexadecimalRegex, field)
}
func isNumber(top interface{}, current interface{}, field interface{}, param string) bool {
st := reflect.ValueOf(field)
switch st.Kind() {
case reflect.String:
return numberRegex.MatchString(field.(string))
}
panic(fmt.Sprintf("Bad field type %T", field))
return matchesRegex(numberRegex, field)
}
func isNumeric(top interface{}, current interface{}, field interface{}, param string) bool {
st := reflect.ValueOf(field)
switch st.Kind() {
case reflect.String:
return numericRegex.MatchString(field.(string))
}
panic(fmt.Sprintf("Bad field type %T", field))
return matchesRegex(numericRegex, field)
}
func isAlphanum(top interface{}, current interface{}, field interface{}, param string) bool {
st := reflect.ValueOf(field)
switch st.Kind() {
case reflect.String:
return alphaNumericRegex.MatchString(field.(string))
}
panic(fmt.Sprintf("Bad field type %T", field))
return matchesRegex(alphaNumericRegex, field)
}
func isAlpha(top interface{}, current interface{}, field interface{}, param string) bool {
st := reflect.ValueOf(field)
switch st.Kind() {
case reflect.String:
return alphaRegex.MatchString(field.(string))
}
panic(fmt.Sprintf("Bad field type %T", field))
return matchesRegex(alphaRegex, field)
}
func hasValue(top interface{}, current interface{}, field interface{}, param string) bool {
@ -767,10 +668,7 @@ func hasMaxOf(top interface{}, current interface{}, field interface{}, param str
func asInt(param string) int64 {
i, err := strconv.ParseInt(param, 0, 64)
if err != nil {
panic(err.Error())
}
panicIf(err)
return i
}
@ -780,10 +678,7 @@ func asInt(param string) int64 {
func asUint(param string) uint64 {
i, err := strconv.ParseUint(param, 0, 64)
if err != nil {
panic(err.Error())
}
panicIf(err)
return i
}
@ -793,10 +688,13 @@ func asUint(param string) uint64 {
func asFloat(param string) float64 {
i, err := strconv.ParseFloat(param, 64)
panicIf(err)
return i
}
func panicIf(err error) {
if err != nil {
panic(err.Error())
}
return i
}

@ -1,10 +1,8 @@
/*
Package validator implements value validations for structs and individual fields based on tags.
Package validator implements value validations for structs and individual fields based on tags. It can also handle Cross Field validation and even Cross Field Cross Struct validation for nested structs.
Built In Validator
v3 no longer contains a built in Validator instance.
myValidator = validator.NewValidator("validate", validator.BakedInValidators)
errs := myValidator.ValidateStruct(//your struct)
@ -36,6 +34,9 @@ A simple example usage:
errs.StructErrors will be empty <-- fields that were structs
errs.Errors will have 1 error of type FieldValidationError
NOTE: Anonymous Structs - they don't have names so expect the Struct name
within StructValidationErrors to be blank.
Error Handling
The error can be used like so
@ -103,9 +104,9 @@ Cross Field Validation can be implemented, for example Start & End Date range va
// when calling myValidator.ValidateFieldByTag(field, tag) val will be nil
//
// Because of the specific requirements and field names within each persons project that
// uses this library it is likely that custom functions will need to be created.
// however there are some build in Generic Cross Field validation, see Baked In Validators and
// Tags below
// uses this library it is likely that custom functions will need to be created for your
// Cross Field Validation needs, however there are some build in Generic Cross Field validations,
// see Baked In Validators and Tags below
func isDateRangeValid(val interface{}, field interface{}, param string) bool {
@ -307,10 +308,11 @@ Validator notes:
a regex which conflict with the validation definitions, although workarounds
can be made, they take away from using pure regex's. Furthermore it's quick
and dirty but the regex's become harder to maintain and are not reusable, so
it's as much as a programming philosiphy as anything.
it's as much a programming philosiphy as anything.
In place of this new validator functions should be created; a regex can be
used within the validator function and even be precompiled for better efficiency.
used within the validator function and even be precompiled for better efficiency
within regexes.go.
And the best reason, you can sumit a pull request and we can keep on adding to the
validation library of this package!

@ -14,8 +14,6 @@ const (
hslRegexString = "^hsl\\(\\s*(0|[1-9]\\d?|[12]\\d\\d|3[0-5]\\d|360)\\s*,\\s*((0|[1-9]\\d?|100)%)\\s*,\\s*((0|[1-9]\\d?|100)%)\\s*\\)$"
hslaRegexString = "^hsla\\(\\s*(0|[1-9]\\d?|[12]\\d\\d|3[0-5]\\d|360)\\s*,\\s*((0|[1-9]\\d?|100)%)\\s*,\\s*((0|[1-9]\\d?|100)%)\\s*,\\s*((0.[1-9]*)|[01])\\s*\\)$"
emailRegexString = "^(((([a-zA-Z]|\\d|[!#\\$%&'\\*\\+\\-\\/=\\?\\^_`{\\|}~]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])+(\\.([a-zA-Z]|\\d|[!#\\$%&'\\*\\+\\-\\/=\\?\\^_`{\\|}~]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])+)*)|((\\x22)((((\\x20|\\x09)*(\\x0d\\x0a))?(\\x20|\\x09)+)?(([\\x01-\\x08\\x0b\\x0c\\x0e-\\x1f\\x7f]|\\x21|[\\x23-\\x5b]|[\\x5d-\\x7e]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])|(\\([\\x01-\\x09\\x0b\\x0c\\x0d-\\x7f]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}]))))*(((\\x20|\\x09)*(\\x0d\\x0a))?(\\x20|\\x09)+)?(\\x22)))@((([a-zA-Z]|\\d|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])|(([a-zA-Z]|\\d|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])([a-zA-Z]|\\d|-|\\.|_|~|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])*([a-zA-Z]|\\d|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])))\\.)+(([a-zA-Z]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])|(([a-zA-Z]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])([a-zA-Z]|\\d|-|\\.|_|~|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])*([a-zA-Z]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])))\\.?$"
// urlRegexString = `^((ftp|http|https):\/\/)?(\S+(:\S*)?@)?((([1-9]\d?|1\d\d|2[01]\d|22[0-3])(\.(1?\d{1,2}|2[0-4]\d|25[0-5])){2}(?:\.([0-9]\d?|1\d\d|2[0-4]\d|25[0-4]))|((www\.)?)?(([a-z\x{00a1}-\x{ffff}0-9]+-?-?_?)*[a-z\x{00a1}-\x{ffff}0-9]+)(?:\.([a-z\x{00a1}-\x{ffff}]{2,}))?)|localhost)(:(\d{1,5}))?((\/|\?|#)[^\s]*)?$`
// urlRegexString = "^(?:(?:https?|ftp):\\/\\/)(?:\\S+(?::\\S*)?@)?(?:(?!10(?:\\.\\d{1,3}){3})(?!127(?:\\.\\d{1,3}){3})(?!169\\.254(?:\\.\\d{1,3}){2})(?!192\\.168(?:\\.\\d{1,3}){2})(?!172\\.(?:1[6-9]|2\\d|3[0-1])(?:\\.\\d{1,3}){2})(?:[1-9]\\d?|1\\d\\d|2[01]\\d|22[0-3])(?:\\.(?:1?\\d{1,2}|2[0-4]\\d|25[0-5])){2}(?:\\.(?:[1-9]\\d?|1\\d\\d|2[0-4]\\d|25[0-4]))|(?:(?:[a-z\u00a1-\uffff0-9]+-?)*[a-z\u00a1-\uffff0-9]+)(?:\\.(?:[a-z\u00a1-\uffff0-9]+-?)*[a-z\u00a1-\uffff0-9]+)*(?:\\.(?:[a-z\u00a1-\uffff]{2,})))(?::\\d{2,5})?(?:\\/[^\\s]*)?$"
)
var (
@ -31,3 +29,8 @@ var (
hslaRegex = regexp.MustCompile(hslaRegexString)
emailRegex = regexp.MustCompile(emailRegexString)
)
func matchesRegex(regex *regexp.Regexp, field interface{}) bool {
fieldAsString := field.(string) //this will panic inherently
return regex.MatchString(fieldAsString)
}

@ -9,6 +9,7 @@
package validator
import (
"bytes"
"errors"
"fmt"
"reflect"
@ -28,7 +29,8 @@ const (
validationStructErrMsg = "Struct:%s\n"
)
// FieldValidationError contains a single fields validation error
// FieldValidationError contains a single field's validation error along
// with other properties that may be needed for error message creation
type FieldValidationError struct {
Field string
ErrorTag string
@ -44,7 +46,8 @@ func (e *FieldValidationError) Error() string {
return fmt.Sprintf(validationFieldErrMsg, e.Field, e.ErrorTag)
}
// StructValidationErrors is hierarchical list of field and struct errors
// StructValidationErrors is hierarchical list of field and struct validation errors
// for a non hierarchical representation please see the Flatten method for StructValidationErrors
type StructValidationErrors struct {
// Name of the Struct
Struct string
@ -58,21 +61,20 @@ type StructValidationErrors struct {
// This is intended for use in development + debugging and not intended to be a production error message.
// it also allows StructValidationErrors to be used as an Error interface
func (e *StructValidationErrors) Error() string {
s := fmt.Sprintf(validationStructErrMsg, e.Struct)
buff := bytes.NewBufferString(fmt.Sprintf(validationStructErrMsg, e.Struct))
for _, err := range e.Errors {
s += err.Error()
buff.WriteString(err.Error())
}
for _, sErr := range e.StructErrors {
s += sErr.Error()
for _, err := range e.StructErrors {
buff.WriteString(err.Error())
}
return fmt.Sprintf("%s\n\n", s)
buff.WriteString("\n\n")
return buff.String()
}
// Flatten flattens the StructValidationErrors hierarchical sctructure into a flat namespace style field name
// Flatten flattens the StructValidationErrors hierarchical structure into a flat namespace style field name
// for those that want/need it
func (e *StructValidationErrors) Flatten() map[string]*FieldValidationError {
@ -110,17 +112,16 @@ type ValidationFunc func(top interface{}, current interface{}, f interface{}, pa
// Validator implements the Validator Struct
// NOTE: Fields within are not thread safe and that is on purpose
// Functions Tags etc. should all be predifined before use, so subscribe to the philosiphy
// Functions and Tags should all be predifined before use, so subscribe to the philosiphy
// or make it thread safe on your end
type Validator struct {
// TagName being used.
// tagName being used.
tagName string
// validationFuncs is a map of validation functions and the tag keys
validationFuncs map[string]ValidationFunc
}
// NewValidator creates a new Validator instance
// NOTE: it is not necessary to create a new validator as the internal one will do in 99.9% of cases, but the option is there.
// NewValidator creates a new Validator instance for use.
func NewValidator(tagName string, funcs map[string]ValidationFunc) *Validator {
return &Validator{
tagName: tagName,
@ -128,12 +129,14 @@ func NewValidator(tagName string, funcs map[string]ValidationFunc) *Validator {
}
}
// SetTag sets tagName of the Validator to one of your choosing
// SetTag sets tagName of the Validator to one of your choosing after creation
// perhaps to dodge a tag name conflict in a specific section of code
func (v *Validator) SetTag(tagName string) {
v.tagName = tagName
}
// AddFunction adds a ValidationFunc to a Validator's map of validators denoted by the key
// NOTE: if the key already exists, it will get replaced.
func (v *Validator) AddFunction(key string, f ValidationFunc) error {
if len(key) == 0 {
@ -141,38 +144,30 @@ func (v *Validator) AddFunction(key string, f ValidationFunc) error {
}
if f == nil {
return errors.New("Function Key cannot be empty")
return errors.New("Function cannot be empty")
}
// Commented out to allow overwritting of Baked In Function if so desired.
// if v.ValidationFuncs[key] != nil {
// return errors.New(fmt.Sprintf("Validation Function with key: %s already exists.", key))
// }
v.validationFuncs[key] = f
return nil
}
// ValidateStruct validates a struct and returns a struct containing the errors
// ValidateStruct validates a struct, even it's nested structs, and returns a struct containing the errors
// NOTE: Nested Arrays, or Maps of structs do not get validated only the Array or Map itself; the reason is that there is no good
// way to represent or report which struct within the array has the error, besides can validate the struct prior to adding it to
// the Array or Map.
func (v *Validator) ValidateStruct(s interface{}) *StructValidationErrors {
return v.validateStructRecursive(s, s, s)
}
// validateStructRecursive validates a struct recursivly and passes the top level struct around for use in validator functions and returns a struct containing the errors
// validateStructRecursive validates a struct recursivly and passes the top level and current struct around for use in validator functions and returns a struct containing the errors
func (v *Validator) validateStructRecursive(top interface{}, current interface{}, s interface{}) *StructValidationErrors {
structValue := reflect.ValueOf(s)
structType := reflect.TypeOf(s)
structName := structType.Name()
validationErrors := &StructValidationErrors{
Struct: structName,
Errors: map[string]*FieldValidationError{},
StructErrors: map[string]*StructValidationErrors{},
}
if structValue.Kind() == reflect.Ptr && !structValue.IsNil() {
return v.validateStructRecursive(top, current, structValue.Elem().Interface())
}
@ -181,6 +176,12 @@ func (v *Validator) validateStructRecursive(top interface{}, current interface{}
panic("interface passed for validation is not a struct")
}
validationErrors := &StructValidationErrors{
Struct: structName,
Errors: map[string]*FieldValidationError{},
StructErrors: map[string]*StructValidationErrors{},
}
var numFields = structValue.NumField()
for i := 0; i < numFields; i++ {
@ -255,7 +256,7 @@ func (v *Validator) ValidateFieldByTag(f interface{}, tag string) *FieldValidati
return v.ValidateFieldByTagAndValue(nil, f, tag)
}
// ValidateFieldByTagAndValue allows validation of a single field, still using tag style validation to check multiple errors
// ValidateFieldByTagAndValue allows validation of a single field, possibly even against another fields value, still using tag style validation to check multiple errors
func (v *Validator) ValidateFieldByTagAndValue(val interface{}, f interface{}, tag string) *FieldValidationError {
return v.validateFieldByNameAndTagAndValue(nil, val, f, "", tag)

@ -1163,7 +1163,7 @@ func (ms *MySuite) TestHsla(c *C) {
c.Assert(err.ErrorTag, Equals, "hsla")
i := 1
c.Assert(func() { myValidator.ValidateFieldByTag(i, "hsla") }, PanicMatches, "Bad field type int")
c.Assert(func() { myValidator.ValidateFieldByTag(i, "hsla") }, PanicMatches, "interface conversion: interface is int, not string")
}
func (ms *MySuite) TestHsl(c *C) {
@ -1197,7 +1197,7 @@ func (ms *MySuite) TestHsl(c *C) {
c.Assert(err.ErrorTag, Equals, "hsl")
i := 1
c.Assert(func() { myValidator.ValidateFieldByTag(i, "hsl") }, PanicMatches, "Bad field type int")
c.Assert(func() { myValidator.ValidateFieldByTag(i, "hsl") }, PanicMatches, "interface conversion: interface is int, not string")
}
func (ms *MySuite) TestRgba(c *C) {
@ -1230,7 +1230,7 @@ func (ms *MySuite) TestRgba(c *C) {
c.Assert(err.ErrorTag, Equals, "rgba")
i := 1
c.Assert(func() { myValidator.ValidateFieldByTag(i, "rgba") }, PanicMatches, "Bad field type int")
c.Assert(func() { myValidator.ValidateFieldByTag(i, "rgba") }, PanicMatches, "interface conversion: interface is int, not string")
}
func (ms *MySuite) TestRgb(c *C) {
@ -1259,7 +1259,7 @@ func (ms *MySuite) TestRgb(c *C) {
c.Assert(err.ErrorTag, Equals, "rgb")
i := 1
c.Assert(func() { myValidator.ValidateFieldByTag(i, "rgb") }, PanicMatches, "Bad field type int")
c.Assert(func() { myValidator.ValidateFieldByTag(i, "rgb") }, PanicMatches, "interface conversion: interface is int, not string")
}
func (ms *MySuite) TestEmail(c *C) {
@ -1289,7 +1289,7 @@ func (ms *MySuite) TestEmail(c *C) {
c.Assert(err.ErrorTag, Equals, "email")
i := true
c.Assert(func() { myValidator.ValidateFieldByTag(i, "email") }, PanicMatches, "Bad field type bool")
c.Assert(func() { myValidator.ValidateFieldByTag(i, "email") }, PanicMatches, "interface conversion: interface is bool, not string")
}
func (ms *MySuite) TestHexColor(c *C) {
@ -1313,7 +1313,7 @@ func (ms *MySuite) TestHexColor(c *C) {
c.Assert(err.ErrorTag, Equals, "hexcolor")
i := true
c.Assert(func() { myValidator.ValidateFieldByTag(i, "hexcolor") }, PanicMatches, "Bad field type bool")
c.Assert(func() { myValidator.ValidateFieldByTag(i, "hexcolor") }, PanicMatches, "interface conversion: interface is bool, not string")
}
func (ms *MySuite) TestHexadecimal(c *C) {
@ -1328,7 +1328,7 @@ func (ms *MySuite) TestHexadecimal(c *C) {
c.Assert(err.ErrorTag, Equals, "hexadecimal")
i := true
c.Assert(func() { myValidator.ValidateFieldByTag(i, "hexadecimal") }, PanicMatches, "Bad field type bool")
c.Assert(func() { myValidator.ValidateFieldByTag(i, "hexadecimal") }, PanicMatches, "interface conversion: interface is bool, not string")
}
func (ms *MySuite) TestNumber(c *C) {
@ -1373,7 +1373,7 @@ func (ms *MySuite) TestNumber(c *C) {
c.Assert(err.ErrorTag, Equals, "number")
i := 1
c.Assert(func() { myValidator.ValidateFieldByTag(i, "number") }, PanicMatches, "Bad field type int")
c.Assert(func() { myValidator.ValidateFieldByTag(i, "number") }, PanicMatches, "interface conversion: interface is int, not string")
}
func (ms *MySuite) TestNumeric(c *C) {
@ -1413,7 +1413,7 @@ func (ms *MySuite) TestNumeric(c *C) {
c.Assert(err.ErrorTag, Equals, "numeric")
i := 1
c.Assert(func() { myValidator.ValidateFieldByTag(i, "numeric") }, PanicMatches, "Bad field type int")
c.Assert(func() { myValidator.ValidateFieldByTag(i, "numeric") }, PanicMatches, "interface conversion: interface is int, not string")
}
func (ms *MySuite) TestAlphaNumeric(c *C) {
@ -1427,7 +1427,7 @@ func (ms *MySuite) TestAlphaNumeric(c *C) {
c.Assert(err, NotNil)
c.Assert(err.ErrorTag, Equals, "alphanum")
c.Assert(func() { myValidator.ValidateFieldByTag(1, "alphanum") }, PanicMatches, "Bad field type int")
c.Assert(func() { myValidator.ValidateFieldByTag(1, "alphanum") }, PanicMatches, "interface conversion: interface is int, not string")
}
func (ms *MySuite) TestAlpha(c *C) {
@ -1441,7 +1441,7 @@ func (ms *MySuite) TestAlpha(c *C) {
c.Assert(err, NotNil)
c.Assert(err.ErrorTag, Equals, "alpha")
c.Assert(func() { myValidator.ValidateFieldByTag(1, "alpha") }, PanicMatches, "Bad field type int")
c.Assert(func() { myValidator.ValidateFieldByTag(1, "alpha") }, PanicMatches, "interface conversion: interface is int, not string")
}
func (ms *MySuite) TestFlattening(c *C) {

Loading…
Cancel
Save