Browse Source

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
pull/7726/head
M. J. Fromberger 2 years ago
committed by GitHub
parent
commit
fbe86ea645
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 144 additions and 160 deletions
  1. +2
    -2
      internal/rpc/core/events.go
  2. +2
    -2
      light/provider/http/http_test.go
  3. +1
    -5
      light/rpc/client.go
  4. +13
    -28
      rpc/jsonrpc/server/http_json_handler.go
  5. +3
    -5
      rpc/jsonrpc/server/http_json_handler_test.go
  6. +9
    -11
      rpc/jsonrpc/server/http_server_test.go
  7. +5
    -21
      rpc/jsonrpc/server/http_uri_handler.go
  8. +12
    -30
      rpc/jsonrpc/server/ws_handler.go
  9. +79
    -45
      rpc/jsonrpc/types/types.go
  10. +18
    -11
      rpc/jsonrpc/types/types_test.go

+ 2
- 2
internal/rpc/core/events.go View File

@ -67,7 +67,7 @@ func (env *Environment) Subscribe(ctx context.Context, query string) (*coretypes
return return
} else if errors.Is(err, tmpubsub.ErrTerminated) { } else if errors.Is(err, tmpubsub.ErrTerminated) {
// The subscription was terminated by the publisher. // The subscription was terminated by the publisher.
resp := rpctypes.RPCServerError(subscriptionID, err)
resp := callInfo.RPCRequest.MakeError(err)
ok := callInfo.WSConn.TryWriteRPCResponse(opctx, resp) ok := callInfo.WSConn.TryWriteRPCResponse(opctx, resp)
if !ok { if !ok {
env.Logger.Info("Unable to write response (slow client)", 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. // We have a message to deliver to the client.
resp := rpctypes.NewRPCSuccessResponse(subscriptionID, &coretypes.ResultEvent{
resp := callInfo.RPCRequest.MakeResponse(&coretypes.ResultEvent{
Query: query, Query: query,
Data: msg.Data(), Data: msg.Data(),
Events: msg.Events(), Events: msg.Events(),


+ 2
- 2
light/provider/http/http_test.go View File

@ -80,12 +80,12 @@ func TestProvider(t *testing.T) {
lb, err = p.LightBlock(ctx, 9001) lb, err = p.LightBlock(ctx, 9001)
require.Error(t, err) require.Error(t, err)
require.Nil(t, lb) require.Nil(t, lb)
assert.Equal(t, provider.ErrHeightTooHigh, err)
assert.ErrorIs(t, err, provider.ErrHeightTooHigh)
lb, err = p.LightBlock(ctx, 1) lb, err = p.LightBlock(ctx, 1)
require.Error(t, err) require.Error(t, err)
require.Nil(t, lb) 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 // if the provider is unable to provide four more blocks then we should return
// an unreliable peer error // an unreliable peer error


+ 1
- 5
light/rpc/client.go View File

@ -655,11 +655,7 @@ func (c *Client) SubscribeWS(ctx context.Context, query string) (*coretypes.Resu
case resultEvent := <-out: case resultEvent := <-out:
// We should have a switch here that performs a validation // We should have a switch here that performs a validation
// depending on the event's type. // 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(): case <-bctx.Done():
return return
} }


+ 13
- 28
rpc/jsonrpc/server/http_json_handler.go View File

@ -13,7 +13,6 @@ import (
"strconv" "strconv"
"github.com/tendermint/tendermint/libs/log" "github.com/tendermint/tendermint/libs/log"
"github.com/tendermint/tendermint/rpc/coretypes"
rpctypes "github.com/tendermint/tendermint/rpc/jsonrpc/types" 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 // For POST requests, reject a non-root URL path. This should not happen
// in the standard configuration, since the wrapper checks the path. // in the standard configuration, since the wrapper checks the path.
if hreq.URL.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 return
} }
b, err := io.ReadAll(hreq.Body) b, err := io.ReadAll(hreq.Body)
if err != nil { 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 return
} }
@ -46,7 +45,8 @@ func makeJSONRPCHandler(funcMap map[string]*RPCFunc, logger log.Logger) http.Han
requests, err := parseRequests(b) requests, err := parseRequests(b)
if err != nil { 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 return
} }
@ -60,7 +60,7 @@ func makeJSONRPCHandler(funcMap map[string]*RPCFunc, logger log.Logger) http.Han
rpcFunc, ok := funcMap[req.Method] rpcFunc, ok := funcMap[req.Method]
if !ok || rpcFunc.ws { if !ok || rpcFunc.ws {
responses = append(responses, rpctypes.RPCMethodNotFoundError(req.ID))
responses = append(responses, req.MakeErrorf(rpctypes.CodeMethodNotFound, req.Method))
continue continue
} }
@ -71,32 +71,17 @@ func makeJSONRPCHandler(funcMap map[string]*RPCFunc, logger log.Logger) http.Han
}) })
args, err := parseParams(ctx, rpcFunc, req.Params) args, err := parseParams(ctx, rpcFunc, req.Params)
if err != nil { 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 continue
} }
returns := rpcFunc.f.Call(args) returns := rpcFunc.f.Call(args)
logger.Debug("HTTPJSONRPC", "method", req.Method, "args", args, "returns", returns)
result, err := unreflectResult(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))
} }
} }


