Skip to content

Commit

Permalink
Handle JSON RPC errors. Resolves issue #672. (#673)
Browse files Browse the repository at this point in the history
* Handle JSON RPC errors. Resolves issue #672.

* Refactor decode func to receive JSON RPC response. Remove error func.

* Comment tweaks.
  • Loading branch information
rossmcf authored and peterbourgon committed Mar 13, 2018
1 parent 52bb1a8 commit feff11c
Show file tree
Hide file tree
Showing 4 changed files with 77 additions and 22 deletions.
18 changes: 12 additions & 6 deletions examples/addsvc/pkg/addtransport/jsonrpc.go
Original file line number Diff line number Diff line change
Expand Up @@ -137,9 +137,12 @@ func encodeSumResponse(_ context.Context, obj interface{}) (json.RawMessage, err
return b, nil
}

func decodeSumResponse(_ context.Context, msg json.RawMessage) (interface{}, error) {
var res addendpoint.SumResponse
err := json.Unmarshal(msg, &res)
func decodeSumResponse(_ context.Context, res jsonrpc.Response) (interface{}, error) {
if res.Error != nil {
return nil, *res.Error
}
var sumres addendpoint.SumResponse
err := json.Unmarshal(res.Result, &sumres)
if err != nil {
return nil, fmt.Errorf("couldn't unmarshal body to SumResponse: %s", err)
}
Expand Down Expand Up @@ -185,9 +188,12 @@ func encodeConcatResponse(_ context.Context, obj interface{}) (json.RawMessage,
return b, nil
}

func decodeConcatResponse(_ context.Context, msg json.RawMessage) (interface{}, error) {
var res addendpoint.ConcatResponse
err := json.Unmarshal(msg, &res)
func decodeConcatResponse(_ context.Context, res jsonrpc.Response) (interface{}, error) {
if res.Error != nil {
return nil, *res.Error
}
var concatres addendpoint.ConcatResponse
err := json.Unmarshal(res.Result, &concatres)
if err != nil {
return nil, fmt.Errorf("couldn't unmarshal body to ConcatResponse: %s", err)
}
Expand Down
12 changes: 8 additions & 4 deletions transport/http/jsonrpc/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -67,10 +67,14 @@ func DefaultRequestEncoder(_ context.Context, req interface{}) (json.RawMessage,
return json.Marshal(req)
}

// DefaultResponseDecoder unmarshals the given JSON to interface{}.
func DefaultResponseDecoder(_ context.Context, res json.RawMessage) (interface{}, error) {
// DefaultResponseDecoder unmarshals the result to interface{}, or returns an
// error, if found.
func DefaultResponseDecoder(_ context.Context, res Response) (interface{}, error) {
if res.Error != nil {
return nil, *res.Error
}
var result interface{}
err := json.Unmarshal(res, &result)
err := json.Unmarshal(res.Result, &result)
if err != nil {
return nil, err
}
Expand Down Expand Up @@ -203,7 +207,7 @@ func (c Client) Endpoint() endpoint.Endpoint {
ctx = f(ctx, resp)
}

return c.dec(ctx, rpcRes.Result)
return c.dec(ctx, rpcRes)
}
}

Expand Down
49 changes: 47 additions & 2 deletions transport/http/jsonrpc/client_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -62,12 +62,12 @@ func TestClientHappyPath(t *testing.T) {
fin = func(ctx context.Context, err error) {
finalizerCalled = true
}
decode = func(ctx context.Context, res json.RawMessage) (interface{}, error) {
decode = func(ctx context.Context, res jsonrpc.Response) (interface{}, error) {
if ac := ctx.Value(afterCalledKey); ac == nil {
t.Fatal("after not called")
}
var result int
err := json.Unmarshal(res, &result)
err := json.Unmarshal(res.Result, &result)
if err != nil {
return nil, err
}
Expand Down Expand Up @@ -206,6 +206,51 @@ func TestCanUseDefaults(t *testing.T) {
}
}

func TestClientCanHandleJSONRPCError(t *testing.T) {
var testbody = `{
"jsonrpc": "2.0",
"error": {
"code": -32603,
"message": "Bad thing happened."
}
}`
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
w.Write([]byte(testbody))
}))

sut := jsonrpc.NewClient(mustParse(server.URL), "add")

_, err := sut.Endpoint()(context.Background(), 5)
if err == nil {
t.Fatal("Expected error, got none.")
}

{
want := "Bad thing happened."
got := err.Error()
if got != want {
t.Fatalf("error message: want=%s, got=%s", want, got)
}
}

type errorCoder interface {
ErrorCode() int
}
ec, ok := err.(errorCoder)
if !ok {
t.Fatal("Error is not errorCoder")
}

{
want := -32603
got := ec.ErrorCode()
if got != want {
t.Fatalf("error code: want=%d, got=%d", want, got)
}
}
}

func TestDefaultAutoIncrementer(t *testing.T) {
sut := jsonrpc.NewAutoIncrementID(0)
var want uint64
Expand Down
20 changes: 10 additions & 10 deletions transport/http/jsonrpc/encode_decode.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,29 +20,29 @@ type EndpointCodec struct {
// EndpointCodecMap maps the Request.Method to the proper EndpointCodec
type EndpointCodecMap map[string]EndpointCodec

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

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

// Client-Side Codec

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

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

0 comments on commit feff11c

Please sign in to comment.