diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index f0f7e3a..d3edf0d 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -11,7 +11,7 @@ If you don't know what to do, there are some features and functions that need to - [ ] Update actual [list of functions](https://github.com/asaskevich/govalidator#list-of-functions) - [ ] Update [list of validators](https://github.com/asaskevich/govalidator#validatestruct-2) that available for `ValidateStruct` and add new - [ ] Implement new validators: `IsFQDN`, `IsIMEI`, `IsPostalCode`, `IsISIN`, `IsISRC` etc -- [ ] Implement [validation by maps](https://github.com/asaskevich/govalidator/issues/224) +- [x] Implement [validation by maps](https://github.com/asaskevich/govalidator/issues/224) - [ ] Implement fuzzing testing - [ ] Implement some struct/map/array utilities - [ ] Implement map/array validation diff --git a/README.md b/README.md index 40f9a87..6e3c33d 100644 --- a/README.md +++ b/README.md @@ -165,6 +165,7 @@ func IsRequestURL(rawurl string) bool func IsSSN(str string) bool func IsSemver(str string) bool func IsTime(str string, format string) bool +func IsType(in interface{}, params ...string) bool func IsURL(str string) bool func IsUTFDigit(str string) bool func IsUTFLetter(str string) bool @@ -204,6 +205,7 @@ func ToString(obj interface{}) string func Trim(str, chars string) string func Truncate(str string, length int, ending string) string func UnderscoreToCamelCase(s string) string +func ValidateMap(s map[string]interface{}, o map[string]interface{}) (bool, error) func ValidateStruct(s interface{}) (bool, error) func WhiteList(str, chars string) string type ConditionIterator @@ -227,6 +229,27 @@ type Validator ```go println(govalidator.IsURL(`http://user@pass:domain.com/path/page`)) ``` +###### IsType +```go +println(govalidator.IsType("Bob", "string")) +println(govalidator.IsType(1, "int")) +i := 1 +println(govalidator.IsType(&i, "*int")) +``` + +IsType can be used through the tag `type` which is essential for map validation: +```go +type User struct { + Name string `valid:"type(string)"` + Age int `valid:"type(int)"` + Meta interface{} `valid:"type(string)"` +} +result, err := govalidator.ValidateStruct(user{"Bob", 20, "meta"}) +if err != nil { + println("error: " + err.Error()) +} +println(result) +``` ###### ToString ```go type User struct { @@ -335,6 +358,11 @@ Validators with parameters "in(string1|string2|...|stringN)": IsIn, "rsapub(keylength)" : IsRsaPub, ``` +Validators with parameters for any type + +```go +"type(type)": IsType, +``` And here is small example of usage: ```go @@ -370,6 +398,41 @@ if err != nil { } println(result) ``` +###### ValidateMap [#2](https://github.com/asaskevich/govalidator/pull/338) +If you want to validate maps, you can use the map to be validated and a validation map that contain the same tags used in ValidateStruct, both maps have to be in the form `map[string]interface{}` + +So here is small example of usage: +```go +var mapTemplate = map[string]interface{}{ + "name":"required,alpha", + "family":"required,alpha", + "email":"required,email", + "cell-phone":"numeric", + "address":map[string]interface{}{ + "line1":"required,alphanum", + "line2":"alphanum", + "postal-code":"numeric", + }, +} + +var inputMap = map[string]interface{}{ + "name":"Bob", + "family":"Smith", + "email":"foo@bar.baz", + "address":map[string]interface{}{ + "line1":"", + "line2":"", + "postal-code":"", + }, +} + +result, err := govalidator.ValidateMap(mapTemplate, inputMap) +if err != nil { + println("error: " + err.Error()) +} +println(result) +``` + ###### WhiteList ```go // Remove all characters from string ignoring characters between "a" and "z" @@ -445,7 +508,7 @@ If you don't know what to do, there are some features and functions that need to - [ ] Update actual [list of functions](https://github.com/asaskevich/govalidator#list-of-functions) - [ ] Update [list of validators](https://github.com/asaskevich/govalidator#validatestruct-2) that available for `ValidateStruct` and add new - [ ] Implement new validators: `IsFQDN`, `IsIMEI`, `IsPostalCode`, `IsISIN`, `IsISRC` etc -- [ ] Implement [validation by maps](https://github.com/asaskevich/govalidator/issues/224) +- [x] Implement [validation by maps](https://github.com/asaskevich/govalidator/issues/224) - [ ] Implement fuzzing testing - [ ] Implement some struct/map/array utilities - [ ] Implement map/array validation diff --git a/types.go b/types.go index 4f7e927..1322db2 100644 --- a/types.go +++ b/types.go @@ -16,6 +16,7 @@ type CustomTypeValidator func(i interface{}, o interface{}) bool // ParamValidator is a wrapper for validator functions that accepts additional parameters. type ParamValidator func(str string, params ...string) bool +type InterfaceParamValidator func(in interface{}, params ...string) bool type tagOptionsMap map[string]tagOption func (t tagOptionsMap) orderedKeys() []string { @@ -46,6 +47,16 @@ type UnsupportedTypeError struct { // It implements the methods to sort by string. type stringValues []reflect.Value +// InterfaceParamTagMap is a map of functions accept variants parameters for an interface value +var InterfaceParamTagMap = map[string]InterfaceParamValidator{ + "type": IsType, +} + +// InterfaceParamTagRegexMap maps interface param tags to their respective regexes. +var InterfaceParamTagRegexMap = map[string]*regexp.Regexp{ + "type": regexp.MustCompile(`^type\((.*)\)$`), +} + // ParamTagMap is a map of functions accept variants parameters var ParamTagMap = map[string]ParamValidator{ "length": ByteLength, diff --git a/validator.go b/validator.go index b18bbcb..597d2c0 100644 --- a/validator.go +++ b/validator.go @@ -733,6 +733,109 @@ func PrependPathToErrors(err error, path string) error { return err } +// ValidateMap use validation map for fields. +// result will be equal to `false` if there are any errors. +// m is the validation map in the form +// map[string]interface{}{"name":"required,alpha","address":map[string]interface{}{"line1":"required,alphanum"}} +func ValidateMap(s map[string]interface{}, m map[string]interface{}) (bool, error) { + if s == nil { + return true, nil + } + result := true + var err error + var errs Errors + var index int + val := reflect.ValueOf(s) + for key, value := range s { + presentResult := true + validator, ok := m[key] + if !ok { + presentResult = false + var err error + err = fmt.Errorf("all map keys has to be present in the validation map; got %s", key) + err = PrependPathToErrors(err, key) + errs = append(errs, err) + } + valueField := reflect.ValueOf(value) + mapResult := true + typeResult := true + structResult := true + resultField := true + switch subValidator := validator.(type) { + case map[string]interface{}: + var err error + if v, ok := value.(map[string]interface{}); !ok { + mapResult = false + err = fmt.Errorf("map validator has to be for the map type only; got %s", valueField.Type().String()) + err = PrependPathToErrors(err, key) + errs = append(errs, err) + } else { + mapResult, err = ValidateMap(v, subValidator) + if err != nil { + mapResult = false + err = PrependPathToErrors(err, key) + errs = append(errs, err) + } + } + case string: + if (valueField.Kind() == reflect.Struct || + (valueField.Kind() == reflect.Ptr && valueField.Elem().Kind() == reflect.Struct)) && + subValidator != "-" { + var err error + structResult, err = ValidateStruct(valueField.Interface()) + if err != nil { + err = PrependPathToErrors(err, key) + errs = append(errs, err) + } + } + resultField, err = typeCheck(valueField, reflect.StructField{ + Name: key, + PkgPath: "", + Type: val.Type(), + Tag: reflect.StructTag(fmt.Sprintf("%s:%q", tagName, subValidator)), + Offset: 0, + Index: []int{index}, + Anonymous: false, + }, val, nil) + if err != nil { + errs = append(errs, err) + } + case nil: + // already handlerd when checked before + default: + typeResult = false + err = fmt.Errorf("map validator has to be either map[string]interface{} or string; got %s", valueField.Type().String()) + err = PrependPathToErrors(err, key) + errs = append(errs, err) + } + result = result && presentResult && typeResult && resultField && structResult && mapResult + index++ + } + // check required keys + requiredResult := true + for key, value := range m { + if schema, ok := value.(string); ok { + tags := parseTagIntoMap(schema) + if required, ok := tags["required"]; ok { + if _, ok := s[key]; !ok { + requiredResult = false + if required.customErrorMessage != "" { + err = Error{key, fmt.Errorf(required.customErrorMessage), true, "required", []string{}} + } else { + err = Error{key, fmt.Errorf("required field missing"), false, "required", []string{}} + } + errs = append(errs, err) + } + } + } + } + + if len(errs) > 0 { + err = errs + } + return result && requiredResult, err +} + // ValidateStruct use tags for fields. // result will be equal to `false` if there are any errors. func ValidateStruct(s interface{}) (bool, error) { @@ -856,6 +959,15 @@ func IsSemver(str string) bool { return rxSemver.MatchString(str) } +// IsType check if interface is of some type +func IsType(v interface{}, params ...string) bool { + if len(params) == 1 { + typ := params[0] + return strings.Replace(reflect.TypeOf(v).String(), " ", "", -1) == strings.Replace(typ, " ", "", -1) + } + return false +} + // IsTime check if string is valid according to given format func IsTime(str string, format string) bool { _, err := time.Parse(format, str) @@ -1062,6 +1174,45 @@ func typeCheck(v reflect.Value, t reflect.StructField, o reflect.Value, options }() } + for _, validatorSpec := range optionsOrder { + validatorStruct := options[validatorSpec] + var negate bool + validator := validatorSpec + customMsgExists := len(validatorStruct.customErrorMessage) > 0 + + // Check whether the tag looks like '!something' or 'something' + if validator[0] == '!' { + validator = validator[1:] + negate = true + } + + // Check for interface param validators + for key, value := range InterfaceParamTagRegexMap { + ps := value.FindStringSubmatch(validator) + if len(ps) == 0 { + continue + } + + validatefunc, ok := InterfaceParamTagMap[key] + if !ok { + continue + } + + delete(options, validatorSpec) + + field := fmt.Sprint(v) + if result := validatefunc(v.Interface(), ps[1:]...); (!result && !negate) || (result && negate) { + if customMsgExists { + return false, Error{t.Name, TruncatingErrorf(validatorStruct.customErrorMessage, field, validator), customMsgExists, stripParams(validatorSpec), []string{}} + } + if negate { + return false, Error{t.Name, fmt.Errorf("%s does validate as %s", field, validator), customMsgExists, stripParams(validatorSpec), []string{}} + } + return false, Error{t.Name, fmt.Errorf("%s does not validate as %s", field, validator), customMsgExists, stripParams(validatorSpec), []string{}} + } + } + } + switch v.Kind() { case reflect.Bool, reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, diff --git a/validator_test.go b/validator_test.go index e2400d9..e77523d 100644 --- a/validator_test.go +++ b/validator_test.go @@ -2,6 +2,7 @@ package govalidator import ( "fmt" + "reflect" "strings" "testing" "time" @@ -2305,6 +2306,35 @@ func TestValidateMissingValidationDeclarationStruct(t *testing.T) { SetFieldsRequiredByDefault(false) } +func structToMaps(in interface{}) (map[string]interface{}, map[string]interface{}, error) { + out := map[string]interface{}{} + tags := map[string]interface{}{} + + v := reflect.ValueOf(in) + if v.Kind() == reflect.Ptr { + v = v.Elem() + } + if v.Kind() != reflect.Struct { + return nil, nil, fmt.Errorf("structToMaps accepts only structs; got %T", v) + } + t := v.Type() + for i := 0; i < v.NumField(); i++ { + f := t.Field(i) + if v.Field(i).Kind() == reflect.Struct { + innerOut, innerTags, err := structToMaps(v.Field(i).Interface()) + if err != nil { + return nil, nil, err + } + out[f.Name] = innerOut + tags[f.Name] = innerTags + } else { + out[f.Name] = v.Field(i).Interface() + tags[f.Name] = f.Tag.Get(tagName) + } + } + return out, tags, nil +} + func TestFieldRequiredByDefault(t *testing.T) { var tests = []struct { param FieldRequiredByDefault @@ -2321,6 +2351,18 @@ func TestFieldRequiredByDefault(t *testing.T) { t.Errorf("Got Error on ValidateStruct(%q): %s", test.param, err) } } + mapParams, mapValidator, err := structToMaps(test.param) + if err != nil { + t.Errorf("Got Error on structToMaps(%q): %s", test.param, err) + } else { + actual, err := ValidateMap(mapParams, mapValidator) + if actual != test.expected { + t.Errorf("Expected ValidateMap(%q, %q) of %q to be %v, got %v", mapParams, mapValidator, test.param, test.expected, actual) + if err != nil { + t.Errorf("Got Error on ValidateMap(%q, %q) of %q: %s", mapParams, mapValidator, test.param, err) + } + } + } } SetFieldsRequiredByDefault(false) } @@ -2341,6 +2383,18 @@ func TestMultipleFieldsRequiredByDefault(t *testing.T) { t.Errorf("Got Error on ValidateStruct(%q): %s", test.param, err) } } + mapParams, mapValidator, err := structToMaps(test.param) + if err != nil { + t.Errorf("Got Error on structToMaps(%q): %s", test.param, err) + } else { + actual, err := ValidateMap(mapParams, mapValidator) + if actual != test.expected { + t.Errorf("Expected ValidateMap(%q, %q) of %q to be %v, got %v", mapParams, mapValidator, test.param, test.expected, actual) + if err != nil { + t.Errorf("Got Error on ValidateMap(%q, %q) of %q: %s", mapParams, mapValidator, test.param, err) + } + } + } } SetFieldsRequiredByDefault(false) } @@ -2364,6 +2418,18 @@ func TestFieldsRequiredByDefaultButExemptStruct(t *testing.T) { t.Errorf("Got Error on ValidateStruct(%q): %s", test.param, err) } } + mapParams, mapValidator, err := structToMaps(test.param) + if err != nil { + t.Errorf("Got Error on structToMaps(%q): %s", test.param, err) + } else { + actual, err := ValidateMap(mapParams, mapValidator) + if actual != test.expected { + t.Errorf("Expected ValidateMap(%q, %q) of %q to be %v, got %v", mapParams, mapValidator, test.param, test.expected, actual) + if err != nil { + t.Errorf("Got Error on ValidateMap(%q, %q) of %q: %s", mapParams, mapValidator, test.param, err) + } + } + } } SetFieldsRequiredByDefault(false) } @@ -2388,6 +2454,18 @@ func TestFieldsRequiredByDefaultButExemptOrOptionalStruct(t *testing.T) { t.Errorf("Got Error on ValidateStruct(%q): %s", test.param, err) } } + mapParams, mapValidator, err := structToMaps(test.param) + if err != nil { + t.Errorf("Got Error on structToMaps(%q): %s", test.param, err) + } else { + actual, err := ValidateMap(mapParams, mapValidator) + if actual != test.expected { + t.Errorf("Expected ValidateMap(%q, %q) of %q to be %v, got %v", mapParams, mapValidator, test.param, test.expected, actual) + if err != nil { + t.Errorf("Got Error on ValidateMap(%q, %q) of %q: %s", mapParams, mapValidator, test.param, err) + } + } + } } SetFieldsRequiredByDefault(false) } @@ -2400,7 +2478,16 @@ func TestInvalidValidator(t *testing.T) { invalidStruct := InvalidStruct{1} if valid, err := ValidateStruct(&invalidStruct); valid || err == nil || err.Error() != `Field: The following validator is invalid or can't be applied to the field: "someInvalidValidator"` { - t.Errorf("Got an unexpected result for struct with invalid validator: %t %s", valid, err) + t.Errorf("ValidateStruct: Got an unexpected result for struct with invalid validator: %t %s", valid, err) + } + mapParams, mapValidator, err := structToMaps(invalidStruct) + if err != nil { + t.Errorf("Got Error on structToMaps(%q): %s", invalidStruct, err) + } else { + if valid, err := ValidateMap(mapParams, mapValidator); valid || err == nil || + err.Error() != `Field: The following validator is invalid or can't be applied to the field: "someInvalidValidator"` { + t.Errorf("ValidateMap: Got an unexpected result for struct with invalid validator: %t %s", valid, err) + } } } @@ -2418,22 +2505,54 @@ func TestCustomValidator(t *testing.T) { } if valid, err := ValidateStruct(&ValidStruct{Field: 1}); !valid || err != nil { - t.Errorf("Got an unexpected result for struct with custom always true validator: %t %s", valid, err) + t.Errorf("ValidateStruct: Got an unexpected result for struct with custom always true validator: %t %s", valid, err) + } + + if mapParams, mapValidator, err := structToMaps(&ValidStruct{Field: 1}); err != nil { + t.Errorf("Got Error on structToMaps(%q): %s", &ValidStruct{Field: 1}, err) + } else { + if valid, err := ValidateMap(mapParams, mapValidator); !valid || err != nil { + t.Errorf("ValidateMap: Got an unexpected result for struct with custom always true validator: %t %s", valid, err) + } } if valid, err := ValidateStruct(&InvalidStruct{Field: 1}); valid || err == nil || err.Error() != "Value: 1 Custom validator error: customFalseValidator" { fmt.Println(err) - t.Errorf("Got an unexpected result for struct with custom always false validator: %t %s", valid, err) + t.Errorf("ValidateStruct: Got an unexpected result for struct with custom always false validator: %t %s", valid, err) + } + + if mapParams, mapValidator, err := structToMaps(&InvalidStruct{Field: 1}); err != nil { + t.Errorf("Got Error on structToMaps(%q): %s", &ValidStruct{Field: 1}, err) + } else { + if valid, err := ValidateMap(mapParams, mapValidator); valid || err == nil || err.Error() != "Value: 1 Custom validator error: customFalseValidator" { + t.Errorf("ValidateMap: Got an unexpected result for struct with custom always false validator: %t %s", valid, err) + } } mixedStruct := StructWithCustomAndBuiltinValidator{} if valid, err := ValidateStruct(&mixedStruct); valid || err == nil || err.Error() != "Field: non zero value required" { - t.Errorf("Got an unexpected result for invalid struct with custom and built-in validators: %t %s", valid, err) + t.Errorf("ValidateStruct: Got an unexpected result for invalid struct with custom and built-in validators: %t %s", valid, err) + } + + if mapParams, mapValidator, err := structToMaps(&mixedStruct); err != nil { + t.Errorf("Got Error on structToMaps(%q): %s", &ValidStruct{Field: 1}, err) + } else { + if valid, err := ValidateMap(mapParams, mapValidator); valid || err == nil || err.Error() != "Field: non zero value required" { + t.Errorf("ValidateMap: Got an unexpected result for invalid struct with custom and built-in validators: %t %s", valid, err) + } } mixedStruct.Field = 1 if valid, err := ValidateStruct(&mixedStruct); !valid || err != nil { - t.Errorf("Got an unexpected result for valid struct with custom and built-in validators: %t %s", valid, err) + t.Errorf("ValidateStruct: Got an unexpected result for valid struct with custom and built-in validators: %t %s", valid, err) + } + + if mapParams, mapValidator, err := structToMaps(&mixedStruct); err != nil { + t.Errorf("Got Error on structToMaps(%q): %s", &ValidStruct{Field: 1}, err) + } else { + if valid, err := ValidateMap(mapParams, mapValidator); !valid || err != nil { + t.Errorf("ValidateMap: Got an unexpected result for valid struct with custom and built-in validators: %t %s", valid, err) + } } } @@ -2457,6 +2576,13 @@ func TestStructWithCustomByteArray(t *testing.T) { t.Errorf("v.Email should have been 'test@example.com' but was '%s'", v.Email) } } + // for ValidateMap + case map[string]interface{}: + if len(v["Email"].(string)) > 0 { + if v["Email"].(string) != "test@example.com" { + t.Errorf("v.Email should have been 'test@example.com' but was '%s'", v["Email"].(string)) + } + } default: t.Errorf("Context object passed to custom validator should have been a StructWithCustomByteArray but was %T (%+v)", o, o) } @@ -2475,6 +2601,9 @@ func TestStructWithCustomByteArray(t *testing.T) { switch v := o.(type) { case StructWithCustomByteArray: return len(v.ID) >= v.CustomMinLength + // for ValidateMap + case map[string]interface{}: + return len(v["iD"].(CustomByteArray)) > v["CustomMinLength"].(int) } return false })) @@ -2497,6 +2626,18 @@ func TestStructWithCustomByteArray(t *testing.T) { t.Errorf("Got Error on ValidateStruct(%q): %s", test.param, err) } } + mapParams, mapValidator, err := structToMaps(test.param) + if err != nil { + t.Errorf("Got Error on structToMaps(%q): %s", test.param, err) + } else { + actual, err := ValidateMap(mapParams, mapValidator) + if actual != test.expected { + t.Errorf("Expected ValidateMap(%q, %q) of %q to be %v, got %v", mapParams, mapValidator, test.param, test.expected, actual) + if err != nil { + t.Errorf("Got Error on ValidateMap(%q, %q) of %q: %s", mapParams, mapValidator, test.param, err) + } + } + } } SetFieldsRequiredByDefault(false) } @@ -2523,6 +2664,18 @@ func TestValidateNegationStruct(t *testing.T) { t.Errorf("Got Error on ValidateStruct(%q): %s", test.param, err) } } + mapParams, mapValidator, err := structToMaps(test.param) + if err != nil { + t.Errorf("Got Error on structToMaps(%q): %s", test.param, err) + } else { + actual, err := ValidateMap(mapParams, mapValidator) + if actual != test.expected { + t.Errorf("Expected ValidateMap(%q, %q) of %q to be %v, got %v", mapParams, mapValidator, test.param, test.expected, actual) + if err != nil { + t.Errorf("Got Error on ValidateMap(%q, %q) of %q: %s", mapParams, mapValidator, test.param, err) + } + } + } } } @@ -2544,6 +2697,18 @@ func TestLengthStruct(t *testing.T) { t.Errorf("Got Error on ValidateStruct(%q): %s", test.param, err) } } + mapParams, mapValidator, err := structToMaps(test.param) + if err != nil { + t.Errorf("Got Error on structToMaps(%q): %s", test.param, err) + } else { + actual, err := ValidateMap(mapParams, mapValidator) + if actual != test.expected { + t.Errorf("Expected ValidateMap(%q, %q) of %q to be %v, got %v", mapParams, mapValidator, test.param, test.expected, actual) + if err != nil { + t.Errorf("Got Error on ValidateMap(%q, %q) of %q: %s", mapParams, mapValidator, test.param, err) + } + } + } } } @@ -2569,6 +2734,18 @@ func TestStringLengthStruct(t *testing.T) { t.Errorf("Got Error on ValidateStruct(%q): %s", test.param, err) } } + mapParams, mapValidator, err := structToMaps(test.param) + if err != nil { + t.Errorf("Got Error on structToMaps(%q): %s", test.param, err) + } else { + actual, err := ValidateMap(mapParams, mapValidator) + if actual != test.expected { + t.Errorf("Expected ValidateMap(%q, %q) of %q to be %v, got %v", mapParams, mapValidator, test.param, test.expected, actual) + if err != nil { + t.Errorf("Got Error on ValidateMap(%q, %q) of %q: %s", mapParams, mapValidator, test.param, err) + } + } + } } } @@ -2590,6 +2767,18 @@ func TestStringMatchesStruct(t *testing.T) { t.Errorf("Got Error on ValidateStruct(%q): %s", test.param, err) } } + mapParams, mapValidator, err := structToMaps(test.param) + if err != nil { + t.Errorf("Got Error on structToMaps(%q): %s", test.param, err) + } else { + actual, err := ValidateMap(mapParams, mapValidator) + if actual != test.expected { + t.Errorf("Expected ValidateMap(%q, %q) of %q to be %v, got %v", mapParams, mapValidator, test.param, test.expected, actual) + if err != nil { + t.Errorf("Got Error on ValidateMap(%q, %q) of %q: %s", mapParams, mapValidator, test.param, err) + } + } + } } } @@ -2612,6 +2801,18 @@ func TestIsInStruct(t *testing.T) { t.Errorf("Got Error on ValidateStruct(%q): %s", test.param, err) } } + mapParams, mapValidator, err := structToMaps(test.param) + if err != nil { + t.Errorf("Got Error on structToMaps(%q): %s", test.param, err) + } else { + actual, err := ValidateMap(mapParams, mapValidator) + if actual != test.expected { + t.Errorf("Expected ValidateMap(%q, %q) of %q to be %v, got %v", mapParams, mapValidator, test.param, test.expected, actual) + if err != nil { + t.Errorf("Got Error on ValidateMap(%q, %q) of %q: %s", mapParams, mapValidator, test.param, err) + } + } + } } } @@ -2638,6 +2839,18 @@ func TestRequiredIsInStruct(t *testing.T) { t.Errorf("Got Error on ValidateStruct(%q): %s", test.param, err) } } + mapParams, mapValidator, err := structToMaps(test.param) + if err != nil { + t.Errorf("Got Error on structToMaps(%q): %s", test.param, err) + } else { + actual, err := ValidateMap(mapParams, mapValidator) + if actual != test.expected { + t.Errorf("Expected ValidateMap(%q, %q) of %q to be %v, got %v", mapParams, mapValidator, test.param, test.expected, actual) + if err != nil { + t.Errorf("Got Error on ValidateMap(%q, %q) of %q: %s", mapParams, mapValidator, test.param, err) + } + } + } } } @@ -2664,6 +2877,18 @@ func TestEmptyRequiredIsInStruct(t *testing.T) { t.Errorf("Got Error on ValidateStruct(%q): %s", test.param, err) } } + mapParams, mapValidator, err := structToMaps(test.param) + if err != nil { + t.Errorf("Got Error on structToMaps(%q): %s", test.param, err) + } else { + actual, err := ValidateMap(mapParams, mapValidator) + if actual != test.expected { + t.Errorf("Expected ValidateMap(%q, %q) of %q to be %v, got %v", mapParams, mapValidator, test.param, test.expected, actual) + if err != nil { + t.Errorf("Got Error on ValidateMap(%q, %q) of %q: %s", mapParams, mapValidator, test.param, err) + } + } + } } } @@ -2701,6 +2926,23 @@ func TestEmptyStringPtr(t *testing.T) { } else if test.expectedErr != "" { t.Errorf("Expected error on ValidateStruct(%q).", test.param) } + mapParams, mapValidator, err := structToMaps(test.param) + if err != nil { + t.Errorf("Got Error on structToMaps(%q): %s", test.param, err) + } else { + actual, err := ValidateMap(mapParams, mapValidator) + if actual != test.expected { + t.Errorf("Expected ValidateMap(%q, %q) of %q to be %v, got %v", mapParams, mapValidator, test.param, test.expected, actual) + } + if err != nil { + if err.Error() != test.expectedErr { + t.Errorf("Got Error on ValidateMap(%q, %q) of %q. Expected: %s Actual: %s", + mapParams, mapValidator, test.param, test.expectedErr, err) + } + } else if test.expectedErr != "" { + t.Errorf("Expected error on ValidateMap(%q, %q) of %q.", mapParams, mapValidator, test.param) + } + } } SetNilPtrAllowedByRequired(false) } @@ -2782,6 +3024,23 @@ func TestNestedStruct(t *testing.T) { } else if test.expectedErr != "" { t.Errorf("Expected error on ValidateStruct(%q).", test.param) } + mapParams, mapValidator, err := structToMaps(test.param) + if err != nil { + t.Errorf("Got Error on structToMaps(%q): %s", test.param, err) + } else { + actual, err := ValidateMap(mapParams, mapValidator) + if actual != test.expected { + t.Errorf("Expected ValidateMap(%q, %q) of %q to be %v, got %v", mapParams, mapValidator, test.param, test.expected, actual) + } + if err != nil { + if err.Error() != test.expectedErr { + t.Errorf("Got Error on ValidateMap(%q, %q) of %q. Expected: %s Actual: %s", + mapParams, mapValidator, test.param, test.expectedErr, err) + } + } else if test.expectedErr != "" { + t.Errorf("Expected error on ValidateMap(%q, %q) of %q.", mapParams, mapValidator, test.param) + } + } } } @@ -2833,6 +3092,18 @@ func TestFunkyIsInStruct(t *testing.T) { // t.Errorf("Got Error on ValidateStruct(%q): %s", test.param, err) // } // } +// mapParams, mapValidator, err := structToMaps(test.param) +// if err != nil { +// t.Errorf("Got Error on structToMaps(%q): %s", test.param, err) +// } else { +// actual, err := ValidateMap(mapParams, mapValidator) +// if actual != test.expected { +// t.Errorf("Expected ValidateMap(%q, %q) of %q to be %v, got %v", mapParams, mapValidator, test.param, test.expected, actual) +// if err != nil { +// t.Errorf("Got Error on ValidateMap(%q, %q) of %q: %s", mapParams, mapValidator, test.param, err) +// } +// } +// } // } // } @@ -2999,6 +3270,18 @@ func TestRequired(t *testing.T) { t.Errorf("Got Error on ValidateStruct(%q): %s", test.param, err) } } + mapParams, mapValidator, err := structToMaps(test.param) + if err != nil { + t.Errorf("Got Error on structToMaps(%q): %s", test.param, err) + } else { + actual, err := ValidateMap(mapParams, mapValidator) + if actual != test.expected { + t.Errorf("Expected ValidateMap(%q, %q) of %q to be %v, got %v", mapParams, mapValidator, test.param, test.expected, actual) + if err != nil { + t.Errorf("Got Error on ValidateMap(%q, %q) of %q: %s", mapParams, mapValidator, test.param, err) + } + } + } } } @@ -3154,6 +3437,46 @@ func TestValidateStructPointers(t *testing.T) { } } +func TestValidateMapPointers(t *testing.T) { + // Struct which uses pointers for values + type UserWithPointers struct { + Name *string `valid:"-"` + Email *string `valid:"email"` + FavoriteFood *string `valid:"length(0|32)"` + Nerd *bool `valid:"-"` + } + + var tests = []struct { + param string + expected string + }{ + {"Name", ""}, + {"Email", "invalid does not validate as email"}, + {"FavoriteFood", ""}, + {"Nerd", ""}, + } + + name := "Herman" + email := "invalid" + food := "Pizza" + nerd := true + user := &UserWithPointers{&name, &email, &food, &nerd} + var err error + mapParams, mapValidator, err := structToMaps(user) + if err != nil { + t.Errorf("Got Error on structToMaps(%+v): %s", user, err) + } else { + _, err = ValidateMap(mapParams, mapValidator) + } + + for _, test := range tests { + actual := ErrorByField(err, test.param) + if actual != test.expected { + t.Errorf("Expected ErrorByField(%q) to be %v, got %v", test.param, test.expected, actual) + } + } +} + func ExampleValidateStruct() { type Post struct { Title string `valid:"alphanum,required"` @@ -3488,3 +3811,283 @@ bQIDAQAB } } } + +func TestValidateMap(t *testing.T) { + t.Parallel() + var tests = []struct { + params map[string]interface{} + validator map[string]interface{} + expected bool + }{ + { + map[string]interface{}{ + "name": "Bob", + "family": "Smith", + "email": "foo@bar.baz", + "address": map[string]interface{}{ + "line1": "123456", + "line2": "", + "postal-code": "", + }, + }, + map[string]interface{}{ + "name": "required,alpha", + "family": "required,alpha", + "email": "required,email", + "cell-phone": "numeric", + "address": map[string]interface{}{ + "line1": "required,alphanum", + "line2": "alphanum", + "postal-code": "numeric", + }, + }, + true, + }, + { + map[string]interface{}{ + "name": "Bob", + "family": "Smith", + "email": "foo@bar.baz", + "address": map[string]interface{}{ + "line1": "", + "line2": "", + "postal-code": "", + }, + }, + map[string]interface{}{ + "name": "required,alpha", + "family": "required,alpha", + "email": "required,email", + "cell-phone": "numeric", + "address": map[string]interface{}{ + "line1": "required,alphanum", + "line2": "alphanum", + "postal-code": "numeric", + }, + }, + false, + }, + { + map[string]interface{}{ + "name": "Bob", + "family": "Smith", + "email": "foo@bar.baz", + "address": map[string]interface{}{ + "line1": "+123", + "line2": "", + "postal-code": "", + }, + }, + map[string]interface{}{ + "name": "required,alpha", + "family": "required,alpha", + "email": "required,email", + "cell-phone": "numeric", + "address": map[string]interface{}{ + "line1": "required,alphanum", + "line2": "alphanum", + "postal-code": "numeric", + }, + }, + false, + }, + } + + for _, test := range tests { + actual, err := ValidateMap(test.params, test.validator) + if actual != test.expected { + t.Errorf("Expected ValidateMap(%q, %q) to be %v, got %v", test.params, test.validator, test.expected, actual) + if err != nil { + t.Errorf("Got Error on ValidateMap(%q, %q): %s", test.params, test.validator, err) + } + } + } +} + +func TestValidateMapMissing(t *testing.T) { + t.Parallel() + var tests = []struct { + params map[string]interface{} + validator map[string]interface{} + expected bool + }{ + { + map[string]interface{}{ + "name": "Bob", + }, + map[string]interface{}{ + "name": "required,alpha", + "family": "required,alpha", + }, + false, + }, + { + map[string]interface{}{ + "name": "Bob", + "submap": map[string]interface{}{ + "family": "Smith", + }, + }, + map[string]interface{}{ + "name": "required,alpha", + "submap": map[string]interface{}{ + "name": "required,alpha", + "family": "required,alpha", + }, + }, + false, + }, + } + + for _, test := range tests { + actual, err := ValidateMap(test.params, test.validator) + if actual != test.expected { + t.Errorf("Expected ValidateMap(%q, %q) to be %v, got %v", test.params, test.validator, test.expected, actual) + if err != nil { + t.Errorf("Got Error on ValidateMap(%q, %q): %s", test.params, test.validator, err) + } + } + } +} + +func TestValidateMapMissingValidator(t *testing.T) { + t.Parallel() + var tests = []struct { + params map[string]interface{} + validator map[string]interface{} + expected bool + }{ + { + map[string]interface{}{ + "name": "Bob", + "family": "Smith", + }, + map[string]interface{}{ + "name": "required,alpha", + }, + false, + }, + { + map[string]interface{}{ + "name": "Bob", + "submap": map[string]interface{}{ + "name": "Bob", + "family": "Smith", + }, + }, + map[string]interface{}{ + "name": "required,alpha", + "submap": map[string]interface{}{ + "family": "required,alpha", + }, + }, + false, + }, + } + + for _, test := range tests { + actual, err := ValidateMap(test.params, test.validator) + if actual != test.expected { + t.Errorf("Expected ValidateMap(%q, %q) to be %v, got %v", test.params, test.validator, test.expected, actual) + if err != nil { + t.Errorf("Got Error on ValidateMap(%q, %q): %s", test.params, test.validator, err) + } + } + } +} + +func TestIsType(t *testing.T) { + t.Parallel() + i := 1 + ptr := &i + var tests = []struct { + param interface{} + expected bool + }{ + { + struct { + Name string `valid:"type(string)"` + Age int `valid:"type(int)"` + }{"Bob", 20}, + true, + }, + { + struct { + Name string `valid:"type(int)"` + Age int `valid:"type(int)"` + }{"Bob", 20}, + false, + }, + { + struct { + PtrToInt *int `valid:"type(int)"` + }{ptr}, + false, + }, + { + struct { + PtrToInt *int `valid:"type(*int)"` + PtrPtrToInt **int `valid:"type(**int)"` + }{ptr, &ptr}, + true, + }, + { + struct { + StringInterface interface{} `valid:"type(string)"` + }{"Bob"}, + true, + }, + { + struct { + StringInterface interface{} `valid:"type(int)"` + }{"Bob"}, + false, + }, + { + struct { + Map map[string]interface{} `valid:"type(map[string]interface {})"` + }{map[string]interface{}{"x": struct{}{}}}, + true, + }, + { + struct { + Map map[string]interface{} `valid:"type(map[string]interface{})"` + }{map[string]interface{}{"x": struct{}{}}}, + true, + }, + { + struct { + Map interface{} `valid:"type(map[string]interface{})"` + }{map[string]interface{}{"x": struct{}{}}}, + true, + }, + { + struct { + Array []string `valid:"type([]string)"` + }{[]string{"Bob"}}, + true, + }, + } + + for _, test := range tests { + actual, err := ValidateStruct(test.param) + if actual != test.expected { + t.Errorf("Expected ValidateStruct(%q) to be %v, got %v", test.param, test.expected, actual) + if err != nil { + t.Errorf("Got Error on ValidateStruct(%q): %s", test.param, err) + } + } + mapParams, mapValidator, err := structToMaps(test.param) + if err != nil { + t.Errorf("Got Error on structToMaps(%q): %s", test.param, err) + } else { + actual, err := ValidateMap(mapParams, mapValidator) + if actual != test.expected { + t.Errorf("Expected ValidateMap(%q, %q) of %q to be %v, got %v", mapParams, mapValidator, test.param, test.expected, actual) + if err != nil { + t.Errorf("Got Error on ValidateMap(%q, %q) of %q: %s", mapParams, mapValidator, test.param, err) + } + } + } + } +}