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

Commit

Permalink
add resp3 protocol part 1(add data type and implement some simple) (#77)
Browse files Browse the repository at this point in the history
  • Loading branch information
chyroc authored and kasvith committed Aug 27, 2018
1 parent ae7d6e3 commit 46f3522
Show file tree
Hide file tree
Showing 3 changed files with 269 additions and 0 deletions.
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
}

// 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`)
}

0 comments on commit 46f3522

Please sign in to comment.