@cryptotaxi247 / kubo / commits / 8f62ac827

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 +}