diff --git a/rpc/lib/client/http_client.go b/rpc/lib/client/http_client.go index 5386779d3..9ad6386dc 100644 --- a/rpc/lib/client/http_client.go +++ b/rpc/lib/client/http_client.go @@ -67,11 +67,9 @@ func NewJSONRPCClient(remote string) *JSONRPCClient { } func (c *JSONRPCClient) Call(method string, params map[string]interface{}, result interface{}) (interface{}, error) { - request := types.RPCRequest{ - JSONRPC: "2.0", - Method: method, - Params: params, - ID: "", + request, err := types.MapToRequest("", method, params) + if err != nil { + return nil, err } requestBytes, err := json.Marshal(request) if err != nil { diff --git a/rpc/lib/client/ws_client.go b/rpc/lib/client/ws_client.go index df9cb3812..833668d85 100644 --- a/rpc/lib/client/ws_client.go +++ b/rpc/lib/client/ws_client.go @@ -130,35 +130,31 @@ func (wsc *WSClient) receiveEventsRoutine() { // Subscribe to an event. Note the server must have a "subscribe" route // defined. func (wsc *WSClient) Subscribe(eventid string) error { - err := wsc.WriteJSON(types.RPCRequest{ - JSONRPC: "2.0", - ID: "", - Method: "subscribe", - Params: map[string]interface{}{"event": eventid}, - }) + params := map[string]interface{}{"event": eventid} + request, err := types.MapToRequest("", "subscribe", params) + if err == nil { + err = wsc.WriteJSON(request) + } return err } // Unsubscribe from an event. Note the server must have a "unsubscribe" route // defined. func (wsc *WSClient) Unsubscribe(eventid string) error { - err := wsc.WriteJSON(types.RPCRequest{ - JSONRPC: "2.0", - ID: "", - Method: "unsubscribe", - Params: map[string]interface{}{"event": eventid}, - }) + params := map[string]interface{}{"event": eventid} + request, err := types.MapToRequest("", "unsubscribe", params) + if err == nil { + err = wsc.WriteJSON(request) + } return err } // Call asynchronously calls a given method by sending an RPCRequest to the // server. Results will be available on ResultsCh, errors, if any, on ErrorsCh. func (wsc *WSClient) Call(method string, params map[string]interface{}) error { - err := wsc.WriteJSON(types.RPCRequest{ - JSONRPC: "2.0", - Method: method, - Params: params, - ID: "", - }) + request, err := types.MapToRequest("", method, params) + if err == nil { + err = wsc.WriteJSON(request) + } return err } diff --git a/rpc/lib/rpc_test.go b/rpc/lib/rpc_test.go index a14551f4f..3a2b36d51 100644 --- a/rpc/lib/rpc_test.go +++ b/rpc/lib/rpc_test.go @@ -306,12 +306,9 @@ func TestWSHandlesArrayParams(t *testing.T) { val := "acbd" params := []interface{}{val} - err = cl.WriteJSON(types.RPCRequest{ - JSONRPC: "2.0", - ID: "", - Method: "echo_ws", - Params: params, - }) + request, err := types.ArrayToRequest("", "echo_ws", params) + require.Nil(t, err) + err = cl.WriteJSON(request) require.Nil(t, err) select { diff --git a/rpc/lib/server/handlers.go b/rpc/lib/server/handlers.go index 51eaf1401..321b72d6d 100644 --- a/rpc/lib/server/handlers.go +++ b/rpc/lib/server/handlers.go @@ -140,15 +140,23 @@ func makeJSONRPCHandler(funcMap map[string]*RPCFunc) http.HandlerFunc { } } -// Convert a []interface{} OR a map[string]interface{} to properly typed values +// raw is unparsed json (from json.RawMessage). It either has +// and array or a map behind it, let's parse this all without resorting to wire... // // argsOffset should be 0 for RPC calls, and 1 for WS requests, where len(rpcFunc.args) != len(rpcFunc.argNames). // Example: // rpcFunc.args = [rpctypes.WSRPCContext string] // rpcFunc.argNames = ["arg"] -func jsonParamsToArgs(rpcFunc *RPCFunc, paramsI interface{}, argsOffset int) ([]reflect.Value, error) { +func jsonParamsToArgs(rpcFunc *RPCFunc, raw []byte, argsOffset int) ([]reflect.Value, error) { values := make([]reflect.Value, len(rpcFunc.argNames)) + // right now, this is the same as before, but the whole parsing is in one function... + var paramsI interface{} + err := json.Unmarshal(raw, ¶msI) + if err != nil { + return nil, err + } + switch params := paramsI.(type) { case map[string]interface{}: @@ -188,13 +196,13 @@ func jsonParamsToArgs(rpcFunc *RPCFunc, paramsI interface{}, argsOffset int) ([] } // Convert a []interface{} OR a map[string]interface{} to properly typed values -func jsonParamsToArgsRPC(rpcFunc *RPCFunc, paramsI interface{}) ([]reflect.Value, error) { - return jsonParamsToArgs(rpcFunc, paramsI, 0) +func jsonParamsToArgsRPC(rpcFunc *RPCFunc, params *json.RawMessage) ([]reflect.Value, error) { + return jsonParamsToArgs(rpcFunc, *params, 0) } // Same as above, but with the first param the websocket connection -func jsonParamsToArgsWS(rpcFunc *RPCFunc, paramsI interface{}, wsCtx types.WSRPCContext) ([]reflect.Value, error) { - values, err := jsonParamsToArgs(rpcFunc, paramsI, 1) +func jsonParamsToArgsWS(rpcFunc *RPCFunc, params *json.RawMessage, wsCtx types.WSRPCContext) ([]reflect.Value, error) { + values, err := jsonParamsToArgs(rpcFunc, *params, 1) if err != nil { return nil, err } diff --git a/rpc/lib/types/types.go b/rpc/lib/types/types.go index a91811e53..8076e4b0d 100644 --- a/rpc/lib/types/types.go +++ b/rpc/lib/types/types.go @@ -8,19 +8,37 @@ import ( ) type RPCRequest struct { - JSONRPC string `json:"jsonrpc"` - ID string `json:"id"` - Method string `json:"method"` - Params interface{} `json:"params"` // must be map[string]interface{} or []interface{} + JSONRPC string `json:"jsonrpc"` + ID string `json:"id"` + Method string `json:"method"` + Params *json.RawMessage `json:"params"` // must be map[string]interface{} or []interface{} } -func NewRPCRequest(id string, method string, params map[string]interface{}) RPCRequest { +func NewRPCRequest(id string, method string, params json.RawMessage) RPCRequest { return RPCRequest{ JSONRPC: "2.0", ID: id, Method: method, - Params: params, + Params: ¶ms, + } +} + +func MapToRequest(id string, method string, params map[string]interface{}) (RPCRequest, error) { + payload, err := json.Marshal(params) + if err != nil { + return RPCRequest{}, err + } + request := NewRPCRequest(id, method, payload) + return request, nil +} + +func ArrayToRequest(id string, method string, params []interface{}) (RPCRequest, error) { + payload, err := json.Marshal(params) + if err != nil { + return RPCRequest{}, err } + request := NewRPCRequest(id, method, payload) + return request, nil } //----------------------------------------