redis.go

 1package redis
 2
 3import (
 4	"context"
 5	"time"
 6
 7	"github.com/charmbracelet/soft-serve/server/cache"
 8	"github.com/redis/go-redis/v9"
 9)
10
11// Cache is a Redis cache.
12type Cache struct {
13	client *redis.Client
14}
15
16// NewCache returns a new Redis cache.
17// It converts non-string types to JSON before storing/retrieving them.
18func NewCache(ctx context.Context, _ ...cache.Option) (cache.Cache, error) {
19	cfg, err := NewConfig("")
20	if err != nil {
21		return nil, err
22	}
23
24	client := redis.NewClient(&redis.Options{
25		Addr:     cfg.Addr,
26		Username: cfg.Username,
27		Password: cfg.Password,
28		DB:       cfg.DB,
29	})
30
31	return &Cache{
32		client: client,
33	}, client.Ping(ctx).Err()
34}
35
36type option struct {
37	cache.Item
38	ttl time.Duration
39}
40
41func (*option) item() {}
42
43// WithTTL sets the TTL for the cache item.
44func WithTTL(ttl time.Duration) cache.ItemOption {
45	return func(io cache.Item) {
46		i := io.(*option)
47		i.ttl = ttl
48	}
49}
50
51// Contains implements cache.Cache.
52func (r *Cache) Contains(ctx context.Context, key string) bool {
53	return r.client.Exists(ctx, key).Val() == 1
54}
55
56// Delete implements cache.Cache.
57func (r *Cache) Delete(ctx context.Context, key string) {
58	r.client.Del(ctx, key)
59}
60
61// Get implements cache.Cache.
62func (r *Cache) Get(ctx context.Context, key string) (value any, ok bool) {
63	val := r.client.Get(ctx, key)
64	if val.Err() != nil {
65		return nil, false
66	}
67
68	return val.Val(), true
69}
70
71// Keys implements cache.Cache.
72func (r *Cache) Keys(ctx context.Context) []string {
73	return r.client.Keys(ctx, "*").Val()
74}
75
76// Len implements cache.Cache.
77func (r *Cache) Len(ctx context.Context) int64 {
78	return r.client.DBSize(ctx).Val()
79}
80
81// Set implements cache.Cache.
82func (r *Cache) Set(ctx context.Context, key string, val any, opts ...cache.ItemOption) {
83	var opt option
84	for _, o := range opts {
85		o(&opt)
86	}
87	r.client.Set(ctx, key, val, opt.ttl)
88}