You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
277 lines
5.6 KiB
277 lines
5.6 KiB
package validator
|
|
|
|
import (
|
|
"fmt"
|
|
"reflect"
|
|
"strings"
|
|
"sync"
|
|
"sync/atomic"
|
|
)
|
|
|
|
type tagType uint8
|
|
|
|
const (
|
|
typeDefault tagType = iota
|
|
typeOmitEmpty
|
|
typeNoStructLevel
|
|
typeStructOnly
|
|
typeDive
|
|
typeOr
|
|
)
|
|
|
|
const (
|
|
invalidValidation = "Invalid validation tag on field '%s'"
|
|
undefinedValidation = "Undefined validation function '%s' on field '%s'"
|
|
)
|
|
|
|
type structCache struct {
|
|
lock sync.Mutex
|
|
m atomic.Value // map[reflect.Type]*cStruct
|
|
}
|
|
|
|
func (sc *structCache) Get(key reflect.Type) (c *cStruct, found bool) {
|
|
c, found = sc.m.Load().(map[reflect.Type]*cStruct)[key]
|
|
return
|
|
}
|
|
|
|
func (sc *structCache) Set(key reflect.Type, value *cStruct) {
|
|
|
|
m := sc.m.Load().(map[reflect.Type]*cStruct)
|
|
|
|
nm := make(map[reflect.Type]*cStruct, len(m)+1)
|
|
for k, v := range m {
|
|
nm[k] = v
|
|
}
|
|
nm[key] = value
|
|
sc.m.Store(nm)
|
|
}
|
|
|
|
type tagCache struct {
|
|
lock sync.Mutex
|
|
m atomic.Value // map[string]*cTag
|
|
}
|
|
|
|
func (tc *tagCache) Get(key string) (c *cTag, found bool) {
|
|
c, found = tc.m.Load().(map[string]*cTag)[key]
|
|
return
|
|
}
|
|
|
|
func (tc *tagCache) Set(key string, value *cTag) {
|
|
|
|
m := tc.m.Load().(map[string]*cTag)
|
|
|
|
nm := make(map[string]*cTag, len(m)+1)
|
|
for k, v := range m {
|
|
nm[k] = v
|
|
}
|
|
nm[key] = value
|
|
tc.m.Store(nm)
|
|
}
|
|
|
|
type cStruct struct {
|
|
name string
|
|
fields []*cField
|
|
fn StructLevelFunc
|
|
}
|
|
|
|
type cField struct {
|
|
idx int
|
|
name string
|
|
altName string
|
|
namesEqual bool
|
|
cTags *cTag
|
|
}
|
|
|
|
// Name return field name
|
|
func (cf *cField) Name() string {
|
|
return cf.name
|
|
}
|
|
|
|
type cTag struct {
|
|
tag string
|
|
aliasTag string
|
|
actualAliasTag string
|
|
param string
|
|
hasAlias bool
|
|
typeof tagType
|
|
hasTag bool
|
|
fn Func
|
|
next *cTag
|
|
}
|
|
|
|
// Param return tag param
|
|
func (ct *cTag) Param() string {
|
|
return ct.param
|
|
}
|
|
|
|
func (v *Validate) extractStructCache(current reflect.Value, sName string) *cStruct {
|
|
|
|
v.structCache.lock.Lock()
|
|
defer v.structCache.lock.Unlock() // leave as defer! because if inner panics, it will never get unlocked otherwise!
|
|
|
|
typ := current.Type()
|
|
|
|
// could have been multiple trying to access, but once first is done this ensures struct
|
|
// isn't parsed again.
|
|
cs, ok := v.structCache.Get(typ)
|
|
if ok {
|
|
return cs
|
|
}
|
|
|
|
cs = &cStruct{name: sName, fields: make([]*cField, 0), fn: v.structLevelFuncs[typ]}
|
|
|
|
numFields := current.NumField()
|
|
|
|
var ctag *cTag
|
|
var fld reflect.StructField
|
|
var tag string
|
|
var customName string
|
|
|
|
for i := 0; i < numFields; i++ {
|
|
|
|
fld = typ.Field(i)
|
|
|
|
if !fld.Anonymous && len(fld.PkgPath) > 0 {
|
|
continue
|
|
}
|
|
|
|
tag = fld.Tag.Get(v.tagName)
|
|
|
|
if tag == skipValidationTag {
|
|
continue
|
|
}
|
|
|
|
customName = fld.Name
|
|
|
|
if v.hasTagNameFunc {
|
|
|
|
name := v.tagNameFunc(fld)
|
|
|
|
if len(name) > 0 {
|
|
customName = name
|
|
}
|
|
}
|
|
|
|
// NOTE: cannot use shared tag cache, because tags may be equal, but things like alias may be different
|
|
// and so only struct level caching can be used instead of combined with Field tag caching
|
|
|
|
if len(tag) > 0 {
|
|
ctag, _ = v.parseFieldTagsRecursive(tag, fld.Name, "", false)
|
|
} else {
|
|
// even if field doesn't have validations need cTag for traversing to potential inner/nested
|
|
// elements of the field.
|
|
ctag = new(cTag)
|
|
}
|
|
|
|
cs.fields = append(cs.fields, &cField{
|
|
idx: i,
|
|
name: fld.Name,
|
|
altName: customName,
|
|
cTags: ctag,
|
|
namesEqual: fld.Name == customName,
|
|
})
|
|
}
|
|
|
|
v.structCache.Set(typ, cs)
|
|
|
|
return cs
|
|
}
|
|
|
|
func (v *Validate) parseFieldTagsRecursive(tag string, fieldName string, alias string, hasAlias bool) (firstCtag *cTag, current *cTag) {
|
|
|
|
var t string
|
|
var ok bool
|
|
noAlias := len(alias) == 0
|
|
tags := strings.Split(tag, tagSeparator)
|
|
|
|
for i := 0; i < len(tags); i++ {
|
|
|
|
t = tags[i]
|
|
|
|
if noAlias {
|
|
alias = t
|
|
}
|
|
|
|
// check map for alias and process new tags, otherwise process as usual
|
|
if tagsVal, found := v.aliases[t]; found {
|
|
|
|
if i == 0 {
|
|
firstCtag, current = v.parseFieldTagsRecursive(tagsVal, fieldName, t, true)
|
|
} else {
|
|
next, curr := v.parseFieldTagsRecursive(tagsVal, fieldName, t, true)
|
|
current.next, current = next, curr
|
|
|
|
}
|
|
|
|
continue
|
|
}
|
|
|
|
if i == 0 {
|
|
current = &cTag{aliasTag: alias, hasAlias: hasAlias, hasTag: true}
|
|
firstCtag = current
|
|
} else {
|
|
current.next = &cTag{aliasTag: alias, hasAlias: hasAlias, hasTag: true}
|
|
current = current.next
|
|
}
|
|
|
|
switch t {
|
|
|
|
case diveTag:
|
|
current.typeof = typeDive
|
|
continue
|
|
|
|
case omitempty:
|
|
current.typeof = typeOmitEmpty
|
|
continue
|
|
|
|
case structOnlyTag:
|
|
current.typeof = typeStructOnly
|
|
continue
|
|
|
|
case noStructLevelTag:
|
|
current.typeof = typeNoStructLevel
|
|
continue
|
|
|
|
default:
|
|
|
|
// if a pipe character is needed within the param you must use the utf8Pipe representation "0x7C"
|
|
orVals := strings.Split(t, orSeparator)
|
|
|
|
for j := 0; j < len(orVals); j++ {
|
|
|
|
vals := strings.SplitN(orVals[j], tagKeySeparator, 2)
|
|
|
|
if noAlias {
|
|
alias = vals[0]
|
|
current.aliasTag = alias
|
|
} else {
|
|
current.actualAliasTag = t
|
|
}
|
|
|
|
if j > 0 {
|
|
current.next = &cTag{aliasTag: alias, actualAliasTag: current.actualAliasTag, hasAlias: hasAlias, hasTag: true}
|
|
current = current.next
|
|
}
|
|
|
|
current.tag = vals[0]
|
|
if len(current.tag) == 0 {
|
|
panic(strings.TrimSpace(fmt.Sprintf(invalidValidation, fieldName)))
|
|
}
|
|
|
|
if current.fn, ok = v.validations[current.tag]; !ok {
|
|
panic(strings.TrimSpace(fmt.Sprintf(undefinedValidation, current.tag, fieldName)))
|
|
}
|
|
|
|
if len(orVals) > 1 {
|
|
current.typeof = typeOr
|
|
}
|
|
|
|
if len(vals) > 1 {
|
|
current.param = strings.Replace(strings.Replace(vals[1], utf8HexComma, ",", -1), utf8Pipe, "|", -1)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return
|
|
}
|
|
|