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 ./...
|
44
gapr.go
44
gapr.go
|
@ -6,7 +6,6 @@ import (
|
|||
"reflect"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
var (
|
||||
|
@ -14,14 +13,22 @@ var (
|
|||
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{
|
||||
rand: rand.New(rand.NewSource(time.Now().Unix())),
|
||||
tagName: o.TagName,
|
||||
rand: rand.New(rand.NewSource(o.RandomSeed)),
|
||||
}
|
||||
}
|
||||
|
||||
type Gapr struct {
|
||||
rand *rand.Rand
|
||||
tagName string
|
||||
rand *rand.Rand
|
||||
}
|
||||
|
||||
func (g *Gapr) Map(input any) (any, error) {
|
||||
|
@ -33,7 +40,7 @@ func (g *Gapr) Map(input any) (any, error) {
|
|||
v = v.Elem()
|
||||
}
|
||||
|
||||
if !canMap(t) {
|
||||
if !canMap(t, v) {
|
||||
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) {
|
||||
if canMap(v.Type()) {
|
||||
if canMap(v.Type(), v) {
|
||||
return g.Map(v.Interface())
|
||||
} else {
|
||||
return v.Interface(), nil
|
||||
|
@ -90,7 +97,7 @@ func (g *Gapr) mapMap(t reflect.Type, v reflect.Value) (any, error) {
|
|||
|
||||
iter := v.MapRange()
|
||||
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 {
|
||||
return nil, err
|
||||
} else {
|
||||
|
@ -112,7 +119,7 @@ func (g *Gapr) mapSliceOrArray(v reflect.Value) (any, error) {
|
|||
|
||||
for i := 0; i < length; i++ {
|
||||
idxVal := v.Index(i)
|
||||
if canMap(idxVal.Type()) {
|
||||
if canMap(idxVal.Type(), idxVal) {
|
||||
if mappedVal, err := g.Map(idxVal.Interface()); err != nil {
|
||||
return nil, err
|
||||
} 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) {
|
||||
fieldName = f.Name
|
||||
tagVal, present := f.Tag.Lookup("incomplete")
|
||||
tagVal, present := f.Tag.Lookup(g.tagName)
|
||||
if !present {
|
||||
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
|
||||
}
|
||||
|
||||
dropProbability, err := strconv.ParseFloat(tagSplit[0], 64)
|
||||
if err != nil {
|
||||
return false, "", err
|
||||
if tagSplit[0] != "" {
|
||||
dropProbability, err := strconv.ParseFloat(tagSplit[0], 64)
|
||||
if err != nil {
|
||||
return false, "", err
|
||||
}
|
||||
|
||||
drop = g.rand.Float64() < dropProbability
|
||||
}
|
||||
|
||||
drop = g.rand.Float64() < dropProbability
|
||||
if len(tagSplit) > 1 && 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
|
||||
}
|
||||
|
||||
func canMap(t reflect.Type) bool {
|
||||
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())
|
||||
}
|
||||
|
||||
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
|
||||
default:
|
||||
return false
|
||||
|
|
60
gapr_test.go
60
gapr_test.go
|
@ -1,6 +1,7 @@
|
|||
package gapr_test
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"code.icb4dc0.de/prskr/gapr"
|
||||
|
@ -9,11 +10,12 @@ import (
|
|||
func TestGapr_Map(t *testing.T) {
|
||||
type args struct {
|
||||
input any
|
||||
opts []gapr.Option
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
want map[string]any
|
||||
want any
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
|
@ -27,7 +29,10 @@ func TestGapr_Map(t *testing.T) {
|
|||
Surname: "Tester",
|
||||
},
|
||||
},
|
||||
want: nil,
|
||||
want: map[string]any{
|
||||
"GivenName": "Ted",
|
||||
"Surname": "Tester",
|
||||
},
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
|
@ -41,7 +46,10 @@ func TestGapr_Map(t *testing.T) {
|
|||
Surname: "Tester",
|
||||
},
|
||||
},
|
||||
want: nil,
|
||||
want: map[string]any{
|
||||
"GivenName": "Ted",
|
||||
"Surname": "Tester",
|
||||
},
|
||||
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,
|
||||
},
|
||||
{
|
||||
name: "Test simple map",
|
||||
args: args{
|
||||
input: struct {
|
||||
GivenName string
|
||||
Surname string
|
||||
}{
|
||||
GivenName: "Ted",
|
||||
Surname: "Tester",
|
||||
input: map[string]any{
|
||||
"GivenName": "Ted",
|
||||
"Surname": "Tester",
|
||||
},
|
||||
},
|
||||
want: nil,
|
||||
want: map[string]any{
|
||||
"GivenName": "Ted",
|
||||
"Surname": "Tester",
|
||||
},
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
|
@ -93,13 +109,13 @@ func TestGapr_Map(t *testing.T) {
|
|||
GivenName string
|
||||
Children []struct {
|
||||
Name string
|
||||
Age uint `incomplete:"0.2,givenName"`
|
||||
Age uint `gapr:",age"`
|
||||
}
|
||||
}{
|
||||
GivenName: "Ted",
|
||||
Children: []struct {
|
||||
Name string
|
||||
Age uint `incomplete:"0.2,givenName"`
|
||||
Age uint `gapr:",age"`
|
||||
}{
|
||||
{
|
||||
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,
|
||||
},
|
||||
{
|
||||
|
@ -116,7 +140,7 @@ func TestGapr_Map(t *testing.T) {
|
|||
args: args{
|
||||
input: map[string]any{
|
||||
"Hello": struct {
|
||||
GivenName string `incomplete:"0.2,givenName"`
|
||||
GivenName string `gapr:",givenName"`
|
||||
Surname string
|
||||
}{
|
||||
GivenName: "Ted",
|
||||
|
@ -130,7 +154,7 @@ func TestGapr_Map(t *testing.T) {
|
|||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
g := gapr.New()
|
||||
g := gapr.New(tt.args.opts...)
|
||||
got, err := g.Map(tt.args.input)
|
||||
if (got == nil) == (err == nil) {
|
||||
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)
|
||||
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