Skip to content

Commit b548945

Browse files
committed
add support for generics
1 parent 24011a4 commit b548945

3 files changed

Lines changed: 59 additions & 73 deletions

File tree

README.md

Lines changed: 3 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
[![Go Build & Test](https://github.com/mycrEEpy/mempot/actions/workflows/build.yml/badge.svg)](https://github.com/mycrEEpy/mempot/actions/workflows/build.yml)
66
[![Go Coverage](https://github.com/mycreepy/mempot/wiki/coverage.svg)](https://raw.githack.com/wiki/mycreepy/mempot/coverage.html)
77

8-
`mempot` is a small and easy memory cache for Go.
8+
`mempot` is a small and easy generic memory cache for Go.
99

1010
## Usage
1111

@@ -19,20 +19,15 @@ import (
1919
)
2020

2121
func main() {
22-
cache := mempot.New()
22+
cache := mempot.NewCache[string, string](mempot.Config{})
2323

2424
cache.Set("foo", "bar")
2525

2626
item, ok := cache.Get("foo")
2727
if !ok {
2828
panic("item not found or expired")
2929
}
30-
31-
data, ok := item.Data.(string)
32-
if !ok {
33-
panic("item data is not a string")
34-
}
3530

36-
fmt.Println(data)
31+
fmt.Println(item.Data)
3732
}
3833
```

mempot.go

Lines changed: 46 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,21 @@ import (
77
"time"
88
)
99

10+
// Config allows to alter the configuration of a Cache.
11+
//
12+
// Context is by default an empty background context.
13+
// DefaultTTL is by default 15 minutes.
14+
// CleanupInterval is by default 5 minutes.
15+
type Config struct {
16+
Context context.Context
17+
DefaultTTL time.Duration
18+
CleanupInterval time.Duration
19+
}
20+
1021
// Cache holds the data you want to cache in memory.
11-
type Cache struct {
22+
type Cache[K comparable, T any] struct {
1223
mut sync.RWMutex
13-
data map[string]Item
24+
data map[K]Item[T]
1425

1526
defaultTTL time.Duration
1627
cleanupInterval time.Duration
@@ -19,137 +30,118 @@ type Cache struct {
1930
cancel context.CancelFunc
2031
}
2132

22-
// Item is a unit of data which can be cached and has an expiration as Unix epoch.
23-
type Item struct {
24-
Data any
33+
// Item is a unit of typed data which can be cached and has an expiration as Unix epoch.
34+
type Item[T any] struct {
35+
Data T
2536
TTL int64
2637
}
2738

2839
// Expired returns true if the data of the Item has expired.
29-
func (i *Item) Expired() bool {
40+
func (i *Item[T]) Expired() bool {
3041
return time.Now().Unix() > i.TTL
3142
}
3243

33-
// Option can alter the behavior of a Cache.
34-
type Option func(*Cache)
35-
36-
// New create a new Cache instance.
37-
func New(opts ...Option) *Cache {
38-
c := &Cache{
39-
data: make(map[string]Item),
44+
// NewCache create a new Cache instance with K as key and T as data.
45+
func NewCache[K comparable, T any](cfg Config) *Cache[K, T] {
46+
c := &Cache[K, T]{
47+
data: make(map[K]Item[T]),
4048
defaultTTL: time.Minute * 15,
4149
cleanupInterval: time.Minute * 5,
4250
}
4351

4452
c.ctx, c.cancel = context.WithCancel(context.Background())
4553

46-
for _, opt := range opts {
47-
opt(c)
54+
if cfg.Context != nil {
55+
c.ctx = cfg.Context
4856
}
4957

50-
go c.cleanup()
51-
52-
return c
53-
}
54-
55-
// WithDefaultTTL changes the default time-to-live for an Item in the Cache.
56-
// Default is 15m.
57-
func WithDefaultTTL(ttl time.Duration) Option {
58-
return func(c *Cache) {
59-
c.defaultTTL = ttl
58+
if cfg.DefaultTTL > 0 {
59+
c.defaultTTL = cfg.DefaultTTL
6060
}
61-
}
6261

63-
// WithCleanupInterval changes the default interval at which expired Items are removed from the Cache.
64-
// Default is 5m.
65-
func WithCleanupInterval(interval time.Duration) Option {
66-
return func(c *Cache) {
67-
c.cleanupInterval = interval
62+
if cfg.CleanupInterval > 0 {
63+
c.cleanupInterval = cfg.CleanupInterval
6864
}
69-
}
7065

71-
// WithContext adds a custom context for the Cache.
72-
// If the context is canceled, the cleanup ticker will stop.
73-
func WithContext(ctx context.Context) Option {
74-
return func(c *Cache) {
75-
c.ctx = ctx
76-
}
66+
go c.cleanup()
67+
68+
return c
7769
}
7870

7971
// Set will add an Item to the Cache with the default time-to-live.
80-
func (c *Cache) Set(key string, value any) {
72+
func (c *Cache[K, T]) Set(key K, value T) {
8173
c.SetWithTTL(key, value, c.defaultTTL)
8274
}
8375

8476
// SetWithTTL will add an Item to the Cache with the given time-to-live.
85-
func (c *Cache) SetWithTTL(key string, data any, ttl time.Duration) {
77+
func (c *Cache[K, T]) SetWithTTL(key K, data T, ttl time.Duration) {
8678
c.mut.Lock()
87-
c.data[key] = Item{Data: data, TTL: time.Now().Add(ttl).Unix()}
79+
c.data[key] = Item[T]{Data: data, TTL: time.Now().Add(ttl).Unix()}
8880
c.mut.Unlock()
8981
}
9082

9183
// Get returns an Item and true if the Item was found in the Cache and has not been expired.
9284
// An empty Item and false is returned when the Item was not found or has been expired.
93-
func (c *Cache) Get(key string) (Item, bool) {
85+
func (c *Cache[K, T]) Get(key K) (Item[T], bool) {
9486
c.mut.RLock()
9587
item, ok := c.data[key]
9688
c.mut.RUnlock()
9789

9890
if item.Expired() {
99-
return Item{}, false
91+
return Item[T]{}, false
10092
}
10193

10294
return item, ok
10395
}
10496

10597
// QueryFunc is a function to retrieve data which will be put into the Cache.
106-
type QueryFunc func(key string) (any, error)
98+
type QueryFunc[K comparable, T any] func(key K) (T, error)
10799

108100
// Remember tries to get the Item from the Cache, if the Item is not found or expired QueryFunc is called
109101
// to retrieve the data from source and put it into the Cache.
110-
func (c *Cache) Remember(key string, query QueryFunc) (Item, error) {
102+
func (c *Cache[K, T]) Remember(key K, query QueryFunc[K, T]) (Item[T], error) {
111103
return c.RememberWithTTL(key, query, c.defaultTTL)
112104
}
113105

114106
// RememberWithTTL tries to get the Item from the Cache, if the Item is not found or expired QueryFunc is called
115107
// to retrieve the data from source and put it into the Cache with the given time-to-live.
116-
func (c *Cache) RememberWithTTL(key string, query QueryFunc, ttl time.Duration) (Item, error) {
108+
func (c *Cache[K, T]) RememberWithTTL(key K, query QueryFunc[K, T], ttl time.Duration) (Item[T], error) {
117109
item, ok := c.Get(key)
118110
if ok {
119111
return item, nil
120112
}
121113

122114
data, err := query(key)
123115
if err != nil {
124-
return Item{}, fmt.Errorf("failed to query data: %w", err)
116+
return Item[T]{}, fmt.Errorf("failed to query data: %w", err)
125117
}
126118

127119
c.SetWithTTL(key, data, ttl)
128120

129-
return Item{Data: data, TTL: time.Now().Add(c.defaultTTL).Unix()}, nil
121+
return Item[T]{Data: data, TTL: time.Now().Add(c.defaultTTL).Unix()}, nil
130122
}
131123

132124
// Delete removes an Item from the Cache.
133-
func (c *Cache) Delete(key string) {
125+
func (c *Cache[K, T]) Delete(key K) {
134126
c.mut.Lock()
135127
delete(c.data, key)
136128
c.mut.Unlock()
137129
}
138130

139131
// Reset removes all Items from the Cache.
140-
func (c *Cache) Reset() {
132+
func (c *Cache[K, T]) Reset() {
141133
c.mut.Lock()
142-
c.data = make(map[string]Item)
134+
c.data = make(map[K]Item[T])
143135
c.mut.Unlock()
144136
}
145137

146138
// Cancel will cancel the default Context of the Cache which stops the cleanup ticker.
147139
// This only has an effect when the Cache has not been created with a custom context.
148-
func (c *Cache) Cancel() {
140+
func (c *Cache[K, T]) Cancel() {
149141
c.cancel()
150142
}
151143

152-
func (c *Cache) cleanup() {
144+
func (c *Cache[K, T]) cleanup() {
153145
ticker := time.NewTicker(c.cleanupInterval)
154146

155147
for {
@@ -158,7 +150,7 @@ func (c *Cache) cleanup() {
158150
ticker.Stop()
159151
return
160152
case <-ticker.C:
161-
toBeDeleted := make([]string, 0)
153+
toBeDeleted := make([]K, 0)
162154

163155
c.mut.RLock()
164156
for key, item := range c.data {

mempot_test.go

Lines changed: 10 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,11 @@ const data = "bar"
1313
func TestCache(t *testing.T) {
1414
ctx, cancel := context.WithCancel(context.Background())
1515

16-
cache := New(WithDefaultTTL(30*time.Second), WithCleanupInterval(4*time.Second), WithContext(ctx))
16+
cache := NewCache[string, string](Config{
17+
Context: ctx,
18+
DefaultTTL: 30 * time.Second,
19+
CleanupInterval: 4 * time.Second,
20+
})
1721

1822
cache.SetWithTTL(key, "bar", 2*time.Second)
1923

@@ -22,13 +26,8 @@ func TestCache(t *testing.T) {
2226
t.Error("item not found")
2327
}
2428

25-
got, ok := item.Data.(string)
26-
if !ok {
27-
t.Error("item data is not a string")
28-
}
29-
30-
if got != data {
31-
t.Errorf("got %s, want %s", got, data)
29+
if item.Data != data {
30+
t.Errorf("got %s, want %s", item.Data, data)
3231
}
3332

3433
// wait for cleanup
@@ -55,14 +54,14 @@ func TestCache(t *testing.T) {
5554
t.Error("item still exists after delete all")
5655
}
5756

58-
_, err := cache.Remember(key, func(key string) (any, error) {
59-
return nil, errors.New("data not available")
57+
_, err := cache.Remember(key, func(key string) (string, error) {
58+
return "", errors.New("data not available")
6059
})
6160
if err == nil {
6261
t.Error("QueryFunc failed but Remember did not return an error")
6362
}
6463

65-
_, err = cache.Remember(key, func(key string) (any, error) {
64+
_, err = cache.Remember(key, func(key string) (string, error) {
6665
return data, nil
6766
})
6867
if err != nil {

0 commit comments

Comments
 (0)