179 lines
3.4 KiB
Go
179 lines
3.4 KiB
Go
package gapr
|
|
|
|
import (
|
|
"errors"
|
|
"math/rand"
|
|
"reflect"
|
|
"strconv"
|
|
"strings"
|
|
)
|
|
|
|
var (
|
|
ErrNotSupportedType = errors.New("input is not a supported type")
|
|
ErrNotStringKey = errors.New("key type is not string - not supported right now")
|
|
)
|
|
|
|
func New(opts ...Option) *Gapr {
|
|
o := defaultOptions()
|
|
|
|
for _, opt := range opts {
|
|
opt.apply(&o)
|
|
}
|
|
|
|
return &Gapr{
|
|
tagName: o.TagName,
|
|
rand: rand.New(rand.NewSource(o.RandomSeed)),
|
|
}
|
|
}
|
|
|
|
type Gapr struct {
|
|
tagName string
|
|
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()
|
|
v = v.Elem()
|
|
}
|
|
|
|
if !canMap(t, v) {
|
|
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:
|
|
return g.mapSliceOrArray(v)
|
|
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(), v) {
|
|
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(), iter.Value()) {
|
|
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
|
|
}
|
|
|
|
func (g *Gapr) mapSliceOrArray(v reflect.Value) (any, error) {
|
|
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(), idxVal) {
|
|
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(g.tagName)
|
|
if !present {
|
|
return false, f.Name, nil
|
|
}
|
|
|
|
tagSplit := strings.Split(tagVal, ",")
|
|
if len(tagSplit) < 1 {
|
|
return false, f.Name, nil
|
|
}
|
|
|
|
if tagSplit[0] != "" {
|
|
dropProbability, err := strconv.ParseFloat(tagSplit[0], 64)
|
|
if err != nil {
|
|
return false, "", err
|
|
}
|
|
|
|
drop = g.rand.Float64() < dropProbability
|
|
}
|
|
|
|
if len(tagSplit) > 1 && tagSplit[1] != "" {
|
|
fieldName = strings.TrimSpace(tagSplit[1])
|
|
}
|
|
|
|
return drop, fieldName, nil
|
|
}
|
|
|
|
func canMap(t reflect.Type, v reflect.Value) bool {
|
|
if t.Kind() == reflect.Pointer {
|
|
t = t.Elem()
|
|
}
|
|
|
|
if t.Kind() == reflect.Interface {
|
|
t = reflect.TypeOf(v.Interface())
|
|
}
|
|
|
|
switch t.Kind() {
|
|
case reflect.Struct, reflect.Map, reflect.Slice, reflect.Array:
|
|
return true
|
|
default:
|
|
return false
|
|
}
|
|
}
|