Skip to content
This repository has been archived by the owner on Apr 2, 2021. It is now read-only.

add resp3 protocol part 1(add data type and implement some simple) #77

Merged
merged 1 commit into from
Aug 27, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 28 additions & 0 deletions internal/protcl/errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -101,3 +101,31 @@ func (e *ErrUnknownCommand) Error() string {
func (ErrUnknownCommand) Recoverable() bool {
return true
}

// ErrProtocolType is for protocol type error
type ErrProtocolType struct {
Type byte
}

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If these errors should not disconnect client, you can implement Recoverable interface on them

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

// Recoverable whether error is recoverable or not
func (e *ErrProtocolType) Recoverable() bool {
return true
}

func (e *ErrProtocolType) Error() string {
return fmt.Sprintf("unknown protocol type: %s", string(e.Type))
}

// ErrUnexpectString is for unexpect string error
type ErrUnexpectString struct {
Str string
}

// Recoverable whether error is recoverable or not
func (e *ErrUnexpectString) Recoverable() bool {
return true
}

func (e *ErrUnexpectString) Error() string {
return fmt.Sprintf("unexpect string: %s", e.Str)
}
179 changes: 179 additions & 0 deletions internal/protcl/resp3.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,179 @@
package protcl

import (
"bufio"
"fmt"
"strconv"
)

// resp3 protocol type
const (
Resp3SimpleString = '+' // +<string>\n
Resp3BlobString = '$' // $<length>\n<bytes>\n
Resp3VerbatimString = '=' // =<length>\n<format(3 bytes)>\n<bytes>\n
Resp3SimpleError = '-' // -<string>\n
Resp3BolbError = '!' // !<length>\n<bytes>\n
Resp3Number = ':' // :<number>\n
Resp3Double = ',' // ,<floating-point-number>\n
Resp3BigNumber = '(' // (<big number>\n
Resp3Null = '_' // _\n
Resp3Boolean = '#' // #t\n or #f\n
Resp3Array = '*' // *<elements number>\n... numelements other types ...
Resp3Map = '%' // %<elements number>\n... numelements other types ...
Resp3Set = '~' // ~<elements number>\n... numelements other types ...
)

// LF is \n
const LF = '\n'

// Resp3 the response of resp3 protocol
type Resp3 struct {
Type byte
Str string
Integer int
Boolean bool
}

func (r *Resp3) String() string {
switch r.Type {
case RepSimpleString, Resp3BlobString:
return fmt.Sprintf("%q", r.Str)
case Resp3SimpleError, Resp3BolbError:
return "(error) " + r.Str
case Resp3Number:
return "(integer) " + strconv.Itoa(r.Integer)
case Resp3Null:
return "(null)"
case Resp3Boolean:
if r.Boolean {
return "(boolean) true"
}
return "(boolean) false"
}

return "(error) unknown protocol type: " + string(r.Type)
}

// Resp3Parser is for parser resp3 protocol
type Resp3Parser struct {
reader *bufio.Reader
}

// NewResp3Parser return a Resp3Parser
func NewResp3Parser(r *bufio.Reader) *Resp3Parser {
return &Resp3Parser{reader: r}
}

// Parse return Resp3
func (r *Resp3Parser) Parse() (*Resp3, error) {
b, err := r.reader.ReadByte()
if err != nil {
return nil, err
}

switch b {
case Resp3SimpleString, Resp3SimpleError:
str, err := r.stringBeforeLF()
if err != nil {
return nil, err
}
return &Resp3{Type: b, Str: str}, nil
case Resp3BlobString, Resp3BolbError:
length, err := r.intBeforeLF()
if err != nil {
return nil, err
}

bs, err := r.readLengthBytesWithLF(length)
if err != nil {
return nil, err
}

return &Resp3{Type: b, Str: string(bs)}, nil
case Resp3Number:
integer, err := r.intBeforeLF()
if err != nil {
return nil, err
}
return &Resp3{Type: b, Integer: integer}, nil
case Resp3Null:
if _, err := r.readLengthBytesWithLF(0); err != nil {
return nil, err
}
return &Resp3{Type: b}, nil
case Resp3Boolean:
buf, err := r.readLengthBytesWithLF(1)
if err != nil {
return nil, err
}

switch buf[0] {
case 't':
return &Resp3{Type: b, Boolean: true}, nil
case 'f':
return &Resp3{Type: b, Boolean: false}, nil
}
return nil, &ErrUnexpectString{Str: "t/f"}
}

return nil, &ErrProtocolType{Type: b}
}

func (r *Resp3Parser) stringBeforeLF() (string, error) {
buf, err := r.reader.ReadBytes(LF)
if err != nil {
return "", err
}
bs, err := trimLastLF(buf)
if err != nil {
return "", err
}
return string(bs), nil
}

func (r *Resp3Parser) intBeforeLF() (int, error) {
buf, err := r.reader.ReadBytes(LF)
if err != nil {
return 0, err
}
bs, err := trimLastLF(buf)
if err != nil {
return 0, err
}
s := string(bs)
i, err := strconv.Atoi(s)
if err != nil {
return 0, &ErrCastFailedToInt{Val: s}
}
return i, nil
}

func (r *Resp3Parser) readLengthBytesWithLF(length int) ([]byte, error) {
if length == 0 {
if b, err := r.reader.ReadByte(); err != nil {
return nil, err
} else if b != LF {
return nil, &ErrUnexpectString{Str: "<LF>"}
}
return nil, nil
}

buf := make([]byte, length+1)
n, err := r.reader.Read(buf)
if err != nil {
return nil, err
} else if n < length+1 {
return nil, &ErrUnexpectedLineEnd{}
}

return trimLastLF(buf)
}

func trimLastLF(buf []byte) ([]byte, error) {
bufLen := len(buf)
if len(buf) == 0 || buf[bufLen-1] != LF {
return nil, &ErrUnexpectedLineEnd{}
}

return buf[:bufLen-1], nil
}
62 changes: 62 additions & 0 deletions internal/protcl/resp3_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
package protcl

import (
"bufio"
"strings"
"testing"

testifyAssert "github.com/stretchr/testify/assert"
)

func testResp3Parser(t *testing.T, input, expect string) {
assert := testifyAssert.New(t)

parser := NewResp3Parser(bufio.NewReader(strings.NewReader(input)))
result, err := parser.Parse()
assert.Nil(err)
assert.Equal(expect, result.String())
}

func testResp3Error(t *testing.T, input, e string) {
assert := testifyAssert.New(t)

parser := NewResp3Parser(bufio.NewReader(strings.NewReader(input)))
result, err := parser.Parse()
assert.NotNil(err)
assert.Nil(result)
assert.Equal(e, err.Error())
}

func TestResp3Parser(t *testing.T) {
// simple string
testResp3Error(t, "+string", `EOF`)
testResp3Parser(t, "+string\n", `"string"`)

// blob string
testResp3Parser(t, "$0\n\n", `""`)
testResp3Error(t, "$1\n\n", `unexpected line end`)
testResp3Error(t, "$1\naa\n", `unexpected line end`)
testResp3Parser(t, "$10\n1234567890\n", `"1234567890"`)

// simple error
testResp3Parser(t, "-error\n", `(error) error`)
testResp3Parser(t, "-Err error\n", `(error) Err error`)

// blob error
testResp3Parser(t, "!3\nerr\n", `(error) err`)
testResp3Parser(t, "!17\nErr this is error\n", `(error) Err this is error`)

// number
testResp3Parser(t, ":-1\n", `(integer) -1`)
testResp3Parser(t, ":0\n", `(integer) 0`)
testResp3Parser(t, ":100\n", `(integer) 100`)

// null
testResp3Parser(t, "_\n", "(null)")

// boolean
testResp3Parser(t, "#t\n", `(boolean) true`)
testResp3Parser(t, "#f\n", `(boolean) false`)
testResp3Error(t, "#x\n", `unexpect string: t/f`)
testResp3Error(t, "#\n", `unexpected line end`)
}