Skip to content

Commit 316030a

Browse files
committed
Merge remote-tracking branch 'origin/main' into ft/short-stack
2 parents a870eca + b03fc55 commit 316030a

17 files changed

+1288
-45
lines changed

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
.idea
2+
*.log

cache/cache.go

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
package cache
2+
3+
import (
4+
"github.com/dgraph-io/ristretto"
5+
"time"
6+
)
7+
8+
type Cache interface {
9+
Set(key string, value interface{}, ttl time.Duration) error
10+
Get(key string, result any) error
11+
Del(key string) error
12+
}
13+
14+
type CfgCache struct {
15+
*ristretto.Config
16+
Type string
17+
Redis *RedisConfig
18+
}
19+
20+
func NewCache(cfg *CfgCache) Cache {
21+
var cache Cache
22+
if cfg.Type == "redis" && cfg.Redis != nil {
23+
cache = NewRedisCache(cfg.Redis)
24+
} else {
25+
if cfg.Config == nil {
26+
cache, _ = NewRistrettoCacheDefault()
27+
} else {
28+
cache, _ = NewRistrettoCache(&ristretto.Config{
29+
NumCounters: cfg.NumCounters,
30+
MaxCost: cfg.MaxCost,
31+
BufferItems: cfg.BufferItems,
32+
})
33+
}
34+
}
35+
36+
return cache
37+
}

cache/cache_test.go

Lines changed: 197 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,197 @@
1+
package cache_test
2+
3+
import (
4+
"testing"
5+
"time"
6+
7+
"github.com/KyberNetwork/kutils/cache"
8+
"github.com/stretchr/testify/require"
9+
)
10+
11+
func TestCache(t *testing.T) {
12+
cacheTypes := []struct {
13+
name string
14+
config *cache.CfgCache
15+
}{
16+
{"Ristretto", &cache.CfgCache{
17+
Type: "ristretto",
18+
}},
19+
{"Redis", &cache.CfgCache{
20+
Type: "ristretto",
21+
Redis: &cache.RedisConfig{
22+
Addresses: "localhost:6379",
23+
},
24+
}},
25+
}
26+
27+
type example struct {
28+
Name string
29+
Age int
30+
Value uint64
31+
}
32+
33+
for _, ct := range cacheTypes {
34+
t.Run(ct.name, func(t *testing.T) {
35+
var err error
36+
37+
sCache := cache.NewCache(ct.config)
38+
// Example usage
39+
key1 := "exampleKey"
40+
key2 := "exampleKey1"
41+
input1 := example{
42+
Name: t.Name(),
43+
Value: 1,
44+
}
45+
input2 := "demo 123"
46+
ttl := 5 * time.Minute
47+
48+
err = sCache.Set(key1, input1, ttl)
49+
require.NoError(t, err, "Error setting cache")
50+
err = sCache.Set(key2, input2, ttl)
51+
require.NoError(t, err, "Error setting cache")
52+
var res1 *example
53+
err = sCache.Get(key1, &res1)
54+
require.NoError(t, err, "Error getting cache")
55+
require.Equal(t, input1, *res1, "Error expected cache value")
56+
57+
var res2 string
58+
err = sCache.Get(key2, &res2)
59+
require.NoError(t, err, "Error getting cache")
60+
require.Equal(t, input2, res2, "Error expected cache value")
61+
})
62+
t.Run("Pointer Types", func(t *testing.T) {
63+
sCache := cache.NewCache(ct.config)
64+
key := "pointerTest"
65+
input := &example{Name: "pointer", Age: 25, Value: 100}
66+
err := sCache.Set(key, input, time.Minute)
67+
require.NoError(t, err)
68+
69+
var result *example
70+
err = sCache.Get(key, &result)
71+
require.NoError(t, err)
72+
require.Equal(t, input, result)
73+
})
74+
t.Run("Slice Types", func(t *testing.T) {
75+
sCache := cache.NewCache(ct.config)
76+
key := "sliceTest"
77+
input := []*example{{Name: "pointer", Age: 25, Value: 100}}
78+
err := sCache.Set(key, input, time.Minute)
79+
require.NoError(t, err)
80+
81+
var result []*example
82+
err = sCache.Get(key, &result)
83+
require.NoError(t, err)
84+
require.Equal(t, input, result)
85+
})
86+
87+
t.Run("Non-existent Key", func(t *testing.T) {
88+
sCache := cache.NewCache(ct.config)
89+
var result string
90+
err := sCache.Get("nonexistentKey", &result)
91+
require.Error(t, err)
92+
require.Contains(t, err.Error(), "key not found")
93+
})
94+
95+
t.Run("Type Mismatch", func(t *testing.T) {
96+
sCache := cache.NewCache(ct.config)
97+
key := "typeMismatch"
98+
err := sCache.Set(key, 42, time.Minute)
99+
require.NoError(t, err)
100+
101+
var result string
102+
err = sCache.Get(key, &result)
103+
require.Error(t, err)
104+
})
105+
106+
t.Run("Nil Pointer", func(t *testing.T) {
107+
sCache := cache.NewCache(ct.config)
108+
key := "nilPointer"
109+
err := sCache.Set(key, "test", time.Minute)
110+
require.NoError(t, err)
111+
112+
var result *string
113+
err = sCache.Get(key, result) // passing nil pointer
114+
require.Error(t, err)
115+
require.Contains(t, err.Error(), "nil")
116+
})
117+
118+
t.Run("Non-existent Key", func(t *testing.T) {
119+
sCache := cache.NewCache(ct.config)
120+
var result string
121+
err := sCache.Get("nonexistentKey", &result)
122+
require.Error(t, err)
123+
require.Contains(t, err.Error(), "key not found")
124+
})
125+
126+
t.Run("Type Mismatch", func(t *testing.T) {
127+
sCache := cache.NewCache(ct.config)
128+
key := "typeMismatch"
129+
err := sCache.Set(key, 42, time.Minute)
130+
require.NoError(t, err)
131+
132+
var result string
133+
err = sCache.Get(key, &result)
134+
require.Error(t, err)
135+
})
136+
137+
t.Run("Nil Pointer", func(t *testing.T) {
138+
sCache := cache.NewCache(ct.config)
139+
key := "nilPointer"
140+
err := sCache.Set(key, "test", time.Minute)
141+
require.NoError(t, err)
142+
143+
var result *string
144+
err = sCache.Get(key, result) // passing nil pointer
145+
require.Error(t, err)
146+
require.Contains(t, err.Error(), "nil")
147+
})
148+
149+
//t.Run("Complex Types", func(t *testing.T) {
150+
// sCache := cache.NewCache(ct.config)
151+
// key := "complexType"
152+
// input := map[string]interface{}{
153+
// "name": "test",
154+
// "data": []int{1, 2, 3},
155+
// }
156+
// err := sCache.Set(key, input, time.Minute)
157+
// require.NoError(t, err)
158+
//
159+
// var result *map[string]interface{}
160+
// err = sCache.Get(key, &result)
161+
// require.NoError(t, err)
162+
// require.Equal(t, input, *result)
163+
//})
164+
}
165+
}
166+
167+
func TestDelKey(t *testing.T) {
168+
cacheTypes := []struct {
169+
name string
170+
config *cache.CfgCache
171+
}{
172+
{"Ristretto", &cache.CfgCache{
173+
Type: "ristretto",
174+
}},
175+
{"Redis", &cache.CfgCache{
176+
Type: "ristretto",
177+
Redis: &cache.RedisConfig{
178+
Addresses: "localhost:6379",
179+
},
180+
}},
181+
}
182+
for _, ct := range cacheTypes {
183+
t.Run(ct.name, func(t *testing.T) {
184+
sCache := cache.NewCache(ct.config)
185+
key := "test_key"
186+
value := "123test"
187+
err := sCache.Set(key, value, time.Minute)
188+
require.NoError(t, err)
189+
err = sCache.Del(key)
190+
require.NoError(t, err)
191+
192+
var result string
193+
err = sCache.Get(key, &result)
194+
require.Error(t, err)
195+
})
196+
}
197+
}

