From a29aef7e1c09911f3de054c0bd00522609b09b3e Mon Sep 17 00:00:00 2001 From: John Date: Mon, 21 Sep 2020 23:51:30 +0800 Subject: [PATCH] improve package gvalid for custom rule --- util/gvalid/gvalid_check.go | 29 ++++++------- util/gvalid/gvalid_custom_rule.go | 3 +- util/gvalid/gvalid_error.go | 24 +++++++++++ util/gvalid/gvalid_message.go | 8 ++++ util/gvalid/gvalid_unit_custom_rule_test.go | 46 ++++++++++++++++++++- util/gvalid/gvalid_z_example_test.go | 35 ++++++++++++++++ util/gvalid/i18n/cn/validation.toml | 3 +- util/gvalid/i18n/en/validation.toml | 3 +- 8 files changed, 133 insertions(+), 18 deletions(-) diff --git a/util/gvalid/gvalid_check.go b/util/gvalid/gvalid_check.go index 12c9a51b0..965d76276 100644 --- a/util/gvalid/gvalid_check.go +++ b/util/gvalid/gvalid_check.go @@ -124,9 +124,9 @@ func doCheck(key string, value interface{}, rules string, messages interface{}, // It converts value to string and then does the validation. var ( // Do not trim it as the space is also part of the value. - val = gconv.String(value) - data = make(map[string]string) - errorMsgs = make(map[string]string) + val = gconv.String(value) + data = make(map[string]string) + errorMsgArray = make(map[string]string) ) if len(params) > 0 { for k, v := range gconv.Map(params[0]) { @@ -198,7 +198,7 @@ func doCheck(key string, value interface{}, rules string, messages interface{}, "min-length", "max-length": if msg := checkLength(val, ruleKey, ruleVal, customMsgMap); msg != "" { - errorMsgs[ruleKey] = msg + errorMsgArray[ruleKey] = msg } else { match = true } @@ -209,7 +209,7 @@ func doCheck(key string, value interface{}, rules string, messages interface{}, "max", "between": if msg := checkRange(val, ruleKey, ruleVal, customMsgMap); msg != "" { - errorMsgs[ruleKey] = msg + errorMsgArray[ruleKey] = msg } else { match = true } @@ -246,7 +246,7 @@ func doCheck(key string, value interface{}, rules string, messages interface{}, var msg string msg = getErrorMessageByRule(ruleKey, customMsgMap) msg = strings.Replace(msg, ":format", ruleVal, -1) - errorMsgs[ruleKey] = msg + errorMsgArray[ruleKey] = msg } // Values of two fields should be equal as string. @@ -260,7 +260,7 @@ func doCheck(key string, value interface{}, rules string, messages interface{}, var msg string msg = getErrorMessageByRule(ruleKey, customMsgMap) msg = strings.Replace(msg, ":field", ruleVal, -1) - errorMsgs[ruleKey] = msg + errorMsgArray[ruleKey] = msg } // Values of two fields should not be equal as string. @@ -275,7 +275,7 @@ func doCheck(key string, value interface{}, rules string, messages interface{}, var msg string msg = getErrorMessageByRule(ruleKey, customMsgMap) msg = strings.Replace(msg, ":field", ruleVal, -1) - errorMsgs[ruleKey] = msg + errorMsgArray[ruleKey] = msg } // Field value should be in range of. @@ -451,6 +451,7 @@ func doCheck(key string, value interface{}, rules string, messages interface{}, match = gregex.IsMatchString(`^([0-9A-Fa-f]{2}[\-:]){5}[0-9A-Fa-f]{2}$`, val) default: + // Custom validation rules. if f, ok := customRuleFuncMap[ruleKey]; ok { var ( dataMap map[string]interface{} @@ -461,12 +462,12 @@ func doCheck(key string, value interface{}, rules string, messages interface{}, } if err := f(value, message, dataMap); err != nil { match = false - errorMsgs[ruleKey] = err.Error() + errorMsgArray[ruleKey] = err.Error() } else { match = true } } else { - errorMsgs[ruleKey] = "Invalid rule name: " + ruleKey + errorMsgArray[ruleKey] = "Invalid rule name: " + ruleKey } } @@ -474,15 +475,15 @@ func doCheck(key string, value interface{}, rules string, messages interface{}, if !match { // It does nothing if the error message for this rule // is already set in previous validation. - if _, ok := errorMsgs[ruleKey]; !ok { - errorMsgs[ruleKey] = getErrorMessageByRule(ruleKey, customMsgMap) + if _, ok := errorMsgArray[ruleKey]; !ok { + errorMsgArray[ruleKey] = getErrorMessageByRule(ruleKey, customMsgMap) } } index++ } - if len(errorMsgs) > 0 { + if len(errorMsgArray) > 0 { return newError([]string{rules}, ErrorMap{ - key: errorMsgs, + key: errorMsgArray, }) } return nil diff --git a/util/gvalid/gvalid_custom_rule.go b/util/gvalid/gvalid_custom_rule.go index 4cac5907e..156d99bbe 100644 --- a/util/gvalid/gvalid_custom_rule.go +++ b/util/gvalid/gvalid_custom_rule.go @@ -13,7 +13,8 @@ import ( // RuleFunc is the custom function for data validation. // The parameter specifies the value for this rule to validate. // The parameter specifies the custom error message or configured i18n message for this rule. -// The parameter specifies all the parameters that needs . +// The parameter specifies all the parameters that needs. You can ignore parameter if +// you do not really need it in your custom validation rule. type RuleFunc func(value interface{}, message string, params map[string]interface{}) error var ( diff --git a/util/gvalid/gvalid_error.go b/util/gvalid/gvalid_error.go index 06f3146f9..b48a66563 100644 --- a/util/gvalid/gvalid_error.go +++ b/util/gvalid/gvalid_error.go @@ -49,17 +49,26 @@ func newErrorStr(key, err string) *Error { // Map returns the first error message as map. func (e *Error) Map() map[string]string { + if e == nil { + return map[string]string{} + } _, m := e.FirstItem() return m } // Maps returns all error messages as map. func (e *Error) Maps() ErrorMap { + if e == nil { + return nil + } return e.errors } // FirstItem returns the field name and error messages for the first validation rule error. func (e *Error) FirstItem() (key string, messages map[string]string) { + if e == nil { + return "", map[string]string{} + } if e.firstItem != nil { return e.firstKey, e.firstItem } @@ -85,6 +94,9 @@ func (e *Error) FirstItem() (key string, messages map[string]string) { // FirstRule returns the first error rule and message string. func (e *Error) FirstRule() (rule string, err string) { + if e == nil { + return "", "" + } // By sequence. if len(e.rules) > 0 { for _, v := range e.rules { @@ -112,22 +124,34 @@ func (e *Error) FirstRule() (rule string, err string) { // FirstString returns the first error message as string. // Note that the returned message might be different if it has no sequence. func (e *Error) FirstString() (err string) { + if e == nil { + return "" + } _, err = e.FirstRule() return } // String returns all error messages as string, multiple error messages joined using char ';'. func (e *Error) String() string { + if e == nil { + return "" + } return strings.Join(e.Strings(), "; ") } // Error implements interface of error.Error. func (e *Error) Error() string { + if e == nil { + return "" + } return e.String() } // Strings returns all error messages as string array. func (e *Error) Strings() (errs []string) { + if e == nil { + return []string{} + } errs = make([]string, 0) // By sequence. if len(e.rules) > 0 { diff --git a/util/gvalid/gvalid_message.go b/util/gvalid/gvalid_message.go index 68d2ccea8..c3b6e6c2b 100644 --- a/util/gvalid/gvalid_message.go +++ b/util/gvalid/gvalid_message.go @@ -57,6 +57,7 @@ var defaultMessages = map[string]string{ "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. @@ -71,5 +72,12 @@ func getErrorMessageByRule(ruleKey string, customMsgMap map[string]string) strin if content == "" { content = defaultMessages[ruleKey] } + // If there's no configured rule message, it uses default one. + if content == "" { + content = gi18n.GetContent(`gf.gvalid.rule.__default__`) + if content == "" { + content = defaultMessages["__default__"] + } + } return content } diff --git a/util/gvalid/gvalid_unit_custom_rule_test.go b/util/gvalid/gvalid_unit_custom_rule_test.go index 0cf282955..e6740c2c2 100644 --- a/util/gvalid/gvalid_unit_custom_rule_test.go +++ b/util/gvalid/gvalid_unit_custom_rule_test.go @@ -16,7 +16,7 @@ import ( "github.com/gogf/gf/util/gvalid" ) -func Test_CustomRule(t *testing.T) { +func Test_CustomRule1(t *testing.T) { rule := "custom" err := gvalid.RegisterRule(rule, func(value interface{}, message string, params map[string]interface{}) error { pass := gconv.String(value) @@ -62,3 +62,47 @@ func Test_CustomRule(t *testing.T) { t.Assert(err, nil) }) } + +func Test_CustomRule2(t *testing.T) { + rule := "required-map" + err := gvalid.RegisterRule(rule, func(value interface{}, message string, params map[string]interface{}) error { + m := gconv.Map(value) + if len(m) == 0 { + return errors.New(message) + } + return nil + }) + gtest.Assert(err, nil) + // Check. + gtest.C(t, func(t *gtest.T) { + errStr := "data map should not be empty" + t.Assert(gvalid.Check(g.Map{}, rule, errStr).String(), errStr) + t.Assert(gvalid.Check(g.Map{"k": "v"}, rule, errStr).String(), nil) + }) + // Error with struct validation. + gtest.C(t, func(t *gtest.T) { + type T struct { + Value map[string]string `v:"uid@required-map#自定义错误"` + Data string `p:"data"` + } + st := &T{ + Value: map[string]string{}, + Data: "123456", + } + err := gvalid.CheckStruct(st, nil) + t.Assert(err.String(), "自定义错误") + }) + // No error with struct validation. + gtest.C(t, func(t *gtest.T) { + type T struct { + Value map[string]string `v:"uid@required-map#自定义错误"` + Data string `p:"data"` + } + st := &T{ + Value: map[string]string{"k": "v"}, + Data: "123456", + } + err := gvalid.CheckStruct(st, nil) + t.Assert(err, nil) + }) +} diff --git a/util/gvalid/gvalid_z_example_test.go b/util/gvalid/gvalid_z_example_test.go index f6f55cf84..ee43a9575 100644 --- a/util/gvalid/gvalid_z_example_test.go +++ b/util/gvalid/gvalid_z_example_test.go @@ -7,8 +7,11 @@ package gvalid_test import ( + "errors" "fmt" "github.com/gogf/gf/container/gvar" + "github.com/gogf/gf/frame/g" + "github.com/gogf/gf/util/gconv" "github.com/gogf/gf/util/gvalid" ) @@ -106,3 +109,35 @@ func ExampleCheckStruct3() { // Output: // project id must between 1, 10000 } + +func ExampleRegisterRule() { + rule := "unique-name" + gvalid.RegisterRule(rule, func(value interface{}, message string, params map[string]interface{}) error { + var ( + id = gconv.Int(params["Id"]) + name = gconv.String(value) + ) + n, err := g.Table("user").Where("id != ? and name = ?", id, name).Count() + if err != nil { + return err + } + if n > 0 { + return errors.New(message) + } + return nil + }) + type User struct { + Id int + Name string `v:"required|unique-name # 请输入用户名称|用户名称已被占用"` + Pass string `v:"required|length:6,18"` + } + user := &User{ + Id: 1, + Name: "john", + Pass: "123456", + } + err := gvalid.CheckStruct(user, nil) + fmt.Println(err.Error()) + // Output: + // 用户名称已被占用 +} diff --git a/util/gvalid/i18n/cn/validation.toml b/util/gvalid/i18n/cn/validation.toml index d1240a633..7ed7501a0 100644 --- a/util/gvalid/i18n/cn/validation.toml +++ b/util/gvalid/i18n/cn/validation.toml @@ -40,4 +40,5 @@ "gf.gvalid.rule.different" = ":attribute 字段值不能与:field相同" "gf.gvalid.rule.in" = ":attribute 字段值不合法" "gf.gvalid.rule.not-in" = ":attribute 字段值不合法" -"gf.gvalid.rule.regex" = ":attribute 字段值不合法" \ No newline at end of file +"gf.gvalid.rule.regex" = ":attribute 字段值不合法" +"gf.gvalid.rule.__default__" = ":attribute 字段值不合法" \ No newline at end of file diff --git a/util/gvalid/i18n/en/validation.toml b/util/gvalid/i18n/en/validation.toml index 0132b3086..f6cb071da 100644 --- a/util/gvalid/i18n/en/validation.toml +++ b/util/gvalid/i18n/en/validation.toml @@ -40,4 +40,5 @@ "gf.gvalid.rule.different" = "The :attribute value must be different from field :field" "gf.gvalid.rule.in" = "The :attribute value is not in acceptable range" "gf.gvalid.rule.not-in" = "The :attribute value is not in acceptable range" -"gf.gvalid.rule.regex" = "The :attribute value is invalid" \ No newline at end of file +"gf.gvalid.rule.regex" = "The :attribute value is invalid" +"gf.gvalid.rule.__default__" = "The :attribute value is invalid" \ No newline at end of file