483 lines
12 KiB
Go
483 lines
12 KiB
Go
|
package validator
|
||
|
|
||
|
import (
|
||
|
"context"
|
||
|
"fmt"
|
||
|
"reflect"
|
||
|
"strconv"
|
||
|
)
|
||
|
|
||
|
// per validate construct
|
||
|
type validate struct {
|
||
|
v *Validate
|
||
|
top reflect.Value
|
||
|
ns []byte // ns是命名空间,见
|
||
|
actualNs []byte
|
||
|
// 如果想修改 错误信息的打印, 直接改这个错误的 .Error()方法应该就可以了, 不对不对,还有一个翻译在执行
|
||
|
errs ValidationErrors //校验失败的 记录错误的 结构体的 field[] , 依次验证field,所以可以加个判断,只要有一个错误,就停下来 //每次用完都会清零,在put回pool
|
||
|
includeExclude map[string]struct{} // reset only if StructPartial or StructExcept are called, no need otherwise
|
||
|
ffn FilterFunc
|
||
|
slflParent reflect.Value // StructLevel & FieldLevel
|
||
|
slCurrent reflect.Value // StructLevel & FieldLevel
|
||
|
flField reflect.Value // StructLevel & FieldLevel
|
||
|
cf *cField // StructLevel & FieldLevel
|
||
|
ct *cTag // StructLevel & FieldLevel
|
||
|
misc []byte // misc reusable
|
||
|
str1 string // misc reusable // 真正显示的 错误,有别名就是别名
|
||
|
str2 string // misc reusable // 失败的field信息,如 User.Name (结构体名称+field真名称),如果没有别名,就是str1
|
||
|
fldIsPointer bool // StructLevel & FieldLevel
|
||
|
isPartial bool
|
||
|
hasExcludes bool
|
||
|
}
|
||
|
|
||
|
// parent and current will be the same the first run of validateStruct
|
||
|
func (v *validate) validateStruct(ctx context.Context, parent reflect.Value, current reflect.Value, typ reflect.Type, ns []byte, structNs []byte, ct *cTag) {
|
||
|
|
||
|
cs, ok := v.v.structCache.Get(typ)
|
||
|
if !ok {
|
||
|
cs = v.v.extractStructCache(current, typ.Name())
|
||
|
}
|
||
|
|
||
|
if len(ns) == 0 && len(cs.name) != 0 {
|
||
|
// 但实际上,`:` 左边的我都不需要,所以还是改 翻译 比较好 , 而且可能会影响到 未知的错误
|
||
|
ns = append(ns, cs.name...) // 去掉这里的ns的内容,出现错误时,应该就就不会输出 struct.field:"err", 而是 field:"err",
|
||
|
ns = append(ns, '.')
|
||
|
|
||
|
structNs = append(structNs, cs.name...)
|
||
|
structNs = append(structNs, '.')
|
||
|
}
|
||
|
|
||
|
// ct is nil on top level struct, and structs as fields that have no tag info
|
||
|
// so if nil or if not nil and the structonly tag isn't present
|
||
|
if ct == nil || ct.typeof != typeStructOnly {
|
||
|
|
||
|
var f *cField
|
||
|
|
||
|
for i := 0; i < len(cs.fields); i++ {
|
||
|
// yang 加个判断, 有一个验证不通过就退出
|
||
|
if len(v.errs) != 0 {
|
||
|
break
|
||
|
}
|
||
|
// yang 修改分割线
|
||
|
f = cs.fields[i]
|
||
|
|
||
|
if v.isPartial {
|
||
|
|
||
|
if v.ffn != nil {
|
||
|
// used with StructFiltered
|
||
|
if v.ffn(append(structNs, f.name...)) {
|
||
|
continue
|
||
|
}
|
||
|
|
||
|
} else {
|
||
|
// used with StructPartial & StructExcept
|
||
|
_, ok = v.includeExclude[string(append(structNs, f.name...))]
|
||
|
|
||
|
if (ok && v.hasExcludes) || (!ok && !v.hasExcludes) {
|
||
|
continue
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
v.traverseField(ctx, parent, current.Field(f.idx), ns, structNs, f, f.cTags)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// check if any struct level validations, after all field validations already checked.
|
||
|
// first iteration will have no info about nostructlevel tag, and is checked prior to
|
||
|
// calling the next iteration of validateStruct called from traverseField.
|
||
|
if cs.fn != nil {
|
||
|
|
||
|
v.slflParent = parent
|
||
|
v.slCurrent = current
|
||
|
v.ns = ns
|
||
|
v.actualNs = structNs
|
||
|
|
||
|
cs.fn(ctx, v)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// traverseField validates any field, be it a struct or single field, ensures it's validity and passes it along to be validated via it's tag options
|
||
|
func (v *validate) traverseField(ctx context.Context, parent reflect.Value, current reflect.Value, ns []byte, structNs []byte, cf *cField, ct *cTag) {
|
||
|
var typ reflect.Type
|
||
|
var kind reflect.Kind
|
||
|
|
||
|
current, kind, v.fldIsPointer = v.extractTypeInternal(current, false)
|
||
|
|
||
|
switch kind {
|
||
|
case reflect.Ptr, reflect.Interface, reflect.Invalid:
|
||
|
|
||
|
if ct == nil {
|
||
|
return
|
||
|
}
|
||
|
|
||
|
if ct.typeof == typeOmitEmpty || ct.typeof == typeIsDefault {
|
||
|
return
|
||
|
}
|
||
|
|
||
|
if ct.hasTag {
|
||
|
if kind == reflect.Invalid {
|
||
|
v.str1 = string(append(ns, cf.altName...))
|
||
|
if v.v.hasTagNameFunc {
|
||
|
v.str2 = string(append(structNs, cf.name...))
|
||
|
} else {
|
||
|
v.str2 = v.str1
|
||
|
}
|
||
|
v.errs = append(v.errs,
|
||
|
&fieldError{
|
||
|
v: v.v,
|
||
|
tag: ct.aliasTag,
|
||
|
actualTag: ct.tag,
|
||
|
ns: v.str1,
|
||
|
structNs: v.str2,
|
||
|
fieldLen: uint8(len(cf.altName)),
|
||
|
structfieldLen: uint8(len(cf.name)),
|
||
|
param: ct.param,
|
||
|
kind: kind,
|
||
|
},
|
||
|
)
|
||
|
return
|
||
|
}
|
||
|
|
||
|
v.str1 = string(append(ns, cf.altName...))
|
||
|
if v.v.hasTagNameFunc {
|
||
|
v.str2 = string(append(structNs, cf.name...))
|
||
|
} else {
|
||
|
v.str2 = v.str1
|
||
|
}
|
||
|
if !ct.runValidationWhenNil {
|
||
|
v.errs = append(v.errs,
|
||
|
&fieldError{
|
||
|
v: v.v,
|
||
|
tag: ct.aliasTag,
|
||
|
actualTag: ct.tag,
|
||
|
ns: v.str1,
|
||
|
structNs: v.str2,
|
||
|
fieldLen: uint8(len(cf.altName)),
|
||
|
structfieldLen: uint8(len(cf.name)),
|
||
|
value: current.Interface(),
|
||
|
param: ct.param,
|
||
|
kind: kind,
|
||
|
typ: current.Type(),
|
||
|
},
|
||
|
)
|
||
|
return
|
||
|
}
|
||
|
}
|
||
|
|
||
|
case reflect.Struct:
|
||
|
|
||
|
typ = current.Type()
|
||
|
|
||
|
if typ != timeType {
|
||
|
|
||
|
if ct != nil {
|
||
|
|
||
|
if ct.typeof == typeStructOnly {
|
||
|
goto CONTINUE
|
||
|
} else if ct.typeof == typeIsDefault {
|
||
|
// set Field Level fields
|
||
|
v.slflParent = parent
|
||
|
v.flField = current
|
||
|
v.cf = cf
|
||
|
v.ct = ct
|
||
|
|
||
|
if !ct.fn(ctx, v) {
|
||
|
v.str1 = string(append(ns, cf.altName...))
|
||
|
|
||
|
if v.v.hasTagNameFunc {
|
||
|
v.str2 = string(append(structNs, cf.name...))
|
||
|
} else {
|
||
|
v.str2 = v.str1
|
||
|
}
|
||
|
|
||
|
v.errs = append(v.errs,
|
||
|
&fieldError{
|
||
|
v: v.v,
|
||
|
tag: ct.aliasTag,
|
||
|
actualTag: ct.tag,
|
||
|
ns: v.str1,
|
||
|
structNs: v.str2,
|
||
|
fieldLen: uint8(len(cf.altName)),
|
||
|
structfieldLen: uint8(len(cf.name)),
|
||
|
value: current.Interface(),
|
||
|
param: ct.param,
|
||
|
kind: kind,
|
||
|
typ: typ,
|
||
|
},
|
||
|
)
|
||
|
return
|
||
|
}
|
||
|
}
|
||
|
|
||
|
ct = ct.next
|
||
|
}
|
||
|
|
||
|
if ct != nil && ct.typeof == typeNoStructLevel {
|
||
|
return
|
||
|
}
|
||
|
|
||
|
CONTINUE:
|
||
|
// if len == 0 then validating using 'Var' or 'VarWithValue'
|
||
|
// Var - doesn't make much sense to do it that way, should call 'Struct', but no harm...
|
||
|
// VarWithField - this allows for validating against each field within the struct against a specific value
|
||
|
// pretty handy in certain situations
|
||
|
if len(cf.name) > 0 {
|
||
|
ns = append(append(ns, cf.altName...), '.')
|
||
|
structNs = append(append(structNs, cf.name...), '.')
|
||
|
}
|
||
|
|
||
|
v.validateStruct(ctx, current, current, typ, ns, structNs, ct)
|
||
|
return
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if !ct.hasTag {
|
||
|
return
|
||
|
}
|
||
|
|
||
|
typ = current.Type()
|
||
|
|
||
|
OUTER:
|
||
|
for {
|
||
|
if ct == nil {
|
||
|
return
|
||
|
}
|
||
|
|
||
|
switch ct.typeof {
|
||
|
|
||
|
case typeOmitEmpty:
|
||
|
|
||
|
// set Field Level fields
|
||
|
v.slflParent = parent
|
||
|
v.flField = current
|
||
|
v.cf = cf
|
||
|
v.ct = ct
|
||
|
|
||
|
if !hasValue(v) {
|
||
|
return
|
||
|
}
|
||
|
|
||
|
ct = ct.next
|
||
|
continue
|
||
|
|
||
|
case typeEndKeys:
|
||
|
return
|
||
|
|
||
|
case typeDive:
|
||
|
|
||
|
ct = ct.next
|
||
|
|
||
|
// traverse slice or map here
|
||
|
// or panic ;)
|
||
|
switch kind {
|
||
|
case reflect.Slice, reflect.Array:
|
||
|
|
||
|
var i64 int64
|
||
|
reusableCF := &cField{}
|
||
|
|
||
|
for i := 0; i < current.Len(); i++ {
|
||
|
|
||
|
i64 = int64(i)
|
||
|
|
||
|
v.misc = append(v.misc[0:0], cf.name...)
|
||
|
v.misc = append(v.misc, '[')
|
||
|
v.misc = strconv.AppendInt(v.misc, i64, 10)
|
||
|
v.misc = append(v.misc, ']')
|
||
|
|
||
|
reusableCF.name = string(v.misc)
|
||
|
|
||
|
if cf.namesEqual {
|
||
|
reusableCF.altName = reusableCF.name
|
||
|
} else {
|
||
|
|
||
|
v.misc = append(v.misc[0:0], cf.altName...)
|
||
|
v.misc = append(v.misc, '[')
|
||
|
v.misc = strconv.AppendInt(v.misc, i64, 10)
|
||
|
v.misc = append(v.misc, ']')
|
||
|
|
||
|
reusableCF.altName = string(v.misc)
|
||
|
}
|
||
|
v.traverseField(ctx, parent, current.Index(i), ns, structNs, reusableCF, ct)
|
||
|
}
|
||
|
|
||
|
case reflect.Map:
|
||
|
|
||
|
var pv string
|
||
|
reusableCF := &cField{}
|
||
|
|
||
|
for _, key := range current.MapKeys() {
|
||
|
|
||
|
pv = fmt.Sprintf("%v", key.Interface())
|
||
|
|
||
|
v.misc = append(v.misc[0:0], cf.name...)
|
||
|
v.misc = append(v.misc, '[')
|
||
|
v.misc = append(v.misc, pv...)
|
||
|
v.misc = append(v.misc, ']')
|
||
|
|
||
|
reusableCF.name = string(v.misc)
|
||
|
|
||
|
if cf.namesEqual {
|
||
|
reusableCF.altName = reusableCF.name
|
||
|
} else {
|
||
|
v.misc = append(v.misc[0:0], cf.altName...)
|
||
|
v.misc = append(v.misc, '[')
|
||
|
v.misc = append(v.misc, pv...)
|
||
|
v.misc = append(v.misc, ']')
|
||
|
|
||
|
reusableCF.altName = string(v.misc)
|
||
|
}
|
||
|
|
||
|
if ct != nil && ct.typeof == typeKeys && ct.keys != nil {
|
||
|
v.traverseField(ctx, parent, key, ns, structNs, reusableCF, ct.keys)
|
||
|
// can be nil when just keys being validated
|
||
|
if ct.next != nil {
|
||
|
v.traverseField(ctx, parent, current.MapIndex(key), ns, structNs, reusableCF, ct.next)
|
||
|
}
|
||
|
} else {
|
||
|
v.traverseField(ctx, parent, current.MapIndex(key), ns, structNs, reusableCF, ct)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
default:
|
||
|
// throw error, if not a slice or map then should not have gotten here
|
||
|
// bad dive tag
|
||
|
panic("dive error! can't dive on a non slice or map")
|
||
|
}
|
||
|
|
||
|
return
|
||
|
|
||
|
case typeOr:
|
||
|
|
||
|
v.misc = v.misc[0:0]
|
||
|
|
||
|
for {
|
||
|
|
||
|
// set Field Level fields
|
||
|
v.slflParent = parent
|
||
|
v.flField = current
|
||
|
v.cf = cf
|
||
|
v.ct = ct
|
||
|
|
||
|
if ct.fn(ctx, v) {
|
||
|
|
||
|
// drain rest of the 'or' values, then continue or leave
|
||
|
for {
|
||
|
|
||
|
ct = ct.next
|
||
|
|
||
|
if ct == nil {
|
||
|
return
|
||
|
}
|
||
|
|
||
|
if ct.typeof != typeOr {
|
||
|
continue OUTER
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
v.misc = append(v.misc, '|')
|
||
|
v.misc = append(v.misc, ct.tag...)
|
||
|
|
||
|
if ct.hasParam {
|
||
|
v.misc = append(v.misc, '=')
|
||
|
v.misc = append(v.misc, ct.param...)
|
||
|
}
|
||
|
|
||
|
if ct.isBlockEnd || ct.next == nil {
|
||
|
// if we get here, no valid 'or' value and no more tags
|
||
|
v.str1 = string(append(ns, cf.altName...))
|
||
|
|
||
|
if v.v.hasTagNameFunc {
|
||
|
v.str2 = string(append(structNs, cf.name...))
|
||
|
} else {
|
||
|
v.str2 = v.str1
|
||
|
}
|
||
|
|
||
|
if ct.hasAlias {
|
||
|
|
||
|
v.errs = append(v.errs,
|
||
|
&fieldError{
|
||
|
v: v.v,
|
||
|
tag: ct.aliasTag,
|
||
|
actualTag: ct.actualAliasTag,
|
||
|
ns: v.str1,
|
||
|
structNs: v.str2,
|
||
|
fieldLen: uint8(len(cf.altName)),
|
||
|
structfieldLen: uint8(len(cf.name)),
|
||
|
value: current.Interface(),
|
||
|
param: ct.param,
|
||
|
kind: kind,
|
||
|
typ: typ,
|
||
|
},
|
||
|
)
|
||
|
|
||
|
} else {
|
||
|
|
||
|
tVal := string(v.misc)[1:]
|
||
|
|
||
|
v.errs = append(v.errs,
|
||
|
&fieldError{
|
||
|
v: v.v,
|
||
|
tag: tVal,
|
||
|
actualTag: tVal,
|
||
|
ns: v.str1,
|
||
|
structNs: v.str2,
|
||
|
fieldLen: uint8(len(cf.altName)),
|
||
|
structfieldLen: uint8(len(cf.name)),
|
||
|
value: current.Interface(),
|
||
|
param: ct.param,
|
||
|
kind: kind,
|
||
|
typ: typ,
|
||
|
},
|
||
|
)
|
||
|
}
|
||
|
|
||
|
return
|
||
|
}
|
||
|
|
||
|
ct = ct.next
|
||
|
}
|
||
|
|
||
|
default:
|
||
|
|
||
|
// set Field Level fields
|
||
|
v.slflParent = parent
|
||
|
v.flField = current
|
||
|
v.cf = cf
|
||
|
v.ct = ct
|
||
|
|
||
|
if !ct.fn(ctx, v) { // 这是 验证函数的进行校验, 不成功就会记录下来
|
||
|
|
||
|
v.str1 = string(append(ns, cf.altName...)) //如果不想要 命名空间,就不要拼接 ns
|
||
|
|
||
|
if v.v.hasTagNameFunc {
|
||
|
v.str2 = string(append(structNs, cf.name...))
|
||
|
} else {
|
||
|
v.str2 = v.str1
|
||
|
}
|
||
|
|
||
|
v.errs = append(v.errs,
|
||
|
&fieldError{
|
||
|
v: v.v,
|
||
|
tag: ct.aliasTag,
|
||
|
actualTag: ct.tag,
|
||
|
ns: v.str1,
|
||
|
structNs: v.str2,
|
||
|
fieldLen: uint8(len(cf.altName)),
|
||
|
structfieldLen: uint8(len(cf.name)),
|
||
|
value: current.Interface(),
|
||
|
param: ct.param,
|
||
|
kind: kind,
|
||
|
typ: typ,
|
||
|
},
|
||
|
)
|
||
|
|
||
|
return
|
||
|
}
|
||
|
ct = ct.next
|
||
|
}
|
||
|
}
|
||
|
|
||
|
}
|