2023-01-30 16:40:50 +00:00
|
|
|
package gapr
|
|
|
|
|
|
|
|
import (
|
|
|
|
"errors"
|
|
|
|
"math/rand"
|
|
|
|
"reflect"
|
|
|
|
"strconv"
|
|
|
|
"strings"
|
|
|
|
"time"
|
|
|
|
)
|
|
|
|
|
|
|
|
var (
|
|
|
|
ErrNotSupportedType = errors.New("input is not a supported type")
|
|
|
|
ErrNotStringKey = errors.New("key type is not string - not supported right now")
|
|
|
|
)
|
|
|
|
|
|
|
|
func New() *Gapr {
|
|
|
|
return &Gapr{
|
|
|
|
rand: rand.New(rand.NewSource(time.Now().Unix())),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
type Gapr struct {
|
|
|
|
rand *rand.Rand
|
|
|
|
}
|
|
|
|
|
|
|
|
func (g *Gapr) Map(input any) (any, error) {
|
|
|
|
t := reflect.TypeOf(input)
|
|
|
|
v := reflect.ValueOf(input)
|
|
|
|
|
|
|
|
if t.Kind() == reflect.Pointer {
|
|
|
|
t = t.Elem()
|
|
|
|
}
|
|
|
|
|
|
|
|
if !canMap(t) {
|
|
|
|
return nil, ErrNotSupportedType
|
|
|
|
}
|
|
|
|
|
|
|
|
switch t.Kind() {
|
|
|
|
case reflect.Map:
|
|
|
|
return g.mapMap(t, v)
|
|
|
|
case reflect.Struct, reflect.Interface:
|
|
|
|
return g.mapStruct(t, v)
|
|
|
|
case reflect.Slice, reflect.Array:
|
2023-01-30 16:54:14 +00:00
|
|
|
return g.mapSliceOrArray(v)
|
2023-01-30 16:40:50 +00:00
|
|
|
default:
|
|
|
|
return nil, ErrNotSupportedType
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (g *Gapr) mapStruct(t reflect.Type, v reflect.Value) (any, error) {
|
|
|
|
numberOfFields := t.NumField()
|
|
|
|
mapped := make(map[string]any, numberOfFields)
|
|
|
|
|
|
|
|
for i := 0; i < numberOfFields; i++ {
|
|
|
|
drop, fieldName, err := g.fieldMeta(t.Field(i))
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
if drop {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
if mappedVal, err := g.mapField(v.Field(i)); err != nil {
|
|
|
|
return nil, err
|
|
|
|
} else {
|
|
|
|
mapped[fieldName] = mappedVal
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return mapped, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (g *Gapr) mapField(v reflect.Value) (any, error) {
|
|
|
|
if canMap(v.Type()) {
|
|
|
|
return g.Map(v.Interface())
|
|
|
|
} else {
|
|
|
|
return v.Interface(), nil
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (g *Gapr) mapMap(t reflect.Type, v reflect.Value) (any, error) {
|
|
|
|
mapped := make(map[string]any, v.Len())
|
|
|
|
|
|
|
|
if t.Key().Kind() != reflect.String {
|
|
|
|
return nil, ErrNotStringKey
|
|
|
|
}
|
|
|
|
|
|
|
|
iter := v.MapRange()
|
|
|
|
for iter.Next() {
|
|
|
|
if canMap(iter.Value().Type()) {
|
|
|
|
if mappedValue, err := g.Map(iter.Value().Interface()); err != nil {
|
|
|
|
return nil, err
|
|
|
|
} else {
|
|
|
|
mapped[iter.Key().String()] = mappedValue
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
mapped[iter.Key().String()] = iter.Value().Interface()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return mapped, nil
|
|
|
|
}
|
|
|
|
|
2023-01-30 16:54:14 +00:00
|
|
|
func (g *Gapr) mapSliceOrArray(v reflect.Value) (any, error) {
|
2023-01-30 16:40:50 +00:00
|
|
|
var (
|
|
|
|
length = v.Len()
|
|
|
|
target = reflect.ValueOf(make([]any, length, length))
|
|
|
|
)
|
|
|
|
|
|
|
|
for i := 0; i < length; i++ {
|
|
|
|
idxVal := v.Index(i)
|
|
|
|
if canMap(idxVal.Type()) {
|
|
|
|
if mappedVal, err := g.Map(idxVal.Interface()); err != nil {
|
|
|
|
return nil, err
|
|
|
|
} else {
|
|
|
|
target.Index(i).Set(reflect.ValueOf(mappedVal))
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
target.Index(i).Set(idxVal)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return target.Interface(), nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (g *Gapr) fieldMeta(f reflect.StructField) (drop bool, fieldName string, err error) {
|
|
|
|
fieldName = f.Name
|
|
|
|
tagVal, present := f.Tag.Lookup("incomplete")
|
|
|
|
if !present {
|
|
|
|
return false, f.Name, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
tagSplit := strings.Split(tagVal, ",")
|
|
|
|
if len(tagSplit) < 2 {
|
|
|
|
return false, f.Name, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
dropProbability, err := strconv.ParseFloat(tagSplit[0], 64)
|
|
|
|
if err != nil {
|
|
|
|
return false, "", err
|
|
|
|
}
|
|
|
|
|
2023-01-30 16:54:14 +00:00
|
|
|
drop = g.rand.Float64() < dropProbability
|
2023-01-30 16:40:50 +00:00
|
|
|
if tagSplit[1] != "" {
|
|
|
|
fieldName = strings.TrimSpace(tagSplit[1])
|
|
|
|
}
|
|
|
|
|
|
|
|
return drop, fieldName, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func canMap(t reflect.Type) bool {
|
|
|
|
if t.Kind() == reflect.Pointer {
|
|
|
|
t = t.Elem()
|
|
|
|
}
|
|
|
|
|
|
|
|
switch t.Kind() {
|
|
|
|
case reflect.Struct, reflect.Map, reflect.Interface, reflect.Slice, reflect.Array:
|
|
|
|
return true
|
|
|
|
default:
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
}
|