Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Handle JSON RPC errors. Resolves issue #672. #673

Merged
merged 3 commits into from
Mar 13, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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)