proxy-pool/internal/adapters/redisactivity/scripts.go

113 lines
2.8 KiB
Go

package redisactivity
import (
"context"
"crypto/rand"
_ "embed"
"encoding/hex"
"errors"
"fmt"
"time"
"github.com/redis/go-redis/v9"
extractionDomain "proxy-pool/internal/domain/extraction"
)
type scriptStatus string
const (
scriptOK scriptStatus = "ok"
scriptInvalid scriptStatus = "invalid"
scriptNotFound scriptStatus = "not_found"
scriptConflict scriptStatus = "conflict"
scriptStale scriptStatus = "stale"
scriptUnavailable scriptStatus = "unavailable"
scriptInsufficient scriptStatus = "insufficient"
)
type upsertScriptReply struct {
Status scriptStatus `json:"status"`
Accepted int `json:"accepted"`
Inserted int `json:"inserted"`
Refreshed int `json:"refreshed"`
Dropped int `json:"dropped"`
}
type healthScriptReply struct {
Status scriptStatus `json:"status"`
Record string `json:"record,omitempty"`
}
type extractScriptReply struct {
Status scriptStatus `json:"status"`
Record string `json:"record,omitempty"`
}
type ownershipScriptReply struct {
Status scriptStatus `json:"status"`
Record string `json:"record,omitempty"`
}
type maintenanceScriptReply struct {
Status scriptStatus `json:"status"`
Count int `json:"count"`
}
//go:embed scripts/upsert.lua
var upsertSource string
//go:embed scripts/health.lua
var healthSource string
var (
upsertScript = redis.NewScript(upsertSource)
healthScript = redis.NewScript(healthSource)
)
func runScript(ctx context.Context, client redis.Scripter, script *redis.Script, keys []string, args ...any) (any, error) {
result, err := script.Run(ctx, client, keys, args...).Result()
if err != nil {
if errors.Is(err, context.Canceled) || errors.Is(err, context.DeadlineExceeded) {
return nil, err
}
return nil, errors.Join(
extractionDomain.ErrStoreUnavailable,
fmt.Errorf("run redis activity script: %w", err),
)
}
return result, nil
}
func newOperationID() (string, error) {
var value [16]byte
if _, err := rand.Read(value[:]); err != nil {
return "", fmt.Errorf("create redis activity operation ID: %w", err)
}
return hex.EncodeToString(value[:]), nil
}
func operationTTLMillis(ttl time.Duration) int64 {
milliseconds := ttl / time.Millisecond
if ttl%time.Millisecond != 0 {
milliseconds++
}
return int64(milliseconds)
}
func decodeScriptResult(result any, destination any) error {
var payload string
switch value := result.(type) {
case string:
payload = value
case []byte:
payload = string(value)
default:
return errors.Join(extractionDomain.ErrStoreUnavailable, errors.New("invalid Redis script reply type"))
}
if err := decodeJSON(payload, destination); err != nil {
return errors.Join(extractionDomain.ErrStoreUnavailable, fmt.Errorf("decode Redis script reply: %w", err))
}
return nil
}