cache/redis_cache.go

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
package cache
2+
3+
import (
4+
"context"
5+
"encoding/json"
6+
"errors"
7+
"fmt"
8+
"strings"
9+
"time"
10+
11+
"github.com/redis/go-redis/v9"
12+
)
13+
14+
// RedisConfig contains all configuration of redis
15+
type RedisConfig struct {
16+
Addresses string
17+
MasterName string
18+
DBNumber int
19+
Username string
20+
Password string
21+
SentinelUsername string
22+
SentinelPassword string
23+
Prefix string
24+
Separator string
25+
ReadTimeout time.Duration
26+
WriteTimeout time.Duration
27+
RouteRandomly bool
28+
ReplicaOnly bool
29+
}
30+
31+
type RedisCache struct {
32+
client redis.UniversalClient
33+
}
34+
35+
func NewRedisCache(cfg *RedisConfig) *RedisCache {
36+
addrs := strings.Split(cfg.Addresses, ",")
37+
if cfg.MasterName != "" {
38+
client := redis.NewFailoverClusterClient(&redis.FailoverOptions{
39+
MasterName: cfg.MasterName,
40+
SentinelAddrs: addrs,
41+
DB: cfg.DBNumber,
42+
Username: cfg.Username,
43+
Password: cfg.Password,
44+
SentinelUsername: cfg.SentinelUsername,
45+
SentinelPassword: cfg.SentinelPassword,
46+
ReadTimeout: cfg.ReadTimeout,
47+
WriteTimeout: cfg.WriteTimeout,
48+
RouteRandomly: cfg.RouteRandomly,
49+
ReplicaOnly: cfg.ReplicaOnly,
50+
})
51+
return &RedisCache{client: client}
52+
}
53+
client := redis.NewUniversalClient(&redis.UniversalOptions{
54+
Addrs: addrs,
55+
MasterName: cfg.MasterName,
56+
DB: cfg.DBNumber,
57+
Username: cfg.Username,
58+
Password: cfg.Password,
59+
SentinelUsername: cfg.SentinelUsername,
60+
SentinelPassword: cfg.SentinelPassword,
61+
ReadTimeout: cfg.ReadTimeout,
62+
WriteTimeout: cfg.WriteTimeout,
63+
})
64+
return &RedisCache{client: client}
65+
}
66+
67+
func (r *RedisCache) Set(key string, value interface{}, ttl time.Duration) error {
68+
// Marshal the value to JSON
69+
jsonData, err := json.Marshal(value)
70+
if err != nil {
71+
return fmt.Errorf("failed to marshal value: %w", err)
72+
}
73+
return r.client.Set(context.Background(), key, jsonData, ttl).Err()
74+
}
75+
76+
func (r *RedisCache) Get(key string, result any) error {
77+
value, err := r.client.Get(context.Background(), key).Result()
78+
if errors.Is(err, redis.Nil) {
79+
return fmt.Errorf("key not found")
80+
}
81+
err = json.Unmarshal([]byte(value), result)
82+
return err
83+
}
84+
85+
func (r *RedisCache) Del(key string) error {
86+
return r.client.Del(context.Background(), key).Err()
87+
}

0 commit comments

Comments
 (0)