-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathredis.go
More file actions
156 lines (130 loc) · 4.04 KB
/
Copy pathredis.go
File metadata and controls
156 lines (130 loc) · 4.04 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
package cachex
import (
"context"
"encoding"
"encoding/json"
"time"
"github.com/pkg/errors"
"github.com/redis/go-redis/v9"
)
// RedisCache is a cache implementation using Redis
type RedisCache[T any] struct {
client redis.UniversalClient
keyPrefix string
ttl time.Duration
useBinary bool // true if T implements encoding.BinaryMarshaler and encoding.BinaryUnmarshaler
}
var _ Cache[any] = &RedisCache[any]{}
// RedisCacheConfig holds configuration for RedisCache
type RedisCacheConfig struct {
// Client is the Redis client (supports both single and cluster)
Client redis.UniversalClient
// KeyPrefix is the prefix for all keys (optional)
KeyPrefix string
// TTL is the time-to-live for cache entries
// Zero means no expiration
TTL time.Duration
}
// NewRedisCache creates a new Redis-based cache with configuration
func NewRedisCache[T any](config *RedisCacheConfig) *RedisCache[T] {
if config.Client == nil {
panic("Client is required")
}
// Check if T is a type that can skip JSON marshaling/unmarshaling
var zero T
var useBinary bool
// Standard practice: MarshalBinary on value receiver, UnmarshalBinary on pointer receiver
// Only support: T implements BinaryMarshaler, *T implements BinaryUnmarshaler
_, hasMarshal := any(zero).(encoding.BinaryMarshaler)
_, hasUnmarshal := any(&zero).(encoding.BinaryUnmarshaler)
if hasMarshal && hasUnmarshal {
useBinary = true
}
return &RedisCache[T]{
client: config.Client,
keyPrefix: config.KeyPrefix,
ttl: config.TTL,
useBinary: useBinary,
}
}
func (r *RedisCache[T]) prefixedKey(key string) string {
return r.keyPrefix + key
}
// Set stores a value in the cache
func (r *RedisCache[T]) Set(ctx context.Context, key string, value T) error {
var data any
var err error
if r.useBinary {
// Use BinaryMarshaler interface
if marshaler, ok := any(value).(encoding.BinaryMarshaler); ok {
data, err = marshaler.MarshalBinary()
if err != nil {
return errors.Wrapf(err, "failed to marshal binary for key: %s", key)
}
} else {
return errors.Errorf("value does not implement encoding.BinaryMarshaler for key: %s", key)
}
} else {
switch any(value).(type) {
case string, []byte:
data = value
default:
// For other types: marshal to JSON
data, err = json.Marshal(value)
if err != nil {
return errors.Wrapf(err, "failed to marshal value for key: %s", key)
}
}
}
if err := r.client.Set(ctx, r.prefixedKey(key), data, r.ttl).Err(); err != nil {
return errors.Wrapf(err, "failed to set cache entry for key: %s", key)
}
return nil
}
func (r *RedisCache[T]) handleRedisError(err error, key string) error {
if errors.Is(err, redis.Nil) {
return errors.Wrapf(&ErrKeyNotFound{}, "key not found in redis cache for key: %s", key)
}
return errors.Wrapf(err, "failed to get cache entry for key: %s", key)
}
// Get retrieves a value from the cache
func (r *RedisCache[T]) Get(ctx context.Context, key string) (T, error) {
var zero T
cmd := r.client.Get(ctx, r.prefixedKey(key))
if _, ok := any(zero).(string); ok {
str, err := cmd.Result()
if err != nil {
return zero, r.handleRedisError(err, key)
}
return any(str).(T), nil
}
data, err := cmd.Bytes()
if err != nil {
return zero, r.handleRedisError(err, key)
}
if _, ok := any(zero).([]byte); ok {
return any(data).(T), nil
}
if r.useBinary {
var value T
if unmarshaler, ok := any(&value).(encoding.BinaryUnmarshaler); ok {
if err := unmarshaler.UnmarshalBinary(data); err != nil {
return zero, errors.Wrapf(err, "failed to unmarshal binary for key: %s", key)
}
}
return value, nil
}
// For other types: unmarshal from JSON
var value T
if err := json.Unmarshal(data, &value); err != nil {
return zero, errors.Wrapf(err, "failed to unmarshal value for key: %s", key)
}
return value, nil
}
// Del removes a value from the cache
func (r *RedisCache[T]) Del(ctx context.Context, key string) error {
if err := r.client.Del(ctx, r.prefixedKey(key)).Err(); err != nil {
return errors.Wrapf(err, "failed to delete cache entry for key: %s", key)
}
return nil
}