Skip to content

Commit a4144ea

Browse files
committed
Add SliceCmd.Scan() (hscan pkg) and tests
1 parent 380ab17 commit a4144ea

File tree

4 files changed

+135
-2
lines changed

4 files changed

+135
-2
lines changed

command.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import (
88
"time"
99

1010
"github.com/go-redis/redis/v8/internal"
11+
"github.com/go-redis/redis/v8/internal/hscan"
1112
"github.com/go-redis/redis/v8/internal/proto"
1213
"github.com/go-redis/redis/v8/internal/util"
1314
)
@@ -371,6 +372,13 @@ func (cmd *SliceCmd) String() string {
371372
return cmdString(cmd, cmd.val)
372373
}
373374

375+
// Scan scans the results from a key-value Redis map result set ([]interface{})
376+
// like HMGET and HGETALL to a destination struct.
377+
// The Redis keys are matched to the struct's field with the `redis` tag.
378+
func (cmd *SliceCmd) Scan(val interface{}) error {
379+
return hscan.Scan(cmd.val, val)
380+
}
381+
374382
func (cmd *SliceCmd) readReply(rd *proto.Reader) error {
375383
v, err := rd.ReadArrayReply(sliceParser)
376384
if err != nil {

internal/hscan/hscan.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import (
1111
type decoderFunc func(reflect.Value, string) error
1212

1313
var (
14-
// List of built-in decoders indexed by their numeric constant values (eg: reflect.Bool = 1)
14+
// List of built-in decoders indexed by their numeric constant values (eg: reflect.Bool = 1).
1515
decoders = []decoderFunc{
1616
reflect.Bool: decodeBool,
1717
reflect.Int: decodeInt,

internal/hscan/hscan_test.go

Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
package hscan
2+
3+
import (
4+
"testing"
5+
6+
. "github.com/onsi/ginkgo"
7+
. "github.com/onsi/gomega"
8+
)
9+
10+
type data struct {
11+
Omit string `redis:"-"`
12+
Empty string
13+
14+
String string `redis:"string"`
15+
Bytes []byte `redis:"byte"`
16+
Int int `redis:"int"`
17+
Uint uint `redis:"uint"`
18+
Float float32 `redis:"float"`
19+
Bool bool `redis:"bool"`
20+
}
21+
22+
func TestGinkgoSuite(t *testing.T) {
23+
RegisterFailHandler(Fail)
24+
RunSpecs(t, "hscan")
25+
}
26+
27+
var _ = Describe("Scan", func() {
28+
It("catches bad args", func() {
29+
var d data
30+
31+
Expect(Scan([]interface{}{}, &d)).NotTo(HaveOccurred())
32+
Expect(d).To(Equal(data{}))
33+
34+
Expect(Scan([]interface{}{"key"}, &d)).To(HaveOccurred())
35+
Expect(Scan([]interface{}{"key", "1", "2"}, &d)).To(HaveOccurred())
36+
Expect(Scan([]interface{}{"key", "1"}, nil)).To(HaveOccurred())
37+
38+
var i map[string]interface{}
39+
Expect(Scan([]interface{}{"key", "1"}, &i)).To(HaveOccurred())
40+
Expect(Scan([]interface{}{"key", "1"}, data{})).To(HaveOccurred())
41+
Expect(Scan([]interface{}{"key", nil, "string", nil}, data{})).To(HaveOccurred())
42+
})
43+
44+
It("scans good values", func() {
45+
var d data
46+
47+
// non-tagged fields.
48+
Expect(Scan([]interface{}{"key", "value"}, &d)).NotTo(HaveOccurred())
49+
Expect(d).To(Equal(data{}))
50+
51+
res := []interface{}{"string", "str!",
52+
"byte", "bytes!",
53+
"int", "123",
54+
"uint", "456",
55+
"float", "123.456",
56+
"bool", "1"}
57+
Expect(Scan(res, &d)).NotTo(HaveOccurred())
58+
Expect(d).To(Equal(data{
59+
String: "str!",
60+
Bytes: []byte("bytes!"),
61+
Int: 123,
62+
Uint: 456,
63+
Float: 123.456,
64+
Bool: true,
65+
}))
66+
67+
// Scan a different type with the same values to test that
68+
// the struct spec maps don't conflict.
69+
type data2 struct {
70+
String string `redis:"string"`
71+
Bytes []byte `redis:"byte"`
72+
Int int `redis:"int"`
73+
Uint uint `redis:"uint"`
74+
Float float32 `redis:"float"`
75+
Bool bool `redis:"bool"`
76+
}
77+
var d2 data2
78+
Expect(Scan(res, &d2)).NotTo(HaveOccurred())
79+
Expect(d2).To(Equal(data2{
80+
String: "str!",
81+
Bytes: []byte("bytes!"),
82+
Int: 123,
83+
Uint: 456,
84+
Float: 123.456,
85+
Bool: true,
86+
}))
87+
88+
Expect(Scan([]interface{}{
89+
"string", "",
90+
"float", "1",
91+
"bool", "t"}, &d)).NotTo(HaveOccurred())
92+
Expect(d).To(Equal(data{
93+
String: "",
94+
Bytes: []byte("bytes!"),
95+
Int: 123,
96+
Uint: 456,
97+
Float: 1.0,
98+
Bool: true,
99+
}))
100+
})
101+
102+
It("omits untagged fields", func() {
103+
var d data
104+
105+
Expect(Scan([]interface{}{
106+
"empty", "value",
107+
"omit", "value",
108+
"string", "str!"}, &d)).NotTo(HaveOccurred())
109+
Expect(d).To(Equal(data{
110+
String: "str!",
111+
}))
112+
})
113+
114+
It("catches bad values", func() {
115+
var d data
116+
117+
Expect(Scan([]interface{}{"int", "a"}, &d)).To(HaveOccurred())
118+
Expect(Scan([]interface{}{"uint", "a"}, &d)).To(HaveOccurred())
119+
Expect(Scan([]interface{}{"uint", ""}, &d)).To(HaveOccurred())
120+
Expect(Scan([]interface{}{"float", "b"}, &d)).To(HaveOccurred())
121+
Expect(Scan([]interface{}{"bool", "-1"}, &d)).To(HaveOccurred())
122+
Expect(Scan([]interface{}{"bool", ""}, &d)).To(HaveOccurred())
123+
Expect(Scan([]interface{}{"bool", "123"}, &d)).To(HaveOccurred())
124+
})
125+
})

internal/hscan/structmap.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ func getStructFields(t reflect.Type, fieldTag string) *structFields {
6161
for i := 0; i < num; i++ {
6262
f := t.Field(i)
6363

64-
tag := t.Field(i).Tag.Get(fieldTag)
64+
tag := f.Tag.Get(fieldTag)
6565
if tag == "" || tag == "-" {
6666
continue
6767
}

0 commit comments

Comments
 (0)