+ 3
- 5
rpc/jsonrpc/server/http_json_handler_test.go View File

@ -63,14 +63,12 @@ func TestRPCParams(t *testing.T) {
rec := httptest.NewRecorder() rec := httptest.NewRecorder()
mux.ServeHTTP(rec, req) mux.ServeHTTP(rec, req)
res := rec.Result() res := rec.Result()
defer res.Body.Close()
// Always expecting back a JSONRPCResponse // Always expecting back a JSONRPCResponse
assert.NotZero(t, res.StatusCode, "#%d: should always return code", i) assert.NotZero(t, res.StatusCode, "#%d: should always return code", i)
blob, err := io.ReadAll(res.Body) 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) recv := new(rpctypes.RPCResponse)
assert.Nil(t, json.Unmarshal(blob, recv), "#%d: expecting successful parsing of an RPCResponse:\nblob: %s", i, blob) assert.Nil(t, json.Unmarshal(blob, recv), "#%d: expecting successful parsing of an RPCResponse:\nblob: %s", i, blob)


+ 9
- 11
rpc/jsonrpc/server/http_server_test.go View File

@ -3,7 +3,6 @@ package server
import ( import (
"context" "context"
"crypto/tls" "crypto/tls"
"errors"
"fmt" "fmt"
"io" "io"
"net" "net"
@ -126,16 +125,15 @@ func TestServeTLS(t *testing.T) {
} }
func TestWriteRPCResponse(t *testing.T) { func TestWriteRPCResponse(t *testing.T) {
id := rpctypes.JSONRPCIntID(-1)
req := rpctypes.RPCRequest{ID: rpctypes.JSONRPCIntID(-1)}
// one argument // one argument
w := httptest.NewRecorder() w := httptest.NewRecorder()
logger := log.NewNopLogger() logger := log.NewNopLogger()
writeRPCResponse(w, logger,
rpctypes.NewRPCSuccessResponse(id, &sampleResult{"hello"}))
writeRPCResponse(w, logger, req.MakeResponse(&sampleResult{"hello"}))
resp := w.Result() resp := w.Result()
body, err := io.ReadAll(resp.Body) body, err := io.ReadAll(resp.Body)
_ = resp.Body.Close()
require.NoError(t, resp.Body.Close())
require.NoError(t, err) require.NoError(t, err)
assert.Equal(t, 200, resp.StatusCode) assert.Equal(t, 200, resp.StatusCode)
assert.Equal(t, "application/json", resp.Header.Get("Content-Type")) assert.Equal(t, "application/json", resp.Header.Get("Content-Type"))
@ -145,12 +143,12 @@ func TestWriteRPCResponse(t *testing.T) {
// multiple arguments // multiple arguments
w = httptest.NewRecorder() w = httptest.NewRecorder()
writeRPCResponse(w, logger, writeRPCResponse(w, logger,
rpctypes.NewRPCSuccessResponse(id, &sampleResult{"hello"}),
rpctypes.NewRPCSuccessResponse(id, &sampleResult{"world"}),
req.MakeResponse(&sampleResult{"hello"}),
req.MakeResponse(&sampleResult{"world"}),
) )
resp = w.Result() resp = w.Result()
body, err = io.ReadAll(resp.Body) body, err = io.ReadAll(resp.Body)
_ = resp.Body.Close()
require.NoError(t, resp.Body.Close())
require.NoError(t, err) require.NoError(t, err)
assert.Equal(t, 200, resp.StatusCode) assert.Equal(t, 200, resp.StatusCode)
@ -162,11 +160,11 @@ func TestWriteRPCResponse(t *testing.T) {
func TestWriteHTTPResponse(t *testing.T) { func TestWriteHTTPResponse(t *testing.T) {
w := httptest.NewRecorder() w := httptest.NewRecorder()
logger := log.NewNopLogger() 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() resp := w.Result()
body, err := io.ReadAll(resp.Body) body, err := io.ReadAll(resp.Body)
_ = resp.Body.Close()
require.NoError(t, resp.Body.Close())
require.NoError(t, err) require.NoError(t, err)
assert.Equal(t, http.StatusOK, resp.StatusCode) assert.Equal(t, http.StatusOK, resp.StatusCode)
assert.Equal(t, "application/json", resp.Header.Get("Content-Type")) assert.Equal(t, "application/json", resp.Header.Get("Content-Type"))


+ 5
- 21
rpc/jsonrpc/server/http_uri_handler.go View File

@ -4,7 +4,6 @@ import (
"context" "context"
"encoding/hex" "encoding/hex"
"encoding/json" "encoding/json"
"errors"
"fmt" "fmt"
"net/http" "net/http"
"reflect" "reflect"
@ -12,7 +11,6 @@ import (
"strings" "strings"
"github.com/tendermint/tendermint/libs/log" "github.com/tendermint/tendermint/libs/log"
"github.com/tendermint/tendermint/rpc/coretypes"
rpctypes "github.com/tendermint/tendermint/rpc/jsonrpc/types" 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()) fmt.Fprintln(w, err.Error())
return return
} }
jreq := rpctypes.RPCRequest{ID: uriReqID}
outs := rpcFunc.f.Call(args) outs := rpcFunc.f.Call(args)
logger.Debug("HTTPRestRPC", "method", req.URL.Path, "args", args, "returns", outs) logger.Debug("HTTPRestRPC", "method", req.URL.Path, "args", args, "returns", outs)
result, err := unreflectResult(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))
} }
} }
} }


