// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, // You can obtain one at https://github.com/gogf/gf. package gvalid import ( "strings" "github.com/gogf/gf/v2/container/gset" "github.com/gogf/gf/v2/errors/gcode" "github.com/gogf/gf/v2/errors/gerror" "github.com/gogf/gf/v2/text/gstr" ) // Error is the validation error for validation result. type Error interface { Code() gcode.Code Current() error Error() 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 only. 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). } // newValidationError creates and returns a validation error. func newValidationError(code gcode.Code, rules []fieldRule, fieldRuleErrorMap map[string]map[string]error) *validationError { for field, ruleErrorMap := range fieldRuleErrorMap { for rule, err := range ruleErrorMap { if !gerror.HasStack(err) { ruleErrorMap[rule] = gerror.NewWithOption(gerror.Option{ Stack: false, Text: gstr.Trim(err.Error()), Code: code, }) } } fieldRuleErrorMap[field] = ruleErrorMap } // Filter repeated sequence rules. var ruleNameSet = gset.NewStrSet() for i := 0; i < len(rules); { if !ruleNameSet.AddIfNotExist(rules[i].Name) { // Delete repeated rule. rules = append(rules[:i], rules[i+1:]...) continue } i++ } return &validationError{ code: code, rules: rules, errors: fieldRuleErrorMap, } } // 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. func (e *validationError) Code() gcode.Code { if e == nil { return gcode.CodeNil } return e.code } // Map returns the first error message as map. func (e *validationError) Map() map[string]error { if e == nil { return map[string]error{} } _, m := e.FirstItem() return m } // Maps returns all error messages as map. func (e *validationError) Maps() map[string]map[string]error { if e == nil { return nil } 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]error) { if e == nil { return []map[string]map[string]error{} } 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]error{ v.Name: errorItemMap, }) } } return items } // No sequence. for name, errorRuleMap := range e.errors { items = append(items, map[string]map[string]error{ 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]error) { if e == nil { return "", map[string]error{} } if e.firstItem != nil { return e.firstKey, e.firstItem } // By sequence. if len(e.rules) > 0 { for _, v := range e.rules { if errorItemMap, ok := e.errors[v.Name]; ok { e.firstKey = v.Name e.firstItem = errorItemMap return v.Name, errorItemMap } } } // No sequence. for k, m := range e.errors { e.firstKey = k e.firstItem = m return k, m } return "", nil } // FirstRule returns the first error rule and message string. func (e *validationError) FirstRule() (rule string, err error) { if e == nil { return "", nil } // By sequence. if len(e.rules) > 0 { for _, v := range e.rules { if errorItemMap, ok := e.errors[v.Name]; ok { for _, ruleItem := range strings.Split(v.Rule, "|") { array := strings.Split(ruleItem, ":") ruleItem = strings.TrimSpace(array[0]) if err, ok = errorItemMap[ruleItem]; ok { return ruleItem, err } } } } } // No sequence. for _, errorItemMap := range e.errors { for k, v := range errorItemMap { return k, v } } return "", nil } // FirstError returns the first error message as string. // Note that the returned message might be different if it has no sequence. func (e *validationError) FirstError() (err error) { if e == nil { return nil } _, err = e.FirstRule() return } // Current is alis of FirstError, which implements interface gerror.iCurrent. func (e *validationError) Current() error { return e.FirstError() } // String returns all error messages as string, multiple error messages joined using char ';'. func (e *validationError) String() string { if e == nil { return "" } return strings.Join(e.Strings(), "; ") } // Error implements interface of error.Error. func (e *validationError) Error() string { if e == nil { return "" } return e.String() } // Strings returns all error messages as string array. func (e *validationError) Strings() (errs []string) { if e == nil { return []string{} } errs = make([]string, 0) // By sequence. if len(e.rules) > 0 { for _, v := range e.rules { if errorItemMap, ok := e.errors[v.Name]; ok { // validation error checks. for _, ruleItem := range strings.Split(v.Rule, "|") { ruleItem = strings.TrimSpace(strings.Split(ruleItem, ":")[0]) if err, ok := errorItemMap[ruleItem]; ok { errs = append(errs, err.Error()) } } // internal error checks. for k := range internalErrKeyMap { if err, ok := errorItemMap[k]; ok { errs = append(errs, err.Error()) } } } } return errs } // No sequence. for _, errorItemMap := range e.errors { for _, err := range errorItemMap { errs = append(errs, err.Error()) } } return }