package cache import ( "context" "time" "github.com/redis/go-redis/v9" ) // Cache is a minimal interface for key/value byte storage with TTL. // Both the Valkey implementation and the no-op satisfy it. type Cache interface { Get(ctx context.Context, key string) ([]byte, bool) Set(ctx context.Context, key string, value []byte, ttl time.Duration) } // valkeyCache wraps a go-redis client connected to a Valkey (or Redis) server. type valkeyCache struct { client *redis.Client } // NewValkeyCache parses url (e.g. "redis://localhost:6379"), pings the server, // and returns a ready Cache. Returns an error if the server is unreachable. func NewValkeyCache(url string) (Cache, error) { opts, err := redis.ParseURL(url) if err != nil { return nil, err } client := redis.NewClient(opts) ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) defer cancel() if err := client.Ping(ctx).Err(); err != nil { client.Close() return nil, err } return &valkeyCache{client: client}, nil } func (c *valkeyCache) Get(ctx context.Context, key string) ([]byte, bool) { val, err := c.client.Get(ctx, key).Bytes() if err != nil { return nil, false } return val, true } func (c *valkeyCache) Set(ctx context.Context, key string, value []byte, ttl time.Duration) { // Best-effort: cache errors are non-fatal. c.client.Set(ctx, key, value, ttl) } // noopCache is used when VALKEY_URL is not configured. // All Gets miss; all Sets are discarded. type noopCache struct{} // NewNoopCache returns a Cache that never stores anything. func NewNoopCache() Cache { return &noopCache{} } func (c *noopCache) Get(_ context.Context, _ string) ([]byte, bool) { return nil, false } func (c *noopCache) Set(_ context.Context, _ string, _ []byte, _ time.Duration) { }