2021-08-12 20:03:24 +01:00
|
|
|
package validator
|
|
|
|
|
|
|
|
import (
|
|
|
|
"reflect"
|
|
|
|
"strconv"
|
|
|
|
"strings"
|
|
|
|
"time"
|
|
|
|
)
|
|
|
|
|
|
|
|
// extractTypeInternal gets the actual underlying type of field value.
|
|
|
|
// It will dive into pointers, customTypes and return you the
|
|
|
|
// underlying value and it's kind.
|
|
|
|
func (v *validate) extractTypeInternal(current reflect.Value, nullable bool) (reflect.Value, reflect.Kind, bool) {
|
|
|
|
|
|
|
|
BEGIN:
|
|
|
|
switch current.Kind() {
|
|
|
|
case reflect.Ptr:
|
|
|
|
|
|
|
|
nullable = true
|
|
|
|
|
|
|
|
if current.IsNil() {
|
|
|
|
return current, reflect.Ptr, nullable
|
|
|
|
}
|
|
|
|
|
|
|
|
current = current.Elem()
|
|
|
|
goto BEGIN
|
|
|
|
|
|
|
|
case reflect.Interface:
|
|
|
|
|
|
|
|
nullable = true
|
|
|
|
|
|
|
|
if current.IsNil() {
|
|
|
|
return current, reflect.Interface, nullable
|
|
|
|
}
|
|
|
|
|
|
|
|
current = current.Elem()
|
|
|
|
goto BEGIN
|
|
|
|
|
|
|
|
case reflect.Invalid:
|
|
|
|
return current, reflect.Invalid, nullable
|
|
|
|
|
|
|
|
default:
|
|
|
|
|
|
|
|
if v.v.hasCustomFuncs {
|
|
|
|
|
|
|
|
if fn, ok := v.v.customFuncs[current.Type()]; ok {
|
|
|
|
current = reflect.ValueOf(fn(current))
|
|
|
|
goto BEGIN
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return current, current.Kind(), nullable
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// getStructFieldOKInternal traverses a struct to retrieve a specific field denoted by the provided namespace and
|
|
|
|
// returns the field, field kind and whether is was successful in retrieving the field at all.
|
|
|
|
//
|
|
|
|
// NOTE: when not successful ok will be false, this can happen when a nested struct is nil and so the field
|
|
|
|
// could not be retrieved because it didn't exist.
|
|
|
|
func (v *validate) getStructFieldOKInternal(val reflect.Value, namespace string) (current reflect.Value, kind reflect.Kind, nullable bool, found bool) {
|
|
|
|
|
|
|
|
BEGIN:
|
|
|
|
current, kind, nullable = v.ExtractType(val)
|
|
|
|
if kind == reflect.Invalid {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
if namespace == "" {
|
|
|
|
found = true
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
switch kind {
|
|
|
|
|
|
|
|
case reflect.Ptr, reflect.Interface:
|
|
|
|
return
|
|
|
|
|
|
|
|
case reflect.Struct:
|
|
|
|
|
|
|
|
typ := current.Type()
|
|
|
|
fld := namespace
|
|
|
|
var ns string
|
|
|
|
|
2022-05-02 14:05:18 +01:00
|
|
|
if !typ.ConvertibleTo(timeType) {
|
2021-08-12 20:03:24 +01:00
|
|
|
|
|
|
|
idx := strings.Index(namespace, namespaceSeparator)
|
|
|
|
|
|
|
|
if idx != -1 {
|
|
|
|
fld = namespace[:idx]
|
|
|
|
ns = namespace[idx+1:]
|
|
|
|
} else {
|
|
|
|
ns = ""
|
|
|
|
}
|
|
|
|
|
|
|
|
bracketIdx := strings.Index(fld, leftBracket)
|
|
|
|
if bracketIdx != -1 {
|
|
|
|
fld = fld[:bracketIdx]
|
|
|
|
|
|
|
|
ns = namespace[bracketIdx:]
|
|
|
|
}
|
|
|
|
|
|
|
|
val = current.FieldByName(fld)
|
|
|
|
namespace = ns
|
|
|
|
goto BEGIN
|
|
|
|
}
|
|
|
|
|
|
|
|
case reflect.Array, reflect.Slice:
|
|
|
|
idx := strings.Index(namespace, leftBracket)
|
|
|
|
idx2 := strings.Index(namespace, rightBracket)
|
|
|
|
|
|
|
|
arrIdx, _ := strconv.Atoi(namespace[idx+1 : idx2])
|
|
|
|
|
|
|
|
if arrIdx >= current.Len() {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
startIdx := idx2 + 1
|
|
|
|
|
|
|
|
if startIdx < len(namespace) {
|
|
|
|
if namespace[startIdx:startIdx+1] == namespaceSeparator {
|
|
|
|
startIdx++
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
val = current.Index(arrIdx)
|
|
|
|
namespace = namespace[startIdx:]
|
|
|
|
goto BEGIN
|
|
|
|
|
|
|
|
case reflect.Map:
|
|
|
|
idx := strings.Index(namespace, leftBracket) + 1
|
|
|
|
idx2 := strings.Index(namespace, rightBracket)
|
|
|
|
|
|
|
|
endIdx := idx2
|
|
|
|
|
|
|
|
if endIdx+1 < len(namespace) {
|
|
|
|
if namespace[endIdx+1:endIdx+2] == namespaceSeparator {
|
|
|
|
endIdx++
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
key := namespace[idx:idx2]
|
|
|
|
|
|
|
|
switch current.Type().Key().Kind() {
|
|
|
|
case reflect.Int:
|
|
|
|
i, _ := strconv.Atoi(key)
|
|
|
|
val = current.MapIndex(reflect.ValueOf(i))
|
|
|
|
namespace = namespace[endIdx+1:]
|
|
|
|
|
|
|
|
case reflect.Int8:
|
|
|
|
i, _ := strconv.ParseInt(key, 10, 8)
|
|
|
|
val = current.MapIndex(reflect.ValueOf(int8(i)))
|
|
|
|
namespace = namespace[endIdx+1:]
|
|
|
|
|
|
|
|
case reflect.Int16:
|
|
|
|
i, _ := strconv.ParseInt(key, 10, 16)
|
|
|
|
val = current.MapIndex(reflect.ValueOf(int16(i)))
|
|
|
|
namespace = namespace[endIdx+1:]
|
|
|
|
|
|
|
|
case reflect.Int32:
|
|
|
|
i, _ := strconv.ParseInt(key, 10, 32)
|
|
|
|
val = current.MapIndex(reflect.ValueOf(int32(i)))
|
|
|
|
namespace = namespace[endIdx+1:]
|
|
|
|
|
|
|
|
case reflect.Int64:
|
|
|
|
i, _ := strconv.ParseInt(key, 10, 64)
|
|
|
|
val = current.MapIndex(reflect.ValueOf(i))
|
|
|
|
namespace = namespace[endIdx+1:]
|
|
|
|
|
|
|
|
case reflect.Uint:
|
|
|
|
i, _ := strconv.ParseUint(key, 10, 0)
|
|
|
|
val = current.MapIndex(reflect.ValueOf(uint(i)))
|
|
|
|
namespace = namespace[endIdx+1:]
|
|
|
|
|
|
|
|
case reflect.Uint8:
|
|
|
|
i, _ := strconv.ParseUint(key, 10, 8)
|
|
|
|
val = current.MapIndex(reflect.ValueOf(uint8(i)))
|
|
|
|
namespace = namespace[endIdx+1:]
|
|
|
|
|
|
|
|
case reflect.Uint16:
|
|
|
|
i, _ := strconv.ParseUint(key, 10, 16)
|
|
|
|
val = current.MapIndex(reflect.ValueOf(uint16(i)))
|
|
|
|
namespace = namespace[endIdx+1:]
|
|
|
|
|
|
|
|
case reflect.Uint32:
|
|
|
|
i, _ := strconv.ParseUint(key, 10, 32)
|
|
|
|
val = current.MapIndex(reflect.ValueOf(uint32(i)))
|
|
|
|
namespace = namespace[endIdx+1:]
|
|
|
|
|
|
|
|
case reflect.Uint64:
|
|
|
|
i, _ := strconv.ParseUint(key, 10, 64)
|
|
|
|
val = current.MapIndex(reflect.ValueOf(i))
|
|
|
|
namespace = namespace[endIdx+1:]
|
|
|
|
|
|
|
|
case reflect.Float32:
|
|
|
|
f, _ := strconv.ParseFloat(key, 32)
|
|
|
|
val = current.MapIndex(reflect.ValueOf(float32(f)))
|
|
|
|
namespace = namespace[endIdx+1:]
|
|
|
|
|
|
|
|
case reflect.Float64:
|
|
|
|
f, _ := strconv.ParseFloat(key, 64)
|
|
|
|
val = current.MapIndex(reflect.ValueOf(f))
|
|
|
|
namespace = namespace[endIdx+1:]
|
|
|
|
|
|
|
|
case reflect.Bool:
|
|
|
|
b, _ := strconv.ParseBool(key)
|
|
|
|
val = current.MapIndex(reflect.ValueOf(b))
|
|
|
|
namespace = namespace[endIdx+1:]
|
|
|
|
|
|
|
|
// reflect.Type = string
|
|
|
|
default:
|
|
|
|
val = current.MapIndex(reflect.ValueOf(key))
|
|
|
|
namespace = namespace[endIdx+1:]
|
|
|
|
}
|
|
|
|
|
|
|
|
goto BEGIN
|
|
|
|
}
|
|
|
|
|
|
|
|
// if got here there was more namespace, cannot go any deeper
|
|
|
|
panic("Invalid field namespace")
|
|
|
|
}
|
|
|
|
|
|
|
|
// asInt returns the parameter as a int64
|
|
|
|
// or panics if it can't convert
|
|
|
|
func asInt(param string) int64 {
|
|
|
|
i, err := strconv.ParseInt(param, 0, 64)
|
|
|
|
panicIf(err)
|
|
|
|
|
|
|
|
return i
|
|
|
|
}
|
|
|
|
|
|
|
|
// asIntFromTimeDuration parses param as time.Duration and returns it as int64
|
|
|
|
// or panics on error.
|
|
|
|
func asIntFromTimeDuration(param string) int64 {
|
|
|
|
d, err := time.ParseDuration(param)
|
|
|
|
if err != nil {
|
2023-05-01 10:02:44 +01:00
|
|
|
// attempt parsing as an integer assuming nanosecond precision
|
2021-08-12 20:03:24 +01:00
|
|
|
return asInt(param)
|
|
|
|
}
|
|
|
|
return int64(d)
|
|
|
|
}
|
|
|
|
|
|
|
|
// asIntFromType calls the proper function to parse param as int64,
|
|
|
|
// given a field's Type t.
|
|
|
|
func asIntFromType(t reflect.Type, param string) int64 {
|
|
|
|
switch t {
|
|
|
|
case timeDurationType:
|
|
|
|
return asIntFromTimeDuration(param)
|
|
|
|
default:
|
|
|
|
return asInt(param)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// asUint returns the parameter as a uint64
|
|
|
|
// or panics if it can't convert
|
|
|
|
func asUint(param string) uint64 {
|
|
|
|
|
|
|
|
i, err := strconv.ParseUint(param, 0, 64)
|
|
|
|
panicIf(err)
|
|
|
|
|
|
|
|
return i
|
|
|
|
}
|
|
|
|
|
|
|
|
// asFloat returns the parameter as a float64
|
|
|
|
// or panics if it can't convert
|
|
|
|
func asFloat(param string) float64 {
|
|
|
|
|
|
|
|
i, err := strconv.ParseFloat(param, 64)
|
|
|
|
panicIf(err)
|
|
|
|
|
|
|
|
return i
|
|
|
|
}
|
|
|
|
|
|
|
|
// asBool returns the parameter as a bool
|
|
|
|
// or panics if it can't convert
|
|
|
|
func asBool(param string) bool {
|
|
|
|
|
|
|
|
i, err := strconv.ParseBool(param)
|
|
|
|
panicIf(err)
|
|
|
|
|
|
|
|
return i
|
|
|
|
}
|
|
|
|
|
|
|
|
func panicIf(err error) {
|
|
|
|
if err != nil {
|
|
|
|
panic(err.Error())
|
|
|
|
}
|
|
|
|
}
|