package redis import ( "errors" "fmt" "strconv" "gopkg.in/bufio.v1" ) type multiBulkParser func(rd *bufio.Reader, n int64) (interface{}, error) var ( errReaderTooSmall = errors.New("redis: reader is too small") ) //------------------------------------------------------------------------------ func appendArgs(buf []byte, args []string) []byte { buf = append(buf, '*') buf = strconv.AppendUint(buf, uint64(len(args)), 10) buf = append(buf, '\r', '\n') for _, arg := range args { buf = append(buf, '$') buf = strconv.AppendUint(buf, uint64(len(arg)), 10) buf = append(buf, '\r', '\n') buf = append(buf, arg...) buf = append(buf, '\r', '\n') } return buf } //------------------------------------------------------------------------------ func readLine(rd *bufio.Reader) ([]byte, error) { line, isPrefix, err := rd.ReadLine() if err != nil { return line, err } if isPrefix { return line, errReaderTooSmall } return line, nil } func readN(rd *bufio.Reader, n int) ([]byte, error) { b, err := rd.ReadN(n) if err == bufio.ErrBufferFull { tmp := make([]byte, n) r := copy(tmp, b) b = tmp for { nn, err := rd.Read(b[r:]) r += nn if r >= n { // Ignore error if we read enough. break } if err != nil { return nil, err } } } else if err != nil { return nil, err } return b, nil } //------------------------------------------------------------------------------ func parseReq(rd *bufio.Reader) ([]string, error) { line, err := readLine(rd) if err != nil { return nil, err } if line[0] != '*' { return []string{string(line)}, nil } numReplies, err := strconv.ParseInt(string(line[1:]), 10, 64) if err != nil { return nil, err } args := make([]string, 0, numReplies) for i := int64(0); i < numReplies; i++ { line, err = readLine(rd) if err != nil { return nil, err } if line[0] != '$' { return nil, fmt.Errorf("redis: expected '$', but got %q", line) } argLen, err := strconv.ParseInt(string(line[1:]), 10, 32) if err != nil { return nil, err } arg, err := readN(rd, int(argLen)+2) if err != nil { return nil, err } args = append(args, string(arg[:argLen])) } return args, nil } //------------------------------------------------------------------------------ func parseReply(rd *bufio.Reader, p multiBulkParser) (interface{}, error) { line, err := readLine(rd) if err != nil { return nil, err } switch line[0] { case '-': return nil, errorf(string(line[1:])) case '+': return string(line[1:]), nil case ':': v, err := strconv.ParseInt(string(line[1:]), 10, 64) if err != nil { return nil, err } return v, nil case '$': if len(line) == 3 && line[1] == '-' && line[2] == '1' { return nil, Nil } replyLen, err := strconv.Atoi(string(line[1:])) if err != nil { return nil, err } b, err := readN(rd, replyLen+2) if err != nil { return nil, err } return string(b[:replyLen]), nil case '*': if len(line) == 3 && line[1] == '-' && line[2] == '1' { return nil, Nil } repliesNum, err := strconv.ParseInt(string(line[1:]), 10, 64) if err != nil { return nil, err } return p(rd, repliesNum) } return nil, fmt.Errorf("redis: can't parse %q", line) } func parseSlice(rd *bufio.Reader, n int64) (interface{}, error) { vals := make([]interface{}, 0, n) for i := int64(0); i < n; i++ { v, err := parseReply(rd, parseSlice) if err == Nil { vals = append(vals, nil) } else if err != nil { return nil, err } else { vals = append(vals, v) } } return vals, nil } func parseStringSlice(rd *bufio.Reader, n int64) (interface{}, error) { vals := make([]string, 0, n) for i := int64(0); i < n; i++ { viface, err := parseReply(rd, nil) if err != nil { return nil, err } v, ok := viface.(string) if !ok { return nil, fmt.Errorf("got %T, expected string", viface) } vals = append(vals, v) } return vals, nil } func parseBoolSlice(rd *bufio.Reader, n int64) (interface{}, error) { vals := make([]bool, 0, n) for i := int64(0); i < n; i++ { viface, err := parseReply(rd, nil) if err != nil { return nil, err } v, ok := viface.(int64) if !ok { return nil, fmt.Errorf("got %T, expected int64", viface) } vals = append(vals, v == 1) } return vals, nil } func parseStringStringMap(rd *bufio.Reader, n int64) (interface{}, error) { m := make(map[string]string, n/2) for i := int64(0); i < n; i += 2 { keyiface, err := parseReply(rd, nil) if err != nil { return nil, err } key, ok := keyiface.(string) if !ok { return nil, fmt.Errorf("got %T, expected string", keyiface) } valueiface, err := parseReply(rd, nil) if err != nil { return nil, err } value, ok := valueiface.(string) if !ok { return nil, fmt.Errorf("got %T, expected string", valueiface) } m[key] = value } return m, nil } func parseZSlice(rd *bufio.Reader, n int64) (interface{}, error) { zz := make([]Z, n/2) for i := int64(0); i < n; i += 2 { z := &zz[i/2] memberiface, err := parseReply(rd, nil) if err != nil { return nil, err } member, ok := memberiface.(string) if !ok { return nil, fmt.Errorf("got %T, expected string", memberiface) } z.Member = member scoreiface, err := parseReply(rd, nil) if err != nil { return nil, err } scorestr, ok := scoreiface.(string) if !ok { return nil, fmt.Errorf("got %T, expected string", scoreiface) } score, err := strconv.ParseFloat(scorestr, 64) if err != nil { return nil, err } z.Score = score } return zz, nil }