From 6864d8cc6d35a79f50745f8990cb4d594a8036f4 Mon Sep 17 00:00:00 2001 From: Sam Herrmann Date: Sun, 12 Feb 2023 07:52:17 -0500 Subject: [PATCH] Omit data field from error when empty (#66) The JSON-RPC 2.0 specification allows the data member of an error object to be omitted. Before this commit, if Error.Data was nil then the JSON encoding was "null". That means that the data member was included in every JSON-RPC error object, even when the data member was not explicitly set. This commit adds the omitempty struct tag to the Error.Data field, meaning that the data member is omitted from the JSON encoding unless explicitly set with the Error.SetError() method. Beware that this is a breaking change for clients that may have strict null checks on the data member. --- jsonrpc2.go | 4 ++-- jsonrpc2_test.go | 59 ++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 61 insertions(+), 2 deletions(-) diff --git a/jsonrpc2.go b/jsonrpc2.go index 17e1a59..46273bc 100644 --- a/jsonrpc2.go +++ b/jsonrpc2.go @@ -29,10 +29,10 @@ type JSONRPC2 interface { type Error struct { Code int64 `json:"code"` Message string `json:"message"` - Data *json.RawMessage `json:"data"` + Data *json.RawMessage `json:"data,omitempty"` } -// SetError sets e.Error to the JSON representation of v. If JSON +// SetError sets e.Data to the JSON representation of v. If JSON // marshaling fails, it panics. func (e *Error) SetError(v interface{}) { b, err := json.Marshal(v) diff --git a/jsonrpc2_test.go b/jsonrpc2_test.go index 1a73744..dcc2679 100644 --- a/jsonrpc2_test.go +++ b/jsonrpc2_test.go @@ -18,6 +18,65 @@ import ( websocketjsonrpc2 "github.com/sourcegraph/jsonrpc2/websocket" ) +func TestError_MarshalJSON(t *testing.T) { + tests := []struct { + name string + setError func(err *jsonrpc2.Error) + want string + }{ + { + name: "Data == nil", + want: `{"code":-32603,"message":"Internal error"}`, + }, + { + name: "Error.SetError(nil)", + setError: func(err *jsonrpc2.Error) { + err.SetError(nil) + }, + want: `{"code":-32603,"message":"Internal error","data":null}`, + }, + { + name: "Error.SetError(0)", + setError: func(err *jsonrpc2.Error) { + err.SetError(0) + }, + want: `{"code":-32603,"message":"Internal error","data":0}`, + }, + { + name: `Error.SetError("")`, + setError: func(err *jsonrpc2.Error) { + err.SetError("") + }, + want: `{"code":-32603,"message":"Internal error","data":""}`, + }, + { + name: `Error.SetError(false)`, + setError: func(err *jsonrpc2.Error) { + err.SetError(false) + }, + want: `{"code":-32603,"message":"Internal error","data":false}`, + }, + } + + for _, test := range tests { + e := &jsonrpc2.Error{ + Code: jsonrpc2.CodeInternalError, + Message: "Internal error", + } + if test.setError != nil { + test.setError(e) + } + b, err := json.Marshal(e) + if err != nil { + t.Error(err) + } + got := string(b) + if got != test.want { + t.Fatalf("%s: got %q, want %q", test.name, got, test.want) + } + } +} + // testHandlerA is the "server" handler. type testHandlerA struct{ t *testing.T }