package cache import ( "context" "crypto/tls" "crypto/x509" "fmt" "os" "time" "github.com/go-redis/redis/v7" ) type ValKeyCacheConfig struct { Addrs []string Password string ReadOnly bool DialTimeout time.Duration PoolSize int DefaultTTL time.Duration RootCaFilePath string } type valkeyClient interface { Set(key string, value any, expiration time.Duration) *redis.StatusCmd Get(key string) *redis.StringCmd Del(keys ...string) *redis.IntCmd Exists(keys ...string) *redis.IntCmd Ping() *redis.StatusCmd } type valKeyCache struct { config ValKeyCacheConfig client valkeyClient } func newValKeyCache(c Config) (*valKeyCache, error) { cfg, ok := c.(ValKeyCacheConfig) if !ok { return nil, ErrInvalidConfig } caCert, err := os.ReadFile(cfg.RootCaFilePath) if err != nil { return nil, fmt.Errorf("%w: %s", ErrTlsConfig, err.Error()) } caCertPool := x509.NewCertPool() if ok := caCertPool.AppendCertsFromPEM(caCert); !ok { return nil, fmt.Errorf("%w: %s", ErrTlsConfig, "failed to append CA cert to pool") } tlsConfig := &tls.Config{ RootCAs: caCertPool, MinVersion: tls.VersionTLS12, } client := redis.NewUniversalClient(&redis.UniversalOptions{ Addrs: cfg.Addrs, Password: cfg.Password, ReadOnly: cfg.ReadOnly, DialTimeout: cfg.DialTimeout, PoolSize: cfg.PoolSize, TLSConfig: tlsConfig, }) if err := client.Ping().Err(); err != nil { return nil, fmt.Errorf("%w: %v", ErrConnect, err) } return &valKeyCache{ config: cfg, client: client, }, nil } func (c *valKeyCache) Get(ctx context.Context, key string, valueType ValueType) (string, error) { if err := c.checkInstance(); err != nil { return "", fmt.Errorf("%w: %v", ErrGet, err) } val, err := c.client.Get(getKey(key, valueType)).Result() if err == redis.Nil { return "", ErrKeyNotFound } if err != nil { return "", fmt.Errorf("%w: %v", ErrGet, err) } return val, nil } func (c *valKeyCache) Set(ctx context.Context, key string, valueType ValueType, value any, expiration time.Duration) error { if err := c.checkInstance(); err != nil { return fmt.Errorf("%w: %v", ErrSet, err) } key = getKey(key, valueType) if exists, err := c.client.Exists(key).Result(); err != nil || exists == 1 { if exists == 1 { return ErrKeyExists } return fmt.Errorf("%w: %v", ErrSet, err) } if err := c.client.Set(key, value, expiration).Err(); err != nil { return fmt.Errorf("%w: %v", ErrSet, err) } return nil } func (c *valKeyCache) Del(ctx context.Context, key string, valueType ValueType) error { if err := c.checkInstance(); err != nil { return fmt.Errorf("%w: %v", ErrDel, err.Error()) } if res, err := c.client.Del(getKey(key, valueType)).Result(); err != nil || res == 0 { if res == 0 { return ErrKeyNotFound } return fmt.Errorf("%w: %s", ErrDel, err.Error()) } return nil } func (c *valKeyCache) checkInstance() error { if c == nil { return ErrInvalidInstance } if s := c.client.Ping(); s.Err() != nil { return fmt.Errorf("%w: %v", ErrInvalidInstance, s.Err().Error()) } return nil }