mirror of
https://github.com/redis/go-redis.git
synced 2026-10-22 19:49:00 +03:00
fix(cmd): accept RESP3 doubles in Cmd.Float64 (#4088)
* fix(cmd): accept RESP3 doubles in Cmd.Float64 With RESP3, the default protocol, ReadReply returns a double reply (for example from ZSCORE) as float64. toFloat64 and toFloat32 only handled int64 and string, so Cmd.Float64, Float32, Float64Slice and Float32Slice failed with "unexpected type=float64". Handle float64 the same way toBool handles the RESP3 boolean. * fix(cmd): keep Float32 range error on RESP3 float32(val) turns a RESP3 double outside the float32 range into an infinity without an error, while the RESP2 bulk string path returns the ParseFloat range error. Return the same error for the RESP3 double. An infinity sent by the server is still returned as is. --------- Co-authored-by: Nedyalko Dyakov <1547186+ndyakov@users.noreply.github.com>
This commit is contained in:
14
command.go
14
command.go
@@ -6,6 +6,7 @@ import (
|
||||
"fmt"
|
||||
"io"
|
||||
"maps"
|
||||
"math"
|
||||
"net"
|
||||
"regexp"
|
||||
"strconv"
|
||||
@@ -807,6 +808,17 @@ func (cmd *Cmd) Float32() (float32, error) {
|
||||
|
||||
func toFloat32(val interface{}) (float32, error) {
|
||||
switch val := val.(type) {
|
||||
case float64:
|
||||
// Return the range error that ParseFloat returns for a RESP2 bulk
|
||||
// string when a RESP3 double does not fit in a float32.
|
||||
if f := float32(val); math.IsInf(float64(f), 0) && !math.IsInf(val, 0) {
|
||||
return 0, &strconv.NumError{
|
||||
Func: "ParseFloat",
|
||||
Num: strconv.FormatFloat(val, 'g', -1, 64),
|
||||
Err: strconv.ErrRange,
|
||||
}
|
||||
}
|
||||
return float32(val), nil
|
||||
case int64:
|
||||
return float32(val), nil
|
||||
case string:
|
||||
@@ -831,6 +843,8 @@ func (cmd *Cmd) Float64() (float64, error) {
|
||||
|
||||
func toFloat64(val interface{}) (float64, error) {
|
||||
switch val := val.(type) {
|
||||
case float64:
|
||||
return val, nil
|
||||
case int64:
|
||||
return float64(val), nil
|
||||
case string:
|
||||
|
||||
70
command_float_test.go
Normal file
70
command_float_test.go
Normal file
@@ -0,0 +1,70 @@
|
||||
package redis
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"math"
|
||||
"reflect"
|
||||
"strconv"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/redis/go-redis/v9/internal/proto"
|
||||
)
|
||||
|
||||
// RESP3 sends a double reply, for example from ZSCORE, as a float64 value.
|
||||
// The float helpers of Cmd must accept it, as they accept the RESP2 bulk
|
||||
// string.
|
||||
func TestCmdFloatFromDoubleReply(t *testing.T) {
|
||||
read := func(t *testing.T, reply string) *Cmd {
|
||||
t.Helper()
|
||||
cmd := NewCmd(context.Background())
|
||||
if err := cmd.readReply(proto.NewReader(strings.NewReader(reply))); err != nil {
|
||||
t.Fatalf("readReply(%q): %v", reply, err)
|
||||
}
|
||||
return cmd
|
||||
}
|
||||
|
||||
for _, reply := range []string{",1.5\r\n", "$3\r\n1.5\r\n"} {
|
||||
cmd := read(t, reply)
|
||||
if got, err := cmd.Float64(); err != nil || got != 1.5 {
|
||||
t.Errorf("Float64() for %q = %v, %v; want 1.5, nil", reply, got, err)
|
||||
}
|
||||
if got, err := cmd.Float32(); err != nil || got != 1.5 {
|
||||
t.Errorf("Float32() for %q = %v, %v; want 1.5, nil", reply, got, err)
|
||||
}
|
||||
}
|
||||
|
||||
for _, reply := range []string{"*2\r\n,1.5\r\n,2\r\n", "*2\r\n$3\r\n1.5\r\n$1\r\n2\r\n"} {
|
||||
cmd := read(t, reply)
|
||||
if got, err := cmd.Float64Slice(); err != nil || !reflect.DeepEqual(got, []float64{1.5, 2}) {
|
||||
t.Errorf("Float64Slice() for %q = %v, %v; want [1.5 2], nil", reply, got, err)
|
||||
}
|
||||
if got, err := cmd.Float32Slice(); err != nil || !reflect.DeepEqual(got, []float32{1.5, 2}) {
|
||||
t.Errorf("Float32Slice() for %q = %v, %v; want [1.5 2], nil", reply, got, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// A double outside the float32 range must give the same range error from
|
||||
// Float32 for a RESP3 double as for a RESP2 bulk string. An infinity sent by
|
||||
// the server is not an error.
|
||||
func TestCmdFloat32FromDoubleReplyOutOfRange(t *testing.T) {
|
||||
for _, reply := range []string{",1e100\r\n", "$5\r\n1e100\r\n"} {
|
||||
cmd := NewCmd(context.Background())
|
||||
if err := cmd.readReply(proto.NewReader(strings.NewReader(reply))); err != nil {
|
||||
t.Fatalf("readReply(%q): %v", reply, err)
|
||||
}
|
||||
if got, err := cmd.Float32(); !errors.Is(err, strconv.ErrRange) {
|
||||
t.Errorf("Float32() for %q = %v, %v; want strconv.ErrRange", reply, got, err)
|
||||
}
|
||||
}
|
||||
|
||||
cmd := NewCmd(context.Background())
|
||||
if err := cmd.readReply(proto.NewReader(strings.NewReader(",inf\r\n"))); err != nil {
|
||||
t.Fatalf("readReply: %v", err)
|
||||
}
|
||||
if got, err := cmd.Float32(); err != nil || !math.IsInf(float64(got), 1) {
|
||||
t.Errorf("Float32() for inf = %v, %v; want +Inf, nil", got, err)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user