feat: initial version

This commit is contained in:
Peter 2023-01-30 17:40:50 +01:00
parent 5f026ea9b2
commit 2d8df259cb
No known key found for this signature in database
5 changed files with 346 additions and 20 deletions

27
.editorconfig Normal file
View file

@ -0,0 +1,27 @@
root = true
[*]
charset = utf-8
end_of_line = lf
indent_size = 4
tab_width = 4
indent_style = space
insert_final_newline = false
max_line_length = 120
trim_trailing_whitespace = true
[*.go]
indent_style = tab
ij_smart_tabs = true
ij_go_GROUP_CURRENT_PROJECT_IMPORTS = true
ij_go_group_stdlib_imports = true
ij_go_import_sorting = goimports
ij_go_local_group_mode = project
ij_go_move_all_imports_in_one_declaration = true
ij_go_move_all_stdlib_imports_in_one_group = true
ij_go_remove_redundant_import_aliases = true
[{*.yaml,*.yml}]
indent_size = 2
tab_width = 2
insert_final_newline = true

41
.gitignore vendored
View file

@ -1,23 +1,24 @@
# ---> Go
# If you prefer the allow list template instead of the deny list, see community template:
# https://github.com/github/gitignore/blob/main/community/Golang/Go.AllowList.gitignore
#
# Binaries for programs and plugins
*.exe
*.exe~
*.dll
*.so
*.dylib
#########
# files #
#########
# Test binary, built with `go test -c`
*.test
**/cov.out
**/cov-raw.out
**/*.mock.go
*.key
*.pem
./main
.go/
__debug_bin
# Output of the go coverage tool, specifically when used with LiteIDE
*.out
# Dependency directories (remove the comment below to include it)
# vendor/
# Go workspace file
go.work
###############
# directories #
###############
.idea/
.vscode/
dist/
out/
.task/
public/
.fleet/

164
gapr.go Normal file
View file

@ -0,0 +1,164 @@
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
}
}

131
gapr_test.go Normal file
View file

@ -0,0 +1,131 @@
package gapr_test
import (
"testing"
"code.icb4dc0.de/prskr/gapr"
)
func TestGapr_Map(t *testing.T) {
type args struct {
input any
}
tests := []struct {
name string
args args
want map[string]any
wantErr bool
}{
{
name: "Test simple struct",
args: args{
input: struct {
GivenName string
Surname string
}{
GivenName: "Ted",
Surname: "Tester",
},
},
want: nil,
wantErr: false,
},
{
name: "Test nested struct",
args: args{
input: struct {
GivenName string
Surname string
Address struct {
Street string
HouseNr string
City string
}
}{
GivenName: "Ted",
Surname: "Tester",
Address: struct {
Street string
HouseNr string
City string
}{
Street: "Some Street",
HouseNr: "3a",
City: "Some City",
},
},
},
want: nil,
wantErr: false,
},
{
name: "Test simple map",
args: args{
input: struct {
GivenName string
Surname string
}{
GivenName: "Ted",
Surname: "Tester",
},
},
want: nil,
wantErr: false,
},
{
name: "Test nested slice",
args: args{
input: struct {
GivenName string
Children []struct {
Name string
Age uint `incomplete:"0.2,givenName"`
}
}{
GivenName: "Ted",
Children: []struct {
Name string
Age uint `incomplete:"0.2,givenName"`
}{
{
Name: "Tim",
Age: 11,
},
},
},
},
want: nil,
wantErr: false,
},
{
name: "Test map with nested struct",
args: args{
input: map[string]any{
"Hello": struct {
GivenName string `incomplete:"0.2,givenName"`
Surname string
}{
GivenName: "Ted",
Surname: "Tester",
},
},
},
want: nil,
wantErr: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
g := gapr.New()
got, err := g.Map(tt.args.input)
if (got == nil) == (err == nil) {
t.Errorf("cannot get nil value and nil error")
return
}
if (err != nil) != tt.wantErr {
t.Errorf("Map() error = %v, wantErr %v", err, tt.wantErr)
return
}
})
}
}

3
go.mod Normal file
View file

@ -0,0 +1,3 @@
module code.icb4dc0.de/prskr/gapr
go 1.18