You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 

653 lines
14 KiB

package mapping
import (
"encoding/json"
"errors"
"fmt"
"git.diulo.com/mogfee/kit/core/lang"
"git.diulo.com/mogfee/kit/core/stringx"
"math"
"reflect"
"strconv"
"strings"
"sync"
)
const (
defaultOption = "default"
envOption = "env"
inheritOption = "inherit"
stringOption = "string"
optionalOption = "optional"
optionsOption = "options"
rangeOption = "range"
optionSeparator = "|"
equalToken = "="
escapeChar = '\\'
leftBracket = '('
rightBracket = ')'
leftSquareBracket = '['
rightSquareBracket = ']'
segmentSeparator = ','
)
var (
errUnsupportedType = errors.New("unsupported type on setting field value")
errNumberRange = errors.New("wrong number range setting")
optionsCache = make(map[string]optionsCacheValue)
cacheLock sync.RWMutex
structRequiredCache = make(map[reflect.Type]requiredCacheValue)
structCacheLock sync.RWMutex
)
type (
optionsCacheValue struct {
key string
options *fieldOptions
err error
}
requiredCacheValue struct {
required bool
err error
}
)
// Deref dereferences a type, if pointer type, returns its element type.
func Deref(t reflect.Type) reflect.Type {
for t.Kind() == reflect.Ptr {
t = t.Elem()
}
return t
}
// Repr returns the string representation of v.
func Repr(v any) string {
return lang.Repr(v)
}
// SetValue sets target to value, pointers are processed automatically.
func SetValue(tp reflect.Type, value, target reflect.Value) {
value.Set(convertTypeOfPtr(tp, target))
}
// SetMapIndexValue sets target to value at key position, pointers are processed automatically.
func SetMapIndexValue(tp reflect.Type, value, key, target reflect.Value) {
value.SetMapIndex(key, convertTypeOfPtr(tp, target))
}
// ValidatePtr validates v if it's a valid pointer.
func ValidatePtr(v *reflect.Value) error {
// sequence is very important, IsNil must be called after checking Kind() with reflect.Ptr,
// panic otherwise
if !v.IsValid() || v.Kind() != reflect.Ptr || v.IsNil() {
return fmt.Errorf("not a valid pointer: %v", v)
}
return nil
}
func convertTypeFromString(kind reflect.Kind, str string) (any, error) {
switch kind {
case reflect.Bool:
switch strings.ToLower(str) {
case "1", "true":
return true, nil
case "0", "false":
return false, nil
default:
return false, errTypeMismatch
}
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
intValue, err := strconv.ParseInt(str, 10, 64)
if err != nil {
return 0, fmt.Errorf("the value %q cannot parsed as int", str)
}
return intValue, nil
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
uintValue, err := strconv.ParseUint(str, 10, 64)
if err != nil {
return 0, fmt.Errorf("the value %q cannot parsed as uint", str)
}
return uintValue, nil
case reflect.Float32, reflect.Float64:
floatValue, err := strconv.ParseFloat(str, 64)
if err != nil {
return 0, fmt.Errorf("the value %q cannot parsed as float", str)
}
return floatValue, nil
case reflect.String:
return str, nil
default:
return nil, errUnsupportedType
}
}
func convertTypeOfPtr(tp reflect.Type, target reflect.Value) reflect.Value {
// keep the original value is a pointer
if tp.Kind() == reflect.Ptr && target.CanAddr() {
tp = tp.Elem()
target = target.Addr()
}
for tp.Kind() == reflect.Ptr {
p := reflect.New(target.Type())
p.Elem().Set(target)
target = p
tp = tp.Elem()
}
return target
}
func doParseKeyAndOptions(field reflect.StructField, value string) (string, *fieldOptions, error) {
segments := parseSegments(value)
key := strings.TrimSpace(segments[0])
options := segments[1:]
if len(options) == 0 {
return key, nil, nil
}
var fieldOpts fieldOptions
for _, segment := range options {
option := strings.TrimSpace(segment)
if err := parseOption(&fieldOpts, field.Name, option); err != nil {
return "", nil, err
}
}
return key, &fieldOpts, nil
}
// ensureValue ensures nested members not to be nil.
// If pointer value is nil, set to a new value.
func ensureValue(v reflect.Value) reflect.Value {
for {
if v.Kind() != reflect.Ptr {
break
}
if v.IsNil() {
v.Set(reflect.New(v.Type().Elem()))
}
v = v.Elem()
}
return v
}
func implicitValueRequiredStruct(tag string, tp reflect.Type) (bool, error) {
numFields := tp.NumField()
for i := 0; i < numFields; i++ {
childField := tp.Field(i)
if usingDifferentKeys(tag, childField) {
return true, nil
}
_, opts, err := parseKeyAndOptions(tag, childField)
if err != nil {
return false, err
}
if opts == nil {
if childField.Type.Kind() != reflect.Struct {
return true, nil
}
if required, err := implicitValueRequiredStruct(tag, childField.Type); err != nil {
return false, err
} else if required {
return true, nil
}
} else if !opts.Optional && len(opts.Default) == 0 {
return true, nil
} else if len(opts.OptionalDep) > 0 && opts.OptionalDep[0] == notSymbol {
return true, nil
}
}
return false, nil
}
func isLeftInclude(b byte) (bool, error) {
switch b {
case '[':
return true, nil
case '(':
return false, nil
default:
return false, errNumberRange
}
}
func isRightInclude(b byte) (bool, error) {
switch b {
case ']':
return true, nil
case ')':
return false, nil
default:
return false, errNumberRange
}
}
func maybeNewValue(fieldType reflect.Type, value reflect.Value) {
if fieldType.Kind() == reflect.Ptr && value.IsNil() {
value.Set(reflect.New(value.Type().Elem()))
}
}
func parseGroupedSegments(val string) []string {
val = strings.TrimLeftFunc(val, func(r rune) bool {
return r == leftBracket || r == leftSquareBracket
})
val = strings.TrimRightFunc(val, func(r rune) bool {
return r == rightBracket || r == rightSquareBracket
})
return parseSegments(val)
}
// don't modify returned fieldOptions, it's cached and shared among different calls.
func parseKeyAndOptions(tagName string, field reflect.StructField) (string, *fieldOptions, error) {
value := field.Tag.Get(tagName)
if len(value) == 0 {
return field.Name, nil, nil
}
cacheLock.RLock()
cache, ok := optionsCache[value]
cacheLock.RUnlock()
if ok {
return stringx.TakeOne(cache.key, field.Name), cache.options, cache.err
}
key, options, err := doParseKeyAndOptions(field, value)
cacheLock.Lock()
optionsCache[value] = optionsCacheValue{
key: key,
options: options,
err: err,
}
cacheLock.Unlock()
return stringx.TakeOne(key, field.Name), options, err
}
// support below notations:
// [:5] (:5] [:5) (:5)
// [1:] [1:) (1:] (1:)
// [1:5] [1:5) (1:5] (1:5)
func parseNumberRange(str string) (*numberRange, error) {
if len(str) == 0 {
return nil, errNumberRange
}
leftInclude, err := isLeftInclude(str[0])
if err != nil {
return nil, err
}
str = str[1:]
if len(str) == 0 {
return nil, errNumberRange
}
rightInclude, err := isRightInclude(str[len(str)-1])
if err != nil {
return nil, err
}
str = str[:len(str)-1]
fields := strings.Split(str, ":")
if len(fields) != 2 {
return nil, errNumberRange
}
if len(fields[0]) == 0 && len(fields[1]) == 0 {
return nil, errNumberRange
}
var left float64
if len(fields[0]) > 0 {
var err error
if left, err = strconv.ParseFloat(fields[0], 64); err != nil {
return nil, err
}
} else {
left = -math.MaxFloat64
}
var right float64
if len(fields[1]) > 0 {
var err error
if right, err = strconv.ParseFloat(fields[1], 64); err != nil {
return nil, err
}
} else {
right = math.MaxFloat64
}
if left > right {
return nil, errNumberRange
}
// [2:2] valid
// [2:2) invalid
// (2:2] invalid
// (2:2) invalid
if left == right {
if !leftInclude || !rightInclude {
return nil, errNumberRange
}
}
return &numberRange{
left: left,
leftInclude: leftInclude,
right: right,
rightInclude: rightInclude,
}, nil
}
func parseOption(fieldOpts *fieldOptions, fieldName, option string) error {
switch {
case option == inheritOption:
fieldOpts.Inherit = true
case option == stringOption:
fieldOpts.FromString = true
case strings.HasPrefix(option, optionalOption):
segs := strings.Split(option, equalToken)
switch len(segs) {
case 1:
fieldOpts.Optional = true
case 2:
fieldOpts.Optional = true
fieldOpts.OptionalDep = segs[1]
default:
return fmt.Errorf("field %s has wrong optional", fieldName)
}
case option == optionalOption:
fieldOpts.Optional = true
case strings.HasPrefix(option, optionsOption):
val, err := parseProperty(fieldName, optionsOption, option)
if err != nil {
return err
}
fieldOpts.Options = parseOptions(val)
case strings.HasPrefix(option, defaultOption):
val, err := parseProperty(fieldName, defaultOption, option)
if err != nil {
return err
}
fieldOpts.Default = val
case strings.HasPrefix(option, envOption):
val, err := parseProperty(fieldName, envOption, option)
if err != nil {
return err
}
fieldOpts.EnvVar = val
case strings.HasPrefix(option, rangeOption):
val, err := parseProperty(fieldName, rangeOption, option)
if err != nil {
return err
}
nr, err := parseNumberRange(val)
if err != nil {
return err
}
fieldOpts.Range = nr
}
return nil
}
// parseOptions parses the given options in tag.
// for example: `json:"name,options=foo|bar"` or `json:"name,options=[foo,bar]"`
func parseOptions(val string) []string {
if len(val) == 0 {
return nil
}
if val[0] == leftSquareBracket {
return parseGroupedSegments(val)
}
return strings.Split(val, optionSeparator)
}
func parseProperty(field, tag, val string) (string, error) {
segs := strings.Split(val, equalToken)
if len(segs) != 2 {
return "", fmt.Errorf("field %s has wrong %s", field, tag)
}
return strings.TrimSpace(segs[1]), nil
}
func parseSegments(val string) []string {
var segments []string
var escaped, grouped bool
var buf strings.Builder
for _, ch := range val {
if escaped {
buf.WriteRune(ch)
escaped = false
continue
}
switch ch {
case segmentSeparator:
if grouped {
buf.WriteRune(ch)
} else {
// need to trim spaces, but we cannot ignore empty string,
// because the first segment stands for the key might be empty.
// if ignored, the later tag will be used as the key.
segments = append(segments, strings.TrimSpace(buf.String()))
buf.Reset()
}
case escapeChar:
if grouped {
buf.WriteRune(ch)
} else {
escaped = true
}
case leftBracket, leftSquareBracket:
buf.WriteRune(ch)
grouped = true
case rightBracket, rightSquareBracket:
buf.WriteRune(ch)
grouped = false
default:
buf.WriteRune(ch)
}
}
last := strings.TrimSpace(buf.String())
// ignore last empty string
if len(last) > 0 {
segments = append(segments, last)
}
return segments
}
func setMatchedPrimitiveValue(kind reflect.Kind, value reflect.Value, v any) error {
switch kind {
case reflect.Bool:
value.SetBool(v.(bool))
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
value.SetInt(v.(int64))
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
value.SetUint(v.(uint64))
case reflect.Float32, reflect.Float64:
value.SetFloat(v.(float64))
case reflect.String:
value.SetString(v.(string))
default:
return errUnsupportedType
}
return nil
}
func setValueFromString(kind reflect.Kind, value reflect.Value, str string) error {
if !value.CanSet() {
return errValueNotSettable
}
value = ensureValue(value)
v, err := convertTypeFromString(kind, str)
if err != nil {
return err
}
return setMatchedPrimitiveValue(kind, value, v)
}
func structValueRequired(tag string, tp reflect.Type) (bool, error) {
structCacheLock.RLock()
val, ok := structRequiredCache[tp]
structCacheLock.RUnlock()
if ok {
return val.required, val.err
}
required, err := implicitValueRequiredStruct(tag, tp)
structCacheLock.Lock()
structRequiredCache[tp] = requiredCacheValue{
required: required,
err: err,
}
structCacheLock.Unlock()
return required, err
}
func toFloat64(v any) (float64, bool) {
switch val := v.(type) {
case int:
return float64(val), true
case int8:
return float64(val), true
case int16:
return float64(val), true
case int32:
return float64(val), true
case int64:
return float64(val), true
case uint:
return float64(val), true
case uint8:
return float64(val), true
case uint16:
return float64(val), true
case uint32:
return float64(val), true
case uint64:
return float64(val), true
case float32:
return float64(val), true
case float64:
return val, true
default:
return 0, false
}
}
func usingDifferentKeys(key string, field reflect.StructField) bool {
if len(field.Tag) > 0 {
if _, ok := field.Tag.Lookup(key); !ok {
return true
}
}
return false
}
func validateAndSetValue(kind reflect.Kind, value reflect.Value, str string, opts *fieldOptionsWithContext) error {
if !value.CanSet() {
return errValueNotSettable
}
v, err := convertTypeFromString(kind, str)
if err != nil {
return err
}
if err := validateValueRange(v, opts); err != nil {
return err
}
return setMatchedPrimitiveValue(kind, value, v)
}
func validateJsonNumberRange(v json.Number, opts *fieldOptionsWithContext) error {
if opts == nil || opts.Range == nil {
return nil
}
fv, err := v.Float64()
if err != nil {
return err
}
return validateNumberRange(fv, opts.Range)
}
func validateNumberRange(fv float64, nr *numberRange) error {
if nr == nil {
return nil
}
if (nr.leftInclude && fv < nr.left) || (!nr.leftInclude && fv <= nr.left) {
return errNumberRange
}
if (nr.rightInclude && fv > nr.right) || (!nr.rightInclude && fv >= nr.right) {
return errNumberRange
}
return nil
}
func validateValueInOptions(val any, options []string) error {
if len(options) > 0 {
switch v := val.(type) {
case string:
if !stringx.Contains(options, v) {
return fmt.Errorf(`error: value "%s" is not defined in options "%v"`, v, options)
}
default:
if !stringx.Contains(options, Repr(v)) {
return fmt.Errorf(`error: value "%v" is not defined in options "%v"`, val, options)
}
}
}
return nil
}
func validateValueRange(mapValue any, opts *fieldOptionsWithContext) error {
if opts == nil || opts.Range == nil {
return nil
}
fv, ok := toFloat64(mapValue)
if !ok {
return errNumberRange
}
return validateNumberRange(fv, opts.Range)
}