Skip to content

Commit

Permalink
Collapse the code package into jrpc2.
Browse files Browse the repository at this point in the history
After removing the registration hooks in #91, what is left of the code package
is too small to deserve its own identity. Collapse the package into the root
jrpc2 package.

- Rename the code.FromError helper to jrpc2.ErrorCode.
- Update documentation.

Updates #46.
  • Loading branch information
creachadair committed Mar 19, 2023
1 parent 2670c9f commit 080da1b
Show file tree
Hide file tree
Showing 15 changed files with 120 additions and 138 deletions.
4 changes: 1 addition & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,10 @@ There is also a working [example in the Go playground](https://go.dev/play/p/Gdd

## Packages

* Package [jrpc2](http://godoc.org/github.com/creachadair/jrpc2) implements the base client and server.
* Package [jrpc2](http://godoc.org/github.com/creachadair/jrpc2) implements the base client and server and standard error codes.

* Package [channel](http://godoc.org/github.com/creachadair/jrpc2/channel) defines the communication channel abstraction used by the server & client.

* Package [code](http://godoc.org/github.com/creachadair/jrpc2/code) defines standard error codes as defined by the JSON-RPC 2.0 protocol.

* Package [handler](http://godoc.org/github.com/creachadair/jrpc2/handler) defines support for adapting functions to service methods.

* Package [jhttp](http://godoc.org/github.com/creachadair/jrpc2/jhttp) allows clients and servers to use HTTP as a transport.
Expand Down
6 changes: 2 additions & 4 deletions base.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,6 @@ import (
"encoding/json"
"fmt"
"strings"

"github.com/creachadair/jrpc2/code"
)

// An Assigner assigns a Handler to handle the specified method name, or nil if
Expand Down Expand Up @@ -236,9 +234,9 @@ func isServiceName(s string) bool {
// error types. If err is not a context error, it is returned unchanged.
func filterError(e *Error) error {
switch e.Code {
case code.Cancelled:
case Cancelled:
return context.Canceled
case code.DeadlineExceeded:
case DeadlineExceeded:
return context.DeadlineExceeded
}
return e
Expand Down
9 changes: 4 additions & 5 deletions client.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ import (
"sync"

"github.com/creachadair/jrpc2/channel"
"github.com/creachadair/jrpc2/code"
)

// A Client is a JSON-RPC 2.0 client. The client sends requests and receives
Expand Down Expand Up @@ -210,7 +209,7 @@ func (c *Client) send(ctx context.Context, reqs jmessages) ([]*Response, error)
// on a closing path.
b, err := reqs.toJSON()
if err != nil {
return nil, Errorf(code.InternalError, "marshaling request failed: %v", err)
return nil, Errorf(InternalError, "marshaling request failed: %v", err)
}

var pends []*Response
Expand Down Expand Up @@ -266,9 +265,9 @@ func (c *Client) waitComplete(pctx context.Context, id string, p *Response) {

var jerr *Error
if c.err != nil && !isUninteresting(c.err) {
jerr = &Error{Code: code.InternalError, Message: c.err.Error()}
jerr = &Error{Code: InternalError, Message: c.err.Error()}
} else if err != nil {
jerr = &Error{Code: code.FromError(err), Message: err.Error()}
jerr = &Error{Code: ErrorCode(err), Message: err.Error()}
}

p.ch <- &jmessage{
Expand Down Expand Up @@ -427,7 +426,7 @@ func (c *Client) marshalParams(ctx context.Context, method string, params any) (
if fb := firstByte(pbits); fb != '[' && fb != '{' && !isNull(pbits) {
// JSON-RPC requires that if parameters are provided at all, they are
// an array or an object.
return nil, &Error{Code: code.InvalidRequest, Message: "invalid parameters: array or object required"}
return nil, &Error{Code: InvalidRequest, Message: "invalid parameters: array or object required"}
}
return pbits, nil
}
Expand Down
24 changes: 12 additions & 12 deletions code/code.go → code.go
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
// Copyright (C) 2017 Michael J. Fromberger. All Rights Reserved.

// Package code defines error code values used by the jrpc2 package.
package code
package jrpc2

import (
"context"
"errors"
"fmt"
)

// A Code is an error response code.
// A Code is an error response.
//
// Code values from and including -32768 to -32000 are reserved for pre-defined
// JSON-RPC errors. Any code within this range, but not defined explicitly
Expand Down Expand Up @@ -49,9 +49,9 @@ func (c codeError) Is(err error) bool {
return ok && v.ErrCode() == Code(c)
}

// Err converts c to an error value, which is nil for code.NoError and
// otherwise an error value whose code is c and whose text is based on the
// built-in string for c if one exists.
// Err converts c to an error value, which is nil for NoError and otherwise an
// error value whose code is c and whose text is based on the built-in string
// for c if one exists.
func (c Code) Err() error {
if c == NoError {
return nil
Expand All @@ -69,7 +69,7 @@ const (
InvalidParams Code = -32602 // [std] Invalid method parameters
InternalError Code = -32603 // [std] Internal JSON-RPC error

NoError Code = -32099 // Denotes a nil error (used by FromError)
NoError Code = -32099 // Denotes a nil error (used by ErrorCode)
SystemError Code = -32098 // Errors from the operating environment
Cancelled Code = -32097 // Request cancelled (context.Canceled)
DeadlineExceeded Code = -32096 // Request deadline exceeded (context.DeadlineExceeded)
Expand All @@ -88,13 +88,13 @@ var stdError = map[Code]string{
DeadlineExceeded: "deadline exceeded",
}

// FromError returns a Code to categorize the specified error.
// If err == nil, it returns code.NoError.
// ErrorCode returns a Code to categorize the specified error.
// If err == nil, it returns jrpc2.NoError.
// If err is (or wraps) an ErrCoder, it returns the reported code value.
// If err is context.Canceled, it returns code.Cancelled.
// If err is context.DeadlineExceeded, it returns code.DeadlineExceeded.
// Otherwise it returns code.SystemError.
func FromError(err error) Code {
// If err is context.Canceled, it returns jrpc2.Cancelled.
// If err is context.DeadlineExceeded, it returns jrpc2.DeadlineExceeded.
// Otherwise it returns jrpc2.SystemError.
func ErrorCode(err error) Code {
if err == nil {
return NoError
}
Expand Down
58 changes: 29 additions & 29 deletions code/code_test.go → code_test.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
// Copyright (C) 2017 Michael J. Fromberger. All Rights Reserved.

package code_test
package jrpc2_test

import (
"context"
Expand All @@ -9,49 +9,49 @@ import (
"io"
"testing"

"github.com/creachadair/jrpc2/code"
"github.com/creachadair/jrpc2"
)

type testCoder code.Code
type testCoder jrpc2.Code

func (t testCoder) ErrCode() code.Code { return code.Code(t) }
func (testCoder) Error() string { return "bogus" }
func (t testCoder) ErrCode() jrpc2.Code { return jrpc2.Code(t) }
func (testCoder) Error() string { return "bogus" }

func TestFromError(t *testing.T) {
func TestErrorCode(t *testing.T) {
tests := []struct {
input error
want code.Code
want jrpc2.Code
}{
{nil, code.NoError},
{testCoder(code.ParseError), code.ParseError},
{testCoder(code.InvalidRequest), code.InvalidRequest},
{fmt.Errorf("wrapped parse error: %w", code.ParseError.Err()), code.ParseError},
{context.Canceled, code.Cancelled},
{fmt.Errorf("wrapped cancellation: %w", context.Canceled), code.Cancelled},
{context.DeadlineExceeded, code.DeadlineExceeded},
{fmt.Errorf("wrapped deadline: %w", context.DeadlineExceeded), code.DeadlineExceeded},
{errors.New("other"), code.SystemError},
{io.EOF, code.SystemError},
{nil, jrpc2.NoError},
{testCoder(jrpc2.ParseError), jrpc2.ParseError},
{testCoder(jrpc2.InvalidRequest), jrpc2.InvalidRequest},
{fmt.Errorf("wrapped parse error: %w", jrpc2.ParseError.Err()), jrpc2.ParseError},
{context.Canceled, jrpc2.Cancelled},
{fmt.Errorf("wrapped cancellation: %w", context.Canceled), jrpc2.Cancelled},
{context.DeadlineExceeded, jrpc2.DeadlineExceeded},
{fmt.Errorf("wrapped deadline: %w", context.DeadlineExceeded), jrpc2.DeadlineExceeded},
{errors.New("other"), jrpc2.SystemError},
{io.EOF, jrpc2.SystemError},
}
for _, test := range tests {
if got := code.FromError(test.input); got != test.want {
t.Errorf("FromError(%v): got %v, want %v", test.input, got, test.want)
if got := jrpc2.ErrorCode(test.input); got != test.want {
t.Errorf("ErrorCode(%v): got %v, want %v", test.input, got, test.want)
}
}
}

func TestCodeIs(t *testing.T) {
tests := []struct {
code code.Code
code jrpc2.Code
err error
want bool
}{
{code.NoError, nil, true},
{jrpc2.NoError, nil, true},
{0, nil, false},
{1, code.Code(1).Err(), true},
{2, code.Code(3).Err(), false},
{4, fmt.Errorf("blah: %w", code.Code(4).Err()), true},
{5, fmt.Errorf("nope: %w", code.Code(6).Err()), false},
{1, jrpc2.Code(1).Err(), true},
{2, jrpc2.Code(3).Err(), false},
{4, fmt.Errorf("blah: %w", jrpc2.Code(4).Err()), true},
{5, fmt.Errorf("nope: %w", jrpc2.Code(6).Err()), false},
}
for _, test := range tests {
cerr := test.code.Err()
Expand All @@ -67,11 +67,11 @@ func TestErr(t *testing.T) {
return e1 == e2 || (e1 != nil && e2 != nil && e1.Error() == e2.Error())
}
type test struct {
code code.Code
code jrpc2.Code
want error
}
tests := []test{
{code.NoError, nil},
{jrpc2.NoError, nil},
{0, errors.New("error code 0")},
{1, errors.New("error code 1")},
{-17, errors.New("error code -17")},
Expand All @@ -84,7 +84,7 @@ func TestErr(t *testing.T) {
// Codes reserved by this implementation.
-32098, -32097, -32096,
} {
c := code.Code(v)
c := jrpc2.Code(v)
tests = append(tests, test{
code: c,
want: errors.New(c.String()),
Expand All @@ -95,7 +95,7 @@ func TestErr(t *testing.T) {
if !eqv(got, test.want) {
t.Errorf("Code(%d).Err(): got %#v, want %#v", test.code, got, test.want)
}
if c := code.FromError(got); c != test.code {
if c := jrpc2.ErrorCode(got); c != test.code {
t.Errorf("Code(%d).Err(): got code %v, want %v", test.code, c, test.code)
}
}
Expand Down
22 changes: 10 additions & 12 deletions error.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,22 +6,20 @@ import (
"encoding/json"
"errors"
"fmt"

"github.com/creachadair/jrpc2/code"
)

// Error is the concrete type of errors returned from RPC calls.
type Error struct {
Code code.Code `json:"code"` // the machine-readable error code
Code Code `json:"code"` // the machine-readable error code
Message string `json:"message,omitempty"` // the human-readable error message
Data json.RawMessage `json:"data,omitempty"` // optional ancillary error data
}

// Error renders e to a human-readable string for the error interface.
func (e Error) Error() string { return fmt.Sprintf("[%d] %s", e.Code, e.Message) }

// ErrCode trivially satisfies the code.ErrCoder interface for an *Error.
func (e Error) ErrCode() code.Code { return e.Code }
// ErrCode trivially satisfies the ErrCoder interface for an *Error.
func (e Error) ErrCode() Code { return e.Code }

// WithData marshals v as JSON and constructs a copy of e whose Data field
// includes the result. If v == nil or if marshaling v fails, e is returned
Expand All @@ -44,22 +42,22 @@ var errServerStopped = errors.New("the server has been stopped")
var errClientStopped = errors.New("the client has been stopped")

// errEmptyMethod is the error reported for an empty request method name.
var errEmptyMethod = &Error{Code: code.InvalidRequest, Message: "empty method name"}
var errEmptyMethod = &Error{Code: InvalidRequest, Message: "empty method name"}

// errNoSuchMethod is the error reported for an unknown method name.
var errNoSuchMethod = &Error{Code: code.MethodNotFound, Message: code.MethodNotFound.String()}
var errNoSuchMethod = &Error{Code: MethodNotFound, Message: MethodNotFound.String()}

// errDuplicateID is the error reported for a duplicated request ID.
var errDuplicateID = &Error{Code: code.InvalidRequest, Message: "duplicate request ID"}
var errDuplicateID = &Error{Code: InvalidRequest, Message: "duplicate request ID"}

// errInvalidRequest is the error reported for an invalid request object or batch.
var errInvalidRequest = &Error{Code: code.ParseError, Message: "invalid request value"}
var errInvalidRequest = &Error{Code: ParseError, Message: "invalid request value"}

// errEmptyBatch is the error reported for an empty request batch.
var errEmptyBatch = &Error{Code: code.InvalidRequest, Message: "empty request batch"}
var errEmptyBatch = &Error{Code: InvalidRequest, Message: "empty request batch"}

// errInvalidParams is the error reported for invalid request parameters.
var errInvalidParams = &Error{Code: code.InvalidParams, Message: code.InvalidParams.String()}
var errInvalidParams = &Error{Code: InvalidParams, Message: InvalidParams.String()}

// errTaskNotExecuted is the internal sentinel error for an unassigned task.
var errTaskNotExecuted = new(Error)
Expand All @@ -70,6 +68,6 @@ var ErrConnClosed = errors.New("client connection is closed")

// Errorf returns an error value of concrete type *Error having the specified
// code and formatted message string.
func Errorf(code code.Code, msg string, args ...any) *Error {
func Errorf(code Code, msg string, args ...any) *Error {
return &Error{Code: code, Message: fmt.Sprintf(msg, args...)}
}
5 changes: 2 additions & 3 deletions examples_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ import (
"strings"

"github.com/creachadair/jrpc2"
"github.com/creachadair/jrpc2/code"
"github.com/creachadair/jrpc2/handler"
"github.com/creachadair/jrpc2/internal/testutil"
"github.com/creachadair/jrpc2/server"
Expand Down Expand Up @@ -119,14 +118,14 @@ func ExampleRequest_UnmarshalParams() {
//
// Solution 1: Use the jrpc2.StrictFields helper.
err = req.UnmarshalParams(jrpc2.StrictFields(&t))
if code.FromError(err) != code.InvalidParams {
if jrpc2.ErrorCode(err) != jrpc2.InvalidParams {
log.Fatalf("UnmarshalParams strict: %v", err)
}

// Solution 2: Implement a DisallowUnknownFields method.
var p strictParams
err = req.UnmarshalParams(&p)
if code.FromError(err) != code.InvalidParams {
if jrpc2.ErrorCode(err) != jrpc2.InvalidParams {
log.Fatalf("UnmarshalParams strict: %v", err)
}

Expand Down
9 changes: 4 additions & 5 deletions handler/handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@ import (
"strings"

"github.com/creachadair/jrpc2"
"github.com/creachadair/jrpc2/code"
)

// A Func is wrapper for a jrpc2.Handler function.
Expand Down Expand Up @@ -96,7 +95,7 @@ var (

strictType = reflect.TypeOf((*interface{ DisallowUnknownFields() })(nil)).Elem()

errNoParameters = &jrpc2.Error{Code: code.InvalidParams, Message: "no parameters accepted"}
errNoParameters = &jrpc2.Error{Code: jrpc2.InvalidParams, Message: "no parameters accepted"}
)

// FuncInfo captures type signature information from a valid handler function.
Expand Down Expand Up @@ -179,7 +178,7 @@ func (fi *FuncInfo) Wrap() Func {
newInput = func(ctx reflect.Value, req *jrpc2.Request) ([]reflect.Value, error) {
in := reflect.New(arg.Elem())
if err := req.UnmarshalParams(wrapArg(in)); err != nil {
return nil, jrpc2.Errorf(code.InvalidParams, "invalid parameters: %v", err)
return nil, jrpc2.Errorf(jrpc2.InvalidParams, "invalid parameters: %v", err)
}
return []reflect.Value{ctx, in}, nil
}
Expand All @@ -188,7 +187,7 @@ func (fi *FuncInfo) Wrap() Func {
newInput = func(ctx reflect.Value, req *jrpc2.Request) ([]reflect.Value, error) {
in := reflect.New(arg) // we still need a pointer to unmarshal
if err := req.UnmarshalParams(wrapArg(in)); err != nil {
return nil, jrpc2.Errorf(code.InvalidParams, "invalid parameters: %v", err)
return nil, jrpc2.Errorf(jrpc2.InvalidParams, "invalid parameters: %v", err)
}
// Indirect the pointer back off for the callee.
return []reflect.Value{ctx, in.Elem()}, nil
Expand Down Expand Up @@ -333,7 +332,7 @@ func (s *arrayStub) translate(data []byte) ([]byte, error) {
if err := json.Unmarshal(data, &arr); err != nil {
return nil, err
} else if len(arr) != len(s.posNames) {
return nil, jrpc2.Errorf(code.InvalidParams, "got %d parameters, want %d",
return nil, jrpc2.Errorf(jrpc2.InvalidParams, "got %d parameters, want %d",
len(arr), len(s.posNames))
}

Expand Down
3 changes: 1 addition & 2 deletions handler/handler_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ import (
"testing"

"github.com/creachadair/jrpc2"
"github.com/creachadair/jrpc2/code"
"github.com/creachadair/jrpc2/handler"
"github.com/creachadair/jrpc2/internal/testutil"
"github.com/google/go-cmp/cmp"
Expand Down Expand Up @@ -254,7 +253,7 @@ func TestFuncInfo_SetStrict(t *testing.T) {
"Z": 25
}}`)
rsp, err := fn(context.Background(), req)
if got := code.FromError(err); got != code.InvalidParams {
if got := jrpc2.ErrorCode(err); got != jrpc2.InvalidParams {
t.Errorf("Handler returned (%+v, %v), want InvalidParms", rsp, err)
}
}
Expand Down
Loading

0 comments on commit 080da1b

Please sign in to comment.