fix(cache): harden uninitialised cache state
This commit is contained in:
parent
41150c0548
commit
fbf410e630
2 changed files with 82 additions and 12 deletions
58
cache.go
58
cache.go
|
|
@ -53,6 +53,10 @@ func New(medium coreio.Medium, baseDir string, ttl time.Duration) (*Cache, error
|
|||
baseDir = absolutePath(baseDir)
|
||||
}
|
||||
|
||||
if ttl < 0 {
|
||||
return nil, core.E("cache.New", "ttl must be >= 0", nil)
|
||||
}
|
||||
|
||||
if ttl == 0 {
|
||||
ttl = DefaultTTL
|
||||
}
|
||||
|
|
@ -73,8 +77,8 @@ 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)
|
||||
if err := c.ensureConfigured("cache.Path"); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
baseDir := absolutePath(c.baseDir)
|
||||
|
|
@ -92,8 +96,8 @@ 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)
|
||||
if err := c.ensureReady("cache.Get"); err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
path, err := c.Path(key)
|
||||
|
|
@ -130,8 +134,8 @@ 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)
|
||||
if err := c.ensureReady("cache.Set"); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
path, err := c.Path(key)
|
||||
|
|
@ -148,10 +152,18 @@ func (c *Cache) Set(key string, data any) error {
|
|||
return core.E("cache.Set", "failed to marshal cache data", dataResult.Value.(error))
|
||||
}
|
||||
|
||||
ttl := c.ttl
|
||||
if ttl < 0 {
|
||||
return core.E("cache.Set", "cache ttl must be >= 0", nil)
|
||||
}
|
||||
if ttl == 0 {
|
||||
ttl = DefaultTTL
|
||||
}
|
||||
|
||||
entry := Entry{
|
||||
Data: dataResult.Value.([]byte),
|
||||
CachedAt: time.Now(),
|
||||
ExpiresAt: time.Now().Add(c.ttl),
|
||||
ExpiresAt: time.Now().Add(ttl),
|
||||
}
|
||||
|
||||
entryResult := core.JSONMarshal(entry)
|
||||
|
|
@ -169,8 +181,8 @@ 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)
|
||||
if err := c.ensureReady("cache.Delete"); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
path, err := c.Path(key)
|
||||
|
|
@ -192,8 +204,8 @@ 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.ensureReady("cache.Clear"); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := c.medium.DeleteAll(c.baseDir); err != nil {
|
||||
|
|
@ -206,7 +218,7 @@ func (c *Cache) Clear() error {
|
|||
//
|
||||
// age := c.Age("github/acme/repos")
|
||||
func (c *Cache) Age(key string) time.Duration {
|
||||
if c == nil {
|
||||
if err := c.ensureReady("cache.Age"); err != nil {
|
||||
return -1
|
||||
}
|
||||
|
||||
|
|
@ -286,3 +298,25 @@ func currentDir() string {
|
|||
|
||||
return normalizePath(core.Env("DIR_CWD"))
|
||||
}
|
||||
|
||||
func (c *Cache) ensureConfigured(op string) error {
|
||||
if c == nil {
|
||||
return core.E(op, "cache is nil", nil)
|
||||
}
|
||||
if c.baseDir == "" {
|
||||
return core.E(op, "cache base directory is empty; construct with cache.New", nil)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Cache) ensureReady(op string) error {
|
||||
if err := c.ensureConfigured(op); err != nil {
|
||||
return err
|
||||
}
|
||||
if c.medium == nil {
|
||||
return core.E(op, "cache medium is nil; construct with cache.New", nil)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -68,6 +68,13 @@ func TestCache_New_Good(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestCache_New_Bad(t *testing.T) {
|
||||
_, err := cache.New(coreio.NewMockMedium(), "/tmp/cache-negative-ttl", -time.Second)
|
||||
if err == nil {
|
||||
t.Fatal("expected New to reject negative ttl, got nil")
|
||||
}
|
||||
}
|
||||
|
||||
func TestCache_Path_Good(t *testing.T) {
|
||||
c, _ := newTestCache(t, "/tmp/cache-path", time.Minute)
|
||||
|
||||
|
|
@ -174,6 +181,35 @@ func TestCache_NilReceiver_Good(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestCache_ZeroValue_Ugly(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 zero-value cache")
|
||||
}
|
||||
|
||||
if _, err := c.Get("x", &target); err == nil {
|
||||
t.Fatal("expected Get to fail on zero-value cache")
|
||||
}
|
||||
|
||||
if err := c.Set("x", map[string]string{"foo": "bar"}); err == nil {
|
||||
t.Fatal("expected Set to fail on zero-value cache")
|
||||
}
|
||||
|
||||
if err := c.Delete("x"); err == nil {
|
||||
t.Fatal("expected Delete to fail on zero-value cache")
|
||||
}
|
||||
|
||||
if err := c.Clear(); err == nil {
|
||||
t.Fatal("expected Clear to fail on zero-value cache")
|
||||
}
|
||||
|
||||
if age := c.Age("x"); age != -1 {
|
||||
t.Fatalf("expected Age to return -1 on zero-value cache, got %v", age)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCache_Delete_Good(t *testing.T) {
|
||||
c, _ := newTestCache(t, "/tmp/cache-delete", time.Minute)
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue