fix(config): validate config file types explicitly

Co-Authored-By: Virgil <virgil@lethean.io>
This commit is contained in:
Virgil 2026-03-31 18:06:44 +00:00
parent 1f081bcd92
commit 6dd94aff4c
2 changed files with 62 additions and 7 deletions

View file

@ -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

View file

@ -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()