package redisprovider import ( "bytes" "context" _ "embed" "encoding/json" "errors" "fmt" "io" "github.com/redis/go-redis/v9" controllerProvider "proxy-pool/internal/controller/provider" ) type scriptReply struct { Status string `json:"status"` Generation string `json:"generation,omitempty"` Epoch uint64 `json:"epoch,string,omitempty"` WaitMS int64 `json:"waitMs,omitempty"` } //go:embed scripts/provider.lua var providerSource string var providerScript = redis.NewScript(providerSource) func runScript(ctx context.Context, client redis.Scripter, keys upstreamKeys, args ...any) (scriptReply, error) { var reply scriptReply result, err := providerScript.Run(ctx, client, keys.all(), args...).Result() if err != nil { if errors.Is(err, context.Canceled) || errors.Is(err, context.DeadlineExceeded) { return reply, err } return reply, errors.Join(controllerProvider.ErrCoordinationUnavailable, fmt.Errorf("run Redis provider script: %w", err)) } var payload []byte switch value := result.(type) { case string: payload = []byte(value) case []byte: payload = value default: return reply, errors.Join(controllerProvider.ErrCoordinationUnavailable, fmt.Errorf("decode Redis provider script: unexpected reply type %T", result)) } decoder := json.NewDecoder(bytes.NewReader(payload)) decoder.DisallowUnknownFields() if err := decoder.Decode(&reply); err != nil || reply.Status == "" { return scriptReply{}, errors.Join(controllerProvider.ErrCoordinationUnavailable, fmt.Errorf("decode Redis provider script reply: %w", err)) } var trailing any if err := decoder.Decode(&trailing); !errors.Is(err, io.EOF) { return scriptReply{}, errors.Join(controllerProvider.ErrCoordinationUnavailable, errors.New("decode Redis provider script reply: trailing value")) } return reply, nil }