improve error feature for package gvalid

This commit is contained in:
John Guo 2021-11-09 17:50:14 +08:00
parent e0a0fcbde2
commit 95a4690e69
9 changed files with 105 additions and 111 deletions

View File

@ -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.

View File

@ -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)

View File

@ -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

View File

@ -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
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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:两次密码输入不相等]

View File

@ -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")
})
}

View File

@ -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("错误信息不匹配")
}
}