diff --git a/cmd/root.go b/cmd/root.go index 0d92fcf..7d2ed44 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -95,7 +95,10 @@ func runDiff(cmd *cobra.Command, args []string) error { } } - cfg := config.Load() +cfg, err := config.Load() +if err != nil { + return fmt.Errorf("config: %w", err) +} t := resolveTheme(cfg) styles := ui.NewStyles(t) @@ -149,7 +152,10 @@ func runCommit(cmd *cobra.Command, args []string) error { return nil } - cfg := config.Load() +cfg, err := config.Load() +if err != nil { + return fmt.Errorf("config: %w", err) +} t := resolveTheme(cfg) styles := ui.NewStyles(t) @@ -170,7 +176,10 @@ func runLog(cmd *cobra.Command, args []string) error { return nil } - cfg := config.Load() +cfg, err := config.Load() +if err != nil { + return fmt.Errorf("config: %w", err) +} t := resolveTheme(cfg) styles := ui.NewStyles(t) diff --git a/internal/config/config.go b/internal/config/config.go index fddea70..f382782 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -2,6 +2,8 @@ package config import ( "encoding/json" + "errors" + "fmt" "os" "path/filepath" ) @@ -25,24 +27,30 @@ func Default() Config { } // Load reads config from ~/.config/differ/config.json. -// Returns defaults if file doesn't exist. -func Load() Config { +// Returns defaults and a nil error if the file doesn't exist; otherwise returns defaults plus an error on failures. +func Load() (Config, error) { path, err := configPath() if err != nil { - return Default() + return Default(), fmt.Errorf("could not resolve config path: %w", err) } return LoadFrom(path) } -// LoadFrom reads config from the given path. Returns defaults on error. -func LoadFrom(path string) Config { +// LoadFrom reads config from the given path. +// Returns defaults and a nil error if the file doesn't exist; otherwise returns defaults plus an error on failures (e.g., read/parse errors). +func LoadFrom(path string) (Config, error) { cfg := Default() data, err := os.ReadFile(path) if err != nil { - return cfg + if errors.Is(err, os.ErrNotExist) { + return cfg, nil + } + return cfg, fmt.Errorf("could not read config file %q: %w", path, err) } - _ = json.Unmarshal(data, &cfg) - return cfg + if err := json.Unmarshal(data, &cfg); err != nil { + return cfg, fmt.Errorf("invalid config file %q: %w; fix the JSON or remove the file to use defaults", path, err) + } + return cfg, nil } // Save writes config to ~/.config/differ/config.json. diff --git a/internal/config/config_test.go b/internal/config/config_test.go index 3cfa8af..7f26e1f 100644 --- a/internal/config/config_test.go +++ b/internal/config/config_test.go @@ -3,6 +3,7 @@ package config import ( "os" "path/filepath" + "strings" "testing" ) @@ -38,7 +39,10 @@ func TestSaveAndLoad(t *testing.T) { t.Fatalf("SaveTo: %v", err) } - got := LoadFrom(path) + got, err := LoadFrom(path) + if err != nil { + t.Fatalf("LoadFrom: %v", err) + } if got.Theme != "light" { t.Errorf("Theme=%q, want light", got.Theme) } @@ -56,7 +60,10 @@ func TestSaveAndLoad(t *testing.T) { func TestLoad_NoFile(t *testing.T) { t.Parallel() path := filepath.Join(t.TempDir(), "nonexistent.json") - cfg := LoadFrom(path) + cfg, err := LoadFrom(path) + if err != nil { + t.Fatalf("LoadFrom missing file: %v", err) + } if cfg.Theme != "dark" || cfg.TabWidth != 4 { t.Error("missing file should return defaults") } @@ -69,7 +76,13 @@ func TestLoad_InvalidJSON(t *testing.T) { t.Fatal(err) } - cfg := LoadFrom(path) + cfg, err := LoadFrom(path) + if err == nil { + t.Fatal("LoadFrom should return an error for invalid JSON") + } + if !strings.HasPrefix(err.Error(), "invalid config file") { + t.Fatalf("LoadFrom error = %q, want invalid config prefix", err.Error()) + } if cfg.Theme != "dark" || cfg.TabWidth != 4 { t.Error("invalid JSON should return defaults") }