From ef7b6420a3a5cfec42de1230e47e781b6a762493 Mon Sep 17 00:00:00 2001 From: "M. J. Fromberger" Date: Sun, 19 Mar 2023 10:13:11 -0700 Subject: [PATCH] Collapse the code package into jrpc2. (#97) 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. --- README.md | 4 +-- base.go | 6 ++-- client.go | 9 +++-- code/code.go => code.go | 24 ++++++------- code/code_test.go => code_test.go | 58 +++++++++++++++---------------- error.go | 22 ++++++------ examples_test.go | 5 ++- handler/handler.go | 9 +++-- handler/handler_test.go | 3 +- internal_test.go | 33 +++++++++--------- jhttp/getter.go | 7 ++-- jrpc2_test.go | 41 +++++++++++----------- json.go | 22 ++++++------ opts.go | 4 +-- server.go | 11 +++--- 15 files changed, 120 insertions(+), 138 deletions(-) rename code/code.go => code.go (85%) rename code/code_test.go => code_test.go (54%) diff --git a/README.md b/README.md index 9e05651..cd6fe9c 100644 --- a/README.md +++ b/README.md @@ -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. diff --git a/base.go b/base.go index 6cb54f5..3667dce 100644 --- a/base.go +++ b/base.go @@ -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 @@ -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 diff --git a/client.go b/client.go index 804201d..8ff8a26 100644 --- a/client.go +++ b/client.go @@ -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 @@ -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 @@ -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{ @@ -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 } diff --git a/code/code.go b/code.go similarity index 85% rename from code/code.go rename to code.go index 4a2ad0c..955ba39 100644 --- a/code/code.go +++ b/code.go @@ -1,7 +1,7 @@ // 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" @@ -9,7 +9,7 @@ import ( "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 @@ -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 @@ -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) @@ -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 } diff --git a/code/code_test.go b/code_test.go similarity index 54% rename from code/code_test.go rename to code_test.go index 1b3c202..f35f8e7 100644 --- a/code/code_test.go +++ b/code_test.go @@ -1,6 +1,6 @@ // Copyright (C) 2017 Michael J. Fromberger. All Rights Reserved. -package code_test +package jrpc2_test import ( "context" @@ -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() @@ -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")}, @@ -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()), @@ -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) } } diff --git a/error.go b/error.go index 812122c..1eac43a 100644 --- a/error.go +++ b/error.go @@ -6,13 +6,11 @@ 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 } @@ -20,8 +18,8 @@ type Error struct { // 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 @@ -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) @@ -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...)} } diff --git a/examples_test.go b/examples_test.go index cd63232..af9be28 100644 --- a/examples_test.go +++ b/examples_test.go @@ -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" @@ -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) } diff --git a/handler/handler.go b/handler/handler.go index 1fdbf8a..e17540c 100644 --- a/handler/handler.go +++ b/handler/handler.go @@ -14,7 +14,6 @@ import ( "strings" "github.com/creachadair/jrpc2" - "github.com/creachadair/jrpc2/code" ) // A Func is wrapper for a jrpc2.Handler function. @@ -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. @@ -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 } @@ -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 @@ -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)) } diff --git a/handler/handler_test.go b/handler/handler_test.go index 3b37de1..971f96e 100644 --- a/handler/handler_test.go +++ b/handler/handler_test.go @@ -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" @@ -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) } } diff --git a/internal_test.go b/internal_test.go index a306182..ce31c85 100644 --- a/internal_test.go +++ b/internal_test.go @@ -13,13 +13,12 @@ import ( "time" "github.com/creachadair/jrpc2/channel" - "github.com/creachadair/jrpc2/code" "github.com/fortytw2/leaktest" "github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp/cmpopts" ) -var errInvalidVersion = &Error{Code: code.InvalidRequest, Message: "invalid version marker"} +var errInvalidVersion = &Error{Code: InvalidRequest, Message: "invalid version marker"} func TestParseRequests(t *testing.T) { tests := []struct { @@ -62,10 +61,10 @@ func TestParseRequests(t *testing.T) { }, nil}, // A broken request. - {`{`, nil, Errorf(code.ParseError, "invalid request value")}, + {`{`, nil, Errorf(ParseError, "invalid request value")}, // A broken batch. - {`["bad"{]`, nil, Errorf(code.ParseError, "invalid request value")}, + {`["bad"{]`, nil, Errorf(ParseError, "invalid request value")}, } for _, test := range tests { got, err := ParseRequests([]byte(test.input)) @@ -87,7 +86,7 @@ func errEQ(x, y error) bool { } else if y == nil { return false } - return code.FromError(x) == code.FromError(y) && x.Error() == y.Error() + return ErrorCode(x) == ErrorCode(y) && x.Error() == y.Error() } func TestRequest_UnmarshalParams(t *testing.T) { @@ -100,24 +99,24 @@ func TestRequest_UnmarshalParams(t *testing.T) { input string want any pstring string - code code.Code + code Code }{ // If parameters are set, the target should be updated. - {`{"jsonrpc":"2.0", "id":1, "method":"X", "params":[1,2]}`, []int{1, 2}, "[1,2]", code.NoError}, + {`{"jsonrpc":"2.0", "id":1, "method":"X", "params":[1,2]}`, []int{1, 2}, "[1,2]", NoError}, // If parameters are null, the target should not be modified. - {`{"jsonrpc":"2.0", "id":2, "method":"Y", "params":null}`, "", "", code.NoError}, + {`{"jsonrpc":"2.0", "id":2, "method":"Y", "params":null}`, "", "", NoError}, // If parameters are not set, the target should not be modified. - {`{"jsonrpc":"2.0", "id":2, "method":"Y"}`, 0, "", code.NoError}, + {`{"jsonrpc":"2.0", "id":2, "method":"Y"}`, 0, "", NoError}, // Unmarshaling should work into a struct as long as the fields match. - {`{"jsonrpc":"2.0", "id":3, "method":"Z", "params":{}}`, xy{}, "{}", code.NoError}, - {`{"jsonrpc":"2.0", "id":4, "method":"Z", "params":{"x":17}}`, xy{X: 17}, `{"x":17}`, code.NoError}, + {`{"jsonrpc":"2.0", "id":3, "method":"Z", "params":{}}`, xy{}, "{}", NoError}, + {`{"jsonrpc":"2.0", "id":4, "method":"Z", "params":{"x":17}}`, xy{X: 17}, `{"x":17}`, NoError}, {`{"jsonrpc":"2.0", "id":5, "method":"Z", "params":{"x":23, "y":true}}`, - xy{X: 23, Y: true}, `{"x":23, "y":true}`, code.NoError}, + xy{X: 23, Y: true}, `{"x":23, "y":true}`, NoError}, {`{"jsonrpc":"2.0", "id":6, "method":"Z", "params":{"x":23, "z":"wat"}}`, - xy{X: 23}, `{"x":23, "z":"wat"}`, code.NoError}, + xy{X: 23}, `{"x":23, "z":"wat"}`, NoError}, } for _, test := range tests { var reqs jmessages @@ -132,7 +131,7 @@ func TestRequest_UnmarshalParams(t *testing.T) { target := reflect.New(reflect.TypeOf(test.want)).Interface() { err := req.UnmarshalParams(target) - if got := code.FromError(err); got != test.code { + if got := ErrorCode(err); got != test.code { t.Errorf("UnmarshalParams error: got code %d, want %d [%v]", got, test.code, err) } if err != nil { @@ -207,8 +206,8 @@ func TestClient_contextCancellation(t *testing.T) { rsp.wait() close(stopped) if err := rsp.Error(); err != nil { - if err.Code != code.Cancelled { - t.Errorf("Response error for %q: got %v, want %v", rsp.ID(), err, code.Cancelled) + if err.Code != Cancelled { + t.Errorf("Response error for %q: got %v, want %v", rsp.ID(), err, Cancelled) } } else { t.Errorf("Response for %q: unexpectedly succeeded", rsp.ID()) @@ -310,7 +309,7 @@ func TestMarshalResponse(t *testing.T) { }{ {"", nil, "", `{"jsonrpc":"2.0"}`}, {"null", nil, "", `{"jsonrpc":"2.0","id":null}`}, - {"123", Errorf(code.ParseError, "failed"), "", + {"123", Errorf(ParseError, "failed"), "", `{"jsonrpc":"2.0","id":123,"error":{"code":-32700,"message":"failed"}}`}, {"456", nil, `{"ok":true,"values":[4,5,6]}`, `{"jsonrpc":"2.0","id":456,"result":{"ok":true,"values":[4,5,6]}}`}, diff --git a/jhttp/getter.go b/jhttp/getter.go index 154dfee..58d59c9 100644 --- a/jhttp/getter.go +++ b/jhttp/getter.go @@ -12,7 +12,6 @@ import ( "strings" "github.com/creachadair/jrpc2" - "github.com/creachadair/jrpc2/code" "github.com/creachadair/jrpc2/server" ) @@ -67,7 +66,7 @@ func (g Getter) ServeHTTP(w http.ResponseWriter, req *http.Request) { method, params, err := g.parseHTTPRequest(req) if err != nil { writeJSON(w, http.StatusBadRequest, &jrpc2.Error{ - Code: code.ParseError, + Code: jrpc2.ParseError, Message: err.Error(), }) return @@ -76,8 +75,8 @@ func (g Getter) ServeHTTP(w http.ResponseWriter, req *http.Request) { var result json.RawMessage if err := g.local.Client.CallResult(req.Context(), method, params, &result); err != nil { var status int - switch code.FromError(err) { - case code.MethodNotFound: + switch jrpc2.ErrorCode(err) { + case jrpc2.MethodNotFound: status = http.StatusNotFound default: status = http.StatusInternalServerError diff --git a/jrpc2_test.go b/jrpc2_test.go index 3c65355..6f4e574 100644 --- a/jrpc2_test.go +++ b/jrpc2_test.go @@ -16,7 +16,6 @@ import ( "github.com/creachadair/jrpc2" "github.com/creachadair/jrpc2/channel" - "github.com/creachadair/jrpc2/code" "github.com/creachadair/jrpc2/handler" "github.com/creachadair/jrpc2/server" "github.com/fortytw2/leaktest" @@ -25,7 +24,7 @@ import ( // Static type assertions. var ( - _ code.ErrCoder = (*jrpc2.Error)(nil) + _ jrpc2.ErrCoder = (*jrpc2.Error)(nil) ) var testOK = handler.New(func(ctx context.Context) (string, error) { @@ -71,7 +70,7 @@ func (dummy) Mul(_ context.Context, req struct{ X, Y int }) (int, error) { // Max takes a slice of arguments. func (dummy) Max(_ context.Context, vs []int) (int, error) { if len(vs) == 0 { - return 0, jrpc2.Errorf(code.InvalidParams, "cannot compute max of no elements") + return 0, jrpc2.Errorf(jrpc2.InvalidParams, "cannot compute max of no elements") } max := vs[0] for _, v := range vs[1:] { @@ -406,8 +405,8 @@ func TestServer_stopCancelsHandlers(t *testing.T) { case <-time.After(30 * time.Second): t.Error("Timed out waiting for service handler to fail") case err := <-stopped: - if ec := code.FromError(err); ec != code.Cancelled { - t.Errorf("Client error: got %v (%v), wanted code %v", err, ec, code.Cancelled) + if ec := jrpc2.ErrorCode(err); ec != jrpc2.Cancelled { + t.Errorf("Client error: got %v (%v), wanted code %v", err, ec, jrpc2.Cancelled) } } } @@ -456,9 +455,9 @@ func TestServer_CancelRequest(t *testing.T) { } err := <-errc - got := code.FromError(err) - if got != code.Cancelled { - t.Errorf("Stall: got %v (%v), want %v", err, got, code.Cancelled) + got := jrpc2.ErrorCode(err) + if got != jrpc2.Cancelled { + t.Errorf("Stall: got %v (%v), want %v", err, got, jrpc2.Cancelled) } else { t.Logf("Cancellation succeeded, got expected error: %v", err) } @@ -480,7 +479,7 @@ func TestError_withData(t *testing.T) { return false, jrpc2.ServerFromContext(ctx).Notify(ctx, "PushBack", nil) }), "Code": handler.New(func(ctx context.Context) error { - return code.Code(12345).Err() + return jrpc2.Code(12345).Err() }), }, &server.LocalOptions{ Client: &jrpc2.ClientOptions{ @@ -533,7 +532,7 @@ func TestClient_badCallParams(t *testing.T) { rsp, err := loc.Client.Call(context.Background(), "Test", "bogus") if err == nil { t.Errorf("Call(Test): got %+v, wanted error", rsp) - } else if got, want := code.FromError(err), code.InvalidRequest; got != want { + } else if got, want := jrpc2.ErrorCode(err), jrpc2.InvalidRequest; got != want { t.Errorf("Call(Test): got code %v, want %v", got, want) } } @@ -869,7 +868,7 @@ func TestClient_onCancelHook(t *testing.T) { "computerSaysNo": handler.New(func(ctx context.Context, ids []string) error { defer close(hooked) if req := jrpc2.InboundRequest(ctx); !req.IsNotification() { - return jrpc2.Errorf(code.MethodNotFound, "no such method %q", req.Method()) + return jrpc2.Errorf(jrpc2.MethodNotFound, "no such method %q", req.Method()) } srv := jrpc2.ServerFromContext(ctx) for _, id := range ids { @@ -1069,8 +1068,8 @@ func TestHandler_noParams(t *testing.T) { var rsp string if err := loc.Client.CallResult(context.Background(), "Test", []int{1, 2, 3}, &rsp); err == nil { t.Errorf("Call(Test): got %q, wanted error", rsp) - } else if ec := code.FromError(err); ec != code.InvalidParams { - t.Errorf("Call(Test): got code %v, wanted %v", ec, code.InvalidParams) + } else if ec := jrpc2.ErrorCode(err); ec != jrpc2.InvalidParams { + t.Errorf("Call(Test): got code %v, wanted %v", ec, jrpc2.InvalidParams) } } @@ -1233,17 +1232,17 @@ func TestRequest_strictFields(t *testing.T) { tests := []struct { method string params any - code code.Code + code jrpc2.Code want string }{ - {"Strict", handler.Obj{"alpha": "aiuto"}, code.NoError, "aiuto"}, - {"Strict", handler.Obj{"alpha": "selva me", "charlie": true}, code.NoError, "selva me"}, - {"Strict", handler.Obj{"alpha": "OK", "nonesuch": true}, code.InvalidParams, ""}, - {"Normal", handler.Obj{"alpha": "OK", "nonesuch": true}, code.NoError, "OK"}, + {"Strict", handler.Obj{"alpha": "aiuto"}, jrpc2.NoError, "aiuto"}, + {"Strict", handler.Obj{"alpha": "selva me", "charlie": true}, jrpc2.NoError, "selva me"}, + {"Strict", handler.Obj{"alpha": "OK", "nonesuch": true}, jrpc2.InvalidParams, ""}, + {"Normal", handler.Obj{"alpha": "OK", "nonesuch": true}, jrpc2.NoError, "OK"}, } for _, test := range tests { name := test.method + "/" - if test.code == code.NoError { + if test.code == jrpc2.NoError { name += "OK" } else { name += test.code.String() @@ -1251,10 +1250,10 @@ func TestRequest_strictFields(t *testing.T) { t.Run(name, func(t *testing.T) { var res string err := loc.Client.CallResult(ctx, test.method, test.params, &res) - if err == nil && test.code != code.NoError { + if err == nil && test.code != jrpc2.NoError { t.Errorf("CallResult: got %+v, want error code %v", res, test.code) } else if err != nil { - if c := code.FromError(err); c != test.code { + if c := jrpc2.ErrorCode(err); c != test.code { t.Errorf("CallResult: got error %v, wanted code %v", err, test.code) } } else if res != test.want { diff --git a/json.go b/json.go index 1dbb8b7..e04ed59 100644 --- a/json.go +++ b/json.go @@ -5,8 +5,6 @@ package jrpc2 import ( "bytes" "encoding/json" - - "github.com/creachadair/jrpc2/code" ) // ParseRequests parses a single request or a batch of requests from JSON. @@ -151,7 +149,7 @@ func isValidID(v json.RawMessage) bool { // isValidVersion reports whether v is a valid JSON-RPC version string. func isValidVersion(v string) bool { return v == Version } -func (j *jmessage) fail(code code.Code, msg string) { +func (j *jmessage) fail(code Code, msg string) { if j.err == nil { j.err = &Error{Code: code, Message: msg} } @@ -203,7 +201,7 @@ func (j *jmessage) parseJSON(data []byte) error { var obj map[string]json.RawMessage if err := json.Unmarshal(data, &obj); err != nil { - j.fail(code.ParseError, "request is not a JSON object") + j.fail(ParseError, "request is not a JSON object") return j.err } @@ -213,17 +211,17 @@ func (j *jmessage) parseJSON(data []byte) error { switch key { case "jsonrpc": if json.Unmarshal(val, &j.V) != nil { - j.fail(code.ParseError, "invalid version key") + j.fail(ParseError, "invalid version key") } case "id": if isValidID(val) { j.ID = val } else { - j.fail(code.InvalidRequest, "invalid request ID") + j.fail(InvalidRequest, "invalid request ID") } case "method": if json.Unmarshal(val, &j.M) != nil { - j.fail(code.ParseError, "invalid method name") + j.fail(ParseError, "invalid method name") } case "params": // As a special case, reduce "null" to nil in the parameters. @@ -232,11 +230,11 @@ func (j *jmessage) parseJSON(data []byte) error { j.P = val } if fb := firstByte(j.P); fb != 0 && fb != '[' && fb != '{' { - j.fail(code.InvalidRequest, "parameters must be array or object") + j.fail(InvalidRequest, "parameters must be array or object") } case "error": if json.Unmarshal(val, &j.E) != nil { - j.fail(code.ParseError, "invalid error value") + j.fail(ParseError, "invalid error value") } case "result": j.R = val @@ -247,17 +245,17 @@ func (j *jmessage) parseJSON(data []byte) error { // Report an error for an invalid version marker if !isValidVersion(j.V) { - j.fail(code.InvalidRequest, "invalid version marker") + j.fail(InvalidRequest, "invalid version marker") } // Report an error if request/response fields overlap. if j.M != "" && (j.E != nil || j.R != nil) { - j.fail(code.InvalidRequest, "mixed request and reply fields") + j.fail(InvalidRequest, "mixed request and reply fields") } // Report an error for extraneous fields. if j.err == nil && len(extra) != 0 { - j.err = Errorf(code.InvalidRequest, "extra fields in request").WithData(extra) + j.err = Errorf(InvalidRequest, "extra fields in request").WithData(extra) } return nil } diff --git a/opts.go b/opts.go index ce7f593..6b29cb7 100644 --- a/opts.go +++ b/opts.go @@ -9,8 +9,6 @@ import ( "log" "runtime" "time" - - "github.com/creachadair/jrpc2/code" ) // ServerOptions control the behaviour of a server created by NewServer. @@ -177,7 +175,7 @@ func (c *ClientOptions) handleCallback() func(context.Context, *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: ErrorCode(err), Message: err.Error()} } } bits, _ := rsp.toJSON() diff --git a/server.go b/server.go index 55902aa..1a15b01 100644 --- a/server.go +++ b/server.go @@ -14,7 +14,6 @@ import ( "time" "github.com/creachadair/jrpc2/channel" - "github.com/creachadair/jrpc2/code" "github.com/creachadair/mds/mlink" "golang.org/x/sync/semaphore" ) @@ -475,7 +474,7 @@ func (s *Server) waitCallback(pctx context.Context, id string, p *Response) { p.ch <- &jmessage{ ID: json.RawMessage(id), - E: &Error{Code: code.FromError(err), Message: err.Error()}, + E: &Error{Code: ErrorCode(err), Message: err.Error()}, } } @@ -737,7 +736,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: ErrorCode(err), Message: err.Error()} } nw, err := encode(s.ch, jmessages{{ @@ -789,7 +788,7 @@ func (ts tasks) responses(rpcLog RPCLogger) jmessages { // // However, parse and validation errors must still be reported, with // an ID of null if the request ID was not resolvable. - if c := code.FromError(task.err); c != code.ParseError && c != code.InvalidRequest { + if c := ErrorCode(task.err); c != ParseError && c != InvalidRequest { continue } } @@ -805,10 +804,10 @@ func (ts tasks) responses(rpcLog RPCLogger) jmessages { rsp.R = task.val } else if e, ok := task.err.(*Error); ok { rsp.E = e - } else if c := code.FromError(task.err); c != code.NoError { + } else if c := ErrorCode(task.err); c != NoError { rsp.E = &Error{Code: c, Message: task.err.Error()} } else { - rsp.E = &Error{Code: code.InternalError, Message: task.err.Error()} + rsp.E = &Error{Code: InternalError, Message: task.err.Error()} } rpcLog.LogResponse(task.ctx, &Response{ id: string(rsp.ID),