+ 12
- 30
rpc/jsonrpc/server/ws_handler.go View File

@ -3,7 +3,6 @@ package server
import ( import (
"context" "context"
"encoding/json" "encoding/json"
"errors"
"fmt" "fmt"
"net/http" "net/http"
"runtime/debug" "runtime/debug"
@ -13,7 +12,6 @@ import (
"github.com/tendermint/tendermint/libs/log" "github.com/tendermint/tendermint/libs/log"
"github.com/tendermint/tendermint/rpc/client" "github.com/tendermint/tendermint/rpc/client"
"github.com/tendermint/tendermint/rpc/coretypes"
rpctypes "github.com/tendermint/tendermint/rpc/jsonrpc/types" rpctypes "github.com/tendermint/tendermint/rpc/jsonrpc/types"
) )
@ -276,8 +274,10 @@ func (wsc *wsConnection) readRoutine(ctx context.Context) {
if !ok { if !ok {
err = fmt.Errorf("WSJSONRPC: %v", r) err = fmt.Errorf("WSJSONRPC: %v", r)
} }
req := rpctypes.RPCRequest{ID: uriReqID}
wsc.Logger.Error("Panic in WSJSONRPC handler", "err", err, "stack", string(debug.Stack())) 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) wsc.Logger.Error("error writing RPC response", "err", err)
} }
go wsc.readRoutine(ctx) go wsc.readRoutine(ctx)
@ -317,7 +317,7 @@ func (wsc *wsConnection) readRoutine(ctx context.Context) {
err = dec.Decode(&request) err = dec.Decode(&request)
if err != nil { if err != nil {
if err := wsc.WriteRPCResponse(writeCtx, 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) wsc.Logger.Error("error writing RPC response", "err", err)
} }
continue continue
@ -336,7 +336,8 @@ func (wsc *wsConnection) readRoutine(ctx context.Context) {
// Now, fetch the RPCFunc and execute it. // Now, fetch the RPCFunc and execute it.
rpcFunc := wsc.funcMap[request.Method] rpcFunc := wsc.funcMap[request.Method]
if rpcFunc == nil { 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) wsc.Logger.Error("error writing RPC response", "err", err)
} }
continue continue
@ -348,9 +349,8 @@ func (wsc *wsConnection) readRoutine(ctx context.Context) {
}) })
args, err := parseParams(fctx, rpcFunc, request.Params) args, err := parseParams(fctx, rpcFunc, request.Params)
if err != nil { 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) wsc.Logger.Error("error writing RPC response", "err", err)
} }
continue continue
@ -363,32 +363,14 @@ func (wsc *wsConnection) readRoutine(ctx context.Context) {
var resp rpctypes.RPCResponse var resp rpctypes.RPCResponse
result, err := unreflectResult(returns) 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 { if err := wsc.WriteRPCResponse(writeCtx, resp); err != nil {
wsc.Logger.Error("error writing RPC response", "err", err) wsc.Logger.Error("error writing RPC response", "err", err)
} }
} }
} }
} }


