feat: add options
This commit is contained in:
parent
b59dd4d9aa
commit
1568bf925d
9 changed files with 322 additions and 31 deletions
42
.concourse/branch-validate.yml
Normal file
42
.concourse/branch-validate.yml
Normal file
|
@ -0,0 +1,42 @@
|
||||||
|
---
|
||||||
|
resources:
|
||||||
|
- name: gapr.git
|
||||||
|
type: git
|
||||||
|
icon: github
|
||||||
|
source:
|
||||||
|
uri: https://code.icb4dc0.de/prskr/gapr.git
|
||||||
|
branch: ((branch))
|
||||||
|
|
||||||
|
- name: templates.git
|
||||||
|
type: git
|
||||||
|
icon: github
|
||||||
|
source:
|
||||||
|
uri: https://code.icb4dc0.de/prskr/pipeline-templates.git
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
- name: lint
|
||||||
|
plan:
|
||||||
|
- get: gapr.git
|
||||||
|
trigger: true
|
||||||
|
- get: templates.git
|
||||||
|
- task: lint
|
||||||
|
file: gapr.git/.concourse/tasks/lint.yml
|
||||||
|
input_mapping: {repo: gapr.git}
|
||||||
|
on_success:
|
||||||
|
task: report-success
|
||||||
|
file: templates.git/tasks/gitea-status.yml
|
||||||
|
input_mapping: {repo: gapr.git}
|
||||||
|
vars:
|
||||||
|
project_path: prskr/gapr
|
||||||
|
context: concourse-ci/lint/golangci-lint
|
||||||
|
description: Lint Go files
|
||||||
|
state: success
|
||||||
|
on_failure:
|
||||||
|
task: report-failure
|
||||||
|
file: templates.git/tasks/gitea-status.yml
|
||||||
|
input_mapping: {repo: gapr.git}
|
||||||
|
vars:
|
||||||
|
project_path: prskr/gapr
|
||||||
|
context: concourse-ci/lint/golangci-lint
|
||||||
|
description: Lint Go files
|
||||||
|
state: failure
|
24
.concourse/tasks/lint.yml
Normal file
24
.concourse/tasks/lint.yml
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
---
|
||||||
|
platform: linux
|
||||||
|
|
||||||
|
image_resource:
|
||||||
|
type: registry-image
|
||||||
|
source:
|
||||||
|
repository: docker.io/golangci/golangci-lint
|
||||||
|
tag: latest
|
||||||
|
|
||||||
|
inputs:
|
||||||
|
- name: repo
|
||||||
|
path: .
|
||||||
|
|
||||||
|
params:
|
||||||
|
GO111MODULE: "on"
|
||||||
|
CGO_ENABLED: "0"
|
||||||
|
GITEA_TOKEN: ((gitea-credentials.token))
|
||||||
|
|
||||||
|
run:
|
||||||
|
path: bash
|
||||||
|
args:
|
||||||
|
- -ce
|
||||||
|
- |
|
||||||
|
golangci-lint run -v
|
24
.concourse/tasks/test.yml
Normal file
24
.concourse/tasks/test.yml
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
---
|
||||||
|
platform: linux
|
||||||
|
|
||||||
|
image_resource:
|
||||||
|
type: registry-image
|
||||||
|
source:
|
||||||
|
repository: docker.io/golang
|
||||||
|
tag: 1.19-buster
|
||||||
|
|
||||||
|
inputs:
|
||||||
|
- name: repo
|
||||||
|
path: .
|
||||||
|
|
||||||
|
params:
|
||||||
|
GO111MODULE: "on"
|
||||||
|
CGO_ENABLED: "1"
|
||||||
|
GITEA_TOKEN: ((gitea-credentials.token))
|
||||||
|
|
||||||
|
run:
|
||||||
|
path: bash
|
||||||
|
args:
|
||||||
|
- -ce
|
||||||
|
- |
|
||||||
|
go run gotest.tools/gotestsum@latest ./...
|
34
gapr.go
34
gapr.go
|
@ -6,7 +6,6 @@ import (
|
||||||
"reflect"
|
"reflect"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
@ -14,13 +13,21 @@ var (
|
||||||
ErrNotStringKey = errors.New("key type is not string - not supported right now")
|
ErrNotStringKey = errors.New("key type is not string - not supported right now")
|
||||||
)
|
)
|
||||||
|
|
||||||
func New() *Gapr {
|
func New(opts ...Option) *Gapr {
|
||||||
|
o := defaultOptions()
|
||||||
|
|
||||||
|
for _, opt := range opts {
|
||||||
|
opt.apply(&o)
|
||||||
|
}
|
||||||
|
|
||||||
return &Gapr{
|
return &Gapr{
|
||||||
rand: rand.New(rand.NewSource(time.Now().Unix())),
|
tagName: o.TagName,
|
||||||
|
rand: rand.New(rand.NewSource(o.RandomSeed)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
type Gapr struct {
|
type Gapr struct {
|
||||||
|
tagName string
|
||||||
rand *rand.Rand
|
rand *rand.Rand
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -33,7 +40,7 @@ func (g *Gapr) Map(input any) (any, error) {
|
||||||
v = v.Elem()
|
v = v.Elem()
|
||||||
}
|
}
|
||||||
|
|
||||||
if !canMap(t) {
|
if !canMap(t, v) {
|
||||||
return nil, ErrNotSupportedType
|
return nil, ErrNotSupportedType
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -74,7 +81,7 @@ func (g *Gapr) mapStruct(t reflect.Type, v reflect.Value) (any, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (g *Gapr) mapField(v reflect.Value) (any, error) {
|
func (g *Gapr) mapField(v reflect.Value) (any, error) {
|
||||||
if canMap(v.Type()) {
|
if canMap(v.Type(), v) {
|
||||||
return g.Map(v.Interface())
|
return g.Map(v.Interface())
|
||||||
} else {
|
} else {
|
||||||
return v.Interface(), nil
|
return v.Interface(), nil
|
||||||
|
@ -90,7 +97,7 @@ func (g *Gapr) mapMap(t reflect.Type, v reflect.Value) (any, error) {
|
||||||
|
|
||||||
iter := v.MapRange()
|
iter := v.MapRange()
|
||||||
for iter.Next() {
|
for iter.Next() {
|
||||||
if canMap(iter.Value().Type()) {
|
if canMap(iter.Value().Type(), iter.Value()) {
|
||||||
if mappedValue, err := g.Map(iter.Value().Interface()); err != nil {
|
if mappedValue, err := g.Map(iter.Value().Interface()); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
} else {
|
} else {
|
||||||
|
@ -112,7 +119,7 @@ func (g *Gapr) mapSliceOrArray(v reflect.Value) (any, error) {
|
||||||
|
|
||||||
for i := 0; i < length; i++ {
|
for i := 0; i < length; i++ {
|
||||||
idxVal := v.Index(i)
|
idxVal := v.Index(i)
|
||||||
if canMap(idxVal.Type()) {
|
if canMap(idxVal.Type(), idxVal) {
|
||||||
if mappedVal, err := g.Map(idxVal.Interface()); err != nil {
|
if mappedVal, err := g.Map(idxVal.Interface()); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
} else {
|
} else {
|
||||||
|
@ -128,7 +135,7 @@ func (g *Gapr) mapSliceOrArray(v reflect.Value) (any, error) {
|
||||||
|
|
||||||
func (g *Gapr) fieldMeta(f reflect.StructField) (drop bool, fieldName string, err error) {
|
func (g *Gapr) fieldMeta(f reflect.StructField) (drop bool, fieldName string, err error) {
|
||||||
fieldName = f.Name
|
fieldName = f.Name
|
||||||
tagVal, present := f.Tag.Lookup("incomplete")
|
tagVal, present := f.Tag.Lookup(g.tagName)
|
||||||
if !present {
|
if !present {
|
||||||
return false, f.Name, nil
|
return false, f.Name, nil
|
||||||
}
|
}
|
||||||
|
@ -138,12 +145,15 @@ func (g *Gapr) fieldMeta(f reflect.StructField) (drop bool, fieldName string, er
|
||||||
return false, f.Name, nil
|
return false, f.Name, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if tagSplit[0] != "" {
|
||||||
dropProbability, err := strconv.ParseFloat(tagSplit[0], 64)
|
dropProbability, err := strconv.ParseFloat(tagSplit[0], 64)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, "", err
|
return false, "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
drop = g.rand.Float64() < dropProbability
|
drop = g.rand.Float64() < dropProbability
|
||||||
|
}
|
||||||
|
|
||||||
if len(tagSplit) > 1 && tagSplit[1] != "" {
|
if len(tagSplit) > 1 && tagSplit[1] != "" {
|
||||||
fieldName = strings.TrimSpace(tagSplit[1])
|
fieldName = strings.TrimSpace(tagSplit[1])
|
||||||
}
|
}
|
||||||
|
@ -151,13 +161,17 @@ func (g *Gapr) fieldMeta(f reflect.StructField) (drop bool, fieldName string, er
|
||||||
return drop, fieldName, nil
|
return drop, fieldName, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func canMap(t reflect.Type) bool {
|
func canMap(t reflect.Type, v reflect.Value) bool {
|
||||||
if t.Kind() == reflect.Pointer {
|
if t.Kind() == reflect.Pointer {
|
||||||
t = t.Elem()
|
t = t.Elem()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if t.Kind() == reflect.Interface {
|
||||||
|
t = reflect.TypeOf(v.Interface())
|
||||||
|
}
|
||||||
|
|
||||||
switch t.Kind() {
|
switch t.Kind() {
|
||||||
case reflect.Struct, reflect.Map, reflect.Interface, reflect.Slice, reflect.Array:
|
case reflect.Struct, reflect.Map, reflect.Slice, reflect.Array:
|
||||||
return true
|
return true
|
||||||
default:
|
default:
|
||||||
return false
|
return false
|
||||||
|
|
60
gapr_test.go
60
gapr_test.go
|
@ -1,6 +1,7 @@
|
||||||
package gapr_test
|
package gapr_test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"reflect"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"code.icb4dc0.de/prskr/gapr"
|
"code.icb4dc0.de/prskr/gapr"
|
||||||
|
@ -9,11 +10,12 @@ import (
|
||||||
func TestGapr_Map(t *testing.T) {
|
func TestGapr_Map(t *testing.T) {
|
||||||
type args struct {
|
type args struct {
|
||||||
input any
|
input any
|
||||||
|
opts []gapr.Option
|
||||||
}
|
}
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
args args
|
args args
|
||||||
want map[string]any
|
want any
|
||||||
wantErr bool
|
wantErr bool
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
|
@ -27,7 +29,10 @@ func TestGapr_Map(t *testing.T) {
|
||||||
Surname: "Tester",
|
Surname: "Tester",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
want: nil,
|
want: map[string]any{
|
||||||
|
"GivenName": "Ted",
|
||||||
|
"Surname": "Tester",
|
||||||
|
},
|
||||||
wantErr: false,
|
wantErr: false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -41,7 +46,10 @@ func TestGapr_Map(t *testing.T) {
|
||||||
Surname: "Tester",
|
Surname: "Tester",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
want: nil,
|
want: map[string]any{
|
||||||
|
"GivenName": "Ted",
|
||||||
|
"Surname": "Tester",
|
||||||
|
},
|
||||||
wantErr: false,
|
wantErr: false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -69,21 +77,29 @@ func TestGapr_Map(t *testing.T) {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
want: nil,
|
want: map[string]any{
|
||||||
|
"GivenName": "Ted",
|
||||||
|
"Surname": "Tester",
|
||||||
|
"Address": map[string]any{
|
||||||
|
"Street": "Some Street",
|
||||||
|
"HouseNr": "3a",
|
||||||
|
"City": "Some City",
|
||||||
|
},
|
||||||
|
},
|
||||||
wantErr: false,
|
wantErr: false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "Test simple map",
|
name: "Test simple map",
|
||||||
args: args{
|
args: args{
|
||||||
input: struct {
|
input: map[string]any{
|
||||||
GivenName string
|
"GivenName": "Ted",
|
||||||
Surname string
|
"Surname": "Tester",
|
||||||
}{
|
|
||||||
GivenName: "Ted",
|
|
||||||
Surname: "Tester",
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
want: nil,
|
want: map[string]any{
|
||||||
|
"GivenName": "Ted",
|
||||||
|
"Surname": "Tester",
|
||||||
|
},
|
||||||
wantErr: false,
|
wantErr: false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -93,13 +109,13 @@ func TestGapr_Map(t *testing.T) {
|
||||||
GivenName string
|
GivenName string
|
||||||
Children []struct {
|
Children []struct {
|
||||||
Name string
|
Name string
|
||||||
Age uint `incomplete:"0.2,givenName"`
|
Age uint `gapr:",age"`
|
||||||
}
|
}
|
||||||
}{
|
}{
|
||||||
GivenName: "Ted",
|
GivenName: "Ted",
|
||||||
Children: []struct {
|
Children: []struct {
|
||||||
Name string
|
Name string
|
||||||
Age uint `incomplete:"0.2,givenName"`
|
Age uint `gapr:",age"`
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
Name: "Tim",
|
Name: "Tim",
|
||||||
|
@ -108,7 +124,15 @@ func TestGapr_Map(t *testing.T) {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
want: nil,
|
want: map[string]any{
|
||||||
|
"GivenName": "Ted",
|
||||||
|
"Children": []any{
|
||||||
|
map[string]any{
|
||||||
|
"Name": "Tim",
|
||||||
|
"age": uint(11),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
wantErr: false,
|
wantErr: false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -116,7 +140,7 @@ func TestGapr_Map(t *testing.T) {
|
||||||
args: args{
|
args: args{
|
||||||
input: map[string]any{
|
input: map[string]any{
|
||||||
"Hello": struct {
|
"Hello": struct {
|
||||||
GivenName string `incomplete:"0.2,givenName"`
|
GivenName string `gapr:",givenName"`
|
||||||
Surname string
|
Surname string
|
||||||
}{
|
}{
|
||||||
GivenName: "Ted",
|
GivenName: "Ted",
|
||||||
|
@ -130,7 +154,7 @@ func TestGapr_Map(t *testing.T) {
|
||||||
}
|
}
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
g := gapr.New()
|
g := gapr.New(tt.args.opts...)
|
||||||
got, err := g.Map(tt.args.input)
|
got, err := g.Map(tt.args.input)
|
||||||
if (got == nil) == (err == nil) {
|
if (got == nil) == (err == nil) {
|
||||||
t.Errorf("cannot get nil value and nil error")
|
t.Errorf("cannot get nil value and nil error")
|
||||||
|
@ -140,6 +164,10 @@ func TestGapr_Map(t *testing.T) {
|
||||||
t.Errorf("Map() error = %v, wantErr %v", err, tt.wantErr)
|
t.Errorf("Map() error = %v, wantErr %v", err, tt.wantErr)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if tt.want != nil && !reflect.DeepEqual(got, tt.want) {
|
||||||
|
t.Errorf("Got %v but want %v", got, tt.want)
|
||||||
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
0
go.sum
Normal file
0
go.sum
Normal file
47
keys.go
Normal file
47
keys.go
Normal file
|
@ -0,0 +1,47 @@
|
||||||
|
package gapr
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strings"
|
||||||
|
"unicode"
|
||||||
|
)
|
||||||
|
|
||||||
|
type KeyMapper interface {
|
||||||
|
MapKey(orig string) string
|
||||||
|
}
|
||||||
|
|
||||||
|
type KeyMapperFunc func(orig string) string
|
||||||
|
|
||||||
|
func (f KeyMapperFunc) MapKey(orig string) string {
|
||||||
|
return f(orig)
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
LowercaseKeyMapper KeyMapper = KeyMapperFunc(func(orig string) string {
|
||||||
|
return strings.ToLower(orig)
|
||||||
|
})
|
||||||
|
|
||||||
|
UppercaseKeyMapper KeyMapper = KeyMapperFunc(func(orig string) string {
|
||||||
|
return strings.ToUpper(orig)
|
||||||
|
})
|
||||||
|
|
||||||
|
CamelCaseKeyMapper = genericFirstKeyMapper(unicode.ToLower)
|
||||||
|
|
||||||
|
PascalCaseKeyMapper = genericFirstKeyMapper(unicode.ToUpper)
|
||||||
|
)
|
||||||
|
|
||||||
|
func genericFirstKeyMapper(m func(r rune) rune) KeyMapper {
|
||||||
|
return KeyMapperFunc(func(orig string) string {
|
||||||
|
if len(orig) < 1 {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
mapped := make([]byte, 1, len(orig))
|
||||||
|
mapped[0] = byte(m(rune(orig[0])))
|
||||||
|
|
||||||
|
if len(orig) > 1 {
|
||||||
|
mapped = append(mapped, orig[1:]...)
|
||||||
|
}
|
||||||
|
|
||||||
|
return string(mapped)
|
||||||
|
})
|
||||||
|
}
|
75
keys_test.go
Normal file
75
keys_test.go
Normal file
|
@ -0,0 +1,75 @@
|
||||||
|
package gapr_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"code.icb4dc0.de/prskr/gapr"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestKeyMappers(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
mapper gapr.KeyMapper
|
||||||
|
input string
|
||||||
|
want string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "Empty string",
|
||||||
|
mapper: gapr.CamelCaseKeyMapper,
|
||||||
|
input: "",
|
||||||
|
want: "",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "HelloWorld camel case",
|
||||||
|
mapper: gapr.CamelCaseKeyMapper,
|
||||||
|
input: "HelloWorld",
|
||||||
|
want: "helloWorld",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "helloWorld camel case",
|
||||||
|
mapper: gapr.CamelCaseKeyMapper,
|
||||||
|
input: "helloWorld",
|
||||||
|
want: "helloWorld",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "h camel case",
|
||||||
|
mapper: gapr.CamelCaseKeyMapper,
|
||||||
|
input: "h",
|
||||||
|
want: "h",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "HelloWorld lowercase",
|
||||||
|
mapper: gapr.LowercaseKeyMapper,
|
||||||
|
input: "HelloWorld",
|
||||||
|
want: "helloworld",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "HelloWorld uppercase",
|
||||||
|
mapper: gapr.UppercaseKeyMapper,
|
||||||
|
input: "HelloWorld",
|
||||||
|
want: "HELLOWORLD",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "HelloWorld pascal case",
|
||||||
|
mapper: gapr.PascalCaseKeyMapper,
|
||||||
|
input: "helloWorld",
|
||||||
|
want: "HelloWorld",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "h pascal case",
|
||||||
|
mapper: gapr.PascalCaseKeyMapper,
|
||||||
|
input: "h",
|
||||||
|
want: "H",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tc := range tests {
|
||||||
|
tt := tc
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
if got := tt.mapper.MapKey(tt.input); got != tt.want {
|
||||||
|
t.Errorf("Expected %s but got %s", tt.want, got)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
37
options.go
Normal file
37
options.go
Normal file
|
@ -0,0 +1,37 @@
|
||||||
|
package gapr
|
||||||
|
|
||||||
|
import "time"
|
||||||
|
|
||||||
|
func WithRandomSeed(i int64) Option {
|
||||||
|
return optionFunc(func(o *options) {
|
||||||
|
o.RandomSeed = i
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func WithTagName(tag string) Option {
|
||||||
|
return optionFunc(func(o *options) {
|
||||||
|
o.TagName = tag
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func defaultOptions() options {
|
||||||
|
return options{
|
||||||
|
TagName: "gapr",
|
||||||
|
RandomSeed: time.Now().UTC().Unix(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type Option interface {
|
||||||
|
apply(o *options)
|
||||||
|
}
|
||||||
|
|
||||||
|
type optionFunc func(o *options)
|
||||||
|
|
||||||
|
func (f optionFunc) apply(o *options) {
|
||||||
|
f(o)
|
||||||
|
}
|
||||||
|
|
||||||
|
type options struct {
|
||||||
|
TagName string
|
||||||
|
RandomSeed int64
|
||||||
|
}
|
Loading…
Reference in a new issue