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 5(replace to resp3)
Browse files Browse the repository at this point in the history
  • Loading branch information
chyroc committed Aug 27, 2018
1 parent 6585325 commit c286dcb
Show file tree
Hide file tree
Showing 12 changed files with 334 additions and 270 deletions.
8 changes: 4 additions & 4 deletions internal/arch/command-table.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ import (
)

// CommandFunc holds a function signature which can be used as a command.
type CommandFunc func(*db.DB, []string) *protcl.Message
type CommandFunc func(*db.DB, []string) *protcl.Resp3

// Command holds a command structure which is used to execute a kache command
type Command struct {
Expand Down Expand Up @@ -72,14 +72,14 @@ func getCommand(cmd string) (*Command, error) {
}

// Execute a single command on the given database with args
func (DBCommand) Execute(db *db.DB, cmd string, args []string) *protcl.Message {
func (DBCommand) Execute(db *db.DB, cmd string, args []string) *protcl.Resp3 {
command, err := getCommand(cmd)
if err != nil {
return protcl.NewMessage(nil, err)
return &protcl.Resp3{Type: protcl.Resp3SimpleError, Str: err.Error()}
}

if argsLen := len(args); (command.MinArgs > 0 && argsLen < command.MinArgs) || (command.MaxArgs != -1 && argsLen > command.MaxArgs) {
return protcl.NewMessage(nil, &protcl.ErrWrongNumberOfArgs{Cmd: cmd})
return &protcl.Resp3{Type: protcl.Resp3SimpleError, Str: (&protcl.ErrWrongNumberOfArgs{Cmd: cmd}).Error()}
}

return command.Fn(db, args)
Expand Down
38 changes: 24 additions & 14 deletions internal/arch/command-table_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,38 +9,48 @@ import (
"github.com/kasvith/kache/internal/protcl"
)

func testRespError(t *testing.T, err error, resp3 *protcl.Resp3) {
assert := testifyAssert.New(t)
if err == nil {
assert.NotEqual(protcl.Resp3SimpleError, resp3.Type)
assert.NotEqual(protcl.Resp3BolbError, resp3.Type)
return
}

assert.Equal(err.Error(), resp3.Str)
}

// TestCommandArgsCountValidator will validate the command args count field
func TestCommandArgsCountValidator(t *testing.T) {
assert := testifyAssert.New(t)
cmd := &DBCommand{}
db := db.NewDB()

// ping at most 1
{
assert.Nil(cmd.Execute(db, "ping", nil).Err)
assert.Nil(cmd.Execute(db, "ping", []string{"1"}).Err)
assert.Equal(&protcl.ErrWrongNumberOfArgs{Cmd: "ping"}, cmd.Execute(db, "ping", []string{"1", "2"}).Err)
testRespError(t, nil, cmd.Execute(db, "ping", nil))
testRespError(t, nil, cmd.Execute(db, "ping", []string{"1"}))
testRespError(t, &protcl.ErrWrongNumberOfArgs{Cmd: "ping"}, cmd.Execute(db, "ping", []string{"1", "2"}))
}

// del at least 1
{
assert.Equal(&protcl.ErrWrongNumberOfArgs{Cmd: "del"}, cmd.Execute(db, "del", nil).Err)
assert.Nil(cmd.Execute(db, "del", []string{"1"}).Err)
assert.Nil(cmd.Execute(db, "del", []string{"1", "2"}).Err)
testRespError(t, &protcl.ErrWrongNumberOfArgs{Cmd: "del"}, cmd.Execute(db, "del", nil))
testRespError(t, nil, cmd.Execute(db, "del", []string{"1"}))
testRespError(t, nil, cmd.Execute(db, "del", []string{"1", "2"}))
}

// set equal 2
{
assert.Equal(&protcl.ErrWrongNumberOfArgs{Cmd: "set"}, cmd.Execute(db, "set", nil).Err)
assert.Equal(&protcl.ErrWrongNumberOfArgs{Cmd: "set"}, cmd.Execute(db, "set", []string{"1"}).Err)
assert.Nil(cmd.Execute(db, "set", []string{"1", "2"}).Err)
assert.Equal(&protcl.ErrWrongNumberOfArgs{Cmd: "set"}, cmd.Execute(db, "set", []string{"1", "2", "3"}).Err)
testRespError(t, &protcl.ErrWrongNumberOfArgs{Cmd: "set"}, cmd.Execute(db, "set", nil))
testRespError(t, &protcl.ErrWrongNumberOfArgs{Cmd: "set"}, cmd.Execute(db, "set", []string{"1"}))
testRespError(t, nil, cmd.Execute(db, "set", []string{"1", "2"}))
testRespError(t, &protcl.ErrWrongNumberOfArgs{Cmd: "set"}, cmd.Execute(db, "set", []string{"1", "2", "3"}))
}

// equal 1: get exists incr decr
for _, command := range []string{"get", "exists", "incr", "decr"} {
assert.Equal(&protcl.ErrWrongNumberOfArgs{Cmd: command}, cmd.Execute(db, command, nil).Err)
assert.Nil(cmd.Execute(db, command, []string{"1"}).Err)
assert.Equal(&protcl.ErrWrongNumberOfArgs{Cmd: command}, cmd.Execute(db, command, []string{"1", "2"}).Err)
testRespError(t, &protcl.ErrWrongNumberOfArgs{Cmd: command}, cmd.Execute(db, command, nil))
testRespError(t, nil, cmd.Execute(db, command, []string{"1"}))
testRespError(t, &protcl.ErrWrongNumberOfArgs{Cmd: command}, cmd.Execute(db, command, []string{"1", "2"}))
}
}
12 changes: 8 additions & 4 deletions internal/cli/cli.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ import (
"strings"

"github.com/c-bata/go-prompt"

"github.com/kasvith/kache/internal/protcl"
)

// RunCli start kache-cli command
Expand Down Expand Up @@ -59,18 +61,20 @@ func Executor(s string) {
return
}

if err := c.Write(s); err != nil {
if err := c.Write(protcl.NewSliceResp3(strings.Split(s, " "))); err != nil {
fmt.Println(err)
return
}

resp, err := c.parseResp()
resp, err := c.resp3Parser.Parse()
if err != nil {
fmt.Println(err)
return
} else if resp != nil {
fmt.Println(resp.RenderString())
return
}

fmt.Println(resp)
fmt.Println("(empty)")
}

// Completer used in CLI
Expand Down
20 changes: 12 additions & 8 deletions internal/cli/cli_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,15 @@ package cli

import (
"strconv"
"strings"
"testing"
"time"

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

"github.com/kasvith/kache/internal/config"
"github.com/kasvith/kache/internal/klogs"
"github.com/kasvith/kache/internal/protcl"
"github.com/kasvith/kache/internal/srv"
)

Expand Down Expand Up @@ -37,10 +39,11 @@ func initTestServerClient(t *testing.T) {
func runTestSendRecv(t *testing.T, send, recv string) {
assert := testifyAssert.New(t)

assert.Nil(c.Write(send))
resp, err := c.parseResp()
assert.Nil(c.Write(protcl.NewSliceResp3(strings.Split(send, " "))))
resp, err := c.resp3Parser.Parse()
assert.Nil(err)
assert.Equal(recv, resp)
assert.NotNil(resp)
assert.Equal(recv, resp.RenderString())
}

func TestCli(t *testing.T) {
Expand All @@ -53,14 +56,14 @@ func TestCli(t *testing.T) {
// strings
{
// get not found
runTestSendRecv(t, "get a", "(error) ERR: a not found")
runTestSendRecv(t, "get a", "(error) a not found")

// set
runTestSendRecv(t, "set a 1", `"OK"`)
runTestSendRecv(t, "set b 2", `"OK"`)

// get exist
runTestSendRecv(t, "get a", "1")
runTestSendRecv(t, "get a", `"1"`)

// incr decr
runTestSendRecv(t, "incr b", "(integer) 3")
Expand All @@ -72,10 +75,11 @@ func TestCli(t *testing.T) {
// key space
{
// keys
assert.Nil(c.Write("keys"))
resp, err := c.parseResp()
assert.Nil(c.Write("+keys\n"))
resp, err := c.resp3Parser.Parse()
assert.Nil(err)
assert.Contains([]string{"1) b\n2) a\n", "1) a\n2) b\n"}, resp)
assert.NotNil(resp)
assert.Contains([]string{"(array)\n\t\"a\"\n\t\"b\"", "(array)\n\t\"b\"\n\t\"a\""}, resp.RenderString())

// exists
runTestSendRecv(t, "exists a", "(integer) 1")
Expand Down
53 changes: 5 additions & 48 deletions internal/cli/conn.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,7 @@ package cli
import (
"bufio"
"fmt"
"io"
"net"
"strings"
"time"

"github.com/kasvith/kache/internal/protcl"
Expand All @@ -38,9 +36,9 @@ import (
var c *cli

type cli struct {
conn net.Conn
reader *bufio.Reader
addr string
conn net.Conn
resp3Parser *protcl.Resp3Parser
addr string
}

// Write send string to server
Expand All @@ -49,11 +47,7 @@ func (r *cli) Write(s string) error {
}

func (r *cli) write(s string, reconnect bool) error {
send := make([]byte, len(s)+2)
copy(send[:len(s)], s)
copy(send[len(s):], []byte{'\r', '\n'})

n, err := c.conn.Write(send)
n, err := c.conn.Write([]byte(s))
if n == 0 && err != nil && reconnect {
fmt.Println("reconnecting...")

Expand All @@ -65,43 +59,6 @@ func (r *cli) write(s string, reconnect bool) error {
return err
}

func (r *cli) parseResp() (string, error) {
buf, err := r.reader.ReadBytes('\n')
if err != nil {
if err == io.EOF {
return "", nil
}
return "", err
}

if err := protcl.EndWithCRLF(buf); err != nil {
return "", err
}

switch buf[0] {
case '*':
strs, err := protcl.ParseMultiBulkReply(r.reader, buf)
if err != nil {
return "", err
}
builder := strings.Builder{}
for key, value := range strs {
builder.WriteString(fmt.Sprintf("%d) %s\n", key+1, value))
}
return builder.String(), nil
case '$':
return protcl.ParseBulkString(r.reader, buf)
case '+':
return fmt.Sprintf("%q", buf[1:len(buf)-2]), nil
case ':':
return fmt.Sprintf("(integer) %s", buf[1:len(buf)-2]), nil
case '-':
return fmt.Sprintf("(error) %s", buf[1:len(buf)-2]), nil
}

return "", fmt.Errorf("unsupport resp type: %s", []byte{buf[0]})
}

// Dial conn kache server
func Dial(addr string) error {
conn, err := net.DialTimeout("tcp", addr, time.Second)
Expand All @@ -111,7 +68,7 @@ func Dial(addr string) error {

c = new(cli)
c.conn = conn
c.reader = bufio.NewReader(conn)
c.resp3Parser = protcl.NewResp3Parser(bufio.NewReader(conn))
c.addr = addr

return nil
Expand Down
12 changes: 6 additions & 6 deletions internal/cmds/keys.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,19 +30,19 @@ import (
)

// Exists will check for key existency in given db
func Exists(d *db.DB, args []string) *protcl.Message {
func Exists(d *db.DB, args []string) *protcl.Resp3 {
found := d.Exists(args[0])
return protcl.NewMessage(protcl.NewIntegerReply(found), nil)
return &protcl.Resp3{Type: protcl.Resp3Number, Integer: found}
}

// Del will delete set of keys and return number of deleted keys
func Del(d *db.DB, args []string) *protcl.Message {
func Del(d *db.DB, args []string) *protcl.Resp3 {
deleted := d.Del(args)
return protcl.NewMessage(protcl.NewIntegerReply(deleted), nil)
return &protcl.Resp3{Type: protcl.Resp3Number, Integer: deleted}
}

// Keys will return all keys of the db as a list
func Keys(d *db.DB, args []string) *protcl.Message {
func Keys(d *db.DB, args []string) *protcl.Resp3 {
keys := d.Keys()
return protcl.NewMessage(protcl.NewArrayReply(false, keys), nil)
return &protcl.Resp3{Type: protcl.Resp3Array, Elems: keys}
}
6 changes: 3 additions & 3 deletions internal/cmds/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,10 +30,10 @@ import (
)

// Ping will return PONG when no argument found or will echo the given argument
func Ping(d *db.DB, args []string) *protcl.Message {
func Ping(d *db.DB, args []string) *protcl.Resp3 {
if len(args) == 0 {
return protcl.NewMessage(protcl.NewSimpleStringReply("PONG"), nil)
return &protcl.Resp3{Type: protcl.RepSimpleString, Str: "PONG"}
}

return protcl.NewMessage(protcl.NewBulkStringReply(false, args[0]), nil)
return &protcl.Resp3{Type: protcl.Resp3BlobString, Str: args[0]}
}
26 changes: 13 additions & 13 deletions internal/cmds/strings.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,59 +33,59 @@ import (
)

// Get will find the value of a given string key and return it
func Get(d *db.DB, args []string) *protcl.Message {
func Get(d *db.DB, args []string) *protcl.Resp3 {
val, err := d.Get(args[0])
if err != nil {
return protcl.NewMessage(nil, &protcl.ErrGeneric{Err: err})
return &protcl.Resp3{Type: protcl.Resp3BolbError, Str: err.Error()}
}

if val.Type != db.TypeString {
return protcl.NewMessage(nil, &protcl.ErrWrongType{})
return &protcl.Resp3{Type: protcl.Resp3BolbError, Str: (&protcl.ErrWrongType{}).Error()}
}

return protcl.NewMessage(protcl.NewBulkStringReply(false, util.ToString(val.Value)), nil)
return &protcl.Resp3{Type: protcl.RepBulkString, Str: util.ToString(val.Value)}
}

// Set will create a new string key value pair
func Set(d *db.DB, args []string) *protcl.Message {
func Set(d *db.DB, args []string) *protcl.Resp3 {
key := args[0]
val := args[1]

d.Set(key, db.NewDataNode(db.TypeString, -1, val))

return protcl.NewMessage(protcl.NewSimpleStringReply("OK"), nil)
return &protcl.Resp3{Type: protcl.RepSimpleString, Str: "OK"}
}

// Incr will increment a given string key by 1
// If key not found it will be set to 0 and will do operation
// If key type is invalid it will return an error
func Incr(d *db.DB, args []string) *protcl.Message {
func Incr(d *db.DB, args []string) *protcl.Resp3 {
return accumulateBy(d, args[0], 1, true)
}

// Decr will decrement a given string key by 1
// If key not found it will be set to 0 and will do operation
// If key type is invalid it will return an error
func Decr(d *db.DB, args []string) *protcl.Message {
func Decr(d *db.DB, args []string) *protcl.Resp3 {
return accumulateBy(d, args[0], -1, true)
}

// accumulateBy will accumulate the value of key by given amount
func accumulateBy(d *db.DB, key string, v int, incr bool) *protcl.Message {
func accumulateBy(d *db.DB, key string, v int, incr bool) *protcl.Resp3 {
val, found := d.GetIfNotSet(key, db.NewDataNode(db.TypeString, -1, strconv.Itoa(v)))

if !found {
return protcl.NewMessage(protcl.NewIntegerReply(v), nil)
return &protcl.Resp3{Type: protcl.Resp3Number, Integer: v}
}

if val.Type != db.TypeString {
return protcl.NewMessage(nil, protcl.ErrWrongType{})
return &protcl.Resp3{Type: protcl.Resp3BolbError, Str: (&protcl.ErrWrongType{}).Error()}
}

i, err := strconv.Atoi(util.ToString(val.Value))

if err != nil {
return protcl.NewMessage(nil, &protcl.ErrCastFailedToInt{Val: val.Value})
return &protcl.Resp3{Type: protcl.Resp3BolbError, Str: (&protcl.ErrCastFailedToInt{Val: val.Value}).Error()}
}

var n int
Expand All @@ -97,5 +97,5 @@ func accumulateBy(d *db.DB, key string, v int, incr bool) *protcl.Message {

d.Set(key, db.NewDataNode(db.TypeString, -1, strconv.Itoa(n)))

return protcl.NewMessage(protcl.NewIntegerReply(n), nil)
return &protcl.Resp3{Type: protcl.Resp3Number, Integer: n}
}
Loading

0 comments on commit c286dcb

Please sign in to comment.