From 60a1f49a5c70a5b4cfd100a8d9f9329b7e359969 Mon Sep 17 00:00:00 2001 From: Dave Bryson Date: Fri, 26 May 2017 14:46:33 +0200 Subject: [PATCH] updated json response to match spec by @davebryson --- rpc/core/events.go | 4 +-- rpc/lib/client/http_client.go | 2 +- rpc/lib/server/handlers.go | 33 ++++++++++---------- rpc/lib/server/http_server.go | 2 +- rpc/lib/types/types.go | 57 +++++++++++++++++++++++++++-------- rpc/lib/types/types_test.go | 32 ++++++++++++++++++++ 6 files changed, 97 insertions(+), 33 deletions(-) create mode 100644 rpc/lib/types/types_test.go diff --git a/rpc/core/events.go b/rpc/core/events.go index 4671d341..00fd9a08 100644 --- a/rpc/core/events.go +++ b/rpc/core/events.go @@ -2,7 +2,7 @@ package core import ( ctypes "github.com/tendermint/tendermint/rpc/core/types" - "github.com/tendermint/tendermint/rpc/lib/types" + rpctypes "github.com/tendermint/tendermint/rpc/lib/types" "github.com/tendermint/tendermint/types" ) @@ -39,7 +39,7 @@ func Subscribe(wsCtx rpctypes.WSRPCContext, event string) (*ctypes.ResultSubscri // NOTE: EventSwitch callbacks must be nonblocking // NOTE: RPCResponses of subscribed events have id suffix "#event" tmResult := &ctypes.ResultEvent{event, msg} - wsCtx.TryWriteRPCResponse(rpctypes.NewRPCResponse(wsCtx.Request.ID+"#event", tmResult, "")) + wsCtx.TryWriteRPCResponse(rpctypes.NewRPCSuccessResponse(wsCtx.Request.ID+"#event", tmResult)) }) return &ctypes.ResultSubscribe{}, nil } diff --git a/rpc/lib/client/http_client.go b/rpc/lib/client/http_client.go index 755c3e79..bd3846c6 100644 --- a/rpc/lib/client/http_client.go +++ b/rpc/lib/client/http_client.go @@ -146,7 +146,7 @@ func unmarshalResponseBytes(responseBytes []byte, result interface{}) (interface if err != nil { return nil, errors.Errorf("Error unmarshalling rpc response: %v", err) } - errorStr := response.Error + errorStr := response.Error.Message if errorStr != "" { return nil, errors.Errorf("Response error: %v", errorStr) } diff --git a/rpc/lib/server/handlers.go b/rpc/lib/server/handlers.go index 3b81567e..a4df4b84 100644 --- a/rpc/lib/server/handlers.go +++ b/rpc/lib/server/handlers.go @@ -110,35 +110,35 @@ func makeJSONRPCHandler(funcMap map[string]*RPCFunc, logger log.Logger) http.Han var request types.RPCRequest err := json.Unmarshal(b, &request) if err != nil { - WriteRPCResponseHTTPError(w, http.StatusBadRequest, types.NewRPCResponse("", nil, fmt.Sprintf("Error unmarshalling request: %v", err.Error()))) + WriteRPCResponseHTTP(w, types.RPCParseError("")) return } if len(r.URL.Path) > 1 { - WriteRPCResponseHTTPError(w, http.StatusNotFound, types.NewRPCResponse(request.ID, nil, fmt.Sprintf("Invalid JSONRPC endpoint %s", r.URL.Path))) + WriteRPCResponseHTTP(w, types.RPCInvalidRequestError(request.ID)) return } rpcFunc := funcMap[request.Method] if rpcFunc == nil { - WriteRPCResponseHTTPError(w, http.StatusNotFound, types.NewRPCResponse(request.ID, nil, "RPC method unknown: "+request.Method)) + WriteRPCResponseHTTP(w, types.RPCMethodNotFoundError(request.ID)) return } if rpcFunc.ws { - WriteRPCResponseHTTPError(w, http.StatusMethodNotAllowed, types.NewRPCResponse(request.ID, nil, "RPC method is only for websockets: "+request.Method)) + WriteRPCResponseHTTP(w, types.RPCInternalError(request.ID)) return } args, err := jsonParamsToArgsRPC(rpcFunc, request.Params) if err != nil { - WriteRPCResponseHTTPError(w, http.StatusBadRequest, types.NewRPCResponse(request.ID, nil, fmt.Sprintf("Error converting json params to arguments: %v", err.Error()))) + WriteRPCResponseHTTP(w, types.RPCInvalidParamsError(request.ID)) return } returns := rpcFunc.f.Call(args) logger.Info("HTTPJSONRPC", "method", request.Method, "args", args, "returns", returns) result, err := unreflectResult(returns) if err != nil { - WriteRPCResponseHTTPError(w, http.StatusInternalServerError, types.NewRPCResponse(request.ID, result, err.Error())) + WriteRPCResponseHTTP(w, types.RPCInternalError(request.ID)) return } - WriteRPCResponseHTTP(w, types.NewRPCResponse(request.ID, result, "")) + WriteRPCResponseHTTP(w, types.NewRPCSuccessResponse(request.ID, result)) } } @@ -229,7 +229,7 @@ func makeHTTPHandler(rpcFunc *RPCFunc, logger log.Logger) func(http.ResponseWrit // Exception for websocket endpoints if rpcFunc.ws { return func(w http.ResponseWriter, r *http.Request) { - WriteRPCResponseHTTPError(w, http.StatusMethodNotAllowed, types.NewRPCResponse("", nil, "This RPC method is only for websockets")) + WriteRPCResponseHTTP(w, types.RPCInternalError("")) } } // All other endpoints @@ -237,17 +237,17 @@ func makeHTTPHandler(rpcFunc *RPCFunc, logger log.Logger) func(http.ResponseWrit logger.Debug("HTTP HANDLER", "req", r) args, err := httpParamsToArgs(rpcFunc, r) if err != nil { - WriteRPCResponseHTTPError(w, http.StatusBadRequest, types.NewRPCResponse("", nil, fmt.Sprintf("Error converting http params to args: %v", err.Error()))) + WriteRPCResponseHTTP(w, types.RPCInvalidParamsError("")) return } returns := rpcFunc.f.Call(args) logger.Info("HTTPRestRPC", "method", r.URL.Path, "args", args, "returns", returns) result, err := unreflectResult(returns) if err != nil { - WriteRPCResponseHTTPError(w, http.StatusInternalServerError, types.NewRPCResponse("", nil, err.Error())) + WriteRPCResponseHTTP(w, types.RPCInternalError("")) return } - WriteRPCResponseHTTP(w, types.NewRPCResponse("", result, "")) + WriteRPCResponseHTTP(w, types.NewRPCSuccessResponse("", result)) } } @@ -509,8 +509,7 @@ func (wsc *wsConnection) readRoutine() { var request types.RPCRequest err = json.Unmarshal(in, &request) if err != nil { - errStr := fmt.Sprintf("Error unmarshaling data: %s", err.Error()) - wsc.WriteRPCResponse(types.NewRPCResponse(request.ID, nil, errStr)) + wsc.WriteRPCResponse(types.RPCParseError("")) continue } @@ -518,7 +517,7 @@ func (wsc *wsConnection) readRoutine() { rpcFunc := wsc.funcMap[request.Method] if rpcFunc == nil { - wsc.WriteRPCResponse(types.NewRPCResponse(request.ID, nil, "RPC method unknown: "+request.Method)) + wsc.WriteRPCResponse(types.RPCMethodNotFoundError(request.ID)) continue } var args []reflect.Value @@ -529,7 +528,7 @@ func (wsc *wsConnection) readRoutine() { args, err = jsonParamsToArgsRPC(rpcFunc, request.Params) } if err != nil { - wsc.WriteRPCResponse(types.NewRPCResponse(request.ID, nil, err.Error())) + wsc.WriteRPCResponse(types.RPCInternalError(request.ID)) continue } returns := rpcFunc.f.Call(args) @@ -539,10 +538,10 @@ func (wsc *wsConnection) readRoutine() { result, err := unreflectResult(returns) if err != nil { - wsc.WriteRPCResponse(types.NewRPCResponse(request.ID, nil, err.Error())) + wsc.WriteRPCResponse(types.RPCInternalError(request.ID)) continue } else { - wsc.WriteRPCResponse(types.NewRPCResponse(request.ID, result, "")) + wsc.WriteRPCResponse(types.NewRPCSuccessResponse(request.ID, result)) continue } diff --git a/rpc/lib/server/http_server.go b/rpc/lib/server/http_server.go index 270321b4..2d6f5f1b 100644 --- a/rpc/lib/server/http_server.go +++ b/rpc/lib/server/http_server.go @@ -99,7 +99,7 @@ func RecoverAndLogHandler(handler http.Handler, logger log.Logger) http.Handler // For the rest, logger.Error("Panic in RPC HTTP handler", "err", e, "stack", string(debug.Stack())) rww.WriteHeader(http.StatusInternalServerError) - WriteRPCResponseHTTP(rww, types.NewRPCResponse("", nil, fmt.Sprintf("Internal Server Error: %v", e))) + WriteRPCResponseHTTP(rww, types.RPCInternalError("")) } } diff --git a/rpc/lib/types/types.go b/rpc/lib/types/types.go index f4a2cede..528255a8 100644 --- a/rpc/lib/types/types.go +++ b/rpc/lib/types/types.go @@ -8,6 +8,11 @@ import ( events "github.com/tendermint/tmlibs/events" ) +type RpcError struct { + Code int `json:"code"` + Message string `json:"message"` +} + type RPCRequest struct { JSONRPC string `json:"jsonrpc"` ID string `json:"id"` @@ -50,28 +55,32 @@ func ArrayToRequest(id string, method string, params []interface{}) (RPCRequest, type RPCResponse struct { JSONRPC string `json:"jsonrpc"` - ID string `json:"id"` - Result *json.RawMessage `json:"result"` - Error string `json:"error"` + ID string `json:"id,omitempty"` + Result *json.RawMessage `json:"result,omitempty"` + Error *RpcError `json:"error,omitempty"` } -func NewRPCResponse(id string, res interface{}, err string) RPCResponse { +func NewRPCSuccessResponse(id string, res interface{}) RPCResponse { var raw *json.RawMessage + if res != nil { var js []byte - js, err2 := json.Marshal(res) - if err2 == nil { - rawMsg := json.RawMessage(js) - raw = &rawMsg - } else { - err = err2.Error() + js, err := json.Marshal(res) + if err != nil { + return RPCInternalError(id) } + rawMsg := json.RawMessage(js) + raw = &rawMsg } + + return RPCResponse{JSONRPC: "2.0", ID: id, Result: raw} +} + +func NewRPCErrorResponse(id string, code int, msg string) RPCResponse { return RPCResponse{ JSONRPC: "2.0", ID: id, - Result: raw, - Error: err, + Error: &RpcError{Code: code, Message: msg}, } } @@ -83,6 +92,30 @@ func (resp RPCResponse) String() string { } } +func RPCParseError(id string) RPCResponse { + return NewRPCErrorResponse(id, -32700, "Parse error. Invalid JSON") +} + +func RPCInvalidRequestError(id string) RPCResponse { + return NewRPCErrorResponse(id, -32600, "Invalid Request") +} + +func RPCMethodNotFoundError(id string) RPCResponse { + return NewRPCErrorResponse(id, -32601, "Method not found") +} + +func RPCInvalidParamsError(id string) RPCResponse { + return NewRPCErrorResponse(id, -32602, "Invalid params") +} + +func RPCInternalError(id string) RPCResponse { + return NewRPCErrorResponse(id, -32603, "Internal error") +} + +func RPCServerError(id string) RPCResponse { + return NewRPCErrorResponse(id, -32000, "Server error") +} + //---------------------------------------- // *wsConnection implements this interface. diff --git a/rpc/lib/types/types_test.go b/rpc/lib/types/types_test.go new file mode 100644 index 00000000..14688ece --- /dev/null +++ b/rpc/lib/types/types_test.go @@ -0,0 +1,32 @@ +package rpctypes + +import ( + "encoding/json" + "testing" + + "github.com/stretchr/testify/assert" +) + +type SampleResult struct { + Value string +} + +func TestResponses(t *testing.T) { + assert := assert.New(t) + + a := NewRPCSuccessResponse("1", &SampleResult{"hello"}) + b, _ := json.Marshal(a) + s := `{"jsonrpc":"2.0","id":"1","result":{"Value":"hello"}}` + assert.Equal(string(s), string(b)) + + d := RPCParseError("1") + e, _ := json.Marshal(d) + f := `{"jsonrpc":"2.0","id":"1","error":{"code":-32700,"message":"Parse error. Invalid JSON"}}` + assert.Equal(string(f), string(e)) + + g := RPCMethodNotFoundError("2") + h, _ := json.Marshal(g) + i := `{"jsonrpc":"2.0","id":"2","error":{"code":-32601,"message":"Method not found"}}` + assert.Equal(string(h), string(i)) + +}