From 85430f78ac7b0b17473f787cb6f4d42b6105bca6 Mon Sep 17 00:00:00 2001 From: Hubert Bugaj Date: Tue, 7 May 2024 22:55:28 +0200 Subject: [PATCH 1/2] fix: make resp result/error conformant to spec Co-authored by Ryan --- handler.go | 43 +++++++++++++++++++++++++++++++++++++++---- rpc_test.go | 16 ++++++++-------- 2 files changed, 47 insertions(+), 12 deletions(-) diff --git a/handler.go b/handler.go index 8b2a709..09ef162 100644 --- a/handler.go +++ b/handler.go @@ -104,10 +104,45 @@ func (e *respError) val(errors *Errors) reflect.Value { } type response struct { - Jsonrpc string `json:"jsonrpc"` - Result interface{} `json:"result,omitempty"` - ID interface{} `json:"id"` - Error *respError `json:"error,omitempty"` + Jsonrpc string + Result interface{} + ID interface{} + Error *respError +} + +func (r response) MarshalJSON() ([]byte, error) { + // Custom marshal logic as per JSON-RPC 2.0 spec: + // > `result`: + // > This member is REQUIRED on success. + // > This member MUST NOT exist if there was an error invoking the method. + // + // > `error`: + // > This member is REQUIRED on error. + // > This member MUST NOT exist if there was no error triggered during invocation. + if r.Error != nil { + // If there's an error, exclude result + type responseWithoutResult struct { + Jsonrpc string `json:"jsonrpc"` + ID interface{} `json:"id"` + Error *respError `json:"error"` + } + return json.Marshal(&responseWithoutResult{ + Jsonrpc: r.Jsonrpc, + ID: r.ID, + Error: r.Error, + }) + } + + type responseWithResult struct { + Jsonrpc string `json:"jsonrpc"` + Result interface{} `json:"result"` + ID interface{} `json:"id"` + } + return json.Marshal(&responseWithResult{ + Jsonrpc: r.Jsonrpc, + Result: r.Result, + ID: r.ID, + }) } type handler struct { diff --git a/rpc_test.go b/rpc_test.go index 73bd9c1..febadcc 100644 --- a/rpc_test.go +++ b/rpc_test.go @@ -127,16 +127,16 @@ func TestRawRequests(t *testing.T) { } } - t.Run("inc", tc(`{"jsonrpc": "2.0", "method": "SimpleServerHandler.Inc", "params": [], "id": 1}`, `{"jsonrpc":"2.0","id":1}`, 1, 200)) - t.Run("inc-null", tc(`{"jsonrpc": "2.0", "method": "SimpleServerHandler.Inc", "params": null, "id": 1}`, `{"jsonrpc":"2.0","id":1}`, 1, 200)) - t.Run("inc-noparam", tc(`{"jsonrpc": "2.0", "method": "SimpleServerHandler.Inc", "id": 2}`, `{"jsonrpc":"2.0","id":2}`, 1, 200)) - t.Run("add", tc(`{"jsonrpc": "2.0", "method": "SimpleServerHandler.Add", "params": [10], "id": 4}`, `{"jsonrpc":"2.0","id":4}`, 10, 200)) + t.Run("inc", tc(`{"jsonrpc": "2.0", "method": "SimpleServerHandler.Inc", "params": [], "id": 1}`, `{"jsonrpc":"2.0","id":1,"result":null}`, 1, 200)) + t.Run("inc-null", tc(`{"jsonrpc": "2.0", "method": "SimpleServerHandler.Inc", "params": null, "id": 1}`, `{"jsonrpc":"2.0","id":1,"result":null}`, 1, 200)) + t.Run("inc-noparam", tc(`{"jsonrpc": "2.0", "method": "SimpleServerHandler.Inc", "id": 2}`, `{"jsonrpc":"2.0","id":2,"result":null}`, 1, 200)) + t.Run("add", tc(`{"jsonrpc": "2.0", "method": "SimpleServerHandler.Add", "params": [10], "id": 4}`, `{"jsonrpc":"2.0","id":4,"result":null}`, 10, 200)) // Batch requests t.Run("add", tc(`[{"jsonrpc": "2.0", "method": "SimpleServerHandler.Add", "params": [123], "id": 5}`, `{"jsonrpc":"2.0","id":null,"error":{"code":-32700,"message":"Parse error"}}`, 0, 500)) - t.Run("add", tc(`[{"jsonrpc": "2.0", "method": "SimpleServerHandler.Add", "params": [123], "id": 6}]`, `[{"jsonrpc":"2.0","id":6}]`, 123, 200)) - t.Run("add", tc(`[{"jsonrpc": "2.0", "method": "SimpleServerHandler.Add", "params": [123], "id": 7},{"jsonrpc": "2.0", "method": "SimpleServerHandler.Add", "params": [-122], "id": 8}]`, `[{"jsonrpc":"2.0","id":7},{"jsonrpc":"2.0","id":8}]`, 1, 200)) - t.Run("add", tc(`[{"jsonrpc": "2.0", "method": "SimpleServerHandler.Add", "params": [123], "id": 9},{"jsonrpc": "2.0", "params": [-122], "id": 10}]`, `[{"jsonrpc":"2.0","id":9},{"error":{"code":-32601,"message":"method '' not found"},"id":10,"jsonrpc":"2.0"}]`, 123, 200)) - t.Run("add", tc(` [{"jsonrpc": "2.0", "method": "SimpleServerHandler.Add", "params": [-1], "id": 11}] `, `[{"jsonrpc":"2.0","id":11}]`, -1, 200)) + t.Run("add", tc(`[{"jsonrpc": "2.0", "method": "SimpleServerHandler.Add", "params": [123], "id": 6}]`, `[{"jsonrpc":"2.0","id":6,"result":null}]`, 123, 200)) + t.Run("add", tc(`[{"jsonrpc": "2.0", "method": "SimpleServerHandler.Add", "params": [123], "id": 7},{"jsonrpc": "2.0", "method": "SimpleServerHandler.Add", "params": [-122], "id": 8}]`, `[{"jsonrpc":"2.0","id":7,"result":null},{"jsonrpc":"2.0","id":8,"result":null}]`, 1, 200)) + t.Run("add", tc(`[{"jsonrpc": "2.0", "method": "SimpleServerHandler.Add", "params": [123], "id": 9},{"jsonrpc": "2.0", "params": [-122], "id": 10}]`, `[{"jsonrpc":"2.0","id":9,"result":null},{"error":{"code":-32601,"message":"method '' not found"},"id":10,"jsonrpc":"2.0"}]`, 123, 200)) + t.Run("add", tc(` [{"jsonrpc": "2.0", "method": "SimpleServerHandler.Add", "params": [-1], "id": 11}] `, `[{"jsonrpc":"2.0","id":11,"result":null}]`, -1, 200)) t.Run("add", tc(``, `{"jsonrpc":"2.0","id":null,"error":{"code":-32600,"message":"Invalid request"}}`, 0, 400)) } From b0ce36ad2ea979c72b597080488558dc254aabe1 Mon Sep 17 00:00:00 2001 From: Hubert Bugaj Date: Wed, 8 May 2024 08:33:27 +0200 Subject: [PATCH 2/2] more concise marshal Co-authored-by: Rod Vagg --- handler.go | 29 +++++++---------------------- 1 file changed, 7 insertions(+), 22 deletions(-) diff --git a/handler.go b/handler.go index 09ef162..cbefad0 100644 --- a/handler.go +++ b/handler.go @@ -119,30 +119,15 @@ func (r response) MarshalJSON() ([]byte, error) { // > `error`: // > This member is REQUIRED on error. // > This member MUST NOT exist if there was no error triggered during invocation. + data := make(map[string]interface{}) + data["jsonrpc"] = r.Jsonrpc + data["id"] = r.ID if r.Error != nil { - // If there's an error, exclude result - type responseWithoutResult struct { - Jsonrpc string `json:"jsonrpc"` - ID interface{} `json:"id"` - Error *respError `json:"error"` - } - return json.Marshal(&responseWithoutResult{ - Jsonrpc: r.Jsonrpc, - ID: r.ID, - Error: r.Error, - }) - } - - type responseWithResult struct { - Jsonrpc string `json:"jsonrpc"` - Result interface{} `json:"result"` - ID interface{} `json:"id"` + data["error"] = r.Error + } else { + data["result"] = r.Result } - return json.Marshal(&responseWithResult{ - Jsonrpc: r.Jsonrpc, - Result: r.Result, - ID: r.ID, - }) + return json.Marshal(data) } type handler struct {