Skip to content

Commit

Permalink
fix: set slice / nested maps with test cases
Browse files Browse the repository at this point in the history
  • Loading branch information
krak3n committed May 11, 2020
1 parent f79d8f5 commit 9bb869c
Show file tree
Hide file tree
Showing 7 changed files with 164 additions and 40 deletions.
6 changes: 3 additions & 3 deletions field.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ func (f Field) SetString(x string) {

// SetInt sets the fields value to the given int.
func (f Field) SetInt(x int64) error {
if !f.Value.OverflowInt(x) {
if f.Value.OverflowInt(x) {
return ErrSetValue{
Field: f.Value,
Value: reflect.ValueOf(x),
Expand All @@ -54,7 +54,7 @@ func (f Field) SetInt(x int64) error {

// SetUint sets the fields value to the given uint.
func (f Field) SetUint(x uint64) error {
if !f.Value.OverflowUint(x) {
if f.Value.OverflowUint(x) {
return ErrSetValue{
Field: f.Value,
Value: reflect.ValueOf(x),
Expand All @@ -68,7 +68,7 @@ func (f Field) SetUint(x uint64) error {

// SetFloat sets the fields value to the given float.
func (f Field) SetFloat(x float64) error {
if !f.Value.OverflowFloat(x) {
if f.Value.OverflowFloat(x) {
return ErrSetValue{
Field: f.Value,
Value: reflect.ValueOf(x),
Expand Down
5 changes: 1 addition & 4 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,4 @@ module go.krak3n.codes/gofig

go 1.13

require (
github.com/pelletier/go-toml v1.7.0 // indirect
gopkg.in/yaml.v3 v3.0.0-20200506231410-2ff61e1afc86 // indirect
)
require github.com/google/go-cmp v0.4.0
16 changes: 4 additions & 12 deletions go.sum
Original file line number Diff line number Diff line change
@@ -1,12 +1,4 @@
github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/pelletier/go-toml v1.7.0 h1:7utD74fnzVc/cpcyy8sjrlFr5vYpypUixARcHIMIGuI=
github.com/pelletier/go-toml v1.7.0/go.mod h1:vwGMzjaWMwyfHwgIBhI2YUM4fB6nL6lVAvS1LBMMhTE=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10=
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v3 v3.0.0-20200506231410-2ff61e1afc86 h1:OfFoIUYv/me30yv7XlMy4F9RJw8DEm8WQ6QG1Ph4bH0=
gopkg.in/yaml.v3 v3.0.0-20200506231410-2ff61e1afc86/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
github.com/google/go-cmp v0.4.0 h1:xsAVV57WRhGj6kEIi8ReJzQlHHqcBYCElAvkovg3B/4=
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
4 changes: 2 additions & 2 deletions gofig.go
Original file line number Diff line number Diff line change
Expand Up @@ -124,10 +124,10 @@ func (c *Config) flatten(rv reflect.Value, rt reflect.Type, key string) {
}
}

c.log().Printf("parsed field:%s, tag:%s", ft.Name, t)

path := strings.Trim(strings.Join(append(strings.Split(key, "."), t.name), "."), ".")

c.log().Printf("<Field %s kind:%s path:%s tag:%s>", ft.Name, fv.Kind(), path, t.Stirng())

switch fv.Kind() {
case reflect.Struct:
c.flatten(fv, ft.Type, path)
Expand Down
126 changes: 126 additions & 0 deletions gofig_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
package gofig

import (
"testing"

"github.com/google/go-cmp/cmp"
)

func TestParse(t *testing.T) {
type Config struct {
String string `gofig:"string"`
Slice []int `gofig:"slice"`
Map map[string]string `gofig:"map"`
NestedMap map[string]map[string][]int `gofig:"nestedmap"`
DeeplyNestedMap map[string]map[string]map[string][]int `gofig:"deeplynestedmap"`
}

cases := map[string]struct {
parser *InMemoryParser
want Config
}{
"String": {
parser: func() *InMemoryParser {
p := NewInMemoryParser()
p.Add("string", "bar")

return p
}(),
want: Config{
String: "bar",
},
},
"Slice": {
parser: func() *InMemoryParser {
p := NewInMemoryParser()
p.Add("slice", []int{1, 2, 3})

return p
}(),
want: Config{
Slice: []int{1, 2, 3},
},
},
"Map": {
parser: func() *InMemoryParser {
p := NewInMemoryParser()
p.Add("map.key", "value")

return p
}(),
want: Config{
Map: map[string]string{
"key": "value",
},
},
},
"NestedMap": {
parser: func() *InMemoryParser {
p := NewInMemoryParser()
p.Add("nestedmap.foo.bar", []int{1, 2, 3})
p.Add("nestedmap.foo.baz", []int{4, 5, 6})

return p
}(),
want: Config{
NestedMap: map[string]map[string][]int{
"foo": {
"bar": {1, 2, 3},
"baz": {4, 5, 6},
},
},
},
},
"DeeplyNestedMap": {
parser: func() *InMemoryParser {
p := NewInMemoryParser()
p.Add("deeplynestedmap.foo.bar.fizz", []int{1, 2, 3})
p.Add("deeplynestedmap.foo.bar.buzz", []int{4, 5, 6})
p.Add("deeplynestedmap.foo.baz.fizz", []int{1, 2, 3})
p.Add("deeplynestedmap.foo.baz.buzz", []int{4, 5, 6})

return p
}(),
want: Config{
DeeplyNestedMap: map[string]map[string]map[string][]int{
"foo": {
"bar": {
"fizz": {1, 2, 3},
"buzz": {4, 5, 6},
},
"baz": {
"fizz": {1, 2, 3},
"buzz": {4, 5, 6},
},
},
},
},
},
}

for name, testCase := range cases {
tc := testCase

t.Run(name, func(t *testing.T) {
t.Parallel()

var cfg Config

g, err := New(&cfg, WithDebug(), SetLogger(LoggerFunc(func(v ...interface{}) {
t.Log(v...)
})))

if err != nil {
t.Fatal("want nil error, got:", err)
}

if err := g.Parse(tc.parser); err != nil {
t.Fatal("want nil error, got:", err)
}

if !cmp.Equal(tc.want, cfg) {
t.Errorf("want %+v, got %+v", tc.want, cfg)
}
})
}
}
7 changes: 7 additions & 0 deletions parser.go
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,13 @@ func (p *InMemoryParser) Values() (<-chan func() (string, interface{}), error) {
return ch, nil
}

// NewInMemoryParser constructs a new InMemoryParser.
func NewInMemoryParser() *InMemoryParser {
return &InMemoryParser{
values: make(map[string]interface{}),
}
}

// FromString parsers configuration from a string.
func FromString(parser ReaderParser, v string) Parser {
return ParserFunc(func() (<-chan func() (string, interface{}), error) {
Expand Down
40 changes: 21 additions & 19 deletions set.go
Original file line number Diff line number Diff line change
Expand Up @@ -178,53 +178,55 @@ func setSlice(field Field, value interface{}) error {

// setMap sets a field to a map, also handles nested maps.
func setMap(field Field, key string, value interface{}) error {
fv := field.Value
ft := field.Value.Type()

if fv.IsNil() {
if ft.Key().Kind() != reflect.String {
if field.Value.IsNil() {
if field.Value.Type().Key().Kind() != reflect.String {
return ErrInvalidValue{
Type: ft.Key(),
Type: field.Value.Type().Key(),
}
}

field.Set(reflect.MakeMap(reflect.MapOf(ft.Key(), ft.Elem())))
field.Set(reflect.MakeMap(reflect.MapOf(
field.Value.Type().Key(),
field.Value.Type().Elem())))
}

// Nested map
if ft.Elem().Kind() == reflect.Map {
if field.Value.Type().Elem().Kind() == reflect.Map {
elms := strings.Split(key, ".")
key = strings.Join(elms[:len(elms)-1], ".")

parent, children := elms[0], elms[1:]
key = strings.Join(children, ".")
if key == "" {
return nil
}

v := reflect.New(ft.Elem())
f := Field{key, v.Elem()}
if err := setMap(f, key, value); err != nil {
m := field.Value.MapIndex(reflect.ValueOf(parent))
if !m.IsValid() {
m = reflect.New(field.Value.Type().Elem()).Elem()
}

if err := setMap(Field{key, m}, key, value); err != nil {
return err
}

fv.SetMapIndex(reflect.ValueOf(elms[0]), v.Elem())
field.Value.SetMapIndex(reflect.ValueOf(elms[0]), m)

return nil
}

if reflect.ValueOf(value).Kind() != ft.Elem().Kind() {
if reflect.ValueOf(value).Kind() != field.Value.Type().Elem().Kind() {
return ErrInvalidConversion{
From: reflect.ValueOf(value).Kind(),
To: ft.Elem().Kind(),
To: field.Value.Type().Elem().Kind(),
}
}

v := reflect.New(ft.Elem())
f := Field{key, v.Elem()}
if err := setValue(f, value); err != nil {
v := reflect.New(field.Value.Type().Elem())
if err := setValue(Field{key, v.Elem()}, value); err != nil {
return err
}

fv.SetMapIndex(reflect.ValueOf(key), v.Elem())
field.Value.SetMapIndex(reflect.ValueOf(key), v.Elem())

return nil
}

0 comments on commit 9bb869c

Please sign in to comment.