diff --git a/cmd/olliesrv/internal/agent/history.go b/cmd/olliesrv/internal/agent/history.go index b7afdc4..176a14a 100644 --- a/cmd/olliesrv/internal/agent/history.go +++ b/cmd/olliesrv/internal/agent/history.go @@ -2,7 +2,6 @@ package agent import ( "context" - "encoding/json" "fmt" "os" "strings" @@ -437,8 +436,10 @@ func (s *History) stripCold(ctx context.Context, b backend.Backend) (backend.Usa } estimated := !reported - summaries := make(map[string]string) - json.Unmarshal([]byte(resp.String()), &summaries) + summaries, err := parseColdSummaries(resp.String()) + if err != nil { + return usage, estimated + } // Apply summaries back into history. for _, it := range items { diff --git a/cmd/olliesrv/internal/agent/history_summary.go b/cmd/olliesrv/internal/agent/history_summary.go new file mode 100644 index 0000000..197aeda --- /dev/null +++ b/cmd/olliesrv/internal/agent/history_summary.go @@ -0,0 +1,26 @@ +package agent + +import ( + "encoding/json" + "fmt" + "strings" +) + +func parseColdSummaries(content string) (map[string]string, error) { + content = strings.TrimSpace(content) + if strings.HasPrefix(content, "```") && strings.HasSuffix(content, "```") { + content = strings.TrimSpace(strings.TrimSuffix(strings.TrimPrefix(content, "```"), "```")) + if newline := strings.IndexByte(content, '\n'); newline >= 0 && strings.EqualFold(strings.TrimSpace(content[:newline]), "json") { + content = strings.TrimSpace(content[newline+1:]) + } + } + + var summaries map[string]string + if err := json.Unmarshal([]byte(content), &summaries); err != nil { + return nil, fmt.Errorf("parse cold summaries: %w", err) + } + if summaries == nil { + return nil, fmt.Errorf("parse cold summaries: expected JSON object") + } + return summaries, nil +} diff --git a/cmd/olliesrv/internal/agent/history_summary_test.go b/cmd/olliesrv/internal/agent/history_summary_test.go new file mode 100644 index 0000000..51edc0b --- /dev/null +++ b/cmd/olliesrv/internal/agent/history_summary_test.go @@ -0,0 +1,29 @@ +package agent + +import "testing" + +func TestParseColdSummariesJSON(t *testing.T) { + got, err := parseColdSummaries(`{"0":"summary","1":"another"}`) + if err != nil { + t.Fatal(err) + } + if got["0"] != "summary" || got["1"] != "another" { + t.Fatalf("unexpected summaries: %#v", got) + } +} + +func TestParseColdSummariesFencedJSON(t *testing.T) { + got, err := parseColdSummaries("```json\n{\"0\":\"summary\"}\n```") + if err != nil { + t.Fatal(err) + } + if got["0"] != "summary" { + t.Fatalf("unexpected summaries: %#v", got) + } +} + +func TestParseColdSummariesRejectsInvalidJSON(t *testing.T) { + if _, err := parseColdSummaries("not JSON"); err == nil { + t.Fatal("parseColdSummaries accepted invalid JSON") + } +}