vendor: fzzy/radix/redis
Brian Tiger Chow committed
Feb 6, 2015 at 17:20 UTC
8f62ac8272a1f35c02d89104ca9ab22f0e4e5875
8 files changed
+1517
Godeps/Godeps.json
+5
@@ -108,6 +108,11 @@
108
"ImportPath": "github.com/fd/go-nat",
109
"Rev": "50e7633d5f27d81490026a13e5b92d2e42d8c6bb"
110
},
111
+ {
112
+ "ImportPath": "github.com/fzzy/radix/redis",
113
+ "Comment": "v0.5.1",
114
+ "Rev": "27a863cdffdb0998d13e1e11992b18489aeeaa25"
115
+ },
116
{
117
"ImportPath": "github.com/gorilla/context",
118
"Rev": "14f550f51af52180c2eefed15e5fd18d63c0a64a"
Godeps/_workspace/src/github.com/fzzy/radix/redis/client.go
new
+244
@@ -0,0 +1,244 @@
1
+package redis
2
+
3
+import (
4
+ "bufio"
5
+ "errors"
6
+ "net"
7
+ "strings"
8
+ "time"
9
+
10
+ "github.com/jbenet/go-ipfs/Godeps/_workspace/src/github.com/fzzy/radix/redis/resp"
11
+)
12
+
13
+const (
14
+ bufSize int = 4096
15
+)
16
+
17
+//* Common errors
18
+
19
+var LoadingError error = errors.New("server is busy loading dataset in memory")
20
+var PipelineQueueEmptyError error = errors.New("pipeline queue empty")
21
+
22
+//* Client
23
+
24
+// Client describes a Redis client.
25
+type Client struct {
26
+ // The connection the client talks to redis over. Don't touch this unless
27
+ // you know what you're doing.
28
+ Conn net.Conn
29
+ timeout time.Duration
30
+ reader *bufio.Reader
31
+ pending []*request
32
+ completed []*Reply
33
+}
34
+
35
+// request describes a client's request to the redis server
36
+type request struct {
37
+ cmd string
38
+ args []interface{}
39
+}
40
+
41
+// Dial connects to the given Redis server with the given timeout, which will be
42
+// used as the read/write timeout when communicating with redis
43
+func DialTimeout(network, addr string, timeout time.Duration) (*Client, error) {
44
+ // establish a connection
45
+ conn, err := net.Dial(network, addr)
46
+ if err != nil {
47
+ return nil, err
48
+ }
49
+
50
+ c := new(Client)
51
+ c.Conn = conn
52
+ c.timeout = timeout
53
+ c.reader = bufio.NewReaderSize(conn, bufSize)
54
+ return c, nil
55
+}
56
+
57
+// Dial connects to the given Redis server.
58
+func Dial(network, addr string) (*Client, error) {
59
+ return DialTimeout(network, addr, time.Duration(0))
60
+}
61
+
62
+//* Public methods
63
+
64
+// Close closes the connection.
65
+func (c *Client) Close() error {
66
+ return c.Conn.Close()
67
+}
68
+
69
+// Cmd calls the given Redis command.
70
+func (c *Client) Cmd(cmd string, args ...interface{}) *Reply {
71
+ err := c.writeRequest(&request{cmd, args})
72
+ if err != nil {
73
+ return &Reply{Type: ErrorReply, Err: err}
74
+ }
75
+ return c.ReadReply()
76
+}
77
+
78
+// Append adds the given call to the pipeline queue.
79
+// Use GetReply() to read the reply.
80
+func (c *Client) Append(cmd string, args ...interface{}) {
81
+ c.pending = append(c.pending, &request{cmd, args})
82
+}
83
+
84
+// GetReply returns the reply for the next request in the pipeline queue.
85
+// Error reply with PipelineQueueEmptyError is returned,
86
+// if the pipeline queue is empty.
87
+func (c *Client) GetReply() *Reply {
88
+ if len(c.completed) > 0 {
89
+ r := c.completed[0]
90
+ c.completed = c.completed[1:]
91
+ return r
92
+ }
93
+ c.completed = nil
94
+
95
+ if len(c.pending) == 0 {
96
+ return &Reply{Type: ErrorReply, Err: PipelineQueueEmptyError}
97
+ }
98
+
99
+ nreqs := len(c.pending)
100
+ err := c.writeRequest(c.pending...)
101
+ c.pending = nil
102
+ if err != nil {
103
+ return &Reply{Type: ErrorReply, Err: err}
104
+ }
105
+ r := c.ReadReply()
106
+ c.completed = make([]*Reply, nreqs-1)
107
+ for i := 0; i < nreqs-1; i++ {
108
+ c.completed[i] = c.ReadReply()
109
+ }
110
+
111
+ return r
112
+}
113
+
114
+//* Private methods
115
+
116
+func (c *Client) setReadTimeout() {
117
+ if c.timeout != 0 {
118
+ c.Conn.SetReadDeadline(time.Now().Add(c.timeout))
119
+ }
120
+}
121
+
122
+func (c *Client) setWriteTimeout() {
123
+ if c.timeout != 0 {
124
+ c.Conn.SetWriteDeadline(time.Now().Add(c.timeout))
125
+ }
126
+}
127
+
128
+// This will read a redis reply off of the connection without sending anything
129
+// first (useful after you've sent a SUSBSCRIBE command). This will block until
130
+// a reply is received or the timeout is reached. On timeout an ErrorReply will
131
+// be returned, you can check if it's a timeout like so:
132
+//
133
+// r := conn.ReadReply()
134
+// if r.Err != nil {
135
+// if t, ok := r.Err.(*net.OpError); ok && t.Timeout() {
136
+// // Is timeout
137
+// } else {
138
+// // Not timeout
139
+// }
140
+// }
141
+//
142
+// Note: this is a more low-level function, you really shouldn't have to
143
+// actually use it unless you're writing your own pub/sub code
144
+func (c *Client) ReadReply() *Reply {
145
+ c.setReadTimeout()
146
+ return c.parse()
147
+}
148
+
149
+func (c *Client) writeRequest(requests ...*request) error {
150
+ c.setWriteTimeout()
151
+ for i := range requests {
152
+ req := make([]interface{}, 0, len(requests[i].args)+1)
153
+ req = append(req, requests[i].cmd)
154
+ req = append(req, requests[i].args...)
155
+ err := resp.WriteArbitraryAsFlattenedStrings(c.Conn, req)
156
+ if err != nil {
157
+ c.Close()
158
+ return err
159
+ }
160
+ }
161
+ return nil
162
+}
163
+
164
+func (c *Client) parse() *Reply {
165
+ m, err := resp.ReadMessage(c.reader)
166
+ if err != nil {
167
+ if t, ok := err.(*net.OpError); !ok || !t.Timeout() {
168
+ // close connection except timeout
169
+ c.Close()
170
+ }
171
+ return &Reply{Type: ErrorReply, Err: err}
172
+ }
173
+ r, err := messageToReply(m)
174
+ if err != nil {
175
+ return &Reply{Type: ErrorReply, Err: err}
176
+ }
177
+ return r
178
+}
179
+
180
+// The error return parameter is for bubbling up parse errors and the like, if
181
+// the error is sent by redis itself as an Err message type, then it will be
182
+// sent back as an actual Reply (wrapped in a CmdError)
183
+func messageToReply(m *resp.Message) (*Reply, error) {
184
+ r := &Reply{}
185
+
186
+ switch m.Type {
187
+ case resp.Err:
188
+ errMsg, err := m.Err()
189
+ if err != nil {
190
+ return nil, err
191
+ }
192
+ if strings.HasPrefix(errMsg.Error(), "LOADING") {
193
+ err = LoadingError
194
+ } else {
195
+ err = &CmdError{errMsg}
196
+ }
197
+ r.Type = ErrorReply
198
+ r.Err = err
199
+
200
+ case resp.SimpleStr:
201
+ status, err := m.Bytes()
202
+ if err != nil {
203
+ return nil, err
204
+ }
205
+ r.Type = StatusReply
206
+ r.buf = status
207
+
208
+ case resp.Int:
209
+ i, err := m.Int()
210
+ if err != nil {
211
+ return nil, err
212
+ }
213
+ r.Type = IntegerReply
214
+ r.int = i
215
+
216
+ case resp.BulkStr:
217
+ b, err := m.Bytes()
218
+ if err != nil {
219
+ return nil, err
220
+ }
221
+ r.Type = BulkReply
222
+ r.buf = b
223
+
224
+ case resp.Nil:
225
+ r.Type = NilReply
226
+
227
+ case resp.Array:
228
+ ms, err := m.Array()
229
+ if err != nil {
230
+ return nil, err
231
+ }
232
+ r.Type = MultiReply
233
+ r.Elems = make([]*Reply, len(ms))
234
+ for i := range ms {
235
+ r.Elems[i], err = messageToReply(ms[i])
236
+ if err != nil {
237
+ return nil, err
238
+ }
239
+ }
240
+ }
241
+
242
+ return r, nil
243
+
244
+}
Godeps/_workspace/src/github.com/fzzy/radix/redis/client_test.go
new
+106
@@ -0,0 +1,106 @@
1
+package redis
2
+
3
+import (
4
+ "bufio"
5
+ "bytes"
6
+ "github.com/stretchr/testify/assert"
7
+ . "testing"
8
+ "time"
9
+)
10
+
11
+func dial(t *T) *Client {
12
+ client, err := DialTimeout("tcp", "127.0.0.1:6379", 10*time.Second)
13
+ assert.Nil(t, err)
14
+ return client
15
+}
16
+
17
+func TestCmd(t *T) {
18
+ c := dial(t)
19
+ v, _ := c.Cmd("echo", "Hello, World!").Str()
20
+ assert.Equal(t, "Hello, World!", v)
21
+
22
+ // Test that a bad command properly returns a *CmdError
23
+ err := c.Cmd("non-existant-cmd").Err
24
+ assert.NotEqual(t, "", err.(*CmdError).Error())
25
+
26
+ // Test that application level errors propagate correctly
27
+ c.Cmd("sadd", "foo", "bar")
28
+ _, err = c.Cmd("get", "foo").Str()
29
+ assert.NotEqual(t, "", err.(*CmdError).Error())
30
+}
31
+
32
+func TestPipeline(t *T) {
33
+ c := dial(t)
34
+ c.Append("echo", "foo")
35
+ c.Append("echo", "bar")
36
+ c.Append("echo", "zot")
37
+
38
+ v, _ := c.GetReply().Str()
39
+ assert.Equal(t, "foo", v)
40
+
41
+ v, _ = c.GetReply().Str()
42
+ assert.Equal(t, "bar", v)
43
+
44
+ v, _ = c.GetReply().Str()
45
+ assert.Equal(t, "zot", v)
46
+
47
+ r := c.GetReply()
48
+ assert.Equal(t, ErrorReply, r.Type)
49
+ assert.Equal(t, PipelineQueueEmptyError, r.Err)
50
+}
51
+
52
+func TestParse(t *T) {
53
+ c := dial(t)
54
+
55
+ parseString := func(b string) *Reply {
56
+ c.reader = bufio.NewReader(bytes.NewBufferString(b))
57
+ return c.parse()
58
+ }
59
+
60
+ // missing \n trailing
61
+ r := parseString("foo")
62
+ assert.Equal(t, ErrorReply, r.Type)
63
+ assert.NotNil(t, r.Err)
64
+
65
+ // error reply
66
+ r = parseString("-ERR unknown command 'foobar'\r\n")
67
+ assert.Equal(t, ErrorReply, r.Type)
68
+ assert.Equal(t, "ERR unknown command 'foobar'", r.Err.Error())
69
+
70
+ // LOADING error
71
+ r = parseString("-LOADING Redis is loading the dataset in memory\r\n")
72
+ assert.Equal(t, ErrorReply, r.Type)
73
+ assert.Equal(t, LoadingError, r.Err)
74
+
75
+ // status reply
76
+ r = parseString("+OK\r\n")
77
+ assert.Equal(t, StatusReply, r.Type)
78
+ assert.Equal(t, []byte("OK"), r.buf)
79
+
80
+ // integer reply
81
+ r = parseString(":1337\r\n")
82
+ assert.Equal(t, IntegerReply, r.Type)
83
+ assert.Equal(t, int64(1337), r.int)
84
+
85
+ // null bulk reply
86
+ r = parseString("$-1\r\n")
87
+ assert.Equal(t, NilReply, r.Type)
88
+
89
+ // bulk reply
90
+ r = parseString("$6\r\nfoobar\r\n")
91
+ assert.Equal(t, BulkReply, r.Type)
92
+ assert.Equal(t, []byte("foobar"), r.buf)
93
+
94
+ // null multi bulk reply
95
+ r = parseString("*-1\r\n")
96
+ assert.Equal(t, NilReply, r.Type)
97
+
98
+ // multi bulk reply
99
+ r = parseString("*5\r\n:0\r\n:1\r\n:2\r\n:3\r\n$6\r\nfoobar\r\n")
100
+ assert.Equal(t, MultiReply, r.Type)
101
+ assert.Equal(t, 5, len(r.Elems))
102
+ for i := 0; i < 4; i++ {
103
+ assert.Equal(t, int64(i), r.Elems[i].int)
104
+ }
105
+ assert.Equal(t, []byte("foobar"), r.Elems[4].buf)
106
+}
Godeps/_workspace/src/github.com/fzzy/radix/redis/doc.go
new
+87
@@ -0,0 +1,87 @@
1
+// A simple client for connecting and interacting with redis.
2
+//
3
+// To import inside your package do:
4
+//
5
+// import "github.com/fzzy/radix/redis"
6
+//
7
+// Connecting
8
+//
9
+// Use either Dial or DialTimeout:
10
+//
11
+// client, err := redis.Dial("tcp", "localhost:6379")
12
+// if err != nil {
13
+// // handle err
14
+// }
15
+//
16
+// Make sure to call Close on the client if you want to clean it up before the
17
+// end of the program.
18
+//
19
+// Cmd and Reply
20
+//
21
+// The Cmd method returns a Reply, which has methods for converting to various
22
+// types. Each of these methods returns an error which can either be a
23
+// connection error (e.g. timeout), an application error (e.g. key is wrong
24
+// type), or a conversion error (e.g. cannot convert to integer). You can also
25
+// directly check the error using the Err field:
26
+//
27
+// foo, err := client.Cmd("GET", "foo").Str()
28
+// if err != nil {
29
+// // handle err
30
+// }
31
+//
32
+// // Checking Err field directly
33
+//
34
+// err = client.Cmd("PING").Err
35
+// if err != nil {
36
+// // handle err
37
+// }
38
+//
39
+// Multi Replies
40
+//
41
+// The elements to Multi replies can be accessed as strings using List or
42
+// ListBytes, or you can use the Elems field for more low-level access:
43
+//
44
+// r := client.Cmd("MGET", "foo", "bar", "baz")
45
+//
46
+// // This:
47
+// for _, elemStr := range r.List() {
48
+// fmt.Println(elemStr)
49
+// }
50
+//
51
+// // is equivalent to this:
52
+// for i := range r.Elems {
53
+// elemStr, _ := r.Elems[i].Str()
54
+// fmt.Println(elemStr)
55
+// }
56
+//
57
+// Pipelining
58
+//
59
+// Pipelining is when the client sends a bunch of commands to the server at
60
+// once, and only once all the commands have been sent does it start reading the
61
+// replies off the socket. This is supported using the Append and GetReply
62
+// methods. Append will simply append the command to a buffer without sending
63
+// it, the first time GetReply is called it will send all the commands in the
64
+// buffer and return the Reply for the first command that was sent. Subsequent
65
+// calls to GetReply return Replys for subsequent commands:
66
+//
67
+// client.Append("GET", "foo")
68
+// client.Append("SET", "bar", "foo")
69
+// client.Append("DEL", "baz")
70
+//
71
+// // Read GET foo reply
72
+// foo, err := client.GetReply().Str()
73
+// if err != nil {
74
+// // handle err
75
+// }
76
+//
77
+// // Read SET bar foo reply
78
+// if err := client.GetReply().Err; err != nil {
79
+// // handle err
80
+// }
81
+//
82
+// // Read DEL baz reply
83
+// if err := client.GetReply().Err; err != nil {
84
+// // handle err
85
+// }
86
+//
87
+package redis
Godeps/_workspace/src/github.com/fzzy/radix/redis/reply.go
new
+275
@@ -0,0 +1,275 @@
1
+package redis
2
+
3
+import (
4
+ "errors"
5
+ "strconv"
6
+ "strings"
7
+)
8
+
9
+// A CmdError implements the error interface and is what is returned when the
10
+// server returns an error on the application level (e.g. key doesn't exist or
11
+// is the wrong type), as opposed to a connection/transport error.
12
+//
13
+// You can test if a reply is a CmdError like so:
14
+//
15
+// r := conn.Cmd("GET", "key-which-isnt-a-string")
16
+// if r.Err != nil {
17
+// if cerr, ok := r.Err.(*redis.CmdError); ok {
18
+// // Is CmdError
19
+// } else {
20
+// // Is other error
21
+// }
22
+// }
23
+type CmdError struct {
24
+ Err error
25
+}
26
+
27
+func (cerr *CmdError) Error() string {
28
+ return cerr.Err.Error()
29
+}
30
+
31
+// Returns true if error returned was due to the redis server being read only
32
+func (cerr *CmdError) Readonly() bool {
33
+ return strings.HasPrefix(cerr.Err.Error(), "READONLY")
34
+}
35
+
36
+//* Reply
37
+
38
+/*
39
+ReplyType describes type of a reply.
40
+
41
+Possible values are:
42
+
43
+StatusReply -- status reply
44
+ErrorReply -- error reply
45
+IntegerReply -- integer reply
46
+NilReply -- nil reply
47
+BulkReply -- bulk reply
48
+MultiReply -- multi bulk reply
49
+*/
50
+type ReplyType uint8
51
+
52
+const (
53
+ StatusReply ReplyType = iota
54
+ ErrorReply
55
+ IntegerReply
56
+ NilReply
57
+ BulkReply
58
+ MultiReply
59
+)
60
+
61
+// Reply holds a Redis reply.
62
+type Reply struct {
63
+ Type ReplyType // Reply type
64
+ Elems []*Reply // Sub-replies
65
+ Err error // Reply error
66
+ buf []byte
67
+ int int64
68
+}
69
+
70
+// Bytes returns the reply value as a byte string or
71
+// an error, if the reply type is not StatusReply or BulkReply.
72
+func (r *Reply) Bytes() ([]byte, error) {
73
+ if r.Type == ErrorReply {
74
+ return nil, r.Err
75
+ }
76
+ if !(r.Type == StatusReply || r.Type == BulkReply) {
77
+ return nil, errors.New("string value is not available for this reply type")
78
+ }
79
+
80
+ return r.buf, nil
81
+}
82
+
83
+// Str is a convenience method for calling Reply.Bytes() and converting it to string
84
+func (r *Reply) Str() (string, error) {
85
+ b, err := r.Bytes()
86
+ if err != nil {
87
+ return "", err
88
+ }
89
+
90
+ return string(b), nil
91
+}
92
+
93
+// Int64 returns the reply value as a int64 or an error,
94
+// if the reply type is not IntegerReply or the reply type
95
+// BulkReply could not be parsed to an int64.
96
+func (r *Reply) Int64() (int64, error) {
97
+ if r.Type == ErrorReply {
98
+ return 0, r.Err
99
+ }
100
+ if r.Type != IntegerReply {
101
+ s, err := r.Str()
102
+ if err == nil {
103
+ i64, err := strconv.ParseInt(s, 10, 64)
104
+ if err != nil {
105
+ return 0, errors.New("failed to parse integer value from string value")
106
+ } else {
107
+ return i64, nil
108
+ }
109
+ }
110
+
111
+ return 0, errors.New("integer value is not available for this reply type")
112
+ }
113
+
114
+ return r.int, nil
115
+}
116
+
117
+// Int is a convenience method for calling Reply.Int64() and converting it to int.
118
+func (r *Reply) Int() (int, error) {
119
+ i64, err := r.Int64()
120
+ if err != nil {
121
+ return 0, err
122
+ }
123
+
124
+ return int(i64), nil
125
+}
126
+
127
+// Bool returns false, if the reply value equals to 0 or "0", otherwise true; or
128
+// an error, if the reply type is not IntegerReply or BulkReply.
129
+func (r *Reply) Bool() (bool, error) {
130
+ if r.Type == ErrorReply {
131
+ return false, r.Err
132
+ }
133
+ i, err := r.Int()
134
+ if err == nil {
135
+ if i == 0 {
136
+ return false, nil
137
+ }
138
+
139
+ return true, nil
140
+ }
141
+
142
+ s, err := r.Str()
143
+ if err == nil {
144
+ if s == "0" {
145
+ return false, nil
146
+ }
147
+
148
+ return true, nil
149
+ }
150
+
151
+ return false, errors.New("boolean value is not available for this reply type")
152
+}
153
+
154
+// List returns a multi bulk reply as a slice of strings or an error.
155
+// The reply type must be MultiReply and its elements' types must all be either BulkReply or NilReply.
156
+// Nil elements are returned as empty strings.
157
+// Useful for list commands.
158
+func (r *Reply) List() ([]string, error) {
159
+ // Doing all this in two places instead of just calling ListBytes() so we don't have
160
+ // to iterate twice
161
+ if r.Type == ErrorReply {
162
+ return nil, r.Err
163
+ }
164
+ if r.Type != MultiReply {
165
+ return nil, errors.New("reply type is not MultiReply")
166
+ }
167
+
168
+ strings := make([]string, len(r.Elems))
169
+ for i, v := range r.Elems {
170
+ if v.Type == BulkReply {
171
+ strings[i] = string(v.buf)
172
+ } else if v.Type == NilReply {
173
+ strings[i] = ""
174
+ } else {
175
+ return nil, errors.New("element type is not BulkReply or NilReply")
176
+ }
177
+ }
178
+
179
+ return strings, nil
180
+}
181
+
182
+// ListBytes returns a multi bulk reply as a slice of bytes slices or an error.
183
+// The reply type must be MultiReply and its elements' types must all be either BulkReply or NilReply.
184
+// Nil elements are returned as nil.
185
+// Useful for list commands.
186
+func (r *Reply) ListBytes() ([][]byte, error) {
187
+ if r.Type == ErrorReply {
188
+ return nil, r.Err
189
+ }
190
+ if r.Type != MultiReply {
191
+ return nil, errors.New("reply type is not MultiReply")
192
+ }
193
+
194
+ bufs := make([][]byte, len(r.Elems))
195
+ for i, v := range r.Elems {
196
+ if v.Type == BulkReply {
197
+ bufs[i] = v.buf
198
+ } else if v.Type == NilReply {
199
+ bufs[i] = nil
200
+ } else {
201
+ return nil, errors.New("element type is not BulkReply or NilReply")
202
+ }
203
+ }
204
+
205
+ return bufs, nil
206
+}
207
+
208
+// Hash returns a multi bulk reply as a map[string]string or an error.
209
+// The reply type must be MultiReply,
210
+// it must have an even number of elements,
211
+// they must be in a "key value key value..." order and
212
+// values must all be either BulkReply or NilReply.
213
+// Nil values are returned as empty strings.
214
+// Useful for hash commands.
215
+func (r *Reply) Hash() (map[string]string, error) {
216
+ if r.Type == ErrorReply {
217
+ return nil, r.Err
218
+ }
219
+ rmap := map[string]string{}
220
+
221
+ if r.Type != MultiReply {
222
+ return nil, errors.New("reply type is not MultiReply")
223
+ }
224
+
225
+ if len(r.Elems)%2 != 0 {
226
+ return nil, errors.New("reply has odd number of elements")
227
+ }
228
+
229
+ for i := 0; i < len(r.Elems)/2; i++ {
230
+ var val string
231
+
232
+ key, err := r.Elems[i*2].Str()
233
+ if err != nil {
234
+ return nil, errors.New("key element has no string reply")
235
+ }
236
+
237
+ v := r.Elems[i*2+1]
238
+ if v.Type == BulkReply {
239
+ val = string(v.buf)
240
+ rmap[key] = val
241
+ } else if v.Type == NilReply {
242
+ } else {
243
+ return nil, errors.New("value element type is not BulkReply or NilReply")
244
+ }
245
+ }
246
+
247
+ return rmap, nil
248
+}
249
+
250
+// String returns a string representation of the reply and its sub-replies.
251
+// This method is for debugging.
252
+// Use method Reply.Str() for reading string reply.
253
+func (r *Reply) String() string {
254
+ switch r.Type {
255
+ case ErrorReply:
256
+ return r.Err.Error()
257
+ case StatusReply:
258
+ fallthrough
259
+ case BulkReply:
260
+ return string(r.buf)
261
+ case IntegerReply:
262
+ return strconv.FormatInt(r.int, 10)
263
+ case NilReply:
264
+ return "<nil>"
265
+ case MultiReply:
266
+ s := "[ "
267
+ for _, e := range r.Elems {
268
+ s = s + e.String() + " "
269
+ }
270
+ return s + "]"
271
+ }
272
+
273
+ // This should never execute
274
+ return ""
275
+}
Godeps/_workspace/src/github.com/fzzy/radix/redis/reply_test.go
new
+125
@@ -0,0 +1,125 @@
1
+package redis
2
+
3
+import (
4
+ "github.com/stretchr/testify/assert"
5
+ . "testing"
6
+)
7
+
8
+func TestStr(t *T) {
9
+ r := &Reply{Type: ErrorReply, Err: LoadingError}
10
+ _, err := r.Str()
11
+ assert.Equal(t, LoadingError, err)
12
+
13
+ r = &Reply{Type: IntegerReply}
14
+ _, err = r.Str()
15
+ assert.NotNil(t, err)
16
+
17
+ r = &Reply{Type: StatusReply, buf: []byte("foo")}
18
+ b, err := r.Str()
19
+ assert.Nil(t, err)
20
+ assert.Equal(t, "foo", b)
21
+
22
+ r = &Reply{Type: BulkReply, buf: []byte("foo")}
23
+ b, err = r.Str()
24
+ assert.Nil(t, err)
25
+ assert.Equal(t, "foo", b)
26
+}
27
+
28
+func TestBytes(t *T) {
29
+ r := &Reply{Type: BulkReply, buf: []byte("foo")}
30
+ b, err := r.Bytes()
31
+ assert.Nil(t, err)
32
+ assert.Equal(t, []byte("foo"), b)
33
+}
34
+
35
+func TestInt64(t *T) {
36
+ r := &Reply{Type: ErrorReply, Err: LoadingError}
37
+ _, err := r.Int64()
38
+ assert.Equal(t, LoadingError, err)
39
+
40
+ r = &Reply{Type: IntegerReply, int: 5}
41
+ b, err := r.Int64()
42
+ assert.Nil(t, err)
43
+ assert.Equal(t, int64(5), b)
44
+
45
+ r = &Reply{Type: BulkReply, buf: []byte("5")}
46
+ b, err = r.Int64()
47
+ assert.Nil(t, err)
48
+ assert.Equal(t, int64(5), b)
49
+
50
+ r = &Reply{Type: BulkReply, buf: []byte("foo")}
51
+ _, err = r.Int64()
52
+ assert.NotNil(t, err)
53
+}
54
+
55
+func TestInt(t *T) {
56
+ r := &Reply{Type: IntegerReply, int: 5}
57
+ b, err := r.Int()
58
+ assert.Nil(t, err)
59
+ assert.Equal(t, 5, b)
60
+}
61
+
62
+func TestBool(t *T) {
63
+ r := &Reply{Type: IntegerReply, int: 0}
64
+ b, err := r.Bool()
65
+ assert.Nil(t, err)
66
+ assert.Equal(t, false, b)
67
+
68
+ r = &Reply{Type: StatusReply, buf: []byte("0")}
69
+ b, err = r.Bool()
70
+ assert.Nil(t, err)
71
+ assert.Equal(t, false, b)
72
+
73
+ r = &Reply{Type: IntegerReply, int: 2}
74
+ b, err = r.Bool()
75
+ assert.Nil(t, err)
76
+ assert.Equal(t, true, b)
77
+
78
+ r = &Reply{Type: NilReply}
79
+ _, err = r.Bool()
80
+ assert.NotNil(t, err)
81
+}
82
+
83
+func TestList(t *T) {
84
+ r := &Reply{Type: MultiReply}
85
+ r.Elems = make([]*Reply, 3)
86
+ r.Elems[0] = &Reply{Type: BulkReply, buf: []byte("0")}
87
+ r.Elems[1] = &Reply{Type: NilReply}
88
+ r.Elems[2] = &Reply{Type: BulkReply, buf: []byte("2")}
89
+ l, err := r.List()
90
+ assert.Nil(t, err)
91
+ assert.Equal(t, 3, len(l))
92
+ assert.Equal(t, "0", l[0])
93
+ assert.Equal(t, "", l[1])
94
+ assert.Equal(t, "2", l[2])
95
+}
96
+
97
+func TestBytesList(t *T) {
98
+ r := &Reply{Type: MultiReply}
99
+ r.Elems = make([]*Reply, 3)
100
+ r.Elems[0] = &Reply{Type: BulkReply, buf: []byte("0")}
101
+ r.Elems[1] = &Reply{Type: NilReply}
102
+ r.Elems[2] = &Reply{Type: BulkReply, buf: []byte("2")}
103
+ l, err := r.ListBytes()
104
+ assert.Nil(t, err)
105
+ assert.Equal(t, 3, len(l))
106
+ assert.Equal(t, []byte("0"), l[0])
107
+ assert.Nil(t, l[1])
108
+ assert.Equal(t, []byte("2"), l[2])
109
+}
110
+
111
+func TestHash(t *T) {
112
+ r := &Reply{Type: MultiReply}
113
+ r.Elems = make([]*Reply, 6)
114
+ r.Elems[0] = &Reply{Type: BulkReply, buf: []byte("a")}
115
+ r.Elems[1] = &Reply{Type: BulkReply, buf: []byte("0")}
116
+ r.Elems[2] = &Reply{Type: BulkReply, buf: []byte("b")}
117
+ r.Elems[3] = &Reply{Type: NilReply}
118
+ r.Elems[4] = &Reply{Type: BulkReply, buf: []byte("c")}
119
+ r.Elems[5] = &Reply{Type: BulkReply, buf: []byte("2")}
120
+ h, err := r.Hash()
121
+ assert.Nil(t, err)
122
+ assert.Equal(t, "0", h["a"])
123
+ assert.Equal(t, "", h["b"])
124
+ assert.Equal(t, "2", h["c"])
125
+}
Godeps/_workspace/src/github.com/fzzy/radix/redis/resp/resp.go
new
+466
@@ -0,0 +1,466 @@
1
+// This package provides an easy to use interface for creating and parsing
2
+// messages encoded in the REdis Serialization Protocol (RESP). You can check
3
+// out more details about the protocol here: http://redis.io/topics/protocol
4
+package resp
5
+
6
+import (
7
+ "bufio"
8
+ "bytes"
9
+ "errors"
10
+ "fmt"
11
+ "io"
12
+ "reflect"
13
+ "strconv"
14
+)
15
+
16
+var (
17
+ delim = []byte{'\r', '\n'}
18
+ delimEnd = delim[len(delim)-1]
19
+)
20
+
21
+type Type int
22
+
23
+const (
24
+ SimpleStr Type = iota
25
+ Err
26
+ Int
27
+ BulkStr
28
+ Array
29
+ Nil
30
+)
31
+
32
+const (
33
+ simpleStrPrefix = '+'
34
+ errPrefix = '-'
35
+ intPrefix = ':'
36
+ bulkStrPrefix = '$'
37
+ arrayPrefix = '*'
38
+)
39
+
40
+// Parse errors
41
+var (
42
+ badType = errors.New("wrong type")
43
+ parseErr = errors.New("parse error")
44
+)
45
+
46
+type Message struct {
47
+ Type
48
+ val interface{}
49
+ raw []byte
50
+}
51
+
52
+// NewMessagePParses the given raw message and returns a Message struct
53
+// representing it
54
+func NewMessage(b []byte) (*Message, error) {
55
+ return ReadMessage(bytes.NewReader(b))
56
+}
57
+
58
+// Can be used when writing to a resp stream to write a simple-string-style
59
+// stream (e.g. +OK\r\n) instead of the default bulk-string-style strings.
60
+//
61
+// foo := NewSimpleString("foo")
62
+// bar := NewSimpleString("bar")
63
+// baz := NewSimpleString("baz")
64
+// resp.WriteArbitrary(w, foo)
65
+// resp.WriteArbitrary(w, []interface{}{bar, baz})
66
+//
67
+func NewSimpleString(s string) *Message {
68
+ b := append(make([]byte, 0, len(s) + 3), '+')
69
+ b = append(b, []byte(s)...)
70
+ b = append(b, '\r', '\n')
71
+ return &Message{
72
+ Type: SimpleStr,
73
+ val: s,
74
+ raw: b,
75
+ }
76
+}
77
+
78
+// ReadMessage attempts to read a message object from the given io.Reader, parse
79
+// it, and return a Message struct representing it
80
+func ReadMessage(reader io.Reader) (*Message, error) {
81
+ r := bufio.NewReader(reader)
82
+ return bufioReadMessage(r)
83
+}
84
+
85
+func bufioReadMessage(r *bufio.Reader) (*Message, error) {
86
+ b, err := r.Peek(1)
87
+ if err != nil {
88
+ return nil, err
89
+ }
90
+ switch b[0] {
91
+ case simpleStrPrefix:
92
+ return readSimpleStr(r)
93
+ case errPrefix:
94
+ return readError(r)
95
+ case intPrefix:
96
+ return readInt(r)
97
+ case bulkStrPrefix:
98
+ return readBulkStr(r)
99
+ case arrayPrefix:
100
+ return readArray(r)
101
+ default:
102
+ return nil, badType
103
+ }
104
+}
105
+
106
+func readSimpleStr(r *bufio.Reader) (*Message, error) {
107
+ b, err := r.ReadBytes(delimEnd)
108
+ if err != nil {
109
+ return nil, err
110
+ }
111
+ return &Message{Type: SimpleStr, val: b[1 : len(b)-2], raw: b}, nil
112
+}
113
+
114
+func readError(r *bufio.Reader) (*Message, error) {
115
+ b, err := r.ReadBytes(delimEnd)
116
+ if err != nil {
117
+ return nil, err
118
+ }
119
+ return &Message{Type: Err, val: b[1 : len(b)-2], raw: b}, nil
120
+}
121
+
122
+func readInt(r *bufio.Reader) (*Message, error) {
123
+ b, err := r.ReadBytes(delimEnd)
124
+ if err != nil {
125
+ return nil, err
126
+ }
127
+ i, err := strconv.ParseInt(string(b[1:len(b)-2]), 10, 64)
128
+ if err != nil {
129
+ return nil, parseErr
130
+ }
131
+ return &Message{Type: Int, val: i, raw: b}, nil
132
+}
133
+
134
+func readBulkStr(r *bufio.Reader) (*Message, error) {
135
+ b, err := r.ReadBytes(delimEnd)
136
+ if err != nil {
137
+ return nil, err
138
+ }
139
+ size, err := strconv.ParseInt(string(b[1:len(b)-2]), 10, 64)
140
+ if err != nil {
141
+ return nil, parseErr
142
+ }
143
+ if size < 0 {
144
+ return &Message{Type: Nil, raw: b}, nil
145
+ }
146
+ total := make([]byte, size)
147
+ b2 := total
148
+ var n int
149
+ for len(b2) > 0 {
150
+ n, err = r.Read(b2)
151
+ if err != nil {
152
+ return nil, err
153
+ }
154
+ b2 = b2[n:]
155
+ }
156
+
157
+ // There's a hanging \r\n there, gotta read past it
158
+ trail := make([]byte, 2)
159
+ for i := 0; i < 2; i++ {
160
+ if c, err := r.ReadByte(); err != nil {
161
+ return nil, err
162
+ } else {
163
+ trail[i] = c
164
+ }
165
+ }
166
+
167
+ blens := len(b) + len(total)
168
+ raw := make([]byte, 0, blens+2)
169
+ raw = append(raw, b...)
170
+ raw = append(raw, total...)
171
+ raw = append(raw, trail...)
172
+ return &Message{Type: BulkStr, val: total, raw: raw}, nil
173
+}
174
+
175
+func readArray(r *bufio.Reader) (*Message, error) {
176
+ b, err := r.ReadBytes(delimEnd)
177
+ if err != nil {
178
+ return nil, err
179
+ }
180
+ size, err := strconv.ParseInt(string(b[1:len(b)-2]), 10, 64)
181
+ if err != nil {
182
+ return nil, parseErr
183
+ }
184
+ if size < 0 {
185
+ return &Message{Type: Nil, raw: b}, nil
186
+ }
187
+
188
+ arr := make([]*Message, size)
189
+ for i := range arr {
190
+ m, err := bufioReadMessage(r)
191
+ if err != nil {
192
+ return nil, err
193
+ }
194
+ arr[i] = m
195
+ b = append(b, m.raw...)
196
+ }
197
+ return &Message{Type: Array, val: arr, raw: b}, nil
198
+}
199
+
200
+// Bytes returns a byte slice representing the value of the Message. Only valid
201
+// for a Message of type SimpleStr, Err, and BulkStr. Others will return an
202
+// error
203
+func (m *Message) Bytes() ([]byte, error) {
204
+ if b, ok := m.val.([]byte); ok {
205
+ return b, nil
206
+ }
207
+ return nil, badType
208
+}
209
+
210
+// Str is a Convenience method around Bytes which converts the output to a
211
+// string
212
+func (m *Message) Str() (string, error) {
213
+ b, err := m.Bytes()
214
+ if err != nil {
215
+ return "", err
216
+ }
217
+ return string(b), nil
218
+}
219
+
220
+// Int returns an int64 representing the value of the Message. Only valid for
221
+// Int messages
222
+func (m *Message) Int() (int64, error) {
223
+ if i, ok := m.val.(int64); ok {
224
+ return i, nil
225
+ }
226
+ return 0, badType
227
+}
228
+
229
+// Err returns an error representing the value of the Message. Only valid for
230
+// Err messages
231
+func (m *Message) Err() (error, error) {
232
+ if m.Type != Err {
233
+ return nil, badType
234
+ }
235
+ s, err := m.Str()
236
+ if err != nil {
237
+ return nil, err
238
+ }
239
+ return errors.New(s), nil
240
+}
241
+
242
+// Array returns the Message slice encompassed by this Messsage, assuming the
243
+// Message is of type Array
244
+func (m *Message) Array() ([]*Message, error) {
245
+ if a, ok := m.val.([]*Message); ok {
246
+ return a, nil
247
+ }
248
+ return nil, badType
249
+}
250
+
251
+// WriteMessage takes in the given Message and writes its encoded form to the
252
+// given io.Writer
253
+func WriteMessage(w io.Writer, m *Message) error {
254
+ _, err := w.Write(m.raw)
255
+ return err
256
+}
257
+
258
+// WriteArbitrary takes in any primitive golang value, or Message, and writes
259
+// its encoded form to the given io.Writer, inferring types where appropriate.
260
+func WriteArbitrary(w io.Writer, m interface{}) error {
261
+ b := format(m, false)
262
+ _, err := w.Write(b)
263
+ return err
264
+}
265
+
266
+// WriteArbitraryAsString is similar to WriteArbitraryAsFlattenedString except
267
+// that it won't flatten any embedded arrays.
268
+func WriteArbitraryAsString(w io.Writer, m interface{}) error {
269
+ b := format(m, true)
270
+ _, err := w.Write(b)
271
+ return err
272
+}
273
+
274
+// WriteArbitraryAsFlattenedStrings is similar to WriteArbitrary except that it
275
+// will encode all types except Array as a BulkStr, converting the argument into
276
+// a string first as necessary. It will also flatten any embedded arrays into a
277
+// single long array. This is useful because commands to a redis server must be
278
+// given as an array of bulk strings. If the argument isn't already in a slice
279
+// or map it will be wrapped so that it is written as an Array of size one.
280
+//
281
+// Note that if a Message type is found it will *not* be encoded to a BulkStr,
282
+// but will simply be passed through as whatever type it already represents.
283
+func WriteArbitraryAsFlattenedStrings(w io.Writer, m interface{}) error {
284
+ fm := flatten(m)
285
+ return WriteArbitraryAsString(w, fm)
286
+}
287
+
288
+func format(m interface{}, forceString bool) []byte {
289
+ switch mt := m.(type) {
290
+ case []byte:
291
+ return formatStr(mt)
292
+ case string:
293
+ return formatStr([]byte(mt))
294
+ case bool:
295
+ if mt {
296
+ return formatStr([]byte("1"))
297
+ } else {
298
+ return formatStr([]byte("0"))
299
+ }
300
+ case nil:
301
+ if forceString {
302
+ return formatStr([]byte{})
303
+ } else {
304
+ return formatNil()
305
+ }
306
+ case int:
307
+ return formatInt(int64(mt), forceString)
308
+ case int8:
309
+ return formatInt(int64(mt), forceString)
310
+ case int16:
311
+ return formatInt(int64(mt), forceString)
312
+ case int32:
313
+ return formatInt(int64(mt), forceString)
314
+ case int64:
315
+ return formatInt(mt, forceString)
316
+ case uint:
317
+ return formatInt(int64(mt), forceString)
318
+ case uint8:
319
+ return formatInt(int64(mt), forceString)
320
+ case uint16:
321
+ return formatInt(int64(mt), forceString)
322
+ case uint32:
323
+ return formatInt(int64(mt), forceString)
324
+ case uint64:
325
+ return formatInt(int64(mt), forceString)
326
+ case float32:
327
+ ft := strconv.FormatFloat(float64(mt), 'f', -1, 32)
328
+ return formatStr([]byte(ft))
329
+ case float64:
330
+ ft := strconv.FormatFloat(mt, 'f', -1, 64)
331
+ return formatStr([]byte(ft))
332
+ case error:
333
+ if forceString {
334
+ return formatStr([]byte(mt.Error()))
335
+ } else {
336
+ return formatErr(mt)
337
+ }
338
+
339
+ // We duplicate the below code here a bit, since this is the common case and
340
+ // it'd be better to not get the reflect package involved here
341
+ case []interface{}:
342
+ l := len(mt)
343
+ b := make([]byte, 0, l*1024)
344
+ b = append(b, '*')
345
+ b = append(b, []byte(strconv.Itoa(l))...)
346
+ b = append(b, []byte("\r\n")...)
347
+ for i := 0; i < l; i++ {
348
+ b = append(b, format(mt[i], forceString)...)
349
+ }
350
+ return b
351
+
352
+ case *Message:
353
+ return mt.raw
354
+
355
+ default:
356
+ // Fallback to reflect-based.
357
+ switch reflect.TypeOf(m).Kind() {
358
+ case reflect.Slice:
359
+ rm := reflect.ValueOf(mt)
360
+ l := rm.Len()
361
+ b := make([]byte, 0, l*1024)
362
+ b = append(b, '*')
363
+ b = append(b, []byte(strconv.Itoa(l))...)
364
+ b = append(b, []byte("\r\n")...)
365
+ for i := 0; i < l; i++ {
366
+ vv := rm.Index(i).Interface()
367
+ b = append(b, format(vv, forceString)...)
368
+ }
369
+
370
+ return b
371
+ case reflect.Map:
372
+ rm := reflect.ValueOf(mt)
373
+ l := rm.Len() * 2
374
+ b := make([]byte, 0, l*1024)
375
+ b = append(b, '*')
376
+ b = append(b, []byte(strconv.Itoa(l))...)
377
+ b = append(b, []byte("\r\n")...)
378
+ keys := rm.MapKeys()
379
+ for _, k := range keys {
380
+ kv := k.Interface()
381
+ vv := rm.MapIndex(k).Interface()
382
+ b = append(b, format(kv, forceString)...)
383
+ b = append(b, format(vv, forceString)...)
384
+ }
385
+ return b
386
+ default:
387
+ return formatStr([]byte(fmt.Sprint(m)))
388
+ }
389
+ }
390
+}
391
+
392
+var typeOfBytes = reflect.TypeOf([]byte(nil))
393
+
394
+func flatten(m interface{}) []interface{} {
395
+ t := reflect.TypeOf(m)
396
+
397
+ // If it's a byte-slice we don't want to flatten
398
+ if t == typeOfBytes {
399
+ return []interface{}{m}
400
+ }
401
+
402
+ switch t.Kind() {
403
+ case reflect.Slice:
404
+ rm := reflect.ValueOf(m)
405
+ l := rm.Len()
406
+ ret := make([]interface{}, 0, l)
407
+ for i := 0; i < l; i++ {
408
+ ret = append(ret, flatten(rm.Index(i).Interface())...)
409
+ }
410
+ return ret
411
+
412
+ case reflect.Map:
413
+ rm := reflect.ValueOf(m)
414
+ l := rm.Len() * 2
415
+ keys := rm.MapKeys()
416
+ ret := make([]interface{}, 0, l)
417
+ for _, k := range keys {
418
+ kv := k.Interface()
419
+ vv := rm.MapIndex(k).Interface()
420
+ ret = append(ret, flatten(kv)...)
421
+ ret = append(ret, flatten(vv)...)
422
+ }
423
+ return ret
424
+
425
+ default:
426
+ return []interface{}{m}
427
+ }
428
+}
429
+
430
+func formatStr(b []byte) []byte {
431
+ l := strconv.Itoa(len(b))
432
+ bs := make([]byte, 0, len(l)+len(b)+5)
433
+ bs = append(bs, bulkStrPrefix)
434
+ bs = append(bs, []byte(l)...)
435
+ bs = append(bs, delim...)
436
+ bs = append(bs, b...)
437
+ bs = append(bs, delim...)
438
+ return bs
439
+}
440
+
441
+func formatErr(ierr error) []byte {
442
+ ierrstr := []byte(ierr.Error())
443
+ bs := make([]byte, 0, len(ierrstr)+3)
444
+ bs = append(bs, errPrefix)
445
+ bs = append(bs, ierrstr...)
446
+ bs = append(bs, delim...)
447
+ return bs
448
+}
449
+
450
+func formatInt(i int64, forceString bool) []byte {
451
+ istr := strconv.FormatInt(i, 10)
452
+ if forceString {
453
+ return formatStr([]byte(istr))
454
+ }
455
+ bs := make([]byte, 0, len(istr)+3)
456
+ bs = append(bs, intPrefix)
457
+ bs = append(bs, istr...)
458
+ bs = append(bs, delim...)
459
+ return bs
460
+}
461
+
462
+var nilFormatted = []byte("$-1\r\n")
463
+
464
+func formatNil() []byte {
465
+ return nilFormatted
466
+}
Godeps/_workspace/src/github.com/fzzy/radix/redis/resp/resp_test.go
new
+209
@@ -0,0 +1,209 @@
1
+package resp
2
+
3
+import (
4
+ "bytes"
5
+ "errors"
6
+ "github.com/stretchr/testify/assert"
7
+ . "testing"
8
+)
9
+
10
+func TestRead(t *T) {
11
+ var m *Message
12
+ var err error
13
+
14
+ _, err = NewMessage(nil)
15
+ assert.NotNil(t, err)
16
+
17
+ _, err = NewMessage([]byte{})
18
+ assert.NotNil(t, err)
19
+
20
+ // Simple string
21
+ m, _ = NewMessage([]byte("+ohey\r\n"))
22
+ assert.Equal(t, SimpleStr, m.Type)
23
+ assert.Equal(t, []byte("ohey"), m.val)
24
+
25
+ // Empty simple string
26
+ m, _ = NewMessage([]byte("+\r\n"))
27
+ assert.Equal(t, SimpleStr, m.Type)
28
+ assert.Equal(t, []byte(""), m.val.([]byte))
29
+
30
+ // Error
31
+ m, _ = NewMessage([]byte("-ohey\r\n"))
32
+ assert.Equal(t, Err, m.Type)
33
+ assert.Equal(t, []byte("ohey"), m.val.([]byte))
34
+
35
+ // Empty error
36
+ m, _ = NewMessage([]byte("-\r\n"))
37
+ assert.Equal(t, Err, m.Type)
38
+ assert.Equal(t, []byte(""), m.val.([]byte))
39
+
40
+ // Int
41
+ m, _ = NewMessage([]byte(":1024\r\n"))
42
+ assert.Equal(t, Int, m.Type)
43
+ assert.Equal(t, int64(1024), m.val.(int64))
44
+
45
+ // Bulk string
46
+ m, _ = NewMessage([]byte("$3\r\nfoo\r\n"))
47
+ assert.Equal(t, BulkStr, m.Type)
48
+ assert.Equal(t, []byte("foo"), m.val.([]byte))
49
+
50
+ // Empty bulk string
51
+ m, _ = NewMessage([]byte("$0\r\n\r\n"))
52
+ assert.Equal(t, BulkStr, m.Type)
53
+ assert.Equal(t, []byte(""), m.val.([]byte))
54
+
55
+ // Nil bulk string
56
+ m, _ = NewMessage([]byte("$-1\r\n"))
57
+ assert.Equal(t, Nil, m.Type)
58
+
59
+ // Array
60
+ m, _ = NewMessage([]byte("*2\r\n+foo\r\n+bar\r\n"))
61
+ assert.Equal(t, Array, m.Type)
62
+ assert.Equal(t, 2, len(m.val.([]*Message)))
63
+ assert.Equal(t, SimpleStr, m.val.([]*Message)[0].Type)
64
+ assert.Equal(t, []byte("foo"), m.val.([]*Message)[0].val.([]byte))
65
+ assert.Equal(t, SimpleStr, m.val.([]*Message)[1].Type)
66
+ assert.Equal(t, []byte("bar"), m.val.([]*Message)[1].val.([]byte))
67
+
68
+ // Empty array
69
+ m, _ = NewMessage([]byte("*0\r\n"))
70
+ assert.Equal(t, Array, m.Type)
71
+ assert.Equal(t, 0, len(m.val.([]*Message)))
72
+
73
+ // Nil Array
74
+ m, _ = NewMessage([]byte("*-1\r\n"))
75
+ assert.Equal(t, Nil, m.Type)
76
+
77
+ // Embedded Array
78
+ m, _ = NewMessage([]byte("*3\r\n+foo\r\n+bar\r\n*2\r\n+foo\r\n+bar\r\n"))
79
+ assert.Equal(t, Array, m.Type)
80
+ assert.Equal(t, 3, len(m.val.([]*Message)))
81
+ assert.Equal(t, SimpleStr, m.val.([]*Message)[0].Type)
82
+ assert.Equal(t, []byte("foo"), m.val.([]*Message)[0].val.([]byte))
83
+ assert.Equal(t, SimpleStr, m.val.([]*Message)[1].Type)
84
+ assert.Equal(t, []byte("bar"), m.val.([]*Message)[1].val.([]byte))
85
+ m = m.val.([]*Message)[2]
86
+ assert.Equal(t, 2, len(m.val.([]*Message)))
87
+ assert.Equal(t, SimpleStr, m.val.([]*Message)[0].Type)
88
+ assert.Equal(t, []byte("foo"), m.val.([]*Message)[0].val.([]byte))
89
+ assert.Equal(t, SimpleStr, m.val.([]*Message)[1].Type)
90
+ assert.Equal(t, []byte("bar"), m.val.([]*Message)[1].val.([]byte))
91
+
92
+ // Test that two bulks in a row read correctly
93
+ m, _ = NewMessage([]byte("*2\r\n$3\r\nfoo\r\n$3\r\nbar\r\n"))
94
+ assert.Equal(t, Array, m.Type)
95
+ assert.Equal(t, 2, len(m.val.([]*Message)))
96
+ assert.Equal(t, BulkStr, m.val.([]*Message)[0].Type)
97
+ assert.Equal(t, []byte("foo"), m.val.([]*Message)[0].val.([]byte))
98
+ assert.Equal(t, BulkStr, m.val.([]*Message)[1].Type)
99
+ assert.Equal(t, []byte("bar"), m.val.([]*Message)[1].val.([]byte))
100
+}
101
+
102
+type arbitraryTest struct {
103
+ val interface{}
104
+ expect []byte
105
+}
106
+
107
+var nilMessage, _ = NewMessage([]byte("$-1\r\n"))
108
+
109
+var arbitraryTests = []arbitraryTest{
110
+ {[]byte("OHAI"), []byte("$4\r\nOHAI\r\n")},
111
+ {"OHAI", []byte("$4\r\nOHAI\r\n")},
112
+ {true, []byte("$1\r\n1\r\n")},
113
+ {false, []byte("$1\r\n0\r\n")},
114
+ {nil, []byte("$-1\r\n")},
115
+ {80, []byte(":80\r\n")},
116
+ {int64(-80), []byte(":-80\r\n")},
117
+ {uint64(80), []byte(":80\r\n")},
118
+ {float32(0.1234), []byte("$6\r\n0.1234\r\n")},
119
+ {float64(0.1234), []byte("$6\r\n0.1234\r\n")},
120
+ {errors.New("hi"), []byte("-hi\r\n")},
121
+
122
+ {nilMessage, []byte("$-1\r\n")},
123
+
124
+ {[]int{1, 2, 3}, []byte("*3\r\n:1\r\n:2\r\n:3\r\n")},
125
+ {map[int]int{1: 2}, []byte("*2\r\n:1\r\n:2\r\n")},
126
+
127
+ {NewSimpleString("OK"), []byte("+OK\r\n")},
128
+}
129
+
130
+var arbitraryAsStringTests = []arbitraryTest{
131
+ {[]byte("OHAI"), []byte("$4\r\nOHAI\r\n")},
132
+ {"OHAI", []byte("$4\r\nOHAI\r\n")},
133
+ {true, []byte("$1\r\n1\r\n")},
134
+ {false, []byte("$1\r\n0\r\n")},
135
+ {nil, []byte("$0\r\n\r\n")},
136
+ {80, []byte("$2\r\n80\r\n")},
137
+ {int64(-80), []byte("$3\r\n-80\r\n")},
138
+ {uint64(80), []byte("$2\r\n80\r\n")},
139
+ {float32(0.1234), []byte("$6\r\n0.1234\r\n")},
140
+ {float64(0.1234), []byte("$6\r\n0.1234\r\n")},
141
+ {errors.New("hi"), []byte("$2\r\nhi\r\n")},
142
+
143
+ {nilMessage, []byte("$-1\r\n")},
144
+
145
+ {[]int{1, 2, 3}, []byte("*3\r\n$1\r\n1\r\n$1\r\n2\r\n$1\r\n3\r\n")},
146
+ {map[int]int{1: 2}, []byte("*2\r\n$1\r\n1\r\n$1\r\n2\r\n")},
147
+
148
+ {NewSimpleString("OK"), []byte("+OK\r\n")},
149
+}
150
+
151
+var arbitraryAsFlattenedStringsTests = []arbitraryTest{
152
+ {
153
+ []interface{}{"wat", map[string]interface{}{
154
+ "foo": 1,
155
+ }},
156
+ []byte("*3\r\n$3\r\nwat\r\n$3\r\nfoo\r\n$1\r\n1\r\n"),
157
+ },
158
+}
159
+
160
+func TestWriteArbitrary(t *T) {
161
+ var err error
162
+ buf := bytes.NewBuffer([]byte{})
163
+ for _, test := range arbitraryTests {
164
+ t.Logf("Checking test %v", test)
165
+ buf.Reset()
166
+ err = WriteArbitrary(buf, test.val)
167
+ assert.Nil(t, err)
168
+ assert.Equal(t, test.expect, buf.Bytes())
169
+ }
170
+}
171
+
172
+func TestWriteArbitraryAsString(t *T) {
173
+ var err error
174
+ buf := bytes.NewBuffer([]byte{})
175
+ for _, test := range arbitraryAsStringTests {
176
+ t.Logf("Checking test %v", test)
177
+ buf.Reset()
178
+ err = WriteArbitraryAsString(buf, test.val)
179
+ assert.Nil(t, err)
180
+ assert.Equal(t, test.expect, buf.Bytes())
181
+ }
182
+}
183
+
184
+func TestWriteArbitraryAsFlattenedStrings(t *T) {
185
+ var err error
186
+ buf := bytes.NewBuffer([]byte{})
187
+ for _, test := range arbitraryAsFlattenedStringsTests {
188
+ t.Logf("Checking test %v", test)
189
+ buf.Reset()
190
+ err = WriteArbitraryAsFlattenedStrings(buf, test.val)
191
+ assert.Nil(t, err)
192
+ assert.Equal(t, test.expect, buf.Bytes())
193
+ }
194
+}
195
+
196
+func TestMessageWrite(t *T) {
197
+ var err error
198
+ var m *Message
199
+ buf := bytes.NewBuffer([]byte{})
200
+ for _, test := range arbitraryTests {
201
+ t.Logf("Checking test; %v", test)
202
+ buf.Reset()
203
+ m, err = NewMessage(test.expect)
204
+ assert.Nil(t, err)
205
+ err = WriteMessage(buf, m)
206
+ assert.Nil(t, err)
207
+ assert.Equal(t, test.expect, buf.Bytes())
208
+ }
209
+}