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: return g.mapSliceOrArray(t, 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()) { 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 } func (g *Gapr) mapSliceOrArray(t reflect.Type, 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()) { 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 } drop = g.rand.Float64() < 1.0-dropProbability 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 } }