gtsocial-umbx

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs | README | LICENSE

validator.go (11326B)


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