123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475 |
- package validator
- import (
- "context"
- "fmt"
- "reflect"
- "strconv"
- )
- // per validate contruct
- type validate struct {
- v *Validate
- top reflect.Value
- ns []byte
- actualNs []byte
- errs ValidationErrors
- 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
- 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 = 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++ {
- 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 {
- v.str1 = string(append(ns, cf.altName...))
- if v.v.hasTagNameFunc {
- v.str2 = string(append(structNs, cf.name...))
- } else {
- v.str2 = v.str1
- }
- if kind == reflect.Invalid {
- 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.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 !v.fldIsPointer && !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...))
- 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
- }
- }
- }
|