From 41150c0548346e1ec1a8233b3a598fde458b4f98 Mon Sep 17 00:00:00 2001 From: Virgil Date: Mon, 30 Mar 2026 00:31:21 +0000 Subject: [PATCH] fix(cache): harden nil receiver handling --- cache.go | 28 ++++++++++++++++++++++++++-- cache_test.go | 29 +++++++++++++++++++++++++++++ 2 files changed, 55 insertions(+), 2 deletions(-) diff --git a/cache.go b/cache.go index 4d5e92d..c1e4631 100644 --- a/cache.go +++ b/cache.go @@ -29,8 +29,8 @@ type Cache struct { // Entry is the serialized cache record written to the backing Medium. type Entry struct { Data json.RawMessage `json:"data"` - CachedAt time.Time `json:"cached_at"` - ExpiresAt time.Time `json:"expires_at"` + CachedAt time.Time `json:"cached_at"` + ExpiresAt time.Time `json:"expires_at"` } // New creates a cache and applies default Medium, base directory, and TTL values @@ -73,6 +73,10 @@ func New(medium coreio.Medium, baseDir string, ttl time.Duration) (*Cache, error // // path, err := c.Path("github/acme/repos") func (c *Cache) Path(key string) (string, error) { + if c == nil { + return "", core.E("cache.Path", "cache is nil", nil) + } + baseDir := absolutePath(c.baseDir) path := absolutePath(core.JoinPath(baseDir, key+".json")) pathPrefix := normalizePath(core.Concat(baseDir, pathSeparator())) @@ -88,6 +92,10 @@ func (c *Cache) Path(key string) (string, error) { // // found, err := c.Get("github/acme/repos", &repos) func (c *Cache) Get(key string, dest any) (bool, error) { + if c == nil { + return false, core.E("cache.Get", "cache is nil", nil) + } + path, err := c.Path(key) if err != nil { return false, err @@ -122,6 +130,10 @@ func (c *Cache) Get(key string, dest any) (bool, error) { // // err := c.Set("github/acme/repos", repos) func (c *Cache) Set(key string, data any) error { + if c == nil { + return core.E("cache.Set", "cache is nil", nil) + } + path, err := c.Path(key) if err != nil { return err @@ -157,6 +169,10 @@ func (c *Cache) Set(key string, data any) error { // // err := c.Delete("github/acme/repos") func (c *Cache) Delete(key string) error { + if c == nil { + return core.E("cache.Delete", "cache is nil", nil) + } + path, err := c.Path(key) if err != nil { return err @@ -176,6 +192,10 @@ func (c *Cache) Delete(key string) error { // // err := c.Clear() func (c *Cache) Clear() error { + if c == nil { + return core.E("cache.Clear", "cache is nil", nil) + } + if err := c.medium.DeleteAll(c.baseDir); err != nil { return core.E("cache.Clear", "failed to clear cache", err) } @@ -186,6 +206,10 @@ func (c *Cache) Clear() error { // // age := c.Age("github/acme/repos") func (c *Cache) Age(key string) time.Duration { + if c == nil { + return -1 + } + path, err := c.Path(key) if err != nil { return -1 diff --git a/cache_test.go b/cache_test.go index 1dd7ab1..e65f101 100644 --- a/cache_test.go +++ b/cache_test.go @@ -145,6 +145,35 @@ func TestCache_Age_Good(t *testing.T) { } } +func TestCache_NilReceiver_Good(t *testing.T) { + var c *cache.Cache + var target map[string]string + + if _, err := c.Path("x"); err == nil { + t.Fatal("expected Path to fail on nil receiver") + } + + if _, err := c.Get("x", &target); err == nil { + t.Fatal("expected Get to fail on nil receiver") + } + + if err := c.Set("x", map[string]string{"foo": "bar"}); err == nil { + t.Fatal("expected Set to fail on nil receiver") + } + + if err := c.Delete("x"); err == nil { + t.Fatal("expected Delete to fail on nil receiver") + } + + if err := c.Clear(); err == nil { + t.Fatal("expected Clear to fail on nil receiver") + } + + if age := c.Age("x"); age != -1 { + t.Fatalf("expected Age to return -1 on nil receiver, got %v", age) + } +} + func TestCache_Delete_Good(t *testing.T) { c, _ := newTestCache(t, "/tmp/cache-delete", time.Minute)