mirror of
https://gitee.com/johng/gf
synced 2026-06-06 02:25:47 +08:00
improve package gvalid
This commit is contained in:
28
.example/util/gvalid/gvalid_checkstructwithdata.go
Normal file
28
.example/util/gvalid/gvalid_checkstructwithdata.go
Normal file
@ -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())
|
||||
}
|
||||
}
|
||||
@ -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.
|
||||
|
||||
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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",
|
||||
)
|
||||
}
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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")
|
||||
})
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user