mirror of
https://github.com/tendermint/tendermint.git
synced 2026-01-07 22:05:18 +00:00
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
This commit is contained in:
@@ -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(),
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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"))
|
||||
|
||||
@@ -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))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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{}
|
||||
|
||||
Reference in New Issue
Block a user