486 lines
		
	
	
		
			11 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			486 lines
		
	
	
		
			11 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
| package validator
 | |
| 
 | |
| import (
 | |
| 	"context"
 | |
| 	"fmt"
 | |
| 	"reflect"
 | |
| 	"strconv"
 | |
| )
 | |
| 
 | |
| // per validate construct
 | |
| 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, current, 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.ConvertibleTo(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, parent, current, typ, ns, structNs, ct)
 | |
| 			return
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	if ct == nil || !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) {
 | |
| 					if ct.isBlockEnd {
 | |
| 						ct = ct.next
 | |
| 						continue OUTER
 | |
| 					}
 | |
| 
 | |
| 					// drain rest of the 'or' values, then continue or leave
 | |
| 					for {
 | |
| 
 | |
| 						ct = ct.next
 | |
| 
 | |
| 						if ct == nil {
 | |
| 							return
 | |
| 						}
 | |
| 
 | |
| 						if ct.typeof != typeOr {
 | |
| 							continue OUTER
 | |
| 						}
 | |
| 
 | |
| 						if ct.isBlockEnd {
 | |
| 							ct = ct.next
 | |
| 							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
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| }
 |