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 } //nolint:exhaustive // handled with default case 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)) ) 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()) } //nolint:exhaustive // handled with default case switch t.Kind() { case reflect.Struct, reflect.Map, reflect.Slice, reflect.Array: return true default: return false } }