feat: add options

This commit is contained in:
Peter 2023-01-30 22:06:18 +01:00
parent b59dd4d9aa
commit 1568bf925d
Signed by: prskr
GPG key ID: C1DB5D2E8DB512F9
9 changed files with 322 additions and 31 deletions

View 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
View 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
View 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
View file

@ -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

View file

@ -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
View file

47
keys.go Normal file
View 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
View 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
View 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
}