flake-update-20260505
 1// Package cache provides file-based TTL caching for API responses.
 2package cache
 3
 4import (
 5	"crypto/sha256"
 6	"encoding/json"
 7	"fmt"
 8	"os"
 9	"path/filepath"
10	"time"
11)
12
13// Cache provides file-based TTL caching.
14type Cache struct {
15	dir string
16	ttl time.Duration
17}
18
19type entry struct {
20	ExpiresAt time.Time       `json:"expires_at"`
21	Data      json.RawMessage `json:"data"`
22}
23
24// New creates a cache with the given directory and TTL.
25func New(dir string, ttl time.Duration) *Cache {
26	return &Cache{dir: dir, ttl: ttl}
27}
28
29// Get retrieves a cached value. Returns false if missing or expired.
30func (c *Cache) Get(key string, dest any) bool {
31	path := c.path(key)
32	data, err := os.ReadFile(path)
33	if err != nil {
34		return false
35	}
36
37	var e entry
38	if err := json.Unmarshal(data, &e); err != nil {
39		os.Remove(path)
40		return false
41	}
42
43	if time.Now().After(e.ExpiresAt) {
44		os.Remove(path)
45		return false
46	}
47
48	return json.Unmarshal(e.Data, dest) == nil
49}
50
51// Set stores a value in the cache.
52func (c *Cache) Set(key string, value any) error {
53	data, err := json.Marshal(value)
54	if err != nil {
55		return err
56	}
57
58	e := entry{
59		ExpiresAt: time.Now().Add(c.ttl),
60		Data:      data,
61	}
62
63	out, err := json.Marshal(e)
64	if err != nil {
65		return err
66	}
67
68	if err := os.MkdirAll(c.dir, 0755); err != nil {
69		return err
70	}
71
72	return os.WriteFile(c.path(key), out, 0644)
73}
74
75// GetOrFetch retrieves from cache, or calls fetch and caches the result.
76func GetOrFetch[T any](c *Cache, key string, fetch func() (T, error)) (T, error) {
77	var result T
78	if c != nil && c.Get(key, &result) {
79		return result, nil
80	}
81
82	result, err := fetch()
83	if err != nil {
84		return result, err
85	}
86
87	if c != nil {
88		c.Set(key, result)
89	}
90	return result, nil
91}
92
93func (c *Cache) path(key string) string {
94	hash := sha256.Sum256([]byte(key))
95	return filepath.Join(c.dir, fmt.Sprintf("%x.json", hash[:12]))
96}