Merge branch 'zhiwei' of https://github.com/564104865/gf into zhiwei

This commit is contained in:
546104865 2021-11-09 20:47:28 +08:00
commit be274478d0
16 changed files with 279 additions and 200 deletions

View File

@ -9,10 +9,8 @@ package gdb
import (
"context"
"fmt"
"github.com/gogf/gf/v2/util/gconv"
"time"
"github.com/gogf/gf/v2/text/gregex"
"github.com/gogf/gf/v2/util/gconv"
"github.com/gogf/gf/v2/text/gstr"
)
@ -45,8 +43,7 @@ type Model struct {
distinct string // Force the query to only return distinct results.
lockInfo string // Lock for update or in shared lock.
cacheEnabled bool // Enable sql result cache feature.
cacheDuration time.Duration // Cache TTL duration (< 1 for removing cache, >= 0 for saving cache).
cacheName string // Cache name for custom operation.
cacheOption CacheOption // Cache option for query statement.
unscoped bool // Disables soft deleting features when select/delete operations.
safe bool // If true, it clones and returns a new model object whenever operation done; or else it changes the attribute of current model.
onDuplicate interface{} // onDuplicate is used for ON "DUPLICATE KEY UPDATE" statement.

View File

@ -11,26 +11,32 @@ import (
"time"
)
type CacheOption struct {
// Duration is the TTL for the cache.
// If the parameter `Duration` < 0, which means it clear the cache with given `Name`.
// If the parameter `Duration` = 0, which means it never expires.
// If the parameter `Duration` > 0, which means it expires after `Duration`.
Duration time.Duration
// Name is an optional unique name for the cache.
// The Name is used to bind a name to the cache, which means you can later control the cache
// like changing the `duration` or clearing the cache with specified Name.
Name string
// Force caches the query result whatever the result is nil or not.
// It is used to avoid Cache Penetration.
Force bool
}
// Cache sets the cache feature for the model. It caches the result of the sql, which means
// if there's another same sql request, it just reads and returns the result from cache, it
// but not committed and executed into the database.
//
// If the parameter `duration` < 0, which means it clear the cache with given `name`.
// If the parameter `duration` = 0, which means it never expires.
// If the parameter `duration` > 0, which means it expires after `duration`.
//
// The optional parameter `name` is used to bind a name to the cache, which means you can
// later control the cache like changing the `duration` or clearing the cache with specified
// `name`.
//
// Note that, the cache feature is disabled if the model is performing select statement
// on a transaction.
func (m *Model) Cache(duration time.Duration, name ...string) *Model {
func (m *Model) Cache(option CacheOption) *Model {
model := m.getModel()
model.cacheDuration = duration
if len(name) > 0 {
model.cacheName = name[0]
}
model.cacheOption = option
model.cacheEnabled = true
return model
}
@ -38,9 +44,9 @@ func (m *Model) Cache(duration time.Duration, name ...string) *Model {
// checkAndRemoveCache checks and removes the cache in insert/update/delete statement if
// cache feature is enabled.
func (m *Model) checkAndRemoveCache() {
if m.cacheEnabled && m.cacheDuration < 0 && len(m.cacheName) > 0 {
if m.cacheEnabled && m.cacheOption.Duration < 0 && len(m.cacheOption.Name) > 0 {
var ctx = m.GetCtx()
_, err := m.db.GetCache().Remove(ctx, m.cacheName)
_, err := m.db.GetCache().Remove(ctx, m.cacheOption.Name)
if err != nil {
intlog.Error(ctx, err)
}

View File

@ -437,7 +437,7 @@ func (m *Model) doGetAllBySql(sql string, args ...interface{}) (result Result, e
)
// Retrieve from cache.
if m.cacheEnabled && m.tx == nil {
cacheKey = m.cacheName
cacheKey = m.cacheOption.Name
if len(cacheKey) == 0 {
cacheKey = sql + ", @PARAMS:" + gconv.String(args)
}
@ -461,16 +461,16 @@ func (m *Model) doGetAllBySql(sql string, args ...interface{}) (result Result, e
)
// Cache the result.
if cacheKey != "" && err == nil {
if m.cacheDuration < 0 {
if m.cacheOption.Duration < 0 {
if _, err := cacheObj.Remove(ctx, cacheKey); err != nil {
intlog.Error(m.GetCtx(), err)
}
} else {
// In case of Cache Penetration.
if result == nil {
if result.IsEmpty() && m.cacheOption.Force {
result = Result{}
}
if err := cacheObj.Set(ctx, cacheKey, result, m.cacheDuration); err != nil {
if err := cacheObj.Set(ctx, cacheKey, result, m.cacheOption.Duration); err != nil {
intlog.Error(m.GetCtx(), err)
}
}

View File

@ -2479,7 +2479,11 @@ func Test_Model_Cache(t *testing.T) {
defer dropTable(table)
gtest.C(t, func(t *gtest.T) {
one, err := db.Model(table).Cache(time.Second, "test1").WherePri(1).One()
one, err := db.Model(table).Cache(gdb.CacheOption{
Duration: time.Second,
Name: "test1",
Force: false,
}).WherePri(1).One()
t.AssertNil(err)
t.Assert(one["passport"], "user_1")
@ -2489,63 +2493,107 @@ func Test_Model_Cache(t *testing.T) {
t.AssertNil(err)
t.Assert(n, 1)
one, err = db.Model(table).Cache(time.Second, "test1").WherePri(1).One()
one, err = db.Model(table).Cache(gdb.CacheOption{
Duration: time.Second,
Name: "test1",
Force: false,
}).WherePri(1).One()
t.AssertNil(err)
t.Assert(one["passport"], "user_1")
time.Sleep(time.Second * 2)
one, err = db.Model(table).Cache(time.Second, "test1").WherePri(1).One()
one, err = db.Model(table).Cache(gdb.CacheOption{
Duration: time.Second,
Name: "test1",
Force: false,
}).WherePri(1).One()
t.AssertNil(err)
t.Assert(one["passport"], "user_100")
})
gtest.C(t, func(t *gtest.T) {
one, err := db.Model(table).Cache(time.Second, "test2").WherePri(2).One()
one, err := db.Model(table).Cache(gdb.CacheOption{
Duration: time.Second,
Name: "test2",
Force: false,
}).WherePri(2).One()
t.AssertNil(err)
t.Assert(one["passport"], "user_2")
r, err := db.Model(table).Data("passport", "user_200").Cache(-1, "test2").WherePri(2).Update()
r, err := db.Model(table).Data("passport", "user_200").Cache(gdb.CacheOption{
Duration: -1,
Name: "test2",
Force: false,
}).WherePri(2).Update()
t.AssertNil(err)
n, err := r.RowsAffected()
t.AssertNil(err)
t.Assert(n, 1)
one, err = db.Model(table).Cache(time.Second, "test2").WherePri(2).One()
one, err = db.Model(table).Cache(gdb.CacheOption{
Duration: time.Second,
Name: "test2",
Force: false,
}).WherePri(2).One()
t.AssertNil(err)
t.Assert(one["passport"], "user_200")
})
// transaction.
gtest.C(t, func(t *gtest.T) {
// make cache for id 3
one, err := db.Model(table).Cache(time.Second, "test3").WherePri(3).One()
one, err := db.Model(table).Cache(gdb.CacheOption{
Duration: time.Second,
Name: "test3",
Force: false,
}).WherePri(3).One()
t.AssertNil(err)
t.Assert(one["passport"], "user_3")
r, err := db.Model(table).Data("passport", "user_300").Cache(time.Second, "test3").WherePri(3).Update()
r, err := db.Model(table).Data("passport", "user_300").Cache(gdb.CacheOption{
Duration: time.Second,
Name: "test3",
Force: false,
}).WherePri(3).Update()
t.AssertNil(err)
n, err := r.RowsAffected()
t.AssertNil(err)
t.Assert(n, 1)
err = db.Transaction(context.TODO(), func(ctx context.Context, tx *gdb.TX) error {
one, err := tx.Model(table).Cache(time.Second, "test3").WherePri(3).One()
one, err := tx.Model(table).Cache(gdb.CacheOption{
Duration: time.Second,
Name: "test3",
Force: false,
}).WherePri(3).One()
t.AssertNil(err)
t.Assert(one["passport"], "user_300")
return nil
})
t.AssertNil(err)
one, err = db.Model(table).Cache(time.Second, "test3").WherePri(3).One()
one, err = db.Model(table).Cache(gdb.CacheOption{
Duration: time.Second,
Name: "test3",
Force: false,
}).WherePri(3).One()
t.AssertNil(err)
t.Assert(one["passport"], "user_3")
})
gtest.C(t, func(t *gtest.T) {
// make cache for id 4
one, err := db.Model(table).Cache(time.Second, "test4").WherePri(4).One()
one, err := db.Model(table).Cache(gdb.CacheOption{
Duration: time.Second,
Name: "test4",
Force: false,
}).WherePri(4).One()
t.AssertNil(err)
t.Assert(one["passport"], "user_4")
r, err := db.Model(table).Data("passport", "user_400").Cache(time.Second, "test3").WherePri(4).Update()
r, err := db.Model(table).Data("passport", "user_400").Cache(gdb.CacheOption{
Duration: time.Second,
Name: "test3",
Force: false,
}).WherePri(4).Update()
t.AssertNil(err)
n, err := r.RowsAffected()
t.AssertNil(err)
@ -2553,12 +2601,20 @@ func Test_Model_Cache(t *testing.T) {
err = db.Transaction(context.TODO(), func(ctx context.Context, tx *gdb.TX) error {
// Cache feature disabled.
one, err := tx.Model(table).Cache(time.Second, "test4").WherePri(4).One()
one, err := tx.Model(table).Cache(gdb.CacheOption{
Duration: time.Second,
Name: "test4",
Force: false,
}).WherePri(4).One()
t.AssertNil(err)
t.Assert(one["passport"], "user_400")
// Update the cache.
r, err := tx.Model(table).Data("passport", "user_4000").
Cache(-1, "test4").WherePri(4).Update()
Cache(gdb.CacheOption{
Duration: -1,
Name: "test4",
Force: false,
}).WherePri(4).Update()
t.AssertNil(err)
n, err := r.RowsAffected()
t.AssertNil(err)
@ -2567,7 +2623,11 @@ func Test_Model_Cache(t *testing.T) {
})
t.AssertNil(err)
// Read from db.
one, err = db.Model(table).Cache(time.Second, "test4").WherePri(4).One()
one, err = db.Model(table).Cache(gdb.CacheOption{
Duration: time.Second,
Name: "test4",
Force: false,
}).WherePri(4).One()
t.AssertNil(err)
t.Assert(one["passport"], "user_4000")
})

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

@ -134,7 +134,7 @@ func TestCache_UpdateExpire(t *testing.T) {
newExpire := 10 * time.Second
oldExpire2, err := cache.UpdateExpire(ctx, 1, newExpire)
t.AssertNil(err)
t.Assert(oldExpire2, oldExpire)
t.AssertIN(oldExpire2, g.Slice{oldExpire, `2.999s`})
e, _ := cache.GetExpire(ctx, 1)
t.AssertNE(e, oldExpire)

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

@ -6,16 +6,29 @@
package gvalid
import "context"
import (
"context"
"github.com/gogf/gf/v2/container/gvar"
)
// RuleFunc is the custom function for data validation.
//
// The parameter `rule` specifies the validation rule string, like "required", "between:1,100", etc.
// The parameter `value` specifies the value for this rule to validate.
// The parameter `message` specifies the custom error message or configured i18n message for this rule.
// The parameter `data` specifies the `data` which is passed to the Validator. It might be a type of map/struct or a nil value.
// You can ignore the parameter `data` if you do not really need it in your custom validation rule.
type RuleFunc func(ctx context.Context, rule string, value interface{}, message string, data interface{}) error
type RuleFunc func(ctx context.Context, in RuleFuncInput) error
// RuleFuncInput holds the input parameters that passed to custom rule function RuleFunc.
type RuleFuncInput struct {
// Rule specifies the validation rule string, like "required", "between:1,100", etc.
Rule string
// Message specifies the custom error message or configured i18n message for this rule.
Message string
// Value specifies the value for this rule to validate.
Value *gvar.Var
// Data specifies the `data` which is passed to the Validator. It might be a type of map/struct or a nil value.
// You can ignore the parameter `Data` if you do not really need it in your custom validation rule.
Data *gvar.Var
}
var (
// customRuleFuncMap stores the custom rule functions.
@ -24,13 +37,20 @@ var (
)
// RegisterRule registers custom validation rule and function for package.
// It returns error if there's already the same rule registered previously.
func RegisterRule(rule string, f RuleFunc) error {
func RegisterRule(rule string, f RuleFunc) {
customRuleFuncMap[rule] = f
return nil
}
// DeleteRule deletes custom defined validation rule and its function from global package.
func DeleteRule(rule string) {
delete(customRuleFuncMap, rule)
// RegisterRuleByMap registers custom validation rules using map for package.
func RegisterRuleByMap(m map[string]RuleFunc) {
for k, v := range m {
customRuleFuncMap[k] = v
}
}
// DeleteRule deletes custom defined validation one or more rules and associated functions from global package.
func DeleteRule(rules ...string) {
for _, rule := range rules {
delete(customRuleFuncMap, rule)
}
}

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,8 +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"
@ -60,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 (
@ -86,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 {
@ -133,9 +134,14 @@ func (v *Validator) doCheckValue(ctx context.Context, input doCheckValueInput) E
if customRuleFunc != nil {
// It checks custom validation rules with most priority.
message := v.getErrorMessageByRule(ctx, ruleKey, customMsgMap)
if err := customRuleFunc(ctx, ruleItems[index], input.Value, message, input.DataRaw); err != nil {
if err := customRuleFunc(ctx, RuleFuncInput{
Rule: ruleItems[index],
Message: message,
Value: gvar.New(input.Value),
Data: gvar.New(input.DataRaw),
}); err != nil {
match = false
errorMsgArray[ruleKey] = err.Error()
ruleErrorMap[ruleKey] = err
} else {
match = true
}
@ -154,7 +160,7 @@ func (v *Validator) doCheckValue(ctx context.Context, input doCheckValueInput) E
},
)
if !match && err != nil {
errorMsgArray[ruleKey] = err.Error()
ruleErrorMap[ruleKey] = err
}
}
@ -162,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.
@ -173,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,
},
)
}
@ -217,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
}
@ -231,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
}
@ -282,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.
@ -300,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.
@ -319,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.
@ -505,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:两次密码输入不相等]
@ -127,17 +127,17 @@ func ExampleRegisterRule() {
}
rule := "unique-name"
gvalid.RegisterRule(rule, func(ctx context.Context, rule string, value interface{}, message string, data interface{}) error {
gvalid.RegisterRule(rule, func(ctx context.Context, in gvalid.RuleFuncInput) error {
var (
id = data.(*User).Id
name = gconv.String(value)
id = in.Data.Val().(*User).Id
name = gconv.String(in.Value)
)
n, err := g.Model("user").Where("id != ? and name = ?", id, name).Count()
if err != nil {
return err
}
if n > 0 {
return errors.New(message)
return errors.New(in.Message)
}
return nil
})
@ -149,8 +149,8 @@ func ExampleRegisterRule() {
func ExampleRegisterRule_OverwriteRequired() {
rule := "required"
gvalid.RegisterRule(rule, func(ctx context.Context, rule string, value interface{}, message string, data interface{}) error {
reflectValue := reflect.ValueOf(value)
gvalid.RegisterRule(rule, func(ctx context.Context, in gvalid.RuleFuncInput) error {
reflectValue := reflect.ValueOf(in.Value.Val())
if reflectValue.Kind() == reflect.Ptr {
reflectValue = reflectValue.Elem()
}
@ -171,7 +171,7 @@ func ExampleRegisterRule_OverwriteRequired() {
isEmpty = reflectValue.Len() == 0
}
if isEmpty {
return errors.New(message)
return errors.New(in.Message)
}
return nil
})

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

@ -12,29 +12,27 @@ import (
"testing"
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/util/gconv"
"github.com/gogf/gf/v2/test/gtest"
"github.com/gogf/gf/v2/util/gvalid"
)
func Test_CustomRule1(t *testing.T) {
rule := "custom"
err := gvalid.RegisterRule(
gvalid.RegisterRule(
rule,
func(ctx context.Context, rule string, value interface{}, message string, data interface{}) error {
pass := gconv.String(value)
func(ctx context.Context, in gvalid.RuleFuncInput) error {
pass := in.Value.String()
if len(pass) != 6 {
return errors.New(message)
return errors.New(in.Message)
}
m := gconv.Map(data)
m := in.Data.Map()
if m["data"] != pass {
return errors.New(message)
return errors.New(in.Message)
}
return nil
},
)
gtest.Assert(err, nil)
gtest.C(t, func(t *gtest.T) {
err := gvalid.CheckValue(context.TODO(), "123456", rule, "custom message")
t.Assert(err.String(), "custom message")
@ -71,14 +69,13 @@ func Test_CustomRule1(t *testing.T) {
func Test_CustomRule2(t *testing.T) {
rule := "required-map"
err := gvalid.RegisterRule(rule, func(ctx context.Context, rule string, value interface{}, message string, data interface{}) error {
m := gconv.Map(value)
gvalid.RegisterRule(rule, func(ctx context.Context, in gvalid.RuleFuncInput) error {
m := in.Value.Map()
if len(m) == 0 {
return errors.New(message)
return errors.New(in.Message)
}
return nil
})
gtest.Assert(err, nil)
// Check.
gtest.C(t, func(t *gtest.T) {
errStr := "data map should not be empty"
@ -115,14 +112,13 @@ func Test_CustomRule2(t *testing.T) {
func Test_CustomRule_AllowEmpty(t *testing.T) {
rule := "allow-empty-str"
err := gvalid.RegisterRule(rule, func(ctx context.Context, rule string, value interface{}, message string, data interface{}) error {
s := gconv.String(value)
gvalid.RegisterRule(rule, func(ctx context.Context, in gvalid.RuleFuncInput) error {
s := in.Value.String()
if len(s) == 0 || s == "gf" {
return nil
}
return errors.New(message)
return errors.New(in.Message)
})
gtest.Assert(err, nil)
// Check.
gtest.C(t, func(t *gtest.T) {
errStr := "error"
@ -160,13 +156,13 @@ func Test_CustomRule_AllowEmpty(t *testing.T) {
func TestValidator_RuleFunc(t *testing.T) {
ruleName := "custom_1"
ruleFunc := func(ctx context.Context, rule string, value interface{}, message string, data interface{}) error {
pass := gconv.String(value)
ruleFunc := func(ctx context.Context, in gvalid.RuleFuncInput) error {
pass := in.Value.String()
if len(pass) != 6 {
return errors.New(message)
return errors.New(in.Message)
}
if m := gconv.Map(data); m["data"] != pass {
return errors.New(message)
if m := in.Data.Map(); m["data"] != pass {
return errors.New(in.Message)
}
return nil
}
@ -214,13 +210,13 @@ func TestValidator_RuleFunc(t *testing.T) {
func TestValidator_RuleFuncMap(t *testing.T) {
ruleName := "custom_1"
ruleFunc := func(ctx context.Context, rule string, value interface{}, message string, data interface{}) error {
pass := gconv.String(value)
ruleFunc := func(ctx context.Context, in gvalid.RuleFuncInput) error {
pass := in.Value.String()
if len(pass) != 6 {
return errors.New(message)
return errors.New(in.Message)
}
if m := gconv.Map(data); m["data"] != pass {
return errors.New(message)
if m := in.Data.Map(); m["data"] != pass {
return errors.New(in.Message)
}
return nil
}

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