validator.go raw

   1  package validator
   2  
   3  import (
   4  	"context"
   5  	"fmt"
   6  	"reflect"
   7  	"strconv"
   8  	"unsafe"
   9  )
  10  
  11  // per validate construct
  12  type validate struct {
  13  	v              *Validate
  14  	top            reflect.Value
  15  	ns             []byte
  16  	actualNs       []byte
  17  	errs           ValidationErrors
  18  	includeExclude map[string]struct{} // reset only if StructPartial or StructExcept are called, no need otherwise
  19  	ffn            FilterFunc
  20  	slflParent     reflect.Value // StructLevel & FieldLevel
  21  	slCurrent      reflect.Value // StructLevel & FieldLevel
  22  	flField        reflect.Value // StructLevel & FieldLevel
  23  	cf             *cField       // StructLevel & FieldLevel
  24  	ct             *cTag         // StructLevel & FieldLevel
  25  	misc           []byte        // misc reusable
  26  	str1           string        // misc reusable
  27  	str2           string        // misc reusable
  28  	fldIsPointer   bool          // StructLevel & FieldLevel
  29  	isPartial      bool
  30  	hasExcludes    bool
  31  }
  32  
  33  // parent and current will be the same the first run of validateStruct
  34  func (v *validate) validateStruct(ctx context.Context, parent reflect.Value, current reflect.Value, typ reflect.Type, ns []byte, structNs []byte, ct *cTag) {
  35  
  36  	cs, ok := v.v.structCache.Get(typ)
  37  	if !ok {
  38  		cs = v.v.extractStructCache(current, typ.Name())
  39  	}
  40  
  41  	if len(ns) == 0 && len(cs.name) != 0 {
  42  
  43  		ns = append(ns, cs.name...)
  44  		ns = append(ns, '.')
  45  
  46  		structNs = append(structNs, cs.name...)
  47  		structNs = append(structNs, '.')
  48  	}
  49  
  50  	// ct is nil on top level struct, and structs as fields that have no tag info
  51  	// so if nil or if not nil and the structonly tag isn't present
  52  	if ct == nil || ct.typeof != typeStructOnly {
  53  
  54  		var f *cField
  55  
  56  		for i := 0; i < len(cs.fields); i++ {
  57  
  58  			f = cs.fields[i]
  59  
  60  			if v.isPartial {
  61  
  62  				if v.ffn != nil {
  63  					// used with StructFiltered
  64  					if v.ffn(append(structNs, f.name...)) {
  65  						continue
  66  					}
  67  
  68  				} else {
  69  					// used with StructPartial & StructExcept
  70  					_, ok = v.includeExclude[string(append(structNs, f.name...))]
  71  
  72  					if (ok && v.hasExcludes) || (!ok && !v.hasExcludes) {
  73  						continue
  74  					}
  75  				}
  76  			}
  77  
  78  			v.traverseField(ctx, current, current.Field(f.idx), ns, structNs, f, f.cTags)
  79  		}
  80  	}
  81  
  82  	// check if any struct level validations, after all field validations already checked.
  83  	// first iteration will have no info about nostructlevel tag, and is checked prior to
  84  	// calling the next iteration of validateStruct called from traverseField.
  85  	if cs.fn != nil {
  86  
  87  		v.slflParent = parent
  88  		v.slCurrent = current
  89  		v.ns = ns
  90  		v.actualNs = structNs
  91  
  92  		cs.fn(ctx, v)
  93  	}
  94  }
  95  
  96  // 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
  97  func (v *validate) traverseField(ctx context.Context, parent reflect.Value, current reflect.Value, ns []byte, structNs []byte, cf *cField, ct *cTag) {
  98  	var typ reflect.Type
  99  	var kind reflect.Kind
 100  
 101  	current, kind, v.fldIsPointer = v.extractTypeInternal(current, false)
 102  
 103  	var isNestedStruct bool
 104  
 105  	switch kind {
 106  	case reflect.Ptr, reflect.Interface, reflect.Invalid:
 107  
 108  		if ct == nil {
 109  			return
 110  		}
 111  
 112  		if ct.typeof == typeOmitEmpty || ct.typeof == typeIsDefault {
 113  			return
 114  		}
 115  
 116  		if ct.typeof == typeOmitNil && (kind != reflect.Invalid && current.IsNil()) {
 117  			return
 118  		}
 119  
 120  		if ct.hasTag {
 121  			if kind == reflect.Invalid {
 122  				v.str1 = string(append(ns, cf.altName...))
 123  				if v.v.hasTagNameFunc {
 124  					v.str2 = string(append(structNs, cf.name...))
 125  				} else {
 126  					v.str2 = v.str1
 127  				}
 128  				v.errs = append(v.errs,
 129  					&fieldError{
 130  						v:              v.v,
 131  						tag:            ct.aliasTag,
 132  						actualTag:      ct.tag,
 133  						ns:             v.str1,
 134  						structNs:       v.str2,
 135  						fieldLen:       uint8(len(cf.altName)),
 136  						structfieldLen: uint8(len(cf.name)),
 137  						param:          ct.param,
 138  						kind:           kind,
 139  					},
 140  				)
 141  				return
 142  			}
 143  
 144  			v.str1 = string(append(ns, cf.altName...))
 145  			if v.v.hasTagNameFunc {
 146  				v.str2 = string(append(structNs, cf.name...))
 147  			} else {
 148  				v.str2 = v.str1
 149  			}
 150  			if !ct.runValidationWhenNil {
 151  				v.errs = append(v.errs,
 152  					&fieldError{
 153  						v:              v.v,
 154  						tag:            ct.aliasTag,
 155  						actualTag:      ct.tag,
 156  						ns:             v.str1,
 157  						structNs:       v.str2,
 158  						fieldLen:       uint8(len(cf.altName)),
 159  						structfieldLen: uint8(len(cf.name)),
 160  						value:          getValue(current),
 161  						param:          ct.param,
 162  						kind:           kind,
 163  						typ:            current.Type(),
 164  					},
 165  				)
 166  				return
 167  			}
 168  		}
 169  
 170  		if kind == reflect.Invalid {
 171  			return
 172  		}
 173  
 174  	case reflect.Struct:
 175  		isNestedStruct = !current.Type().ConvertibleTo(timeType)
 176  		// For backward compatibility before struct level validation tags were supported
 177  		// as there were a number of projects relying on `required` not failing on non-pointer
 178  		// structs. Since it's basically nonsensical to use `required` with a non-pointer struct
 179  		// are explicitly skipping the required validation for it. This WILL be removed in the
 180  		// next major version.
 181  		if isNestedStruct && !v.v.requiredStructEnabled && ct != nil && ct.tag == requiredTag {
 182  			ct = ct.next
 183  		}
 184  	}
 185  
 186  	typ = current.Type()
 187  
 188  OUTER:
 189  	for {
 190  		if ct == nil || !ct.hasTag || (isNestedStruct && len(cf.name) == 0) {
 191  			// isNestedStruct check here
 192  			if isNestedStruct {
 193  				// if len == 0 then validating using 'Var' or 'VarWithValue'
 194  				// Var - doesn't make much sense to do it that way, should call 'Struct', but no harm...
 195  				// VarWithField - this allows for validating against each field within the struct against a specific value
 196  				//                pretty handy in certain situations
 197  				if len(cf.name) > 0 {
 198  					ns = append(append(ns, cf.altName...), '.')
 199  					structNs = append(append(structNs, cf.name...), '.')
 200  				}
 201  
 202  				v.validateStruct(ctx, parent, current, typ, ns, structNs, ct)
 203  			}
 204  			return
 205  		}
 206  
 207  		switch ct.typeof {
 208  		case typeNoStructLevel:
 209  			return
 210  
 211  		case typeStructOnly:
 212  			if isNestedStruct {
 213  				// if len == 0 then validating using 'Var' or 'VarWithValue'
 214  				// Var - doesn't make much sense to do it that way, should call 'Struct', but no harm...
 215  				// VarWithField - this allows for validating against each field within the struct against a specific value
 216  				//                pretty handy in certain situations
 217  				if len(cf.name) > 0 {
 218  					ns = append(append(ns, cf.altName...), '.')
 219  					structNs = append(append(structNs, cf.name...), '.')
 220  				}
 221  
 222  				v.validateStruct(ctx, parent, current, typ, ns, structNs, ct)
 223  			}
 224  			return
 225  
 226  		case typeOmitEmpty:
 227  
 228  			// set Field Level fields
 229  			v.slflParent = parent
 230  			v.flField = current
 231  			v.cf = cf
 232  			v.ct = ct
 233  
 234  			if !hasValue(v) {
 235  				return
 236  			}
 237  
 238  			ct = ct.next
 239  			continue
 240  
 241  		case typeOmitNil:
 242  			v.slflParent = parent
 243  			v.flField = current
 244  			v.cf = cf
 245  			v.ct = ct
 246  
 247  			switch field := v.Field(); field.Kind() {
 248  			case reflect.Slice, reflect.Map, reflect.Ptr, reflect.Interface, reflect.Chan, reflect.Func:
 249  				if field.IsNil() {
 250  					return
 251  				}
 252  			default:
 253  				if v.fldIsPointer && field.Interface() == nil {
 254  					return
 255  				}
 256  			}
 257  
 258  			ct = ct.next
 259  			continue
 260  
 261  		case typeEndKeys:
 262  			return
 263  
 264  		case typeDive:
 265  
 266  			ct = ct.next
 267  
 268  			// traverse slice or map here
 269  			// or panic ;)
 270  			switch kind {
 271  			case reflect.Slice, reflect.Array:
 272  
 273  				var i64 int64
 274  				reusableCF := &cField{}
 275  
 276  				for i := 0; i < current.Len(); i++ {
 277  
 278  					i64 = int64(i)
 279  
 280  					v.misc = append(v.misc[0:0], cf.name...)
 281  					v.misc = append(v.misc, '[')
 282  					v.misc = strconv.AppendInt(v.misc, i64, 10)
 283  					v.misc = append(v.misc, ']')
 284  
 285  					reusableCF.name = string(v.misc)
 286  
 287  					if cf.namesEqual {
 288  						reusableCF.altName = reusableCF.name
 289  					} else {
 290  
 291  						v.misc = append(v.misc[0:0], cf.altName...)
 292  						v.misc = append(v.misc, '[')
 293  						v.misc = strconv.AppendInt(v.misc, i64, 10)
 294  						v.misc = append(v.misc, ']')
 295  
 296  						reusableCF.altName = string(v.misc)
 297  					}
 298  					v.traverseField(ctx, parent, current.Index(i), ns, structNs, reusableCF, ct)
 299  				}
 300  
 301  			case reflect.Map:
 302  
 303  				var pv string
 304  				reusableCF := &cField{}
 305  
 306  				for _, key := range current.MapKeys() {
 307  
 308  					pv = fmt.Sprintf("%v", key.Interface())
 309  
 310  					v.misc = append(v.misc[0:0], cf.name...)
 311  					v.misc = append(v.misc, '[')
 312  					v.misc = append(v.misc, pv...)
 313  					v.misc = append(v.misc, ']')
 314  
 315  					reusableCF.name = string(v.misc)
 316  
 317  					if cf.namesEqual {
 318  						reusableCF.altName = reusableCF.name
 319  					} else {
 320  						v.misc = append(v.misc[0:0], cf.altName...)
 321  						v.misc = append(v.misc, '[')
 322  						v.misc = append(v.misc, pv...)
 323  						v.misc = append(v.misc, ']')
 324  
 325  						reusableCF.altName = string(v.misc)
 326  					}
 327  
 328  					if ct != nil && ct.typeof == typeKeys && ct.keys != nil {
 329  						v.traverseField(ctx, parent, key, ns, structNs, reusableCF, ct.keys)
 330  						// can be nil when just keys being validated
 331  						if ct.next != nil {
 332  							v.traverseField(ctx, parent, current.MapIndex(key), ns, structNs, reusableCF, ct.next)
 333  						}
 334  					} else {
 335  						v.traverseField(ctx, parent, current.MapIndex(key), ns, structNs, reusableCF, ct)
 336  					}
 337  				}
 338  
 339  			default:
 340  				// throw error, if not a slice or map then should not have gotten here
 341  				// bad dive tag
 342  				panic("dive error! can't dive on a non slice or map")
 343  			}
 344  
 345  			return
 346  
 347  		case typeOr:
 348  
 349  			v.misc = v.misc[0:0]
 350  
 351  			for {
 352  
 353  				// set Field Level fields
 354  				v.slflParent = parent
 355  				v.flField = current
 356  				v.cf = cf
 357  				v.ct = ct
 358  
 359  				if ct.fn(ctx, v) {
 360  					if ct.isBlockEnd {
 361  						ct = ct.next
 362  						continue OUTER
 363  					}
 364  
 365  					// drain rest of the 'or' values, then continue or leave
 366  					for {
 367  
 368  						ct = ct.next
 369  
 370  						if ct == nil {
 371  							continue OUTER
 372  						}
 373  
 374  						if ct.typeof != typeOr {
 375  							continue OUTER
 376  						}
 377  
 378  						if ct.isBlockEnd {
 379  							ct = ct.next
 380  							continue OUTER
 381  						}
 382  					}
 383  				}
 384  
 385  				v.misc = append(v.misc, '|')
 386  				v.misc = append(v.misc, ct.tag...)
 387  
 388  				if ct.hasParam {
 389  					v.misc = append(v.misc, '=')
 390  					v.misc = append(v.misc, ct.param...)
 391  				}
 392  
 393  				if ct.isBlockEnd || ct.next == nil {
 394  					// if we get here, no valid 'or' value and no more tags
 395  					v.str1 = string(append(ns, cf.altName...))
 396  
 397  					if v.v.hasTagNameFunc {
 398  						v.str2 = string(append(structNs, cf.name...))
 399  					} else {
 400  						v.str2 = v.str1
 401  					}
 402  
 403  					if ct.hasAlias {
 404  
 405  						v.errs = append(v.errs,
 406  							&fieldError{
 407  								v:              v.v,
 408  								tag:            ct.aliasTag,
 409  								actualTag:      ct.actualAliasTag,
 410  								ns:             v.str1,
 411  								structNs:       v.str2,
 412  								fieldLen:       uint8(len(cf.altName)),
 413  								structfieldLen: uint8(len(cf.name)),
 414  								value:          getValue(current),
 415  								param:          ct.param,
 416  								kind:           kind,
 417  								typ:            typ,
 418  							},
 419  						)
 420  
 421  					} else {
 422  
 423  						tVal := string(v.misc)[1:]
 424  
 425  						v.errs = append(v.errs,
 426  							&fieldError{
 427  								v:              v.v,
 428  								tag:            tVal,
 429  								actualTag:      tVal,
 430  								ns:             v.str1,
 431  								structNs:       v.str2,
 432  								fieldLen:       uint8(len(cf.altName)),
 433  								structfieldLen: uint8(len(cf.name)),
 434  								value:          getValue(current),
 435  								param:          ct.param,
 436  								kind:           kind,
 437  								typ:            typ,
 438  							},
 439  						)
 440  					}
 441  
 442  					return
 443  				}
 444  
 445  				ct = ct.next
 446  			}
 447  
 448  		default:
 449  
 450  			// set Field Level fields
 451  			v.slflParent = parent
 452  			v.flField = current
 453  			v.cf = cf
 454  			v.ct = ct
 455  
 456  			if !ct.fn(ctx, v) {
 457  				v.str1 = string(append(ns, cf.altName...))
 458  
 459  				if v.v.hasTagNameFunc {
 460  					v.str2 = string(append(structNs, cf.name...))
 461  				} else {
 462  					v.str2 = v.str1
 463  				}
 464  
 465  				v.errs = append(v.errs,
 466  					&fieldError{
 467  						v:              v.v,
 468  						tag:            ct.aliasTag,
 469  						actualTag:      ct.tag,
 470  						ns:             v.str1,
 471  						structNs:       v.str2,
 472  						fieldLen:       uint8(len(cf.altName)),
 473  						structfieldLen: uint8(len(cf.name)),
 474  						value:          getValue(current),
 475  						param:          ct.param,
 476  						kind:           kind,
 477  						typ:            typ,
 478  					},
 479  				)
 480  
 481  				return
 482  			}
 483  			ct = ct.next
 484  		}
 485  	}
 486  
 487  }
 488  
 489  func getValue(val reflect.Value) interface{} {
 490  	if val.CanInterface() {
 491  		return val.Interface()
 492  	}
 493  
 494  	if val.CanAddr() {
 495  		return reflect.NewAt(val.Type(), unsafe.Pointer(val.UnsafeAddr())).Elem().Interface()
 496  	}
 497  
 498  	switch val.Kind() {
 499  	case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
 500  		return val.Int()
 501  	case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
 502  		return val.Uint()
 503  	case reflect.Complex64, reflect.Complex128:
 504  		return val.Complex()
 505  	case reflect.Float32, reflect.Float64:
 506  		return val.Float()
 507  	default:
 508  		return val.String()
 509  	}
 510  }
 511