diff --git a/README.md b/README.md index b0b2cfe..f41c088 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ Package go-validate-yourself ================ -[![Build Status](https://travis-ci.org/joeybloggs/go-validate-yourself.svg?branch=v2-development)](https://travis-ci.org/joeybloggs/go-validate-yourself) +[![Build Status](https://travis-ci.org/joeybloggs/go-validate-yourself.svg?branch=v2)](https://travis-ci.org/joeybloggs/go-validate-yourself) Package validator implements value validations for structs and individual fields based on tags. @@ -9,20 +9,20 @@ Installation Just use go get. - go get gopkg.in/joeybloggs/go-validate-yourself.v1 + go get gopkg.in/joeybloggs/go-validate-yourself.v2 or to update - go get -u gopkg.in/joeybloggs/go-validate-yourself.v1 + go get -u gopkg.in/joeybloggs/go-validate-yourself.v2 And then just import the package into your own code. - import "gopkg.in/joeybloggs/go-validate-yourself.v1" + import "gopkg.in/joeybloggs/go-validate-yourself.v2" Usage ===== -Please see http://godoc.org/gopkg.in/joeybloggs/go-validate-yourself.v1 for detailed usage docs. +Please see http://godoc.org/gopkg.in/joeybloggs/go-validate-yourself.v2 for detailed usage docs. Contributing ============ diff --git a/baked_in.go b/baked_in.go index 11a7375..07c1563 100644 --- a/baked_in.go +++ b/baked_in.go @@ -34,7 +34,7 @@ var BakedInValidators = map[string]ValidationFunc{ "uri": isURI, } -func isURI(field interface{}, param string) bool { +func isURI(val interface{}, field interface{}, param string) bool { st := reflect.ValueOf(field) @@ -49,7 +49,7 @@ func isURI(field interface{}, param string) bool { panic(fmt.Sprintf("Bad field type %T", field)) } -func isURL(field interface{}, param string) bool { +func isURL(val interface{}, field interface{}, param string) bool { st := reflect.ValueOf(field) @@ -72,7 +72,7 @@ func isURL(field interface{}, param string) bool { panic(fmt.Sprintf("Bad field type %T", field)) } -func isEmail(field interface{}, param string) bool { +func isEmail(val interface{}, field interface{}, param string) bool { st := reflect.ValueOf(field) @@ -85,7 +85,7 @@ func isEmail(field interface{}, param string) bool { panic(fmt.Sprintf("Bad field type %T", field)) } -func isHsla(field interface{}, param string) bool { +func isHsla(val interface{}, field interface{}, param string) bool { st := reflect.ValueOf(field) @@ -98,7 +98,7 @@ func isHsla(field interface{}, param string) bool { panic(fmt.Sprintf("Bad field type %T", field)) } -func isHsl(field interface{}, param string) bool { +func isHsl(val interface{}, field interface{}, param string) bool { st := reflect.ValueOf(field) @@ -111,7 +111,7 @@ func isHsl(field interface{}, param string) bool { panic(fmt.Sprintf("Bad field type %T", field)) } -func isRgba(field interface{}, param string) bool { +func isRgba(val interface{}, field interface{}, param string) bool { st := reflect.ValueOf(field) @@ -124,7 +124,7 @@ func isRgba(field interface{}, param string) bool { panic(fmt.Sprintf("Bad field type %T", field)) } -func isRgb(field interface{}, param string) bool { +func isRgb(val interface{}, field interface{}, param string) bool { st := reflect.ValueOf(field) @@ -137,7 +137,7 @@ func isRgb(field interface{}, param string) bool { panic(fmt.Sprintf("Bad field type %T", field)) } -func isHexcolor(field interface{}, param string) bool { +func isHexcolor(val interface{}, field interface{}, param string) bool { st := reflect.ValueOf(field) @@ -150,7 +150,7 @@ func isHexcolor(field interface{}, param string) bool { panic(fmt.Sprintf("Bad field type %T", field)) } -func isHexadecimal(field interface{}, param string) bool { +func isHexadecimal(val interface{}, field interface{}, param string) bool { st := reflect.ValueOf(field) @@ -163,7 +163,7 @@ func isHexadecimal(field interface{}, param string) bool { panic(fmt.Sprintf("Bad field type %T", field)) } -func isNumber(field interface{}, param string) bool { +func isNumber(val interface{}, field interface{}, param string) bool { st := reflect.ValueOf(field) @@ -176,7 +176,7 @@ func isNumber(field interface{}, param string) bool { panic(fmt.Sprintf("Bad field type %T", field)) } -func isNumeric(field interface{}, param string) bool { +func isNumeric(val interface{}, field interface{}, param string) bool { st := reflect.ValueOf(field) @@ -189,7 +189,7 @@ func isNumeric(field interface{}, param string) bool { panic(fmt.Sprintf("Bad field type %T", field)) } -func isAlphanum(field interface{}, param string) bool { +func isAlphanum(val interface{}, field interface{}, param string) bool { st := reflect.ValueOf(field) @@ -202,7 +202,7 @@ func isAlphanum(field interface{}, param string) bool { panic(fmt.Sprintf("Bad field type %T", field)) } -func isAlpha(field interface{}, param string) bool { +func isAlpha(val interface{}, field interface{}, param string) bool { st := reflect.ValueOf(field) @@ -215,7 +215,7 @@ func isAlpha(field interface{}, param string) bool { panic(fmt.Sprintf("Bad field type %T", field)) } -func hasValue(field interface{}, param string) bool { +func hasValue(val interface{}, field interface{}, param string) bool { st := reflect.ValueOf(field) @@ -229,7 +229,7 @@ func hasValue(field interface{}, param string) bool { } } -func isGte(field interface{}, param string) bool { +func isGte(val interface{}, field interface{}, param string) bool { st := reflect.ValueOf(field) @@ -274,7 +274,7 @@ func isGte(field interface{}, param string) bool { panic(fmt.Sprintf("Bad field type %T", field)) } -func isGt(field interface{}, param string) bool { +func isGt(val interface{}, field interface{}, param string) bool { st := reflect.ValueOf(field) @@ -318,7 +318,7 @@ func isGt(field interface{}, param string) bool { // length tests whether a variable's length is equal to a given // value. For strings it tests the number of characters whereas // for maps and slices it tests the number of items. -func hasLengthOf(field interface{}, param string) bool { +func hasLengthOf(val interface{}, field interface{}, param string) bool { st := reflect.ValueOf(field) @@ -357,12 +357,12 @@ func hasLengthOf(field interface{}, param string) bool { // number. For number types, it's a simple lesser-than test; for // strings it tests the number of characters whereas for maps // and slices it tests the number of items. -func hasMinOf(field interface{}, param string) bool { +func hasMinOf(val interface{}, field interface{}, param string) bool { - return isGte(field, param) + return isGte(val, field, param) } -func isLte(field interface{}, param string) bool { +func isLte(val interface{}, field interface{}, param string) bool { st := reflect.ValueOf(field) @@ -407,7 +407,7 @@ func isLte(field interface{}, param string) bool { panic(fmt.Sprintf("Bad field type %T", field)) } -func isLt(field interface{}, param string) bool { +func isLt(val interface{}, field interface{}, param string) bool { st := reflect.ValueOf(field) @@ -453,9 +453,9 @@ func isLt(field interface{}, param string) bool { // value. For numbers, it's a simple lesser-than test; for // strings it tests the number of characters whereas for maps // and slices it tests the number of items. -func hasMaxOf(field interface{}, param string) bool { +func hasMaxOf(val interface{}, field interface{}, param string) bool { - return isLte(field, param) + return isLte(val, field, param) } // asInt retuns the parameter as a int64 diff --git a/doc.go b/doc.go index 59fdbd0..59888d8 100644 --- a/doc.go +++ b/doc.go @@ -82,7 +82,7 @@ Custom Functions Custom functions can be added //Structure - func customFunc(field interface{}, param string) bool { + func customFunc(val interface{}, field interface{}, param string) bool { if whatever { return false @@ -92,8 +92,34 @@ Custom functions can be added } validator.AddFunction("custom tag name", customFunc) - // NOTE: using the same tag name as an existing function - // will overwrite the existing one + // NOTES: using the same tag name as an existing function + // will overwrite the existing one + +Cross Field Validation + +Cross Field Validation can be implemented, for example Start & End Date range validation + + // NOTE: when calling validator.validateStruct(val) val will be the top level struct passed + // into the function + // when calling validator.ValidateFieldByTagAndValue(val, field, tag) val will be + // whatever you pass, struct, field... + // when calling validator.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 unlikely that any baked in function for this type of validation + // would be added, but you can add your own custom ones and keep all your validation logic + // in one place. + + func isDateRangeValid(val interface{}, field interface{}, param string) bool { + + myStruct := val.(myStructType) + + if myStruct.Start.After(field.(time.Time)) { + return false + } + + return true + } Custom Tag Name diff --git a/validator.go b/validator.go index c7b7a89..125799b 100644 --- a/validator.go +++ b/validator.go @@ -96,8 +96,8 @@ func (e *StructValidationErrors) Flatten() map[string]*FieldValidationError { return errs } -// ValidationFunc that accepts the value of a field and parameter for use in validation (parameter not always used or needed) -type ValidationFunc func(v interface{}, param string) bool +// ValidationFunc that accepts a value(optional usage), a field and parameter(optional usage) for use in validation +type ValidationFunc func(val interface{}, v interface{}, param string) bool // Validator implements the Validator Struct // NOTE: Fields within are not thread safe and that is on purpose @@ -168,6 +168,18 @@ func ValidateStruct(s interface{}) *StructValidationErrors { // ValidateStruct validates a struct and returns a struct containing the errors func (v *Validator) ValidateStruct(s interface{}) *StructValidationErrors { + structValue := reflect.ValueOf(s) + + if structValue.Kind() == reflect.Ptr && !structValue.IsNil() { + return v.ValidateStruct(structValue.Elem().Interface()) + } + + return v.validateStructRecursive(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 +func (v *Validator) validateStructRecursive(top interface{}, s interface{}) *StructValidationErrors { + structValue := reflect.ValueOf(s) structType := reflect.TypeOf(s) structName := structType.Name() @@ -179,7 +191,7 @@ func (v *Validator) ValidateStruct(s interface{}) *StructValidationErrors { } if structValue.Kind() == reflect.Ptr && !structValue.IsNil() { - return v.ValidateStruct(structValue.Elem().Interface()) + return v.validateStructRecursive(top, structValue.Elem().Interface()) } if structValue.Kind() != reflect.Struct && structValue.Kind() != reflect.Interface { @@ -218,7 +230,7 @@ func (v *Validator) ValidateStruct(s interface{}) *StructValidationErrors { if valueField.Type() == reflect.TypeOf(time.Time{}) { - if fieldError := v.validateFieldByNameAndTag(valueField.Interface(), typeField.Name, tag); fieldError != nil { + if fieldError := v.validateFieldByNameAndTagAndValue(top, valueField.Interface(), typeField.Name, tag); fieldError != nil { validationErrors.Errors[fieldError.Field] = fieldError // free up memory reference fieldError = nil @@ -235,7 +247,7 @@ func (v *Validator) ValidateStruct(s interface{}) *StructValidationErrors { default: - if fieldError := v.validateFieldByNameAndTag(valueField.Interface(), typeField.Name, tag); fieldError != nil { + if fieldError := v.validateFieldByNameAndTagAndValue(top, valueField.Interface(), typeField.Name, tag); fieldError != nil { validationErrors.Errors[fieldError.Field] = fieldError // free up memory reference fieldError = nil @@ -253,30 +265,42 @@ func (v *Validator) ValidateStruct(s interface{}) *StructValidationErrors { // ValidateFieldByTag allows validation of a single field with the internal validator, still using tag style validation to check multiple errors func ValidateFieldByTag(f interface{}, tag string) *FieldValidationError { - return internalValidator.validateFieldByNameAndTag(f, "", tag) + return internalValidator.ValidateFieldByTag(f, tag) } // ValidateFieldByTag allows validation of a single field, still using tag style validation to check multiple errors func (v *Validator) ValidateFieldByTag(f interface{}, tag string) *FieldValidationError { - return v.validateFieldByNameAndTag(f, "", tag) + return v.ValidateFieldByTagAndValue(nil, f, tag) +} + +// ValidateFieldByTagAndValue allows validation of a single field with the internal validator, still using tag style validation to check multiple errors +func ValidateFieldByTagAndValue(val interface{}, f interface{}, tag string) *FieldValidationError { + + return internalValidator.ValidateFieldByTagAndValue(val, f, tag) +} + +// ValidateFieldByTagAndValue allows validation of a single field, still using tag style validation to check multiple errors +func (v *Validator) ValidateFieldByTagAndValue(val interface{}, f interface{}, tag string) *FieldValidationError { + + return v.validateFieldByNameAndTagAndValue(val, f, "", tag) } -func (v *Validator) validateFieldByNameAndTag(f interface{}, name string, tag string) *FieldValidationError { +func (v *Validator) validateFieldByNameAndTagAndValue(val interface{}, f interface{}, name string, tag string) *FieldValidationError { // This is a double check if coming from ValidateStruct but need to be here in case function is called directly if tag == "-" { return nil } - if strings.Contains(tag, omitempty) && !hasValue(f, "") { + if strings.Contains(tag, omitempty) && !hasValue(val, f, "") { return nil } valueField := reflect.ValueOf(f) if valueField.Kind() == reflect.Ptr && !valueField.IsNil() { - return v.ValidateFieldByTag(valueField.Elem().Interface(), tag) + return v.validateFieldByNameAndTagAndValue(val, valueField.Elem().Interface(), name, tag) } switch valueField.Kind() { @@ -302,7 +326,7 @@ func (v *Validator) validateFieldByNameAndTag(f interface{}, name string, tag st for _, val := range orVals { - valErr, err = v.validateFieldByNameAndSingleTag(f, name, val) + valErr, err = v.validateFieldByNameAndSingleTag(val, f, name, val) if err == nil { return nil @@ -320,7 +344,7 @@ func (v *Validator) validateFieldByNameAndTag(f interface{}, name string, tag st return valErr } - if valErr, err = v.validateFieldByNameAndSingleTag(f, name, valTag); err != nil { + if valErr, err = v.validateFieldByNameAndSingleTag(val, f, name, valTag); err != nil { valErr.Kind = valueField.Kind() return valErr @@ -330,7 +354,7 @@ func (v *Validator) validateFieldByNameAndTag(f interface{}, name string, tag st return nil } -func (v *Validator) validateFieldByNameAndSingleTag(f interface{}, name string, valTag string) (*FieldValidationError, error) { +func (v *Validator) validateFieldByNameAndSingleTag(val interface{}, f interface{}, name string, valTag string) (*FieldValidationError, error) { vals := strings.Split(valTag, "=") key := strings.Trim(vals[0], " ") @@ -361,7 +385,7 @@ func (v *Validator) validateFieldByNameAndSingleTag(f interface{}, name string, param = strings.Trim(vals[1], " ") } - if err := valFunc(f, param); !err { + if err := valFunc(val, f, param); !err { valErr.Param = param return valErr, errors.New(key) } diff --git a/validator_test.go b/validator_test.go index 56f7f97..ff734fa 100644 --- a/validator_test.go +++ b/validator_test.go @@ -124,11 +124,35 @@ func AssertMapFieldError(s map[string]*validator.FieldValidationError, field str c.Assert(val.ErrorTag, Equals, expectedTag) } -func newValidatorFunc(field interface{}, param string) bool { +func newValidatorFunc(val interface{}, field interface{}, param string) bool { return true } +func isEqualFunc(val interface{}, field interface{}, param string) bool { + + return val.(string) == field.(string) +} + +func (ms *MySuite) TestValidateByTagAndValue(c *C) { + + val := "test" + field := "test" + err := validator.ValidateFieldByTagAndValue(val, field, "required") + c.Assert(err, IsNil) + + validator.AddFunction("isequaltestfunc", isEqualFunc) + + err = validator.ValidateFieldByTagAndValue(val, field, "isequaltestfunc") + c.Assert(err, IsNil) + + val = "unequal" + + err = validator.ValidateFieldByTagAndValue(val, field, "isequaltestfunc") + c.Assert(err, NotNil) + c.Assert(err.ErrorTag, Equals, "isequaltestfunc") +} + func (ms *MySuite) TestAddFunctions(c *C) { validate := validator.NewValidator("validateme", validator.BakedInValidators)