Skip to content

Commit feff11c

Browse files
rossmcfpeterbourgon
authored andcommitted
Handle JSON RPC errors. Resolves issue #672. (#673)
* Handle JSON RPC errors. Resolves issue #672. * Refactor decode func to receive JSON RPC response. Remove error func. * Comment tweaks.
1 parent 52bb1a8 commit feff11c

4 files changed

Lines changed: 77 additions & 22 deletions

File tree

examples/addsvc/pkg/addtransport/jsonrpc.go

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -137,9 +137,12 @@ func encodeSumResponse(_ context.Context, obj interface{}) (json.RawMessage, err
137137
return b, nil
138138
}
139139

140-
func decodeSumResponse(_ context.Context, msg json.RawMessage) (interface{}, error) {
141-
var res addendpoint.SumResponse
142-
err := json.Unmarshal(msg, &res)
140+
func decodeSumResponse(_ context.Context, res jsonrpc.Response) (interface{}, error) {
141+
if res.Error != nil {
142+
return nil, *res.Error
143+
}
144+
var sumres addendpoint.SumResponse
145+
err := json.Unmarshal(res.Result, &sumres)
143146
if err != nil {
144147
return nil, fmt.Errorf("couldn't unmarshal body to SumResponse: %s", err)
145148
}
@@ -185,9 +188,12 @@ func encodeConcatResponse(_ context.Context, obj interface{}) (json.RawMessage,
185188
return b, nil
186189
}
187190

188-
func decodeConcatResponse(_ context.Context, msg json.RawMessage) (interface{}, error) {
189-
var res addendpoint.ConcatResponse
190-
err := json.Unmarshal(msg, &res)
191+
func decodeConcatResponse(_ context.Context, res jsonrpc.Response) (interface{}, error) {
192+
if res.Error != nil {
193+
return nil, *res.Error
194+
}
195+
var concatres addendpoint.ConcatResponse
196+
err := json.Unmarshal(res.Result, &concatres)
191197
if err != nil {
192198
return nil, fmt.Errorf("couldn't unmarshal body to ConcatResponse: %s", err)
193199
}

transport/http/jsonrpc/client.go

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -67,10 +67,14 @@ func DefaultRequestEncoder(_ context.Context, req interface{}) (json.RawMessage,
6767
return json.Marshal(req)
6868
}
6969

70-
// DefaultResponseDecoder unmarshals the given JSON to interface{}.
71-
func DefaultResponseDecoder(_ context.Context, res json.RawMessage) (interface{}, error) {
70+
// DefaultResponseDecoder unmarshals the result to interface{}, or returns an
71+
// error, if found.
72+
func DefaultResponseDecoder(_ context.Context, res Response) (interface{}, error) {
73+
if res.Error != nil {
74+
return nil, *res.Error
75+
}
7276
var result interface{}
73-
err := json.Unmarshal(res, &result)
77+
err := json.Unmarshal(res.Result, &result)
7478
if err != nil {
7579
return nil, err
7680
}
@@ -203,7 +207,7 @@ func (c Client) Endpoint() endpoint.Endpoint {
203207
ctx = f(ctx, resp)
204208
}
205209

206-
return c.dec(ctx, rpcRes.Result)
210+
return c.dec(ctx, rpcRes)
207211
}
208212
}
209213

transport/http/jsonrpc/client_test.go

Lines changed: 47 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -62,12 +62,12 @@ func TestClientHappyPath(t *testing.T) {
6262
fin = func(ctx context.Context, err error) {
6363
finalizerCalled = true
6464
}
65-
decode = func(ctx context.Context, res json.RawMessage) (interface{}, error) {
65+
decode = func(ctx context.Context, res jsonrpc.Response) (interface{}, error) {
6666
if ac := ctx.Value(afterCalledKey); ac == nil {
6767
t.Fatal("after not called")
6868
}
6969
var result int
70-
err := json.Unmarshal(res, &result)
70+
err := json.Unmarshal(res.Result, &result)
7171
if err != nil {
7272
return nil, err
7373
}
@@ -206,6 +206,51 @@ func TestCanUseDefaults(t *testing.T) {
206206
}
207207
}
208208

209+
func TestClientCanHandleJSONRPCError(t *testing.T) {
210+
var testbody = `{
211+
"jsonrpc": "2.0",
212+
"error": {
213+
"code": -32603,
214+
"message": "Bad thing happened."
215+
}
216+
}`
217+
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
218+
w.WriteHeader(http.StatusOK)
219+
w.Write([]byte(testbody))
220+
}))
221+
222+
sut := jsonrpc.NewClient(mustParse(server.URL), "add")
223+
224+
_, err := sut.Endpoint()(context.Background(), 5)
225+
if err == nil {
226+
t.Fatal("Expected error, got none.")
227+
}
228+
229+
{
230+
want := "Bad thing happened."
231+
got := err.Error()
232+
if got != want {
233+
t.Fatalf("error message: want=%s, got=%s", want, got)
234+
}
235+
}
236+
237+
type errorCoder interface {
238+
ErrorCode() int
239+
}
240+
ec, ok := err.(errorCoder)
241+
if !ok {
242+
t.Fatal("Error is not errorCoder")
243+
}
244+
245+
{
246+
want := -32603
247+
got := ec.ErrorCode()
248+
if got != want {
249+
t.Fatalf("error code: want=%d, got=%d", want, got)
250+
}
251+
}
252+
}
253+
209254
func TestDefaultAutoIncrementer(t *testing.T) {
210255
sut := jsonrpc.NewAutoIncrementID(0)
211256
var want uint64

transport/http/jsonrpc/encode_decode.go

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -20,29 +20,29 @@ type EndpointCodec struct {
2020
// EndpointCodecMap maps the Request.Method to the proper EndpointCodec
2121
type EndpointCodecMap map[string]EndpointCodec
2222

23-
// DecodeRequestFunc extracts a user-domain request object from an raw JSON
24-
// It's designed to be used in HTTP servers, for server-side endpoints.
23+
// DecodeRequestFunc extracts a user-domain request object from raw JSON
24+
// It's designed to be used in JSON RPC servers, for server-side endpoints.
2525
// One straightforward DecodeRequestFunc could be something that unmarshals
2626
// JSON from the request body to the concrete request type.
2727
type DecodeRequestFunc func(context.Context, json.RawMessage) (request interface{}, err error)
2828

29-
// EncodeResponseFunc encodes the passed response object to a JSON RPC response.
29+
// EncodeResponseFunc encodes the passed response object to a JSON RPC result.
3030
// It's designed to be used in HTTP servers, for server-side endpoints.
3131
// One straightforward EncodeResponseFunc could be something that JSON encodes
3232
// the object directly.
3333
type EncodeResponseFunc func(context.Context, interface{}) (response json.RawMessage, err error)
3434

3535
// Client-Side Codec
3636

37-
// EncodeRequestFunc encodes the passed request object to raw JSON.
37+
// EncodeRequestFunc encodes the given request object to raw JSON.
3838
// It's designed to be used in JSON RPC clients, for client-side
3939
// endpoints. One straightforward EncodeResponseFunc could be something that
4040
// JSON encodes the object directly.
4141
type EncodeRequestFunc func(context.Context, interface{}) (request json.RawMessage, err error)
4242

43-
// DecodeResponseFunc extracts a user-domain response object from an HTTP
44-
// request object. It's designed to be used in JSON RPC clients, for
45-
// client-side endpoints. One straightforward DecodeRequestFunc could be
46-
// something that JSON decodes from the request body to the concrete
47-
// response type.
48-
type DecodeResponseFunc func(context.Context, json.RawMessage) (response interface{}, err error)
43+
// DecodeResponseFunc extracts a user-domain response object from an JSON RPC
44+
// response object. It's designed to be used in JSON RPC clients, for
45+
// client-side endpoints. It is the responsibility of this function to decide
46+
// whether any error present in the JSON RPC response should be surfaced to the
47+
// client endpoint.
48+
type DecodeResponseFunc func(context.Context, Response) (response interface{}, err error)

0 commit comments

Comments
 (0)