diff --git a/base/fill_struct.go b/base/fill_struct.go index 58e943024..26ea7763b 100644 --- a/base/fill_struct.go +++ b/base/fill_struct.go @@ -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 @@ -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 } @@ -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. @@ -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) diff --git a/base/fill_struct_test.go b/base/fill_struct_test.go index 73563cbe9..6fb4769c1 100644 --- a/base/fill_struct_test.go +++ b/base/fill_struct_test.go @@ -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 { @@ -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") + } +} diff --git a/config/config.go b/config/config.go index ba863614a..773a11b1f 100644 --- a/config/config.go +++ b/config/config.go @@ -28,6 +28,7 @@ type Config struct { P2P *P2P Registry *Registry + Remotes *Remotes CLI *CLI API *API Webapp *Webapp @@ -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. diff --git a/config/config_test.go b/config/config_test.go index 18d81783f..7df5678f5 100644 --- a/config/config_test.go +++ b/config/config_test.go @@ -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) diff --git a/config/remotes.go b/config/remotes.go new file mode 100644 index 000000000..d16737b89 --- /dev/null +++ b/config/remotes.go @@ -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 +} diff --git a/config/testdata/simple.yaml b/config/testdata/simple.yaml index dbe09daff..72159cbbf 100755 --- a/config/testdata/simple.yaml +++ b/config/testdata/simple.yaml @@ -19,6 +19,7 @@ Profile: updated: "2009-02-13T23:31:30Z" RPC: null Registry: null +Remotes: null Render: null Repo: null Revision: 1 diff --git a/lib/config.go b/lib/config.go index 8af623d90..a9fce1ea6 100644 --- a/lib/config.go +++ b/lib/config.go @@ -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" ) @@ -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) }