From 95a4690e692d24cb8b0421f38964a22afca8b99d Mon Sep 17 00:00:00 2001 From: John Guo Date: Tue, 9 Nov 2021 17:50:14 +0800 Subject: [PATCH] improve error feature for package gvalid --- internal/utils/utils_is.go | 2 +- util/gutil/gutil_dump.go | 8 +- util/gvalid/gvalid_error.go | 112 ++++++++++-------- util/gvalid/gvalid_validator_check_map.go | 11 +- util/gvalid/gvalid_validator_check_struct.go | 12 +- util/gvalid/gvalid_validator_check_value.go | 54 +++------ util/gvalid/gvalid_z_example_test.go | 4 +- util/gvalid/gvalid_z_unit_basic_all_test.go | 2 +- util/gvalid/gvalid_z_unit_customerror_test.go | 11 +- 9 files changed, 105 insertions(+), 111 deletions(-) diff --git a/internal/utils/utils_is.go b/internal/utils/utils_is.go index b525df226..ceb4f74e8 100644 --- a/internal/utils/utils_is.go +++ b/internal/utils/utils_is.go @@ -13,7 +13,7 @@ import ( // IsNil checks whether `value` is nil. func IsNil(value interface{}) bool { - return value == nil + return empty.IsNil(value) } // IsEmpty checks whether `value` is empty. diff --git a/util/gutil/gutil_dump.go b/util/gutil/gutil_dump.go index 0e331426f..fb41864ae 100644 --- a/util/gutil/gutil_dump.go +++ b/util/gutil/gutil_dump.go @@ -196,8 +196,12 @@ func doExport(value interface{}, indent string, buffer *bytes.Buffer, option doE if structContentStr == "" { structContentStr = "{}" } else { - structContentStr = fmt.Sprintf(`"%s"`, gstr.AddSlashes(structContentStr)) - attributeCountStr = fmt.Sprintf(`%d`, len(structContentStr)-2) + if strings.HasPrefix(structContentStr, `"`) && strings.HasSuffix(structContentStr, `"`) { + attributeCountStr = fmt.Sprintf(`%d`, len(structContentStr)) + } else { + structContentStr = fmt.Sprintf(`"%s"`, gstr.AddSlashes(structContentStr)) + attributeCountStr = fmt.Sprintf(`%d`, len(structContentStr)-2) + } } if option.WithoutType { buffer.WriteString(structContentStr) diff --git a/util/gvalid/gvalid_error.go b/util/gvalid/gvalid_error.go index 08f553fd7..34a740e0a 100644 --- a/util/gvalid/gvalid_error.go +++ b/util/gvalid/gvalid_error.go @@ -19,50 +19,62 @@ type Error interface { Code() gcode.Code Current() error Error() string - 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 + FirstItem() (key string, messages map[string]error) + FirstRule() (rule string, err error) + FirstError() (err error) + Items() (items []map[string]map[string]error) + Map() map[string]error + Maps() map[string]map[string]error String() string Strings() (errs []string) } // validationError is the validation error for validation result. type validationError struct { - code gcode.Code // Error code. - rules []fieldRule // 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(empty in default). - firstItem map[string]string // The first error rule value(nil in default). + code gcode.Code // Error code. + rules []fieldRule // Rules by sequence, which is used for keeping error sequence. + errors map[string]map[string]error // Error map:map[field]map[rule]message + firstKey string // The first error rule key(empty in default). + firstItem map[string]error // The first error rule value(nil in default). } -// newError creates and returns a validation error. -func newError(code gcode.Code, rules []fieldRule, errors map[string]map[string]string) *validationError { - for field, m := range errors { - for k, v := range m { - v = strings.Replace(v, ":attribute", field, -1) - v, _ = gregex.ReplaceString(`\s{2,}`, ` `, v) - v = gstr.Trim(v) - m[k] = v +// newValidationError creates and returns a validation error. +func newValidationError(code gcode.Code, rules []fieldRule, fieldRuleErrorMap map[string]map[string]error) *validationError { + var ( + s string + ) + for field, ruleErrorMap := range fieldRuleErrorMap { + for rule, err := range ruleErrorMap { + if !gerror.HasStack(err) { + s = strings.Replace(err.Error(), ":attribute", field, -1) + s, _ = gregex.ReplaceString(`\s{2,}`, ` `, s) + ruleErrorMap[rule] = gerror.NewOption(gerror.Option{ + Stack: false, + Text: gstr.Trim(s), + Code: code, + }) + } } - errors[field] = m + fieldRuleErrorMap[field] = ruleErrorMap } return &validationError{ code: code, rules: rules, - errors: errors, + errors: fieldRuleErrorMap, } } -// newErrorStr creates and returns a validation error by string. -func newErrorStr(key, err string) *validationError { - return newError(gcode.CodeInternalError, nil, map[string]map[string]string{ - internalErrorMapKey: { - key: err, +// newValidationErrorByStr creates and returns a validation error by string. +func newValidationErrorByStr(key string, err error) *validationError { + return newValidationError( + gcode.CodeInternalError, + nil, + map[string]map[string]error{ + internalErrorMapKey: { + key: err, + }, }, - }) + ) } // Code returns the error code of current validation error. @@ -74,16 +86,16 @@ func (e *validationError) Code() gcode.Code { } // Map returns the first error message as map. -func (e *validationError) Map() map[string]string { +func (e *validationError) Map() map[string]error { if e == nil { - return map[string]string{} + return map[string]error{} } _, m := e.FirstItem() return m } // Maps returns all error messages as map. -func (e *validationError) Maps() map[string]map[string]string { +func (e *validationError) Maps() map[string]map[string]error { if e == nil { return nil } @@ -92,16 +104,16 @@ func (e *validationError) Maps() map[string]map[string]string { // 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) { +func (e *validationError) Items() (items []map[string]map[string]error) { if e == nil { - return []map[string]map[string]string{} + return []map[string]map[string]error{} } - items = make([]map[string]map[string]string, 0) + items = make([]map[string]map[string]error, 0) // By sequence. if len(e.rules) > 0 { for _, v := range e.rules { if errorItemMap, ok := e.errors[v.Name]; ok { - items = append(items, map[string]map[string]string{ + items = append(items, map[string]map[string]error{ v.Name: errorItemMap, }) } @@ -110,7 +122,7 @@ func (e *validationError) Items() (items []map[string]map[string]string) { } // No sequence. for name, errorRuleMap := range e.errors { - items = append(items, map[string]map[string]string{ + items = append(items, map[string]map[string]error{ name: errorRuleMap, }) } @@ -118,9 +130,9 @@ func (e *validationError) Items() (items []map[string]map[string]string) { } // FirstItem returns the field name and error messages for the first validation rule error. -func (e *validationError) FirstItem() (key string, messages map[string]string) { +func (e *validationError) FirstItem() (key string, messages map[string]error) { if e == nil { - return "", map[string]string{} + return "", map[string]error{} } if e.firstItem != nil { return e.firstKey, e.firstItem @@ -145,9 +157,9 @@ func (e *validationError) FirstItem() (key string, messages map[string]string) { } // FirstRule returns the first error rule and message string. -func (e *validationError) FirstRule() (rule string, err string) { +func (e *validationError) FirstRule() (rule string, err error) { if e == nil { - return "", "" + return "", nil } // By sequence. if len(e.rules) > 0 { @@ -169,26 +181,22 @@ func (e *validationError) FirstRule() (rule string, err string) { return k, v } } - return "", "" + return "", nil } -// FirstString returns the first error message as string. +// FirstError returns the first error message as string. // Note that the returned message might be different if it has no sequence. -func (e *validationError) FirstString() (err string) { +func (e *validationError) FirstError() (err error) { if e == nil { - return "" + return nil } _, err = e.FirstRule() return } -// Current is alis of FirstString, which implements interface gerror.iCurrent. +// Current is alis of FirstError, which implements interface gerror.iCurrent. func (e *validationError) Current() error { - if e == nil { - return nil - } - _, err := e.FirstRule() - return gerror.NewCode(e.code, err) + return e.FirstError() } // String returns all error messages as string, multiple error messages joined using char ';'. @@ -221,13 +229,13 @@ func (e *validationError) Strings() (errs []string) { for _, ruleItem := range strings.Split(v.Rule, "|") { ruleItem = strings.TrimSpace(strings.Split(ruleItem, ":")[0]) if err, ok := errorItemMap[ruleItem]; ok { - errs = append(errs, err) + errs = append(errs, err.Error()) } } // internal error checks. for k, _ := range internalErrKeyMap { if err, ok := errorItemMap[k]; ok { - errs = append(errs, err) + errs = append(errs, err.Error()) } } } @@ -237,7 +245,7 @@ func (e *validationError) Strings() (errs []string) { // No sequence. for _, errorItemMap := range e.errors { for _, err := range errorItemMap { - errs = append(errs, err) + errs = append(errs, err.Error()) } } return diff --git a/util/gvalid/gvalid_validator_check_map.go b/util/gvalid/gvalid_validator_check_map.go index e3f10fa56..4e5b7ad0d 100644 --- a/util/gvalid/gvalid_validator_check_map.go +++ b/util/gvalid/gvalid_validator_check_map.go @@ -8,6 +8,7 @@ package gvalid import ( "context" + "errors" "github.com/gogf/gf/v2/errors/gcode" "github.com/gogf/gf/v2/util/gconv" "strings" @@ -27,7 +28,7 @@ func (v *Validator) doCheckMap(ctx context.Context, params interface{}) Error { var ( checkRules = make([]fieldRule, 0) customMessage = make(CustomMsg) // map[RuleKey]ErrorMsg. - errorMaps = make(map[string]map[string]string) + errorMaps = make(map[string]map[string]error) ) switch assertValue := v.rules.(type) { // Sequence tag: []sequence tag @@ -80,9 +81,9 @@ func (v *Validator) doCheckMap(ctx context.Context, params interface{}) Error { } data := gconv.Map(params) if data == nil { - return newErrorStr( + return newValidationErrorByStr( internalParamsErrRuleName, - "invalid params type: convert to map failed", + errors.New("invalid params type: convert to map failed"), ) } if msg, ok := v.messages.(CustomMsg); ok && len(msg) > 0 { @@ -139,7 +140,7 @@ func (v *Validator) doCheckMap(ctx context.Context, params interface{}) Error { } } if _, ok := errorMaps[checkRuleItem.Name]; !ok { - errorMaps[checkRuleItem.Name] = make(map[string]string) + errorMaps[checkRuleItem.Name] = make(map[string]error) } for ruleKey, errorItemMsgMap := range errorItem { errorMaps[checkRuleItem.Name][ruleKey] = errorItemMsgMap @@ -150,7 +151,7 @@ func (v *Validator) doCheckMap(ctx context.Context, params interface{}) Error { } } if len(errorMaps) > 0 { - return newError(gcode.CodeValidationFailed, checkRules, errorMaps) + return newValidationError(gcode.CodeValidationFailed, checkRules, errorMaps) } return nil } diff --git a/util/gvalid/gvalid_validator_check_struct.go b/util/gvalid/gvalid_validator_check_struct.go index 878e8a728..647a856c1 100644 --- a/util/gvalid/gvalid_validator_check_struct.go +++ b/util/gvalid/gvalid_validator_check_struct.go @@ -23,8 +23,8 @@ func (v *Validator) CheckStruct(ctx context.Context, object interface{}) Error { func (v *Validator) doCheckStruct(ctx context.Context, object interface{}) Error { var ( - errorMaps = make(map[string]map[string]string) // Returning error. - fieldToAliasNameMap = make(map[string]string) // Field names to alias name map. + errorMaps = make(map[string]map[string]error) // Returning error. + fieldToAliasNameMap = make(map[string]string) // Field names to alias name map. ) fieldMap, err := structs.FieldMap(structs.FieldMapInput{ Pointer: object, @@ -32,7 +32,7 @@ func (v *Validator) doCheckStruct(ctx context.Context, object interface{}) Error RecursiveOption: structs.RecursiveOptionEmbedded, }) if err != nil { - return newErrorStr(internalObjectErrRuleName, err.Error()) + return newValidationErrorByStr(internalObjectErrRuleName, err) } // It checks the struct recursively if its attribute is an embedded struct. for _, field := range fieldMap { @@ -59,7 +59,7 @@ func (v *Validator) doCheckStruct(ctx context.Context, 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(internalObjectErrRuleName, err.Error()) + return newValidationErrorByStr(internalObjectErrRuleName, err) } // If there's no struct tag and validation rules, it does nothing and returns quickly. if len(tagField) == 0 && v.messages == nil { @@ -278,7 +278,7 @@ func (v *Validator) doCheckStruct(ctx context.Context, object interface{}) Error } } if _, ok := errorMaps[checkRuleItem.Name]; !ok { - errorMaps[checkRuleItem.Name] = make(map[string]string) + errorMaps[checkRuleItem.Name] = make(map[string]error) } for ruleKey, errorItemMsgMap := range errorItem { errorMaps[checkRuleItem.Name][ruleKey] = errorItemMsgMap @@ -289,7 +289,7 @@ func (v *Validator) doCheckStruct(ctx context.Context, object interface{}) Error } } if len(errorMaps) > 0 { - return newError(gcode.CodeValidationFailed, checkRules, errorMaps) + return newValidationError(gcode.CodeValidationFailed, checkRules, errorMaps) } return nil } diff --git a/util/gvalid/gvalid_validator_check_value.go b/util/gvalid/gvalid_validator_check_value.go index 182a10d10..948601395 100644 --- a/util/gvalid/gvalid_validator_check_value.go +++ b/util/gvalid/gvalid_validator_check_value.go @@ -8,9 +8,9 @@ package gvalid import ( "context" + "errors" "github.com/gogf/gf/v2/container/gvar" "github.com/gogf/gf/v2/errors/gcode" - "github.com/gogf/gf/v2/errors/gerror" "github.com/gogf/gf/v2/text/gstr" "strconv" "strings" @@ -61,7 +61,7 @@ func (v *Validator) doCheckValue(ctx context.Context, input doCheckValueInput) E // It converts value to string and then does the validation. var ( // Do not trim it as the space is also part of the value. - errorMsgArray = make(map[string]string) + ruleErrorMap = make(map[string]error) ) // Custom error messages handling. var ( @@ -87,9 +87,9 @@ func (v *Validator) doCheckValue(ctx context.Context, input doCheckValueInput) E ruleItems[i-1] += "|" + ruleItems[i] ruleItems = append(ruleItems[:i], ruleItems[i+1:]...) } else { - return newErrorStr( + return newValidationErrorByStr( internalRulesErrRuleName, - internalRulesErrRuleName+": "+input.Rule, + errors.New(internalRulesErrRuleName+": "+input.Rule), ) } } else { @@ -141,7 +141,7 @@ func (v *Validator) doCheckValue(ctx context.Context, input doCheckValueInput) E Data: gvar.New(input.DataRaw), }); err != nil { match = false - errorMsgArray[ruleKey] = err.Error() + ruleErrorMap[ruleKey] = err } else { match = true } @@ -160,7 +160,7 @@ func (v *Validator) doCheckValue(ctx context.Context, input doCheckValueInput) E }, ) if !match && err != nil { - errorMsgArray[ruleKey] = err.Error() + ruleErrorMap[ruleKey] = err } } @@ -168,8 +168,8 @@ func (v *Validator) doCheckValue(ctx context.Context, input doCheckValueInput) E if !match { // It does nothing if the error message for this rule // is already set in previous validation. - if _, ok := errorMsgArray[ruleKey]; !ok { - errorMsgArray[ruleKey] = v.getErrorMessageByRule(ctx, ruleKey, customMsgMap) + if _, ok := ruleErrorMap[ruleKey]; !ok { + ruleErrorMap[ruleKey] = errors.New(v.getErrorMessageByRule(ctx, ruleKey, customMsgMap)) } // If it is with error and there's bail rule, // it then does not continue validating for left rules. @@ -179,12 +179,12 @@ func (v *Validator) doCheckValue(ctx context.Context, input doCheckValueInput) E } index++ } - if len(errorMsgArray) > 0 { - return newError( + if len(ruleErrorMap) > 0 { + return newValidationError( gcode.CodeValidationFailed, []fieldRule{{Name: input.Name, Rule: input.Rule}}, - map[string]map[string]string{ - input.Name: errorMsgArray, + map[string]map[string]error{ + input.Name: ruleErrorMap, }, ) } @@ -223,10 +223,7 @@ func (v *Validator) doCheckBuildInRules(ctx context.Context, input doCheckBuildI "max-length", "size": if msg := v.checkLength(ctx, valueStr, input.RuleKey, input.RulePattern, input.CustomMsgMap); msg != "" { - return match, gerror.NewOption(gerror.Option{ - Text: msg, - Code: gcode.CodeValidationFailed, - }) + return match, errors.New(msg) } else { match = true } @@ -237,10 +234,7 @@ func (v *Validator) doCheckBuildInRules(ctx context.Context, input doCheckBuildI "max", "between": if msg := v.checkRange(ctx, valueStr, input.RuleKey, input.RulePattern, input.CustomMsgMap); msg != "" { - return match, gerror.NewOption(gerror.Option{ - Text: msg, - Code: gcode.CodeValidationFailed, - }) + return match, errors.New(msg) } else { match = true } @@ -288,10 +282,7 @@ func (v *Validator) doCheckBuildInRules(ctx context.Context, input doCheckBuildI ) msg = v.getErrorMessageByRule(ctx, input.RuleKey, input.CustomMsgMap) msg = strings.Replace(msg, ":format", input.RulePattern, -1) - return match, gerror.NewOption(gerror.Option{ - Text: msg, - Code: gcode.CodeValidationFailed, - }) + return match, errors.New(msg) } // Values of two fields should be equal as string. @@ -306,10 +297,7 @@ func (v *Validator) doCheckBuildInRules(ctx context.Context, input doCheckBuildI var msg string msg = v.getErrorMessageByRule(ctx, input.RuleKey, input.CustomMsgMap) msg = strings.Replace(msg, ":field", input.RulePattern, -1) - return match, gerror.NewOption(gerror.Option{ - Text: msg, - Code: gcode.CodeValidationFailed, - }) + return match, errors.New(msg) } // Values of two fields should not be equal as string. @@ -325,10 +313,7 @@ func (v *Validator) doCheckBuildInRules(ctx context.Context, input doCheckBuildI var msg string msg = v.getErrorMessageByRule(ctx, input.RuleKey, input.CustomMsgMap) msg = strings.Replace(msg, ":field", input.RulePattern, -1) - return match, gerror.NewOption(gerror.Option{ - Text: msg, - Code: gcode.CodeValidationFailed, - }) + return match, errors.New(msg) } // Field value should be in range of. @@ -511,10 +496,7 @@ func (v *Validator) doCheckBuildInRules(ctx context.Context, input doCheckBuildI match = gregex.IsMatchString(`^([0-9A-Fa-f]{2}[\-:]){5}[0-9A-Fa-f]{2}$`, valueStr) default: - return match, gerror.NewOption(gerror.Option{ - Text: "Invalid rule name: " + input.RuleKey, - Code: gcode.CodeInvalidParameter, - }) + return match, errors.New("Invalid rule name: " + input.RuleKey) } return match, nil } diff --git a/util/gvalid/gvalid_z_example_test.go b/util/gvalid/gvalid_z_example_test.go index c9c8c0128..b4e3e3eec 100644 --- a/util/gvalid/gvalid_z_example_test.go +++ b/util/gvalid/gvalid_z_example_test.go @@ -33,7 +33,7 @@ func ExampleCheckMap() { if e := gvalid.CheckMap(gctx.New(), params, rules); e != nil { fmt.Println(e.Map()) fmt.Println(e.FirstItem()) - fmt.Println(e.FirstString()) + fmt.Println(e.FirstError()) } // May Output: // map[required:账号不能为空 length:账号长度应当在6到16之间] @@ -55,7 +55,7 @@ func ExampleCheckMap2() { if e := gvalid.CheckMap(gctx.New(), params, rules); e != nil { fmt.Println(e.Map()) fmt.Println(e.FirstItem()) - fmt.Println(e.FirstString()) + fmt.Println(e.FirstError()) } // Output: // map[same:两次密码输入不相等] diff --git a/util/gvalid/gvalid_z_unit_basic_all_test.go b/util/gvalid/gvalid_z_unit_basic_all_test.go index 83741d477..647316230 100755 --- a/util/gvalid/gvalid_z_unit_basic_all_test.go +++ b/util/gvalid/gvalid_z_unit_basic_all_test.go @@ -1037,7 +1037,7 @@ func Test_InternalError_String(t *testing.T) { t.Assert(err.String(), "InvalidRules: hh") t.Assert(err.Strings(), g.Slice{"InvalidRules: hh"}) - t.Assert(err.FirstString(), "InvalidRules: hh") + t.Assert(err.FirstError(), "InvalidRules: hh") t.Assert(gerror.Current(err), "InvalidRules: hh") }) } diff --git a/util/gvalid/gvalid_z_unit_customerror_test.go b/util/gvalid/gvalid_z_unit_customerror_test.go index 51941c171..b1a30be68 100755 --- a/util/gvalid/gvalid_z_unit_customerror_test.go +++ b/util/gvalid/gvalid_z_unit_customerror_test.go @@ -35,9 +35,8 @@ func Test_FirstString(t *testing.T) { rule = "ipv4" val = "0.0.0" err = gvalid.CheckValue(context.TODO(), val, rule, nil) - n = err.FirstString() ) - t.Assert(n, "The value must be a valid IPv4 address") + t.Assert(err.FirstError(), "The value must be a valid IPv4 address") }) } @@ -52,12 +51,12 @@ func Test_CustomError1(t *testing.T) { t.Error("规则校验失败") } else { if v, ok := e.Map()["integer"]; ok { - if strings.Compare(v, msgs["integer"]) != 0 { + if strings.Compare(v.Error(), msgs["integer"]) != 0 { t.Error("错误信息不匹配") } } if v, ok := e.Map()["length"]; ok { - if strings.Compare(v, msgs["length"]) != 0 { + if strings.Compare(v.Error(), msgs["length"]) != 0 { t.Error("错误信息不匹配") } } @@ -72,12 +71,12 @@ func Test_CustomError2(t *testing.T) { t.Error("规则校验失败") } else { if v, ok := e.Map()["integer"]; ok { - if strings.Compare(v, "请输入一个整数") != 0 { + if strings.Compare(v.Error(), "请输入一个整数") != 0 { t.Error("错误信息不匹配") } } if v, ok := e.Map()["length"]; ok { - if strings.Compare(v, "参数长度不对啊老铁") != 0 { + if strings.Compare(v.Error(), "参数长度不对啊老铁") != 0 { t.Error("错误信息不匹配") } }