From fbe86ea6459ec96f5bf427789c8109247ac6b302 Mon Sep 17 00:00:00 2001 From: "M. J. Fromberger" Date: Fri, 28 Jan 2022 13:33:02 -0800 Subject: [PATCH] rpc: simplify and consolidate response construction (#7725) Responses are constructed from requests using MakeResponse, MakeError, and MakeErrorf. This ensures the response is always paired with the correct ID, makes cases where there is no ID more explicit at the usage site, and consolidates the handling of error introspection across transports. The logic for unpacking errors and assigning JSON-RPC response types was previously duplicated in three places. Consolidate it in the types package for the RPC subsystem. * update test cases --- internal/rpc/core/events.go | 4 +- light/provider/http/http_test.go | 4 +- light/rpc/client.go | 6 +- rpc/jsonrpc/server/http_json_handler.go | 41 ++---- rpc/jsonrpc/server/http_json_handler_test.go | 8 +- rpc/jsonrpc/server/http_server_test.go | 20 ++- rpc/jsonrpc/server/http_uri_handler.go | 26 +--- rpc/jsonrpc/server/ws_handler.go | 42 ++----- rpc/jsonrpc/types/types.go | 124 ++++++++++++------- rpc/jsonrpc/types/types_test.go | 29 +++-- 10 files changed, 144 insertions(+), 160 deletions(-) diff --git a/internal/rpc/core/events.go b/internal/rpc/core/events.go index 82235ae50..054183817 100644 --- a/internal/rpc/core/events.go +++ b/internal/rpc/core/events.go @@ -67,7 +67,7 @@ func (env *Environment) Subscribe(ctx context.Context, query string) (*coretypes return } else if errors.Is(err, tmpubsub.ErrTerminated) { // The subscription was terminated by the publisher. - resp := rpctypes.RPCServerError(subscriptionID, err) + resp := callInfo.RPCRequest.MakeError(err) ok := callInfo.WSConn.TryWriteRPCResponse(opctx, resp) if !ok { env.Logger.Info("Unable to write response (slow client)", @@ -77,7 +77,7 @@ func (env *Environment) Subscribe(ctx context.Context, query string) (*coretypes } // We have a message to deliver to the client. - resp := rpctypes.NewRPCSuccessResponse(subscriptionID, &coretypes.ResultEvent{ + resp := callInfo.RPCRequest.MakeResponse(&coretypes.ResultEvent{ Query: query, Data: msg.Data(), Events: msg.Events(), diff --git a/light/provider/http/http_test.go b/light/provider/http/http_test.go index 8749d4a11..4c7761d50 100644 --- a/light/provider/http/http_test.go +++ b/light/provider/http/http_test.go @@ -80,12 +80,12 @@ func TestProvider(t *testing.T) { lb, err = p.LightBlock(ctx, 9001) require.Error(t, err) require.Nil(t, lb) - assert.Equal(t, provider.ErrHeightTooHigh, err) + assert.ErrorIs(t, err, provider.ErrHeightTooHigh) lb, err = p.LightBlock(ctx, 1) require.Error(t, err) require.Nil(t, lb) - assert.Equal(t, provider.ErrLightBlockNotFound, err) + assert.ErrorIs(t, err, provider.ErrLightBlockNotFound) // if the provider is unable to provide four more blocks then we should return // an unreliable peer error diff --git a/light/rpc/client.go b/light/rpc/client.go index 79158f452..ba7a2c8fa 100644 --- a/light/rpc/client.go +++ b/light/rpc/client.go @@ -655,11 +655,7 @@ func (c *Client) SubscribeWS(ctx context.Context, query string) (*coretypes.Resu case resultEvent := <-out: // We should have a switch here that performs a validation // depending on the event's type. - callInfo.WSConn.TryWriteRPCResponse(bctx, - rpctypes.NewRPCSuccessResponse( - rpctypes.JSONRPCStringID(fmt.Sprintf("%v#event", callInfo.RPCRequest.ID)), - resultEvent, - )) + callInfo.WSConn.TryWriteRPCResponse(bctx, callInfo.RPCRequest.MakeResponse(resultEvent)) case <-bctx.Done(): return } diff --git a/rpc/jsonrpc/server/http_json_handler.go b/rpc/jsonrpc/server/http_json_handler.go index e41274a45..90defb5b5 100644 --- a/rpc/jsonrpc/server/http_json_handler.go +++ b/rpc/jsonrpc/server/http_json_handler.go @@ -13,7 +13,6 @@ import ( "strconv" "github.com/tendermint/tendermint/libs/log" - "github.com/tendermint/tendermint/rpc/coretypes" rpctypes "github.com/tendermint/tendermint/rpc/jsonrpc/types" ) @@ -25,15 +24,15 @@ func makeJSONRPCHandler(funcMap map[string]*RPCFunc, logger log.Logger) http.Han // For POST requests, reject a non-root URL path. This should not happen // in the standard configuration, since the wrapper checks the path. if hreq.URL.Path != "/" { - writeRPCResponse(w, logger, rpctypes.RPCInvalidRequestError( - nil, fmt.Errorf("invalid path: %q", hreq.URL.Path))) + writeRPCResponse(w, logger, rpctypes.RPCRequest{}.MakeErrorf( + rpctypes.CodeInvalidRequest, "invalid path: %q", hreq.URL.Path)) return } b, err := io.ReadAll(hreq.Body) if err != nil { - writeRPCResponse(w, logger, rpctypes.RPCInvalidRequestError( - nil, fmt.Errorf("reading request body: %w", err))) + writeRPCResponse(w, logger, rpctypes.RPCRequest{}.MakeErrorf( + rpctypes.CodeInvalidRequest, "reading request body: %v", err)) return } @@ -46,7 +45,8 @@ func makeJSONRPCHandler(funcMap map[string]*RPCFunc, logger log.Logger) http.Han requests, err := parseRequests(b) if err != nil { - writeRPCResponse(w, logger, rpctypes.RPCParseError(fmt.Errorf("decoding request: %w", err))) + writeRPCResponse(w, logger, rpctypes.RPCRequest{}.MakeErrorf( + rpctypes.CodeParseError, "decoding request: %v", err)) return } @@ -60,7 +60,7 @@ func makeJSONRPCHandler(funcMap map[string]*RPCFunc, logger log.Logger) http.Han rpcFunc, ok := funcMap[req.Method] if !ok || rpcFunc.ws { - responses = append(responses, rpctypes.RPCMethodNotFoundError(req.ID)) + responses = append(responses, req.MakeErrorf(rpctypes.CodeMethodNotFound, req.Method)) continue } @@ -71,32 +71,17 @@ func makeJSONRPCHandler(funcMap map[string]*RPCFunc, logger log.Logger) http.Han }) args, err := parseParams(ctx, rpcFunc, req.Params) if err != nil { - responses = append(responses, rpctypes.RPCInvalidParamsError( - req.ID, fmt.Errorf("converting JSON parameters: %w", err))) + responses = append(responses, + req.MakeErrorf(rpctypes.CodeInvalidParams, "converting JSON parameters: %v", err)) continue } returns := rpcFunc.f.Call(args) - logger.Debug("HTTPJSONRPC", "method", req.Method, "args", args, "returns", returns) result, err := unreflectResult(returns) - switch e := err.(type) { - // if no error then return a success response - case nil: - responses = append(responses, rpctypes.NewRPCSuccessResponse(req.ID, result)) - - // if this already of type RPC error then forward that error - case *rpctypes.RPCError: - responses = append(responses, rpctypes.NewRPCErrorResponse(req.ID, e.Code, e.Message, e.Data)) - default: // we need to unwrap the error and parse it accordingly - switch errors.Unwrap(err) { - // check if the error was due to an invald request - case coretypes.ErrZeroOrNegativeHeight, coretypes.ErrZeroOrNegativePerPage, - coretypes.ErrPageOutOfRange, coretypes.ErrInvalidRequest: - responses = append(responses, rpctypes.RPCInvalidRequestError(req.ID, err)) - // lastly default all remaining errors as internal errors - default: // includes ctypes.ErrHeightNotAvailable and ctypes.ErrHeightExceedsChainHead - responses = append(responses, rpctypes.RPCInternalError(req.ID, err)) - } + if err == nil { + responses = append(responses, req.MakeResponse(result)) + } else { + responses = append(responses, req.MakeError(err)) } } diff --git a/rpc/jsonrpc/server/http_json_handler_test.go b/rpc/jsonrpc/server/http_json_handler_test.go index 2d56ef072..5e4df724a 100644 --- a/rpc/jsonrpc/server/http_json_handler_test.go +++ b/rpc/jsonrpc/server/http_json_handler_test.go @@ -63,14 +63,12 @@ func TestRPCParams(t *testing.T) { rec := httptest.NewRecorder() mux.ServeHTTP(rec, req) res := rec.Result() - defer res.Body.Close() + // Always expecting back a JSONRPCResponse assert.NotZero(t, res.StatusCode, "#%d: should always return code", i) blob, err := io.ReadAll(res.Body) - if err != nil { - t.Errorf("#%d: err reading body: %v", i, err) - continue - } + require.NoError(t, err, "#%d: reading body", i) + require.NoError(t, res.Body.Close()) recv := new(rpctypes.RPCResponse) assert.Nil(t, json.Unmarshal(blob, recv), "#%d: expecting successful parsing of an RPCResponse:\nblob: %s", i, blob) diff --git a/rpc/jsonrpc/server/http_server_test.go b/rpc/jsonrpc/server/http_server_test.go index ca67de708..12be80d1b 100644 --- a/rpc/jsonrpc/server/http_server_test.go +++ b/rpc/jsonrpc/server/http_server_test.go @@ -3,7 +3,6 @@ package server import ( "context" "crypto/tls" - "errors" "fmt" "io" "net" @@ -126,16 +125,15 @@ func TestServeTLS(t *testing.T) { } func TestWriteRPCResponse(t *testing.T) { - id := rpctypes.JSONRPCIntID(-1) + req := rpctypes.RPCRequest{ID: rpctypes.JSONRPCIntID(-1)} // one argument w := httptest.NewRecorder() logger := log.NewNopLogger() - writeRPCResponse(w, logger, - rpctypes.NewRPCSuccessResponse(id, &sampleResult{"hello"})) + writeRPCResponse(w, logger, req.MakeResponse(&sampleResult{"hello"})) resp := w.Result() body, err := io.ReadAll(resp.Body) - _ = resp.Body.Close() + require.NoError(t, resp.Body.Close()) require.NoError(t, err) assert.Equal(t, 200, resp.StatusCode) assert.Equal(t, "application/json", resp.Header.Get("Content-Type")) @@ -145,12 +143,12 @@ func TestWriteRPCResponse(t *testing.T) { // multiple arguments w = httptest.NewRecorder() writeRPCResponse(w, logger, - rpctypes.NewRPCSuccessResponse(id, &sampleResult{"hello"}), - rpctypes.NewRPCSuccessResponse(id, &sampleResult{"world"}), + req.MakeResponse(&sampleResult{"hello"}), + req.MakeResponse(&sampleResult{"world"}), ) resp = w.Result() body, err = io.ReadAll(resp.Body) - _ = resp.Body.Close() + require.NoError(t, resp.Body.Close()) require.NoError(t, err) assert.Equal(t, 200, resp.StatusCode) @@ -162,11 +160,11 @@ func TestWriteRPCResponse(t *testing.T) { func TestWriteHTTPResponse(t *testing.T) { w := httptest.NewRecorder() logger := log.NewNopLogger() - writeHTTPResponse(w, logger, - rpctypes.RPCInternalError(rpctypes.JSONRPCIntID(-1), errors.New("foo"))) + req := rpctypes.RPCRequest{ID: rpctypes.JSONRPCIntID(-1)} + writeHTTPResponse(w, logger, req.MakeErrorf(rpctypes.CodeInternalError, "foo")) resp := w.Result() body, err := io.ReadAll(resp.Body) - _ = resp.Body.Close() + require.NoError(t, resp.Body.Close()) require.NoError(t, err) assert.Equal(t, http.StatusOK, resp.StatusCode) assert.Equal(t, "application/json", resp.Header.Get("Content-Type")) diff --git a/rpc/jsonrpc/server/http_uri_handler.go b/rpc/jsonrpc/server/http_uri_handler.go index 573631a38..8d626f225 100644 --- a/rpc/jsonrpc/server/http_uri_handler.go +++ b/rpc/jsonrpc/server/http_uri_handler.go @@ -4,7 +4,6 @@ import ( "context" "encoding/hex" "encoding/json" - "errors" "fmt" "net/http" "reflect" @@ -12,7 +11,6 @@ import ( "strings" "github.com/tendermint/tendermint/libs/log" - "github.com/tendermint/tendermint/rpc/coretypes" rpctypes "github.com/tendermint/tendermint/rpc/jsonrpc/types" ) @@ -33,29 +31,15 @@ func makeHTTPHandler(rpcFunc *RPCFunc, logger log.Logger) func(http.ResponseWrit fmt.Fprintln(w, err.Error()) return } + jreq := rpctypes.RPCRequest{ID: uriReqID} outs := rpcFunc.f.Call(args) logger.Debug("HTTPRestRPC", "method", req.URL.Path, "args", args, "returns", outs) result, err := unreflectResult(outs) - switch e := err.(type) { - // if no error then return a success response - case nil: - writeHTTPResponse(w, logger, rpctypes.NewRPCSuccessResponse(uriReqID, result)) - - // if this already of type RPC error then forward that error. - case *rpctypes.RPCError: - writeHTTPResponse(w, logger, rpctypes.NewRPCErrorResponse(uriReqID, e.Code, e.Message, e.Data)) - - default: // we need to unwrap the error and parse it accordingly - switch errors.Unwrap(err) { - case coretypes.ErrZeroOrNegativeHeight, - coretypes.ErrZeroOrNegativePerPage, - coretypes.ErrPageOutOfRange, - coretypes.ErrInvalidRequest: - writeHTTPResponse(w, logger, rpctypes.RPCInvalidRequestError(uriReqID, err)) - default: // ctypes.ErrHeightNotAvailable, ctypes.ErrHeightExceedsChainHead: - writeHTTPResponse(w, logger, rpctypes.RPCInternalError(uriReqID, err)) - } + if err == nil { + writeHTTPResponse(w, logger, jreq.MakeResponse(result)) + } else { + writeHTTPResponse(w, logger, jreq.MakeError(err)) } } } diff --git a/rpc/jsonrpc/server/ws_handler.go b/rpc/jsonrpc/server/ws_handler.go index e511ef16c..2311d9b0b 100644 --- a/rpc/jsonrpc/server/ws_handler.go +++ b/rpc/jsonrpc/server/ws_handler.go @@ -3,7 +3,6 @@ package server import ( "context" "encoding/json" - "errors" "fmt" "net/http" "runtime/debug" @@ -13,7 +12,6 @@ import ( "github.com/tendermint/tendermint/libs/log" "github.com/tendermint/tendermint/rpc/client" - "github.com/tendermint/tendermint/rpc/coretypes" rpctypes "github.com/tendermint/tendermint/rpc/jsonrpc/types" ) @@ -276,8 +274,10 @@ func (wsc *wsConnection) readRoutine(ctx context.Context) { if !ok { err = fmt.Errorf("WSJSONRPC: %v", r) } + req := rpctypes.RPCRequest{ID: uriReqID} wsc.Logger.Error("Panic in WSJSONRPC handler", "err", err, "stack", string(debug.Stack())) - if err := wsc.WriteRPCResponse(writeCtx, rpctypes.RPCInternalError(rpctypes.JSONRPCIntID(-1), err)); err != nil { + if err := wsc.WriteRPCResponse(writeCtx, + req.MakeErrorf(rpctypes.CodeInternalError, "Panic in handler: %v", err)); err != nil { wsc.Logger.Error("error writing RPC response", "err", err) } go wsc.readRoutine(ctx) @@ -317,7 +317,7 @@ func (wsc *wsConnection) readRoutine(ctx context.Context) { err = dec.Decode(&request) if err != nil { if err := wsc.WriteRPCResponse(writeCtx, - rpctypes.RPCParseError(fmt.Errorf("error unmarshaling request: %w", err))); err != nil { + request.MakeErrorf(rpctypes.CodeParseError, "unmarshaling request: %v", err)); err != nil { wsc.Logger.Error("error writing RPC response", "err", err) } continue @@ -336,7 +336,8 @@ func (wsc *wsConnection) readRoutine(ctx context.Context) { // Now, fetch the RPCFunc and execute it. rpcFunc := wsc.funcMap[request.Method] if rpcFunc == nil { - if err := wsc.WriteRPCResponse(writeCtx, rpctypes.RPCMethodNotFoundError(request.ID)); err != nil { + if err := wsc.WriteRPCResponse(writeCtx, + request.MakeErrorf(rpctypes.CodeMethodNotFound, request.Method)); err != nil { wsc.Logger.Error("error writing RPC response", "err", err) } continue @@ -348,9 +349,8 @@ func (wsc *wsConnection) readRoutine(ctx context.Context) { }) args, err := parseParams(fctx, rpcFunc, request.Params) if err != nil { - if err := wsc.WriteRPCResponse(writeCtx, rpctypes.RPCInvalidParamsError( - request.ID, fmt.Errorf("error converting json params to arguments: %w", err)), - ); err != nil { + if err := wsc.WriteRPCResponse(writeCtx, request.MakeErrorf(rpctypes.CodeInvalidParams, + "converting JSON parameters: %v", err)); err != nil { wsc.Logger.Error("error writing RPC response", "err", err) } continue @@ -363,32 +363,14 @@ func (wsc *wsConnection) readRoutine(ctx context.Context) { var resp rpctypes.RPCResponse result, err := unreflectResult(returns) - switch e := err.(type) { - // if no error then return a success response - case nil: - resp = rpctypes.NewRPCSuccessResponse(request.ID, result) - - // if this already of type RPC error then forward that error - case *rpctypes.RPCError: - resp = rpctypes.NewRPCErrorResponse(request.ID, e.Code, e.Message, e.Data) - - default: // we need to unwrap the error and parse it accordingly - switch errors.Unwrap(err) { - // check if the error was due to an invald request - case coretypes.ErrZeroOrNegativeHeight, coretypes.ErrZeroOrNegativePerPage, - coretypes.ErrPageOutOfRange, coretypes.ErrInvalidRequest: - resp = rpctypes.RPCInvalidRequestError(request.ID, err) - - // lastly default all remaining errors as internal errors - default: // includes ctypes.ErrHeightNotAvailable and ctypes.ErrHeightExceedsChainHead - resp = rpctypes.RPCInternalError(request.ID, err) - } + if err == nil { + resp = request.MakeResponse(result) + } else { + resp = request.MakeError(err) } - if err := wsc.WriteRPCResponse(writeCtx, resp); err != nil { wsc.Logger.Error("error writing RPC response", "err", err) } - } } } diff --git a/rpc/jsonrpc/types/types.go b/rpc/jsonrpc/types/types.go index e53a925ed..111a9b656 100644 --- a/rpc/jsonrpc/types/types.go +++ b/rpc/jsonrpc/types/types.go @@ -3,10 +3,13 @@ package types import ( "context" "encoding/json" + "errors" "fmt" "net/http" "reflect" "strings" + + "github.com/tendermint/tendermint/rpc/coretypes" ) // a wrapper to emulate a sum type: jsonrpcid = string | int @@ -43,6 +46,33 @@ func idFromInterface(idInterface interface{}) (jsonrpcid, error) { } } +// ErrorCode is the type of JSON-RPC error codes. +type ErrorCode int + +func (e ErrorCode) String() string { + if s, ok := errorCodeString[e]; ok { + return s + } + return fmt.Sprintf("server error: code %d", e) +} + +// Constants defining the standard JSON-RPC error codes. +const ( + CodeParseError ErrorCode = -32700 // Invalid JSON received by the server + CodeInvalidRequest ErrorCode = -32600 // The JSON sent is not a valid request object + CodeMethodNotFound ErrorCode = -32601 // The method does not exist or is unavailable + CodeInvalidParams ErrorCode = -32602 // Invalid method parameters + CodeInternalError ErrorCode = -32603 // Internal JSON-RPC error +) + +var errorCodeString = map[ErrorCode]string{ + CodeParseError: "Parse error", + CodeInvalidRequest: "Invalid request", + CodeMethodNotFound: "Method not found", + CodeInvalidParams: "Invalid params", + CodeInternalError: "Internal error", +} + //---------------------------------------- // REQUEST @@ -94,6 +124,55 @@ func (req RPCRequest) String() string { return fmt.Sprintf("RPCRequest{%s %s/%X}", req.ID, req.Method, req.Params) } +// MakeResponse constructs a success response to req with the given result. If +// there is an error marshaling result to JSON, it returns an error response. +func (req RPCRequest) MakeResponse(result interface{}) RPCResponse { + data, err := json.Marshal(result) + if err != nil { + return req.MakeErrorf(CodeInternalError, "marshaling result: %v", err) + } + return RPCResponse{ID: req.ID, Result: data} +} + +// MakeErrorf constructs an error response to req with the given code and a +// message constructed by formatting msg with args. +func (req RPCRequest) MakeErrorf(code ErrorCode, msg string, args ...interface{}) RPCResponse { + return RPCResponse{ + ID: req.ID, + Error: &RPCError{ + Code: int(code), + Message: code.String(), + Data: fmt.Sprintf(msg, args...), + }, + } +} + +// MakeError constructs an error response to req from the given error value. +// This function will panic if err == nil. +func (req RPCRequest) MakeError(err error) RPCResponse { + if err == nil { + panic("cannot construct an error response for nil") + } + if e, ok := err.(*RPCError); ok { + return RPCResponse{ID: req.ID, Error: e} + } + if errors.Is(err, coretypes.ErrZeroOrNegativeHeight) || + errors.Is(err, coretypes.ErrZeroOrNegativePerPage) || + errors.Is(err, coretypes.ErrPageOutOfRange) || + errors.Is(err, coretypes.ErrInvalidRequest) { + return RPCResponse{ID: req.ID, Error: &RPCError{ + Code: int(CodeInvalidRequest), + Message: CodeInvalidRequest.String(), + Data: err.Error(), + }} + } + return RPCResponse{ID: req.ID, Error: &RPCError{ + Code: int(CodeInternalError), + Message: CodeInternalError.String(), + Data: err.Error(), + }} +} + // ParamsToRequest constructs a new RPCRequest with the given ID, method, and parameters. func ParamsToRequest(id jsonrpcid, method string, params interface{}) (RPCRequest, error) { payload, err := json.Marshal(params) @@ -169,21 +248,6 @@ func (resp RPCResponse) MarshalJSON() ([]byte, error) { }) } -func NewRPCSuccessResponse(id jsonrpcid, res interface{}) RPCResponse { - result, err := json.Marshal(res) - if err != nil { - return RPCInternalError(id, fmt.Errorf("error marshaling response: %w", err)) - } - return RPCResponse{ID: id, Result: result} -} - -func NewRPCErrorResponse(id jsonrpcid, code int, msg string, data string) RPCResponse { - return RPCResponse{ - ID: id, - Error: &RPCError{Code: code, Message: msg, Data: data}, - } -} - func (resp RPCResponse) String() string { if resp.Error == nil { return fmt.Sprintf("RPCResponse{%s %X}", resp.ID, resp.Result) @@ -191,36 +255,6 @@ func (resp RPCResponse) String() string { return fmt.Sprintf("RPCResponse{%s %v}", resp.ID, resp.Error) } -// From the JSON-RPC 2.0 spec: -// If there was an error in detecting the id in the Request object (e.g. Parse -// error/Invalid Request), it MUST be Null. -func RPCParseError(err error) RPCResponse { - return NewRPCErrorResponse(nil, -32700, "Parse error", err.Error()) -} - -// From the JSON-RPC 2.0 spec: -// If there was an error in detecting the id in the Request object (e.g. Parse -// error/Invalid Request), it MUST be Null. -func RPCInvalidRequestError(id jsonrpcid, err error) RPCResponse { - return NewRPCErrorResponse(id, -32600, "Invalid Request", err.Error()) -} - -func RPCMethodNotFoundError(id jsonrpcid) RPCResponse { - return NewRPCErrorResponse(id, -32601, "Method not found", "") -} - -func RPCInvalidParamsError(id jsonrpcid, err error) RPCResponse { - return NewRPCErrorResponse(id, -32602, "Invalid params", err.Error()) -} - -func RPCInternalError(id jsonrpcid, err error) RPCResponse { - return NewRPCErrorResponse(id, -32603, "Internal error", err.Error()) -} - -func RPCServerError(id jsonrpcid, err error) RPCResponse { - return NewRPCErrorResponse(id, -32000, "Server error", err.Error()) -} - //---------------------------------------- // WSRPCConnection represents a websocket connection. diff --git a/rpc/jsonrpc/types/types_test.go b/rpc/jsonrpc/types/types_test.go index 477d83788..2dbfed895 100644 --- a/rpc/jsonrpc/types/types_test.go +++ b/rpc/jsonrpc/types/types_test.go @@ -2,7 +2,6 @@ package types import ( "encoding/json" - "errors" "fmt" "testing" @@ -32,20 +31,27 @@ var responseTests = []responseTest{ func TestResponses(t *testing.T) { for _, tt := range responseTests { - jsonid := tt.id - a := NewRPCSuccessResponse(jsonid, &SampleResult{"hello"}) - b, _ := json.Marshal(a) + req := RPCRequest{ + ID: tt.id, + Method: "whatever", + } + + a := req.MakeResponse(&SampleResult{"hello"}) + b, err := json.Marshal(a) + require.NoError(t, err) s := fmt.Sprintf(`{"jsonrpc":"2.0","id":%v,"result":{"Value":"hello"}}`, tt.expected) assert.Equal(t, s, string(b)) - d := RPCParseError(errors.New("hello world")) - e, _ := json.Marshal(d) - f := `{"jsonrpc":"2.0","error":{"code":-32700,"message":"Parse error","data":"hello world"}}` + d := req.MakeErrorf(CodeParseError, "hello world") + e, err := json.Marshal(d) + require.NoError(t, err) + f := fmt.Sprintf(`{"jsonrpc":"2.0","id":%v,"error":{"code":-32700,"message":"Parse error","data":"hello world"}}`, tt.expected) assert.Equal(t, f, string(e)) - g := RPCMethodNotFoundError(jsonid) - h, _ := json.Marshal(g) - i := fmt.Sprintf(`{"jsonrpc":"2.0","id":%v,"error":{"code":-32601,"message":"Method not found"}}`, tt.expected) + g := req.MakeErrorf(CodeMethodNotFound, "foo") + h, err := json.Marshal(g) + require.NoError(t, err) + i := fmt.Sprintf(`{"jsonrpc":"2.0","id":%v,"error":{"code":-32601,"message":"Method not found","data":"foo"}}`, tt.expected) assert.Equal(t, string(h), i) } } @@ -59,7 +65,8 @@ func TestUnmarshallResponses(t *testing.T) { ) require.NoError(t, err) - a := NewRPCSuccessResponse(tt.id, &SampleResult{"hello"}) + req := RPCRequest{ID: tt.id} + a := req.MakeResponse(&SampleResult{"hello"}) assert.Equal(t, *response, a) } response := &RPCResponse{}