Add support for JSON-RPC array payload. (#335)

This PR introduces solution for #333 - it adds support for array JSON-RPC payload.

unmarshalMessage tries to unmarshal JSON paylod into *jsonrpcMessage object, and in case of failure, analyzes error and, if it's unmarshalling array error, tries to unmarshal it as an array.
This commit is contained in:
Ivan Daniluk 2017-09-17 16:06:18 +02:00 committed by Ivan Tomilov
parent bd2c3c6754
commit 9eee21f1ca
3 changed files with 72 additions and 11 deletions

View File

@ -3,6 +3,7 @@ package rpc
import ( import (
"context" "context"
"encoding/json" "encoding/json"
"errors"
gethrpc "github.com/ethereum/go-ethereum/rpc" gethrpc "github.com/ethereum/go-ethereum/rpc"
"github.com/status-im/status-go/geth/log" "github.com/status-im/status-go/geth/log"
@ -96,7 +97,6 @@ func methodAndParamsFromBody(body string) (string, []interface{}, json.RawMessag
if msg.Params != nil { if msg.Params != nil {
err = json.Unmarshal(msg.Params, &params) err = json.Unmarshal(msg.Params, &params)
if err != nil { if err != nil {
log.Error("unmarshal params", "error", err)
return "", nil, nil, err return "", nil, nil, err
} }
} }
@ -104,12 +104,44 @@ func methodAndParamsFromBody(body string) (string, []interface{}, json.RawMessag
return msg.Method, params, msg.ID, nil return msg.Method, params, msg.ID, nil
} }
// unmarshalMessage tries to unmarshal JSON-RPC message.
// somehow JSON-RPC input from web3.js can be in two forms:
//
// object: {"jsonrpc":"2.0", …}
// array: [{"jsonrpc":"2.0", …}]
//
// unmarhsalMessage tries first option and in case of error,
// tries to unmarshal it as an array.
//
// TODO(divan): fix the source of this error and cleanup.
func unmarshalMessage(body string) (*jsonrpcMessage, error) { func unmarshalMessage(body string) (*jsonrpcMessage, error) {
var msg jsonrpcMessage var msg jsonrpcMessage
err := json.Unmarshal([]byte(body), &msg) err := json.Unmarshal([]byte(body), &msg)
// check for array case
if e, ok := err.(*json.UnmarshalTypeError); ok {
if e.Value == "array" {
return unmarshalMessageArray(body)
}
}
return &msg, err return &msg, err
} }
func unmarshalMessageArray(body string) (*jsonrpcMessage, error) {
var msgs []*jsonrpcMessage
err := json.Unmarshal([]byte(body), &msgs)
if err != nil {
return nil, err
}
// return first element
if len(msgs) == 0 {
return nil, errors.New("empty array")
} else if len(msgs) > 1 {
log.Warn("JSON-RPC payload has more then 1 objects", "len", len(msgs), "body", body)
}
return msgs[0], nil
}
func newSuccessResponse(result json.RawMessage, id json.RawMessage) string { func newSuccessResponse(result json.RawMessage, id json.RawMessage) string {
if id == nil { if id == nil {
id = defaultMsgID id = defaultMsgID

View File

@ -50,11 +50,12 @@ func TestUnmarshalMessage(t *testing.T) {
func TestMethodAndParamsFromBody(t *testing.T) { func TestMethodAndParamsFromBody(t *testing.T) {
cases := []struct { cases := []struct {
name string name string
body string body string
params []interface{} params []interface{}
method string method string
id json.RawMessage id json.RawMessage
shouldFail bool
}{ }{
{ {
"params_array", "params_array",
@ -67,30 +68,60 @@ func TestMethodAndParamsFromBody(t *testing.T) {
}, },
"subtract", "subtract",
json.RawMessage(`42`), json.RawMessage(`42`),
false,
}, },
{ {
"params_empty_array", "params_empty_array",
`{"jsonrpc": "2.0", "method": "test", "params": []}`, `{"jsonrpc": "2.0", "method": "test", "params": []}`,
[]interface{}{}, []interface{}{},
"test", "test",
json.RawMessage(nil), nil,
false,
}, },
{ {
"params_none", "params_none",
`{"jsonrpc": "2.0", "method": "test"}`, `{"jsonrpc": "2.0", "method": "test"}`,
[]interface{}{}, []interface{}{},
"test", "test",
json.RawMessage(nil), nil,
false,
},
{
"getFilterMessage",
`{"jsonrpc":"2.0","id":44,"method":"shh_getFilterMessages","params":["3de6a8867aeb75be74d68478b853b4b0e063704d30f8231c45d0fcbd97af207e"]}`,
[]interface{}{string("3de6a8867aeb75be74d68478b853b4b0e063704d30f8231c45d0fcbd97af207e")},
"shh_getFilterMessages",
json.RawMessage(`44`),
false,
},
{
"getFilterMessage_array",
`[{"jsonrpc":"2.0","id":44,"method":"shh_getFilterMessages","params":["3de6a8867aeb75be74d68478b853b4b0e063704d30f8231c45d0fcbd97af207e"]}]`,
[]interface{}{string("3de6a8867aeb75be74d68478b853b4b0e063704d30f8231c45d0fcbd97af207e")},
"shh_getFilterMessages",
json.RawMessage(`44`),
false,
},
{
"empty_array",
`[]`,
[]interface{}{},
"",
nil,
true,
}, },
} }
for _, test := range cases { for _, test := range cases {
t.Run(test.name, func(t *testing.T) { t.Run(test.name, func(t *testing.T) {
method, params, id, err := methodAndParamsFromBody(test.body) method, params, id, err := methodAndParamsFromBody(test.body)
if test.shouldFail {
require.Error(t, err)
}
require.NoError(t, err) require.NoError(t, err)
require.Equal(t, test.method, method) require.Equal(t, test.method, method)
require.Equal(t, test.params, params) require.Equal(t, test.params, params)
require.Equal(t, test.id, id) require.EqualValues(t, test.id, id)
}) })
} }
} }

View File

@ -6,7 +6,6 @@ import (
"net/http/httptest" "net/http/httptest"
"testing" "testing"
"github.com/status-im/status-go/geth/log"
"github.com/status-im/status-go/geth/node" "github.com/status-im/status-go/geth/node"
"github.com/status-im/status-go/geth/params" "github.com/status-im/status-go/geth/params"
"github.com/status-im/status-go/geth/rpc" "github.com/status-im/status-go/geth/rpc"
@ -183,7 +182,6 @@ func (s *RPCTestSuite) TestCallRPC() {
"id": 1 "id": 1
}`, }`,
func(resultJSON string) { func(resultJSON string) {
log.Info("eth_sendTransaction")
progress <- struct{}{} progress <- struct{}{}
}, },
}, },