Skip to content

Commit a2efbf3

Browse files
committed
feat(remote): Parse config using FillStruct. Remotes field.
Parse the config using FillStruct. Fix numerous bugs found while supporting the config struct. Add a Remotes component to the config, which uses ArbitrarySetter to support arbitrary remote names.
1 parent d5f8e34 commit a2efbf3

File tree

7 files changed

+197
-48
lines changed

7 files changed

+197
-48
lines changed

base/fill_struct.go

Lines changed: 29 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -23,9 +23,9 @@ type ArbitrarySetter interface {
2323
SetArbitrary(string, interface{}) error
2424
}
2525

26-
// timeObj and ifaceObj are used for reflect.TypeOf
26+
// timeObj and strObj are used for reflect.TypeOf
2727
var timeObj time.Time
28-
var ifaceObj interface{}
28+
var strObj string
2929

3030
// putFieldsToTargetStruct iterates over the fields in the target struct, and assigns each
3131
// field the value from the `fields` map. Recursively call this for an sub structures. Field
@@ -61,8 +61,8 @@ func putFieldsToTargetStruct(fields map[string]interface{}, target reflect.Value
6161
fieldName := target.Type().Field(i).Name
6262
lowerName := strings.ToLower(fieldName)
6363

64-
val := fields[caseMap[lowerName]]
65-
if val == nil {
64+
val, ok := fields[caseMap[lowerName]]
65+
if !ok {
6666
// Nothing to assign to this field, go to next.
6767
continue
6868
}
@@ -156,23 +156,37 @@ func putValueToPlace(val interface{}, place reflect.Value) error {
156156
// as what's done for `pointer` below.
157157
return fmt.Errorf("unknown struct %s", place.Type())
158158
case reflect.Map:
159+
if val == nil {
160+
// If map is nil, nothing more to do.
161+
return nil
162+
}
159163
m, ok := val.(map[string]interface{})
160-
if ok {
161-
place.Set(reflect.ValueOf(m))
164+
if !ok {
165+
return fmt.Errorf("need type map, value %s", val)
166+
}
167+
// Special case map[string]string, convert values to strings.
168+
if place.Type().Elem() == reflect.TypeOf(strObj) {
169+
strmap := make(map[string]string)
170+
for k, v := range m {
171+
strmap[k] = fmt.Sprintf("%s", v)
172+
}
173+
place.Set(reflect.ValueOf(strmap))
162174
return nil
163175
}
164-
return fmt.Errorf("need type map, value %s", val)
176+
place.Set(reflect.ValueOf(m))
177+
return nil
165178
case reflect.Slice:
179+
if val == nil {
180+
// If slice is nil, nothing more to do.
181+
return nil
182+
}
166183
slice, ok := val.([]interface{})
167184
if !ok {
168185
return fmt.Errorf("need type slice, value %s", val)
169186
}
170187
// Get size of type of the slice to deserialize.
171188
size := len(slice)
172-
sliceType := reflect.TypeOf(ifaceObj)
173-
if size > 0 {
174-
sliceType = place.Type().Elem()
175-
}
189+
sliceType := place.Type().Elem()
176190
// Construct a new, empty slice of the same size.
177191
create := reflect.MakeSlice(reflect.SliceOf(sliceType), size, size)
178192
// Fill in each element.
@@ -187,6 +201,10 @@ func putValueToPlace(val interface{}, place reflect.Value) error {
187201
place.Set(create)
188202
return nil
189203
case reflect.Ptr:
204+
if val == nil {
205+
// If pointer is nil, nothing more to do.
206+
return nil
207+
}
190208
// Allocate a new pointer for the sub-component to be filled in.
191209
alloc := reflect.New(place.Type().Elem())
192210
place.Set(alloc)

base/fill_struct_test.go

Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -162,6 +162,9 @@ type Collection struct {
162162
Age int
163163
IsOn bool
164164
Xpos float64
165+
Ptr *int
166+
Dict map[string]string
167+
List []string
165168
}
166169

167170
func (c *Collection) SetArbitrary(key string, val interface{}) error {
@@ -317,3 +320,118 @@ func TestFillMetaCitations(t *testing.T) {
317320
t.Errorf("expected: c.Keywords should expect: %s, got: %s", expect, meta.Keywords)
318321
}
319322
}
323+
324+
func TestFillMapStringToString(t *testing.T) {
325+
jsonData := `{
326+
"Dict": {
327+
"cat": "meow",
328+
"dog": "bark",
329+
"eel": "zap"
330+
}
331+
}`
332+
data := make(map[string]interface{})
333+
err := json.Unmarshal([]byte(jsonData), &data)
334+
if err != nil {
335+
panic(err)
336+
}
337+
338+
var c Collection
339+
err = FillStruct(data, &c)
340+
if err != nil {
341+
panic(err)
342+
}
343+
344+
if len(c.Dict) != 3 {
345+
t.Error("expected 3 elements in Dict")
346+
}
347+
if c.Dict["cat"] != "meow" {
348+
t.Error("expected: Dict[\"cat\"] == \"meow\"")
349+
}
350+
}
351+
352+
func TestStringSlice(t *testing.T) {
353+
jsonData := `{
354+
"List": ["a","b","c"]
355+
}`
356+
data := make(map[string]interface{})
357+
err := json.Unmarshal([]byte(jsonData), &data)
358+
if err != nil {
359+
panic(err)
360+
}
361+
362+
var c Collection
363+
err = FillStruct(data, &c)
364+
if err != nil {
365+
panic(err)
366+
}
367+
368+
if len(c.List) != 3 {
369+
t.Error("expected 3 elements in List")
370+
}
371+
if c.List[0] != "a" {
372+
t.Error("expected: List[0] == \"a\"")
373+
}
374+
}
375+
376+
func TestNilStringSlice(t *testing.T) {
377+
jsonData := `{
378+
"List": null
379+
}`
380+
data := make(map[string]interface{})
381+
err := json.Unmarshal([]byte(jsonData), &data)
382+
if err != nil {
383+
panic(err)
384+
}
385+
386+
var c Collection
387+
err = FillStruct(data, &c)
388+
if err != nil {
389+
panic(err)
390+
}
391+
392+
if c.List != nil {
393+
t.Error("expected null List")
394+
}
395+
}
396+
397+
func TestNilMap(t *testing.T) {
398+
jsonData := `{
399+
"Dict": null
400+
}`
401+
data := make(map[string]interface{})
402+
err := json.Unmarshal([]byte(jsonData), &data)
403+
if err != nil {
404+
panic(err)
405+
}
406+
407+
var c Collection
408+
err = FillStruct(data, &c)
409+
if err != nil {
410+
panic(err)
411+
}
412+
413+
if c.Dict != nil {
414+
t.Error("expected null Dict")
415+
}
416+
}
417+
418+
func TestNilPointer(t *testing.T) {
419+
jsonData := `{
420+
"Ptr": null
421+
}`
422+
data := make(map[string]interface{})
423+
err := json.Unmarshal([]byte(jsonData), &data)
424+
if err != nil {
425+
panic(err)
426+
}
427+
428+
var c Collection
429+
err = FillStruct(data, &c)
430+
if err != nil {
431+
panic(err)
432+
}
433+
434+
if c.Ptr != nil {
435+
t.Error("expected null Ptr")
436+
}
437+
}

config/config.go

Lines changed: 1 addition & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ type Config struct {
2828
P2P *P2P
2929
Registry *Registry
3030

31+
Remotes *Remotes
3132
CLI *CLI
3233
API *API
3334
Webapp *Webapp
@@ -88,20 +89,6 @@ func (cfg Config) SummaryString() (summary string) {
8889
return summary
8990
}
9091

91-
// ReadFromFile reads a YAML configuration file from path
92-
func ReadFromFile(path string) (cfg *Config, err error) {
93-
var data []byte
94-
95-
data, err = ioutil.ReadFile(path)
96-
if err != nil {
97-
return cfg, err
98-
}
99-
100-
cfg = &Config{}
101-
err = yaml.Unmarshal(data, cfg)
102-
return
103-
}
104-
10592
// WriteToFile encodes a configration to YAML and writes it to path
10693
func (cfg Config) WriteToFile(path string) error {
10794
// Never serialize the address mapping to the configuration file.

config/config_test.go

Lines changed: 0 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -13,20 +13,6 @@ import (
1313
"github.com/sergi/go-diff/diffmatchpatch"
1414
)
1515

16-
func TestReadFromFile(t *testing.T) {
17-
_, err := ReadFromFile("testdata/default.yaml")
18-
if err != nil {
19-
t.Errorf("error reading config: %s", err.Error())
20-
return
21-
}
22-
23-
_, err = ReadFromFile("foobar")
24-
if err == nil {
25-
t.Error("expected read from bad path to error")
26-
return
27-
}
28-
}
29-
3016
func TestWriteToFile(t *testing.T) {
3117
path := filepath.Join(os.TempDir(), "config.yaml")
3218
t.Log(path)

config/remotes.go

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
package config
2+
3+
import (
4+
"fmt"
5+
)
6+
7+
// Remotes encapsulates configuration options for remotes
8+
type Remotes struct {
9+
nameMap map[string]string
10+
}
11+
12+
// SetArbitrary is for implementing the ArbitrarySetter interface defined by base/fill_struct.go
13+
func (r *Remotes) SetArbitrary(key string, val interface{}) (err error) {
14+
if r.nameMap == nil {
15+
r.nameMap = map[string]string{}
16+
}
17+
str, ok := val.(string)
18+
if !ok {
19+
return fmt.Errorf("invalid remote value: %s", val)
20+
}
21+
r.nameMap[key] = str
22+
return nil
23+
}
24+
25+
// Get retrieves an address from the name of remote
26+
func (r *Remotes) Get(name string) (string, bool) {
27+
addr, ok := r.nameMap[name]
28+
return addr, ok
29+
}

config/testdata/simple.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ Profile:
1919
updated: "2009-02-13T23:31:30Z"
2020
RPC: null
2121
Registry: null
22+
Remotes: null
2223
Render: null
2324
Repo: null
2425
Revision: 1

lib/config.go

Lines changed: 19 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,12 @@ package lib
33
import (
44
"encoding/json"
55
"fmt"
6+
"io/ioutil"
67

78
"github.com/ghodss/yaml"
89
golog "github.com/ipfs/go-log"
910
"github.com/qri-io/ioes"
11+
"github.com/qri-io/qri/base"
1012
"github.com/qri-io/qri/config"
1113
"github.com/qri-io/qri/config/migrate"
1214
)
@@ -28,21 +30,29 @@ var SaveConfig = func() error {
2830

2931
// LoadConfig loads the global default configuration
3032
func LoadConfig(streams ioes.IOStreams, path string) (err error) {
31-
var cfg *config.Config
32-
cfg, err = config.ReadFromFile(path)
33+
var data []byte
34+
data, err = ioutil.ReadFile(path)
35+
if err != nil {
36+
return err
37+
}
3338

34-
if err == nil && cfg.Profile == nil {
35-
err = fmt.Errorf("missing profile")
36-
return
39+
fields := make(map[string]interface{})
40+
if err = yaml.Unmarshal(data, &fields); err != nil {
41+
return err
3742
}
3843

39-
if err != nil {
40-
err = fmt.Errorf(`couldn't read config file. error: %s, path: %s`, err.Error(), path)
41-
return
44+
cfg := &config.Config{}
45+
if err = base.FillStruct(fields, cfg); err != nil {
46+
return err
47+
}
48+
49+
if cfg.Profile == nil {
50+
err = fmt.Errorf("missing profile")
51+
return err
4252
}
4353

4454
// configure logging straight away
45-
if cfg != nil && cfg.Logging != nil {
55+
if cfg.Logging != nil {
4656
for name, level := range cfg.Logging.Levels {
4757
golog.SetLogLevel(name, level)
4858
}

0 commit comments

Comments
 (0)