gapr/gapr.go
Peter Kurfer ec31dd14d4
All checks were successful
concourse-ci/lint/golangci-lint Lint Go files
refactor: apply golangci-lint findings
2023-01-31 21:14:34 +01:00

181 lines
3.5 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
}
//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
}
}