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 72ea682..04c0f76 100644 --- a/baked_in.go +++ b/baked_in.go @@ -33,7 +33,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) @@ -48,7 +48,7 @@ func isURI(field interface{}, param string) bool { } } -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 { } } -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 { } } -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 { } } -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 { } } -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 { } } -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 { } } -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 { } } -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 { } } -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 { } } -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 { } } -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 { } } -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 { } } -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) @@ -265,7 +265,7 @@ func isGte(field interface{}, param string) bool { } } -func isGt(field interface{}, param string) bool { +func isGt(val interface{}, field interface{}, param string) bool { st := reflect.ValueOf(field) @@ -304,7 +304,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) @@ -344,12 +344,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) @@ -385,7 +385,7 @@ func isLte(field interface{}, param string) bool { } } -func isLt(field interface{}, param string) bool { +func isLt(val interface{}, field interface{}, param string) bool { st := reflect.ValueOf(field) @@ -425,9 +425,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 4ffea06..18316f2 100644 --- a/validator.go +++ b/validator.go @@ -95,8 +95,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 @@ -167,6 +167,87 @@ 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) + // 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.ValidateStruct(structValue.Elem().Interface()) + // } + + // if structValue.Kind() != reflect.Struct && structValue.Kind() != reflect.Interface { + // panic("interface passed for validation is not a struct") + // } + + // var numFields = structValue.NumField() + + // for i := 0; i < numFields; i++ { + + // valueField := structValue.Field(i) + // typeField := structType.Field(i) + + // if valueField.Kind() == reflect.Ptr && !valueField.IsNil() { + // valueField = valueField.Elem() + // } + + // tag := typeField.Tag.Get(v.tagName) + + // if tag == "-" { + // continue + // } + + // // if no validation and not a struct (which may containt fields for validation) + // if tag == "" && valueField.Kind() != reflect.Struct && valueField.Kind() != reflect.Interface { + // continue + // } + + // switch valueField.Kind() { + + // case reflect.Struct, reflect.Interface: + + // if !unicode.IsUpper(rune(typeField.Name[0])) { + // continue + // } + + // if structErrors := v.ValidateStruct(valueField.Interface()); structErrors != nil { + // validationErrors.StructErrors[typeField.Name] = structErrors + // // free up memory map no longer needed + // structErrors = nil + // } + + // default: + + // if fieldError := v.validateFieldByNameAndTag(valueField.Interface(), typeField.Name, tag); fieldError != nil { + // validationErrors.Errors[fieldError.Field] = fieldError + // // free up memory reference + // fieldError = nil + // } + // } + // } + + // if len(validationErrors.Errors) == 0 && len(validationErrors.StructErrors) == 0 { + // return nil + // } + + // return validationErrors +} + +// 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() @@ -178,7 +259,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 { @@ -215,7 +296,7 @@ func (v *Validator) ValidateStruct(s interface{}) *StructValidationErrors { continue } - if structErrors := v.ValidateStruct(valueField.Interface()); structErrors != nil { + if structErrors := v.validateStructRecursive(top, valueField.Interface()); structErrors != nil { validationErrors.StructErrors[typeField.Name] = structErrors // free up memory map no longer needed structErrors = nil @@ -223,7 +304,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 @@ -241,30 +322,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() { @@ -287,7 +380,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 @@ -305,7 +398,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 @@ -315,7 +408,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], " ") @@ -346,7 +439,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 72d39fb..3b2e390 100644 --- a/validator_test.go +++ b/validator_test.go @@ -123,11 +123,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)