Skip to content

Commit

Permalink
better errors, better examples, minor optimization
Browse files Browse the repository at this point in the history
  • Loading branch information
parMaster committed May 31, 2024
1 parent 1744ddf commit c7d03f8
Show file tree
Hide file tree
Showing 4 changed files with 49 additions and 44 deletions.
27 changes: 18 additions & 9 deletions examples/example.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package main

import (
"errors"
"fmt"
"time"

Expand All @@ -14,27 +15,35 @@ func demo() {

cache := mcache.NewCache[string]()

cache.Set("save indefinitely", "value without expiration", 0) // set value without expiration
fmt.Println("Setting key \"save indefinitely\" with value \"value without expiration\" and ttl 0")
cache.Set("save indefinitely", "value without expiration", 0) // set value without expiration

fmt.Println("Setting key \"save for 1 second\" with value \"value will expire in 1 second\" and ttl 1 second")
cache.Set("save for 1 second", "value will expire in 1 second", 1*time.Second) // set value with expiration in 1 second

fmt.Printf("\nRetrieving \"no such key\":\n")
exists, err := cache.Has("no such key")
// either exists or error can be checked
if err != nil {
// possible errors:
// mcache.ErrKeyNotFound
// mcache.ErrExpired
fmt.Println(err)
}
if !exists {
fmt.Println("key doen't exist or expired")
fmt.Printf("\tError retrieving \"no such key\": %v\n", err)
fmt.Printf("\tError is \"mcache.ErrKeyNotFound\": %t\n", errors.Is(err, mcache.ErrKeyNotFound))
}
fmt.Printf("\t\"no such key\" exists: %t\n", exists)

v, _ := cache.Get("save indefinitely")
fmt.Println(v)
fmt.Printf("\nRetrieving \"save indefinitely\":\n")
v, err := cache.Get("save indefinitely")
fmt.Printf("\t\"save indefinitely\" = %v\n", v)
fmt.Printf("\tError retrieving \"save indefinitely\": %v\n", err)

time.Sleep(1 * time.Second)
v, _ = cache.Get("save for 1 second")
fmt.Println(v) // <nil> because key expired
fmt.Printf("\nRetrieving \"save for 1 second\" after 1+ second pause:\n")
v, err = cache.Get("save for 1 second")
fmt.Printf("\t\"save for 1 second\" value = %v\n", v)
fmt.Printf("\tError retrieving \"save for 1 second\": %v\n", err)
fmt.Printf("\tError is \"mcache.ErrExpired\": %t\n", errors.Is(err, mcache.ErrExpired))

}

Expand Down
4 changes: 2 additions & 2 deletions examples/readme_example/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ func main() {
v, err := cache.Get("key")
if err != nil {
// either error can be checked
fmt.Println(err)
fmt.Printf("Error: %v\n", err)
}
if v != "" {
// or value can be checked for "empty" type value
Expand All @@ -29,7 +29,7 @@ func main() {
// possible errors:
// mcache.ErrKeyNotFound - key doesn't exist
// mcache.ErrExpired - key expired
fmt.Println(err)
fmt.Printf("Error: %v\n", err)
}
if exists {
fmt.Println("key exists")
Expand Down
51 changes: 24 additions & 27 deletions main.go
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
package mcache

import (
"fmt"
"errors"
"sync"
"time"
)

// Errors for cache
const (
ErrKeyNotFound = "key not found"
ErrKeyExists = "key already exists"
ErrExpired = "key expired"
var (
ErrKeyNotFound = errors.New("key not found")
ErrKeyExists = errors.New("key already exists")
ErrExpired = errors.New("key expired")
)

// CacheItem is a struct for cache item
Expand Down Expand Up @@ -48,20 +48,18 @@ func NewCache[T any](options ...func(*Cache[T])) *Cache[T] {
return c
}

// Set is a method for setting key-value pair
// If key already exists, and it's not expired, return error
// If key already exists, but it's expired, set new value and return nil
// If key doesn't exist, set new value and return nil
// Set is a method for setting key-value pair.
// If key already exists, and it's not expired, return error.
// If key already exists, but it's expired, set new value and return nil.
// If key doesn't exist, set new value and return nil.
// If ttl is 0, set value without expiration
func (c *Cache[T]) Set(key string, value T, ttl time.Duration) error {
var zeroTime time.Time

c.mx.RLock()
cached, ok := c.data[key]
c.mx.RUnlock()
if ok {
if cached.expiration == zeroTime || cached.expiration.After(time.Now().Add(ttl)) {
return fmt.Errorf(ErrKeyExists)
if cached.expiration.IsZero() || cached.expiration.After(time.Now().Add(ttl)) {
return ErrKeyExists
}
}

Expand All @@ -80,9 +78,9 @@ func (c *Cache[T]) Set(key string, value T, ttl time.Duration) error {
return nil
}

// Get is a method for getting value by key
// If key doesn't exist, return error
// If key exists, but it's expired, delete key, return zeroa value and error
// Get is a method for getting value by key.
// If key doesn't exist, return error.
// If key exists, but it's expired, delete key, return zero value and error.
// If key exists and it's not expired, return value
func (c *Cache[T]) Get(key string) (T, error) {
var none T
Expand All @@ -99,30 +97,29 @@ func (c *Cache[T]) Get(key string) (T, error) {
return c.data[key].value, nil
}

// Has is a method for checking if key exists.
// Has checks if key exists and if it's expired.
// If key doesn't exist, return false.
// If key exists, but it's expired, return false and delete key.
// If key exists and it's not expired, return true.
// If key exists and it's not expired, return true
func (c *Cache[T]) Has(key string) (bool, error) {
c.mx.RLock()
d, ok := c.data[key]
c.mx.RUnlock()
if !ok {
return false, fmt.Errorf(ErrKeyNotFound)
return false, ErrKeyNotFound
}

var zeroTime time.Time
if d.expiration != zeroTime && d.expiration.Before(time.Now()) {
if !d.expiration.IsZero() && d.expiration.Before(time.Now()) {
c.mx.Lock()
delete(c.data, key)
c.mx.Unlock()
return false, fmt.Errorf(ErrExpired)
return false, ErrExpired
}

return true, nil
}

// Del is a method for deleting key-value pair
// Del deletes a key-value pair
func (c *Cache[T]) Del(key string) error {
_, err := c.Has(key)
if err != nil {
Expand All @@ -135,20 +132,20 @@ func (c *Cache[T]) Del(key string) error {
return nil
}

// Clear is a method for clearing cache
// Clears cache by replacing it with a clean one
func (c *Cache[T]) Clear() error {
c.mx.Lock()
c.data = make(map[string]CacheItem[T])
c.mx.Unlock()
return nil
}

// Cleanup is a method for deleting expired keys
// Cleanup deletes expired keys from cache
func (c *Cache[T]) Cleanup() {
now := time.Now()
c.mx.Lock()
var zeroTime time.Time
for k, v := range c.data {
if v.expiration != zeroTime && v.expiration.Before(time.Now()) {
if !v.expiration.IsZero() && v.expiration.Before(now) {
delete(c.data, k)
}
}
Expand Down
11 changes: 5 additions & 6 deletions main_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,7 @@ type testItem struct {
}

func Test_SimpleTest_Mcache(t *testing.T) {
var c Cacher[string] // linter:ignore S1021
c = NewCache[string]()
var c Cacher[string] = NewCache[string]()

assert.NotNil(t, c)
assert.IsType(t, &Cache[string]{}, c)
Expand Down Expand Up @@ -46,7 +45,7 @@ func Test_SimpleTest_Mcache(t *testing.T) {

_, err := c.Get(noSuchKey)
assert.Error(t, err)
assert.Equal(t, ErrKeyNotFound, err.Error())
assert.ErrorIs(t, ErrKeyNotFound, err)

for _, item := range testItems {
has, err := c.Has(item.key)
Expand All @@ -58,7 +57,7 @@ func Test_SimpleTest_Mcache(t *testing.T) {

has, err := c.Has(testItems[1].key)
assert.Error(t, err)
assert.Equal(t, ErrExpired, err.Error())
assert.ErrorIs(t, ErrExpired, err)
assert.False(t, has)

testItems = append(testItems[2:], testItems[0])
Expand All @@ -71,7 +70,7 @@ func Test_SimpleTest_Mcache(t *testing.T) {
has, err := c.Has(item.key)
assert.False(t, has)
assert.Error(t, err)
assert.Equal(t, ErrKeyNotFound, err.Error())
assert.ErrorIs(t, ErrKeyNotFound, err)
}

c.Set("key", "value", time.Second*1)
Expand All @@ -86,7 +85,7 @@ func Test_SimpleTest_Mcache(t *testing.T) {

err = c.Set("key", "not a newer value", 1)
if err != nil {
assert.Equal(t, ErrKeyExists, err.Error())
assert.ErrorIs(t, ErrKeyExists, err)
}
time.Sleep(time.Second * 2)
err = c.Set("key", "even newer value", time.Second*1)
Expand Down

0 comments on commit c7d03f8

Please sign in to comment.