Skip to content

Commit

Permalink
feat(remote): Parse config using FillStruct. Remotes field.
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
dustmop committed Mar 15, 2019
1 parent d5f8e34 commit a2efbf3
Show file tree
Hide file tree
Showing 7 changed files with 197 additions and 48 deletions.
40 changes: 29 additions & 11 deletions base/fill_struct.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,9 @@ type ArbitrarySetter interface {
SetArbitrary(string, interface{}) error
}

// timeObj and ifaceObj are used for reflect.TypeOf
// timeObj and strObj are used for reflect.TypeOf
var timeObj time.Time
var ifaceObj interface{}
var strObj string

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

val := fields[caseMap[lowerName]]
if val == nil {
val, ok := fields[caseMap[lowerName]]
if !ok {
// Nothing to assign to this field, go to next.
continue
}
Expand Down Expand Up @@ -156,23 +156,37 @@ func putValueToPlace(val interface{}, place reflect.Value) error {
// as what's done for `pointer` below.
return fmt.Errorf("unknown struct %s", place.Type())
case reflect.Map:
if val == nil {
// If map is nil, nothing more to do.
return nil
}
m, ok := val.(map[string]interface{})
if ok {
place.Set(reflect.ValueOf(m))
if !ok {
return fmt.Errorf("need type map, value %s", val)
}
// Special case map[string]string, convert values to strings.
if place.Type().Elem() == reflect.TypeOf(strObj) {
strmap := make(map[string]string)
for k, v := range m {
strmap[k] = fmt.Sprintf("%s", v)
}
place.Set(reflect.ValueOf(strmap))
return nil
}
return fmt.Errorf("need type map, value %s", val)
place.Set(reflect.ValueOf(m))
return nil
case reflect.Slice:
if val == nil {
// If slice is nil, nothing more to do.
return nil
}
slice, ok := val.([]interface{})
if !ok {
return fmt.Errorf("need type slice, value %s", val)
}
// Get size of type of the slice to deserialize.
size := len(slice)
sliceType := reflect.TypeOf(ifaceObj)
if size > 0 {
sliceType = place.Type().Elem()
}
sliceType := place.Type().Elem()
// Construct a new, empty slice of the same size.
create := reflect.MakeSlice(reflect.SliceOf(sliceType), size, size)
// Fill in each element.
Expand All @@ -187,6 +201,10 @@ func putValueToPlace(val interface{}, place reflect.Value) error {
place.Set(create)
return nil
case reflect.Ptr:
if val == nil {
// If pointer is nil, nothing more to do.
return nil
}
// Allocate a new pointer for the sub-component to be filled in.
alloc := reflect.New(place.Type().Elem())
place.Set(alloc)
Expand Down
118 changes: 118 additions & 0 deletions base/fill_struct_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,9 @@ type Collection struct {
Age int
IsOn bool
Xpos float64
Ptr *int
Dict map[string]string
List []string
}

func (c *Collection) SetArbitrary(key string, val interface{}) error {
Expand Down Expand Up @@ -317,3 +320,118 @@ func TestFillMetaCitations(t *testing.T) {
t.Errorf("expected: c.Keywords should expect: %s, got: %s", expect, meta.Keywords)
}
}

func TestFillMapStringToString(t *testing.T) {
jsonData := `{
"Dict": {
"cat": "meow",
"dog": "bark",
"eel": "zap"
}
}`
data := make(map[string]interface{})
err := json.Unmarshal([]byte(jsonData), &data)
if err != nil {
panic(err)
}

var c Collection
err = FillStruct(data, &c)
if err != nil {
panic(err)
}

if len(c.Dict) != 3 {
t.Error("expected 3 elements in Dict")
}
if c.Dict["cat"] != "meow" {
t.Error("expected: Dict[\"cat\"] == \"meow\"")
}
}

func TestStringSlice(t *testing.T) {
jsonData := `{
"List": ["a","b","c"]
}`
data := make(map[string]interface{})
err := json.Unmarshal([]byte(jsonData), &data)
if err != nil {
panic(err)
}

var c Collection
err = FillStruct(data, &c)
if err != nil {
panic(err)
}

if len(c.List) != 3 {
t.Error("expected 3 elements in List")
}
if c.List[0] != "a" {
t.Error("expected: List[0] == \"a\"")
}
}

func TestNilStringSlice(t *testing.T) {
jsonData := `{
"List": null
}`
data := make(map[string]interface{})
err := json.Unmarshal([]byte(jsonData), &data)
if err != nil {
panic(err)
}

var c Collection
err = FillStruct(data, &c)
if err != nil {
panic(err)
}

if c.List != nil {
t.Error("expected null List")
}
}

func TestNilMap(t *testing.T) {
jsonData := `{
"Dict": null
}`
data := make(map[string]interface{})
err := json.Unmarshal([]byte(jsonData), &data)
if err != nil {
panic(err)
}

var c Collection
err = FillStruct(data, &c)
if err != nil {
panic(err)
}

if c.Dict != nil {
t.Error("expected null Dict")
}
}

func TestNilPointer(t *testing.T) {
jsonData := `{
"Ptr": null
}`
data := make(map[string]interface{})
err := json.Unmarshal([]byte(jsonData), &data)
if err != nil {
panic(err)
}

var c Collection
err = FillStruct(data, &c)
if err != nil {
panic(err)
}

if c.Ptr != nil {
t.Error("expected null Ptr")
}
}
15 changes: 1 addition & 14 deletions config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ type Config struct {
P2P *P2P
Registry *Registry

Remotes *Remotes
CLI *CLI
API *API
Webapp *Webapp
Expand Down Expand Up @@ -88,20 +89,6 @@ func (cfg Config) SummaryString() (summary string) {
return summary
}

// ReadFromFile reads a YAML configuration file from path
func ReadFromFile(path string) (cfg *Config, err error) {
var data []byte

data, err = ioutil.ReadFile(path)
if err != nil {
return cfg, err
}

cfg = &Config{}
err = yaml.Unmarshal(data, cfg)
return
}

// WriteToFile encodes a configration to YAML and writes it to path
func (cfg Config) WriteToFile(path string) error {
// Never serialize the address mapping to the configuration file.
Expand Down
14 changes: 0 additions & 14 deletions config/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,20 +13,6 @@ import (
"github.com/sergi/go-diff/diffmatchpatch"
)

func TestReadFromFile(t *testing.T) {
_, err := ReadFromFile("testdata/default.yaml")
if err != nil {
t.Errorf("error reading config: %s", err.Error())
return
}

_, err = ReadFromFile("foobar")
if err == nil {
t.Error("expected read from bad path to error")
return
}
}

func TestWriteToFile(t *testing.T) {
path := filepath.Join(os.TempDir(), "config.yaml")
t.Log(path)
Expand Down
29 changes: 29 additions & 0 deletions config/remotes.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package config

import (
"fmt"
)

// Remotes encapsulates configuration options for remotes
type Remotes struct {
nameMap map[string]string
}

// SetArbitrary is for implementing the ArbitrarySetter interface defined by base/fill_struct.go
func (r *Remotes) SetArbitrary(key string, val interface{}) (err error) {
if r.nameMap == nil {
r.nameMap = map[string]string{}
}
str, ok := val.(string)
if !ok {
return fmt.Errorf("invalid remote value: %s", val)
}
r.nameMap[key] = str
return nil
}

// Get retrieves an address from the name of remote
func (r *Remotes) Get(name string) (string, bool) {
addr, ok := r.nameMap[name]
return addr, ok
}
1 change: 1 addition & 0 deletions config/testdata/simple.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ Profile:
updated: "2009-02-13T23:31:30Z"
RPC: null
Registry: null
Remotes: null
Render: null
Repo: null
Revision: 1
Expand Down
28 changes: 19 additions & 9 deletions lib/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,12 @@ package lib
import (
"encoding/json"
"fmt"
"io/ioutil"

"github.com/ghodss/yaml"
golog "github.com/ipfs/go-log"
"github.com/qri-io/ioes"
"github.com/qri-io/qri/base"
"github.com/qri-io/qri/config"
"github.com/qri-io/qri/config/migrate"
)
Expand All @@ -28,21 +30,29 @@ var SaveConfig = func() error {

// LoadConfig loads the global default configuration
func LoadConfig(streams ioes.IOStreams, path string) (err error) {
var cfg *config.Config
cfg, err = config.ReadFromFile(path)
var data []byte
data, err = ioutil.ReadFile(path)
if err != nil {
return err
}

if err == nil && cfg.Profile == nil {
err = fmt.Errorf("missing profile")
return
fields := make(map[string]interface{})
if err = yaml.Unmarshal(data, &fields); err != nil {
return err
}

if err != nil {
err = fmt.Errorf(`couldn't read config file. error: %s, path: %s`, err.Error(), path)
return
cfg := &config.Config{}
if err = base.FillStruct(fields, cfg); err != nil {
return err
}

if cfg.Profile == nil {
err = fmt.Errorf("missing profile")
return err
}

// configure logging straight away
if cfg != nil && cfg.Logging != nil {
if cfg.Logging != nil {
for name, level := range cfg.Logging.Levels {
golog.SetLogLevel(name, level)
}
Expand Down

0 comments on commit a2efbf3

Please sign in to comment.