+ 79
- 45
rpc/jsonrpc/types/types.go View File

@ -3,10 +3,13 @@ package types
import ( import (
"context" "context"
"encoding/json" "encoding/json"
"errors"
"fmt" "fmt"
"net/http" "net/http"
"reflect" "reflect"
"strings" "strings"
"github.com/tendermint/tendermint/rpc/coretypes"
) )
// a wrapper to emulate a sum type: jsonrpcid = string | int // 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 // REQUEST
@ -94,6 +124,55 @@ func (req RPCRequest) String() string {
return fmt.Sprintf("RPCRequest{%s %s/%X}", req.ID, req.Method, req.Params) 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. // ParamsToRequest constructs a new RPCRequest with the given ID, method, and parameters.
func ParamsToRequest(id jsonrpcid, method string, params interface{}) (RPCRequest, error) { func ParamsToRequest(id jsonrpcid, method string, params interface{}) (RPCRequest, error) {
payload, err := json.Marshal(params) 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 { func (resp RPCResponse) String() string {
if resp.Error == nil { if resp.Error == nil {
return fmt.Sprintf("RPCResponse{%s %X}", resp.ID, resp.Result) 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) 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. // WSRPCConnection represents a websocket connection.


+ 18
- 11
rpc/jsonrpc/types/types_test.go View File

@ -2,7 +2,6 @@ package types
import ( import (
"encoding/json" "encoding/json"
"errors"
"fmt" "fmt"
"testing" "testing"
@ -32,20 +31,27 @@ var responseTests = []responseTest{
func TestResponses(t *testing.T) { func TestResponses(t *testing.T) {
for _, tt := range responseTests { 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) s := fmt.Sprintf(`{"jsonrpc":"2.0","id":%v,"result":{"Value":"hello"}}`, tt.expected)
assert.Equal(t, s, string(b)) 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)) 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) assert.Equal(t, string(h), i)
} }
} }
@ -59,7 +65,8 @@ func TestUnmarshallResponses(t *testing.T) {
) )
require.NoError(t, err) 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) assert.Equal(t, *response, a)
} }
response := &RPCResponse{} response := &RPCResponse{}


Loading…
Cancel
Save