feat: initial version
This commit is contained in:
parent
5f026ea9b2
commit
2d8df259cb
5 changed files with 346 additions and 20 deletions
27
.editorconfig
Normal file
27
.editorconfig
Normal 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
41
.gitignore
vendored
|
@ -1,23 +1,24 @@
|
||||||
# ---> Go
|
#########
|
||||||
# If you prefer the allow list template instead of the deny list, see community template:
|
# files #
|
||||||
# https://github.com/github/gitignore/blob/main/community/Golang/Go.AllowList.gitignore
|
#########
|
||||||
#
|
|
||||||
# Binaries for programs and plugins
|
|
||||||
*.exe
|
|
||||||
*.exe~
|
|
||||||
*.dll
|
|
||||||
*.so
|
|
||||||
*.dylib
|
|
||||||
|
|
||||||
# Test binary, built with `go test -c`
|
**/cov.out
|
||||||
*.test
|
**/cov-raw.out
|
||||||
|
**/*.mock.go
|
||||||
|
*.key
|
||||||
|
*.pem
|
||||||
|
./main
|
||||||
|
.go/
|
||||||
|
__debug_bin
|
||||||
|
|
||||||
# Output of the go coverage tool, specifically when used with LiteIDE
|
###############
|
||||||
*.out
|
# directories #
|
||||||
|
###############
|
||||||
# Dependency directories (remove the comment below to include it)
|
|
||||||
# vendor/
|
|
||||||
|
|
||||||
# Go workspace file
|
|
||||||
go.work
|
|
||||||
|
|
||||||
|
.idea/
|
||||||
|
.vscode/
|
||||||
|
dist/
|
||||||
|
out/
|
||||||
|
.task/
|
||||||
|
public/
|
||||||
|
.fleet/
|
164
gapr.go
Normal file
164
gapr.go
Normal 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
131
gapr_test.go
Normal 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
3
go.mod
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
module code.icb4dc0.de/prskr/gapr
|
||||||
|
|
||||||
|
go 1.18
|
Loading…
Reference in a new issue