diff --git a/.example/util/gvalid/gvalid_checkstructwithdata.go b/.example/util/gvalid/gvalid_checkstructwithdata.go new file mode 100644 index 000000000..7e1636f88 --- /dev/null +++ b/.example/util/gvalid/gvalid_checkstructwithdata.go @@ -0,0 +1,28 @@ +package main + +import ( + "context" + "github.com/gogf/gf/frame/g" + "github.com/gogf/gf/util/gconv" + "github.com/gogf/gf/util/gvalid" +) + +func main() { + type User struct { + Name string `v:"required#请输入用户姓名"` + Type int `v:"required#请选择用户类型"` + } + data := g.Map{ + "name": "john", + } + user := User{} + if err := gconv.Scan(data, &user); err != nil { + panic(err) + } + err := gvalid.CheckStructWithData(context.TODO(), user, data, nil) + // 也可以使用 + // err := g.Validator().Data(data).CheckStruct(user) + if err != nil { + g.Dump(err.Items()) + } +} diff --git a/util/gvalid/gvalid.go b/util/gvalid/gvalid.go index 532b6fcfd..275b17771 100644 --- a/util/gvalid/gvalid.go +++ b/util/gvalid/gvalid.go @@ -70,14 +70,14 @@ type apiNoValidation interface { } const ( - // regular expression pattern for single validation rule. - singleRulePattern = `^([\w-]+):{0,1}(.*)` - invalidRulesErrKey = "invalid_rules" - invalidParamsErrKey = "invalid_params" - invalidObjectErrKey = "invalid_object" - - // no validation tag name for struct attribute. - noValidationTagName = "nv" + singleRulePattern = `^([\w-]+):{0,1}(.*)` // regular expression pattern for single validation rule. + internalRulesErrRuleName = "InvalidRules" // rule name for internal invalid rules validation error. + internalParamsErrRuleName = "InvalidParams" // rule name for internal invalid params validation error. + internalObjectErrRuleName = "InvalidObject" // rule name for internal invalid object validation error. + internalErrorMapKey = "__InternalError__" // error map key for internal errors. + internalDefaultRuleName = "__default__" // default rule name for i18n error message format if no i18n message found for specified error rule. + ruleMessagePrefixForI18n = "gf.gvalid.rule." // prefix string for each rule configuration in i18n content. + noValidationTagName = "nv" // no validation tag name for struct attribute. ) var ( @@ -87,9 +87,9 @@ var ( // all internal error keys. internalErrKeyMap = map[string]string{ - invalidRulesErrKey: invalidRulesErrKey, - invalidParamsErrKey: invalidParamsErrKey, - invalidObjectErrKey: invalidObjectErrKey, + internalRulesErrRuleName: internalRulesErrRuleName, + internalParamsErrRuleName: internalParamsErrRuleName, + internalObjectErrRuleName: internalObjectErrRuleName, } // regular expression object for single rule // which is compiled just once and of repeatable usage. @@ -168,6 +168,54 @@ var ( "off": {}, "no": {}, } + // defaultMessages is the default error messages. + // Note that these messages are synchronized from ./i18n/en/validation.toml . + defaultMessages = map[string]string{ + "required": "The :attribute field is required", + "required-if": "The :attribute field is required", + "required-unless": "The :attribute field is required", + "required-with": "The :attribute field is required", + "required-with-all": "The :attribute field is required", + "required-without": "The :attribute field is required", + "required-without-all": "The :attribute field is required", + "date": "The :attribute value is not a valid date", + "date-format": "The :attribute value does not match the format :format", + "email": "The :attribute value must be a valid email address", + "phone": "The :attribute value must be a valid phone number", + "telephone": "The :attribute value must be a valid telephone number", + "passport": "The :attribute value is not a valid passport format", + "password": "The :attribute value is not a valid passport format", + "password2": "The :attribute value is not a valid passport format", + "password3": "The :attribute value is not a valid passport format", + "postcode": "The :attribute value is not a valid passport format", + "resident-id": "The :attribute value is not a valid resident id number", + "bank-card": "The :attribute value must be a valid bank card number", + "qq": "The :attribute value must be a valid QQ number", + "ip": "The :attribute value must be a valid IP address", + "ipv4": "The :attribute value must be a valid IPv4 address", + "ipv6": "The :attribute value must be a valid IPv6 address", + "mac": "The :attribute value must be a valid MAC address", + "url": "The :attribute value must be a valid URL address", + "domain": "The :attribute value must be a valid domain format", + "length": "The :attribute value length must be between :min and :max", + "min-length": "The :attribute value length must be equal or greater than :min", + "max-length": "The :attribute value length must be equal or lesser than :max", + "between": "The :attribute value must be between :min and :max", + "min": "The :attribute value must be equal or greater than :min", + "max": "The :attribute value must be equal or lesser than :max", + "json": "The :attribute value must be a valid JSON string", + "xml": "The :attribute value must be a valid XML string", + "array": "The :attribute value must be an array", + "integer": "The :attribute value must be an integer", + "float": "The :attribute value must be a float", + "boolean": "The :attribute value field must be true or false", + "same": "The :attribute value must be the same as field :field", + "different": "The :attribute value must be different from field :field", + "in": "The :attribute value is not in acceptable range", + "not-in": "The :attribute value is not in acceptable range", + "regex": "The :attribute value is invalid", + internalDefaultRuleName: "The :attribute value is invalid", + } ) // CheckValue checks single value with specified rules. diff --git a/util/gvalid/gvalid_error.go b/util/gvalid/gvalid_error.go index c55da3f56..64e2ae688 100644 --- a/util/gvalid/gvalid_error.go +++ b/util/gvalid/gvalid_error.go @@ -20,6 +20,7 @@ type Error interface { FirstItem() (key string, messages map[string]string) FirstRule() (rule string, err string) FirstString() (err string) + Items() (items []map[string]map[string]string) Map() map[string]string Maps() map[string]map[string]string String() string @@ -30,7 +31,7 @@ type Error interface { type validationError struct { rules []string // Rules by sequence, which is used for keeping error sequence. errors map[string]map[string]string // Error map:map[field]map[rule]message - firstKey string // The first error rule key(nil in default). + firstKey string // The first error rule key(empty in default). firstItem map[string]string // The first error rule value(nil in default). } @@ -54,7 +55,7 @@ func newError(rules []string, errors map[string]map[string]string) *validationEr // newErrorStr creates and returns a validation error by string. func newErrorStr(key, err string) *validationError { return newError(nil, map[string]map[string]string{ - "__gvalid__": { + internalErrorMapKey: { key: err, }, }) @@ -77,6 +78,34 @@ func (e *validationError) Maps() map[string]map[string]string { return e.errors } +// Items retrieves and returns error items array in sequence if possible, +// or else it returns error items with no sequence . +func (e *validationError) Items() (items []map[string]map[string]string) { + if e == nil { + return []map[string]map[string]string{} + } + items = make([]map[string]map[string]string, 0) + // By sequence. + if len(e.rules) > 0 { + for _, v := range e.rules { + name, _, _ := parseSequenceTag(v) + if errorItemMap, ok := e.errors[name]; ok { + items = append(items, map[string]map[string]string{ + name: errorItemMap, + }) + } + } + return items + } + // No sequence. + for name, errorRuleMap := range e.errors { + items = append(items, map[string]map[string]string{ + name: errorRuleMap, + }) + } + return +} + // FirstItem returns the field name and error messages for the first validation rule error. func (e *validationError) FirstItem() (key string, messages map[string]string) { if e == nil { @@ -89,10 +118,10 @@ func (e *validationError) FirstItem() (key string, messages map[string]string) { if len(e.rules) > 0 { for _, v := range e.rules { name, _, _ := parseSequenceTag(v) - if m, ok := e.errors[name]; ok { + if errorItemMap, ok := e.errors[name]; ok { e.firstKey = name - e.firstItem = m - return name, m + e.firstItem = errorItemMap + return name, errorItemMap } } } @@ -113,21 +142,21 @@ func (e *validationError) FirstRule() (rule string, err string) { // By sequence. if len(e.rules) > 0 { for _, v := range e.rules { - name, rule, _ := parseSequenceTag(v) - if m, ok := e.errors[name]; ok { - for _, rule := range strings.Split(rule, "|") { - array := strings.Split(rule, ":") - rule = strings.TrimSpace(array[0]) - if err, ok := m[rule]; ok { - return rule, err + name, ruleStr, _ := parseSequenceTag(v) + if errorItemMap, ok := e.errors[name]; ok { + for _, ruleItem := range strings.Split(ruleStr, "|") { + array := strings.Split(ruleItem, ":") + ruleItem = strings.TrimSpace(array[0]) + if err, ok = errorItemMap[ruleItem]; ok { + return ruleStr, err } } } } } // No sequence. - for _, m := range e.errors { - for k, v := range m { + for _, errorItemMap := range e.errors { + for k, v := range errorItemMap { return k, v } } @@ -178,18 +207,18 @@ func (e *validationError) Strings() (errs []string) { // By sequence. if len(e.rules) > 0 { for _, v := range e.rules { - name, rule, _ := parseSequenceTag(v) - if m, ok := e.errors[name]; ok { + name, ruleStr, _ := parseSequenceTag(v) + if errorItemMap, ok := e.errors[name]; ok { // validation error checks. - for _, rule := range strings.Split(rule, "|") { - rule = strings.TrimSpace(strings.Split(rule, ":")[0]) - if err, ok := m[rule]; ok { + for _, ruleItem := range strings.Split(ruleStr, "|") { + ruleItem = strings.TrimSpace(strings.Split(ruleItem, ":")[0]) + if err, ok := errorItemMap[ruleItem]; ok { errs = append(errs, err) } } // internal error checks. for k, _ := range internalErrKeyMap { - if err, ok := m[k]; ok { + if err, ok := errorItemMap[k]; ok { errs = append(errs, err) } } @@ -198,8 +227,8 @@ func (e *validationError) Strings() (errs []string) { return errs } // No sequence. - for _, m := range e.errors { - for _, err := range m { + for _, errorItemMap := range e.errors { + for _, err := range errorItemMap { errs = append(errs, err) } } diff --git a/util/gvalid/gvalid_validator_check.go b/util/gvalid/gvalid_validator_check.go index ec2e66a83..fd1d78f20 100644 --- a/util/gvalid/gvalid_validator_check.go +++ b/util/gvalid/gvalid_validator_check.go @@ -71,8 +71,8 @@ func (v *Validator) doCheckValue(key string, value interface{}, rules string, me ruleItems = append(ruleItems[:i], ruleItems[i+1:]...) } else { return newErrorStr( - invalidRulesErrKey, - invalidRulesErrKey+": "+rules, + internalRulesErrRuleName, + internalRulesErrRuleName+": "+rules, ) } } else { diff --git a/util/gvalid/gvalid_validator_check_map.go b/util/gvalid/gvalid_validator_check_map.go index e829ddcec..cefb987ca 100644 --- a/util/gvalid/gvalid_validator_check_map.go +++ b/util/gvalid/gvalid_validator_check_map.go @@ -73,7 +73,7 @@ func (v *Validator) doCheckMap(params interface{}) Error { data := gconv.Map(params) if data == nil { return newErrorStr( - "invalid_params", + internalParamsErrRuleName, "invalid params type: convert to map failed", ) } diff --git a/util/gvalid/gvalid_validator_check_struct.go b/util/gvalid/gvalid_validator_check_struct.go index 62c578caf..55e0d7c33 100644 --- a/util/gvalid/gvalid_validator_check_struct.go +++ b/util/gvalid/gvalid_validator_check_struct.go @@ -26,7 +26,7 @@ func (v *Validator) doCheckStruct(object interface{}) Error { ) fieldMap, err := structs.FieldMap(object, aliasNameTagPriority, true) if err != nil { - return newErrorStr("invalid_object", err.Error()) + return newErrorStr(internalObjectErrRuleName, err.Error()) } // It checks the struct recursively the its attribute is an embedded struct. for _, field := range fieldMap { @@ -49,7 +49,7 @@ func (v *Validator) doCheckStruct(object interface{}) Error { // It here must use structs.TagFields not structs.FieldMap to ensure error sequence. tagField, err := structs.TagFields(object, structTagPriority) if err != nil { - return newErrorStr("invalid_object", err.Error()) + return newErrorStr(internalObjectErrRuleName, err.Error()) } // If there's no struct tag and validation rules, it does nothing and returns quickly. if len(tagField) == 0 && v.messages == nil { diff --git a/util/gvalid/gvalid_validator_message.go b/util/gvalid/gvalid_validator_message.go index e1737dce9..7701e5f96 100644 --- a/util/gvalid/gvalid_validator_message.go +++ b/util/gvalid/gvalid_validator_message.go @@ -6,59 +6,6 @@ package gvalid -const ( - ruleMessagePrefixForI18n = "gf.gvalid.rule." -) - -// defaultMessages is the default error messages. -// Note that these messages are synchronized from ./i18n/en/validation.toml . -var defaultMessages = map[string]string{ - "required": "The :attribute field is required", - "required-if": "The :attribute field is required", - "required-unless": "The :attribute field is required", - "required-with": "The :attribute field is required", - "required-with-all": "The :attribute field is required", - "required-without": "The :attribute field is required", - "required-without-all": "The :attribute field is required", - "date": "The :attribute value is not a valid date", - "date-format": "The :attribute value does not match the format :format", - "email": "The :attribute value must be a valid email address", - "phone": "The :attribute value must be a valid phone number", - "telephone": "The :attribute value must be a valid telephone number", - "passport": "The :attribute value is not a valid passport format", - "password": "The :attribute value is not a valid passport format", - "password2": "The :attribute value is not a valid passport format", - "password3": "The :attribute value is not a valid passport format", - "postcode": "The :attribute value is not a valid passport format", - "resident-id": "The :attribute value is not a valid resident id number", - "bank-card": "The :attribute value must be a valid bank card number", - "qq": "The :attribute value must be a valid QQ number", - "ip": "The :attribute value must be a valid IP address", - "ipv4": "The :attribute value must be a valid IPv4 address", - "ipv6": "The :attribute value must be a valid IPv6 address", - "mac": "The :attribute value must be a valid MAC address", - "url": "The :attribute value must be a valid URL address", - "domain": "The :attribute value must be a valid domain format", - "length": "The :attribute value length must be between :min and :max", - "min-length": "The :attribute value length must be equal or greater than :min", - "max-length": "The :attribute value length must be equal or lesser than :max", - "between": "The :attribute value must be between :min and :max", - "min": "The :attribute value must be equal or greater than :min", - "max": "The :attribute value must be equal or lesser than :max", - "json": "The :attribute value must be a valid JSON string", - "xml": "The :attribute value must be a valid XML string", - "array": "The :attribute value must be an array", - "integer": "The :attribute value must be an integer", - "float": "The :attribute value must be a float", - "boolean": "The :attribute value field must be true or false", - "same": "The :attribute value must be the same as field :field", - "different": "The :attribute value must be different from field :field", - "in": "The :attribute value is not in acceptable range", - "not-in": "The :attribute value is not in acceptable range", - "regex": "The :attribute value is invalid", - "__default__": "The :attribute value is invalid", -} - // getErrorMessageByRule retrieves and returns the error message for specified rule. // It firstly retrieves the message from custom message map, and then checks i18n manager, // it returns the default error message if it's not found in custom message map or i18n manager. @@ -79,9 +26,9 @@ func (v *Validator) getErrorMessageByRule(ruleKey string, customMsgMap map[strin } // If there's no configured rule message, it uses default one. if content == "" { - content = v.i18nManager.GetContent(v.ctx, `gf.gvalid.rule.__default__`) + content = v.i18nManager.GetContent(v.ctx, ruleMessagePrefixForI18n+internalDefaultRuleName) if content == "" { - content = defaultMessages["__default__"] + content = defaultMessages[internalDefaultRuleName] } } return content diff --git a/util/gvalid/gvalid_z_unit_basic_all_test.go b/util/gvalid/gvalid_z_unit_basic_all_test.go index 6bdf61a6f..fa687880c 100755 --- a/util/gvalid/gvalid_z_unit_basic_all_test.go +++ b/util/gvalid/gvalid_z_unit_basic_all_test.go @@ -27,9 +27,9 @@ func Test_Check(t *testing.T) { err1 := gvalid.CheckValue(context.TODO(), val1, rule, nil) err2 := gvalid.CheckValue(context.TODO(), val2, rule, nil) err3 := gvalid.CheckValue(context.TODO(), val3, rule, nil) - t.Assert(err1, "invalid_rules: abc:6,16") - t.Assert(err2, "invalid_rules: abc:6,16") - t.Assert(err3, "invalid_rules: abc:6,16") + t.Assert(err1, "InvalidRules: abc:6,16") + t.Assert(err2, "InvalidRules: abc:6,16") + t.Assert(err3, "InvalidRules: abc:6,16") }) } @@ -995,9 +995,9 @@ func Test_InternalError_String(t *testing.T) { aa := a{Name: "2"} err := gvalid.CheckStruct(context.TODO(), &aa, nil) - t.Assert(err.String(), "invalid_rules: hh") - t.Assert(err.Strings(), g.Slice{"invalid_rules: hh"}) - t.Assert(err.FirstString(), "invalid_rules: hh") - t.Assert(gerror.Current(err), "invalid_rules: hh") + t.Assert(err.String(), "InvalidRules: hh") + t.Assert(err.Strings(), g.Slice{"InvalidRules: hh"}) + t.Assert(err.FirstString(), "InvalidRules: hh") + t.Assert(gerror.Current(err), "InvalidRules: hh") }) }