diff --git a/base.go b/base.go index 70296a6..82bf9cb 100644 --- a/base.go +++ b/base.go @@ -425,7 +425,7 @@ func isNull(msg json.RawMessage) bool { // filterError filters an *Error value to distinguish context errors from other // error types. If err is not a context error, it is returned unchanged. func filterError(e *Error) error { - switch e.code { + switch e.Code { case code.Cancelled: return context.Canceled case code.DeadlineExceeded: diff --git a/client.go b/client.go index 540babf..b917fd3 100644 --- a/client.go +++ b/client.go @@ -136,8 +136,8 @@ func (c *Client) deliver(rsp *jmessage) { p.ch <- &jmessage{ ID: rsp.ID, E: &Error{ - code: code.InvalidRequest, - message: fmt.Sprintf("incorrect version marker %q", rsp.V), + Code: code.InvalidRequest, + Message: fmt.Sprintf("incorrect version marker %q", rsp.V), }, } c.log("Invalid response for ID %q", id) @@ -253,9 +253,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: code.InternalError, Message: c.err.Error()} } else if err != nil { - jerr = &Error{code: code.FromError(err), message: err.Error()} + jerr = &Error{Code: code.FromError(err), Message: err.Error()} } p.ch <- &jmessage{ diff --git a/code/code.go b/code/code.go index 10f1c15..ece0f4a 100644 --- a/code/code.go +++ b/code/code.go @@ -24,27 +24,27 @@ func (c Code) String() string { return fmt.Sprintf("error code %d", c) } -// A Coder is a value that can report an error code value. -type Coder interface { - Code() Code +// An ErrCoder is a value that can report an error code value. +type ErrCoder interface { + ErrCode() Code } // A codeError wraps a Code to satisfy the standard error interface. This // indirection prevents a Code from accidentally being used as an error value. -// It also satisfies the Coder interface, allowing the code to be recovered. +// It also satisfies the ErrCoder interface, allowing the code to be recovered. type codeError Code // Error satisfies the error interface using the registered string for the // code, if one is defined, or else a placeholder that describes the value. func (c codeError) Error() string { return Code(c).String() } -// Code trivially satisfies the Coder interface. -func (c codeError) Code() Code { return Code(c) } +// ErrCode trivially satisfies the ErrCoder interface. +func (c codeError) ErrCode() Code { return Code(c) } // Is reports whether err is c or has a code equal to c. func (c codeError) Is(err error) bool { - v, ok := err.(Coder) // including codeError - return ok && v.Code() == Code(c) + v, ok := err.(ErrCoder) // including codeError + return ok && v.ErrCode() == Code(c) } // Err converts c to an error value, which is nil for code.NoError and @@ -102,7 +102,7 @@ func Register(value int32, message string) Code { // FromError returns a Code to categorize the specified error. // If err == nil, it returns code.NoError. -// If err is a Coder, it returns the reported code value. +// If err is 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. @@ -110,9 +110,9 @@ func FromError(err error) Code { if err == nil { return NoError } - var c Coder + var c ErrCoder if errors.As(err, &c) { - return c.Code() + return c.ErrCode() } else if errors.Is(err, context.Canceled) { return Cancelled } else if errors.Is(err, context.DeadlineExceeded) { diff --git a/code/code_test.go b/code/code_test.go index d36eb73..0ba74ee 100644 --- a/code/code_test.go +++ b/code/code_test.go @@ -31,8 +31,8 @@ func TestRegistrationError(t *testing.T) { type testCoder Code -func (t testCoder) Code() Code { return Code(t) } -func (testCoder) Error() string { return "bogus" } +func (t testCoder) ErrCode() Code { return Code(t) } +func (testCoder) Error() string { return "bogus" } func TestFromError(t *testing.T) { tests := []struct { diff --git a/error.go b/error.go index 56e2e9b..89f6077 100644 --- a/error.go +++ b/error.go @@ -10,35 +10,32 @@ import ( // Error is the concrete type of errors returned from RPC calls. type Error struct { - message string - code code.Code - data json.RawMessage + Message string // the human-readable error message + Code code.Code // the machine-readable error code + Data json.RawMessage // 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) } - -// Code returns the error code value associated with e. -func (e Error) Code() code.Code { return e.code } - -// Message returns the message string associated with e. -func (e Error) Message() string { return e.message } +func (e Error) Error() string { return fmt.Sprintf("[%d] %s", e.Code, e.Message) } // HasData reports whether e has error data to unmarshal. -func (e Error) HasData() bool { return len(e.data) != 0 } +func (e Error) HasData() bool { return len(e.Data) != 0 } + +// ErrCode trivially satisfies the code.ErrCoder interface for an *Error. +func (e *Error) ErrCode() code.Code { return e.Code } -// UnmarshalData decodes the error data associated with e into v. It returns +// UnmarshalData decodes the error data associated with e into v. It reports // ErrNoData without modifying v if there was no data message attached to e. func (e Error) UnmarshalData(v interface{}) error { if !e.HasData() { return ErrNoData } - return json.Unmarshal([]byte(e.data), v) + return json.Unmarshal(e.Data, v) } // MarshalJSON implements the json.Marshaler interface for Error values. func (e Error) MarshalJSON() ([]byte, error) { - return json.Marshal(jerror{C: int32(e.code), M: e.message, D: e.data}) + return json.Marshal(jerror{C: int32(e.Code), M: e.Message, D: e.Data}) } // UnmarshalJSON implements the json.Unmarshaler interface for Error values. @@ -47,9 +44,9 @@ func (e *Error) UnmarshalJSON(data []byte) error { if err := json.Unmarshal(data, &v); err != nil { return err } - e.code = code.Code(v.C) - e.message = v.M - e.data = v.D + e.Code = code.Code(v.C) + e.Message = v.M + e.Data = v.D return nil } @@ -79,10 +76,10 @@ func Errorf(code code.Code, msg string, args ...interface{}) error { // specified code, error data, and formatted message string. // If v == nil this behaves identically to Errorf(code, msg, args...). func DataErrorf(code code.Code, v interface{}, msg string, args ...interface{}) error { - e := &Error{code: code, message: fmt.Sprintf(msg, args...)} + e := &Error{Code: code, Message: fmt.Sprintf(msg, args...)} if v != nil { if data, err := json.Marshal(v); err == nil { - e.data = data + e.Data = data } } return e diff --git a/internal_test.go b/internal_test.go index 04c4fe0..7adc847 100644 --- a/internal_test.go +++ b/internal_test.go @@ -196,7 +196,7 @@ func TestClientCancellation(t *testing.T) { rsp := rsps[0] rsp.wait() if err := rsp.Error(); err != nil { - if err.code != code.Cancelled { + if err.Code != code.Cancelled { t.Errorf("Response error for %q: got %v, want %v", rsp.ID(), err, code.Cancelled) } } else { diff --git a/jrpc2_test.go b/jrpc2_test.go index 83dbdc5..ca49a46 100644 --- a/jrpc2_test.go +++ b/jrpc2_test.go @@ -19,6 +19,11 @@ import ( "github.com/google/go-cmp/cmp" ) +// Static type assertions. +var ( + _ code.ErrCoder = (*jrpc2.Error)(nil) +) + var notAuthorized = code.Register(-32095, "request not authorized") var testOK = handler.New(func(ctx context.Context) (string, error) { @@ -284,8 +289,8 @@ func TestErrorOnly(t *testing.T) { t.Errorf("ErrorOnly: got %+v, want error", rsp) } else if e, ok := err.(*jrpc2.Error); !ok { t.Errorf("ErrorOnly: got %v, want *Error", err) - } else if e.Code() != 1 || e.Message() != errMessage { - t.Errorf("ErrorOnly: got (%s, %s), want (1, %s)", e.Code(), e.Message(), errMessage) + } else if e.Code != 1 || e.Message != errMessage { + t.Errorf("ErrorOnly: got (%s, %s), want (1, %s)", e.Code, e.Message, errMessage) } else { var data json.RawMessage if err, want := e.UnmarshalData(&data), jrpc2.ErrNoData; err != want { @@ -456,11 +461,11 @@ func TestErrors(t *testing.T) { if got, err := c.Call(context.Background(), "Err", nil); err == nil { t.Errorf("Call(Push): got %#v, wanted error", got) } else if e, ok := err.(*jrpc2.Error); ok { - if e.Code() != errCode { - t.Errorf("Error code: got %d, want %d", e.Code(), errCode) + if e.Code != errCode { + t.Errorf("Error code: got %d, want %d", e.Code, errCode) } - if e.Message() != errMessage { - t.Errorf("Error message: got %q, want %q", e.Message(), errMessage) + if e.Message != errMessage { + t.Errorf("Error message: got %q, want %q", e.Message, errMessage) } var data json.RawMessage if err := e.UnmarshalData(&data); err != nil { diff --git a/opts.go b/opts.go index 4b474fd..caaa463 100644 --- a/opts.go +++ b/opts.go @@ -242,7 +242,7 @@ func (c *ClientOptions) handleCallback() func(*jmessage) []byte { if e, ok := err.(*Error); ok { rsp.E = e } else { - rsp.E = &Error{code: code.FromError(err), message: err.Error()} + rsp.E = &Error{Code: code.FromError(err), Message: err.Error()} } } bits, _ := json.Marshal(rsp) diff --git a/server.go b/server.go index e1add7e..951c2d2 100644 --- a/server.go +++ b/server.go @@ -543,7 +543,7 @@ func (s *Server) stop(err error) { for id, rsp := range s.call { rsp.ch <- &jmessage{ ID: json.RawMessage(id), - E: &Error{message: "client channel terminated", code: code.Cancelled}, + E: &Error{Message: "client channel terminated", Code: code.Cancelled}, } delete(s.call, id) } @@ -638,7 +638,7 @@ func (s *Server) pushError(err error) { if e, ok := err.(*Error); ok { jerr = e } else { - jerr = &Error{code: code.FromError(err), message: err.Error()} + jerr = &Error{Code: code.FromError(err), Message: err.Error()} } nw, err := encode(s.ch, jmessages{{ @@ -711,9 +711,9 @@ func (ts tasks) responses(rpcLog RPCLogger) jmessages { } else if e, ok := task.err.(*Error); ok { rsp.E = e } else if c := code.FromError(task.err); c != code.NoError { - rsp.E = &Error{code: c, message: task.err.Error()} + rsp.E = &Error{Code: c, Message: task.err.Error()} } else { - rsp.E = &Error{code: code.InternalError, message: task.err.Error()} + rsp.E = &Error{Code: code.InternalError, Message: task.err.Error()} } rpcLog.LogResponse(task.ctx, &Response{ id: string(rsp.ID),