diff --git a/config.go b/config.go index 35c384c..50af7d9 100644 --- a/config.go +++ b/config.go @@ -101,8 +101,28 @@ func New(opts ...Option) (*Config, error) { return c, nil } +func configTypeForPath(path string) (string, error) { + ext := strings.ToLower(filepath.Ext(path)) + if ext == "" && filepath.Base(path) == ".env" { + return "env", nil + } + + switch ext { + case ".yaml", ".yml": + return "yaml", nil + case ".json": + return "json", nil + case ".toml": + return "toml", nil + case ".env": + return "env", nil + default: + return "", coreerr.E("config.configTypeForPath", "unsupported config file type: "+path, nil) + } +} + // LoadFile reads a configuration file from the given medium and path and merges it into the current config. -// It supports YAML and environment files (.env). +// It supports YAML, JSON, TOML, and dotenv files (.env). func (c *Config) LoadFile(m coreio.Medium, path string) error { c.mu.Lock() defer c.mu.Unlock() @@ -112,12 +132,9 @@ func (c *Config) LoadFile(m coreio.Medium, path string) error { return coreerr.E("config.LoadFile", fmt.Sprintf("failed to read config file: %s", path), err) } - ext := filepath.Ext(path) - configType := "yaml" - if ext == "" && filepath.Base(path) == ".env" { - configType = "env" - } else if ext != "" { - configType = strings.TrimPrefix(ext, ".") + configType, err := configTypeForPath(path) + if err != nil { + return coreerr.E("config.LoadFile", "failed to determine config file type: "+path, err) } // Load into file-backed viper diff --git a/config_test.go b/config_test.go index 0444671..cfc376e 100644 --- a/config_test.go +++ b/config_test.go @@ -216,6 +216,44 @@ func TestLoad_InvalidYAML_Bad(t *testing.T) { assert.Contains(t, err.Error(), "failed to parse config file") } +func TestConfig_LoadFile_JSON_Good(t *testing.T) { + m := coreio.NewMockMedium() + m.Files["/tmp/test/config.json"] = `{"app":{"name":"core"}}` + + cfg, err := New(WithMedium(m), WithPath("/tmp/test/config.json")) + assert.NoError(t, err) + + var name string + err = cfg.Get("app.name", &name) + assert.NoError(t, err) + assert.Equal(t, "core", name) +} + +func TestConfig_LoadFile_TOML_Good(t *testing.T) { + m := coreio.NewMockMedium() + m.Files["/tmp/test/config.toml"] = "app = { name = \"core\" }\n" + + cfg, err := New(WithMedium(m), WithPath("/tmp/test/config.toml")) + assert.NoError(t, err) + + var name string + err = cfg.Get("app.name", &name) + assert.NoError(t, err) + assert.Equal(t, "core", name) +} + +func TestConfig_LoadFile_Unsupported_Bad(t *testing.T) { + m := coreio.NewMockMedium() + + cfg, err := New(WithMedium(m), WithPath("/tmp/test/config.txt")) + assert.NoError(t, err) + + m.Files["/tmp/test/config.txt"] = "app.name=core" + err = cfg.LoadFile(m, "/tmp/test/config.txt") + assert.Error(t, err) + assert.Contains(t, err.Error(), "unsupported config file type") +} + func TestSave_Good(t *testing.T) { m := coreio.NewMockMedium()