Skip to content

Commit

Permalink
handler: get rid of the Func wrapper
Browse files Browse the repository at this point in the history
There is no longer any reason to have a distinct type for this, since the
interface was removed and jrpc2.Handler was converted to a type alias.  Clean
up all the uses of handler.Func and use jrpc2.Handler instead.

Updates #46.
  • Loading branch information
creachadair committed Mar 20, 2023
1 parent 74977fd commit e1278b5
Show file tree
Hide file tree
Showing 7 changed files with 30 additions and 31 deletions.
8 changes: 4 additions & 4 deletions bench_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,9 @@ func BenchmarkRoundTrip(b *testing.B) {
// Benchmark the round-trip call cycle for a method that does no useful
// work, as a proxy for overhead for client and server maintenance.
voidService := handler.Map{
"void": handler.Func(func(context.Context, *jrpc2.Request) (any, error) {
"void": func(context.Context, *jrpc2.Request) (any, error) {
return nil, nil
}),
},
}
tests := []struct {
desc string
Expand Down Expand Up @@ -58,9 +58,9 @@ func BenchmarkLoad(b *testing.B) {

// The load testing service has a no-op method to exercise server overhead.
loc := server.NewLocal(handler.Map{
"void": handler.Func(func(context.Context, *jrpc2.Request) (any, error) {
"void": func(context.Context, *jrpc2.Request) (any, error) {
return nil, nil
}),
},
}, nil)
defer loc.Close()

Expand Down
2 changes: 1 addition & 1 deletion handler/example_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ func ExampleCheck() {
// Argument type: []string
// Result type: int
// Reports error? false
// Wrapped type: handler.Func
// Wrapped type: func(context.Context, *jrpc2.Request) (interface {}, error)
}

func ExampleArgs_unmarshal() {
Expand Down
31 changes: 14 additions & 17 deletions handler/handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,9 @@ import (
"github.com/creachadair/jrpc2"
)

// A Func is wrapper for a jrpc2.Handler function.
type Func func(context.Context, *jrpc2.Request) (any, error)

// A Map is a trivial implementation of the jrpc2.Assigner interface that looks
// up method names in a static map of function values.
type Map map[string]Func
type Map map[string]jrpc2.Handler

// Assign implements part of the jrpc2.Assigner interface.
func (m Map) Assign(_ context.Context, method string) jrpc2.Handler { return m[method] }
Expand Down Expand Up @@ -73,14 +70,14 @@ func (m ServiceMap) Names() []string {
}

// New adapts a function to a jrpc2.Handler. The concrete value of fn must be
// function accepted by Check. The resulting Func will handle JSON encoding and
// decoding, call fn, and report appropriate errors.
// function accepted by Check. The resulting jrpc2.Handler will handle JSON
// encoding and decoding, call fn, and report appropriate errors.
//
// New is intended for use during program initialization, and will panic if the
// type of fn does not have one of the accepted forms. Programs that need to
// check for possible errors should call handler.Check directly, and use the
// Wrap method of the resulting FuncInfo to obtain the wrapper.
func New(fn any) Func {
func New(fn any) jrpc2.Handler {
fi, err := Check(fn)
if err != nil {
panic(err)
Expand Down Expand Up @@ -118,21 +115,21 @@ type FuncInfo struct {
// for non-struct arguments.
func (fi *FuncInfo) SetStrict(strict bool) *FuncInfo { fi.strictFields = strict; return fi }

// Wrap adapts the function represented by fi in a Func. The wrapped function
// can obtain the *jrpc2.Request value from its context argument using the
// jrpc2.InboundRequest helper.
// Wrap adapts the function represented by fi to a jrpc2.Handler. The wrapped
// function can obtain the *jrpc2.Request value from its context argument using
// the jrpc2.InboundRequest helper.
//
// This method panics if fi == nil or if it does not represent a valid function
// type. A FuncInfo returned by a successful call to Check is always valid.
func (fi *FuncInfo) Wrap() Func {
func (fi *FuncInfo) Wrap() jrpc2.Handler {
if fi == nil || fi.fn == nil {
panic("handler: invalid FuncInfo value")
}

// Although it is not possible to completely eliminate reflection, the
// intent here is to hoist as much work as possible out of the body of the
// constructed Func wrapper, since that will be executed every time the
// handler is invoked.
// constructed wrapper, since that will be executed every time the handler
// is invoked.
//
// To do this, we "pre-compile" helper functions to unmarshal JSON into the
// input arguments (newInput) and to convert the results from reflectors
Expand All @@ -144,8 +141,8 @@ func (fi *FuncInfo) Wrap() Func {

// Special case: If fn has the exact signature of the Handle method, don't do
// any (additional) reflection at all.
if f, ok := fi.fn.(func(context.Context, *jrpc2.Request) (any, error)); ok {
return Func(f)
if f, ok := fi.fn.(jrpc2.Handler); ok {
return f
}

// If strict field checking or positional decoding are enabled, ensure
Expand Down Expand Up @@ -222,13 +219,13 @@ func (fi *FuncInfo) Wrap() Func {
}

call := reflect.ValueOf(fi.fn).Call
return Func(func(ctx context.Context, req *jrpc2.Request) (any, error) {
return func(ctx context.Context, req *jrpc2.Request) (any, error) {
args, ierr := newInput(reflect.ValueOf(ctx), req)
if ierr != nil {
return nil, ierr
}
return decodeOut(call(args))
})
}
}

// Check checks whether fn can serve as a jrpc2.Handler. The concrete value of
Expand Down
6 changes: 3 additions & 3 deletions handler/handler_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ func TestCheck(t *testing.T) {
// their arguments of different types and structure.
func TestFuncInfo_wrapDecode(t *testing.T) {
tests := []struct {
fn handler.Func
fn jrpc2.Handler
p string
want any
}{
Expand Down Expand Up @@ -109,9 +109,9 @@ func TestFuncInfo_wrapDecode(t *testing.T) {
fmt.Sprintf(`{"jsonrpc":"2.0","id":1,"method":"x","params":%s}`, test.p))
got, err := test.fn(ctx, req)
if err != nil {
t.Errorf("Call %v failed: %v", test.fn, err)
t.Errorf("Call %p failed: %v", test.fn, err)
} else if diff := cmp.Diff(test.want, got); diff != "" {
t.Errorf("Call %v: wrong result (-want, +got)\n%s", test.fn, diff)
t.Errorf("Call %p: wrong result (-want, +got)\n%s", test.fn, diff)
}
}
}
Expand Down
8 changes: 5 additions & 3 deletions handler/positional.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,17 +7,19 @@ import (
"fmt"
"reflect"
"strings"

"github.com/creachadair/jrpc2"
)

// NewPos adapts a function to a jrpc2.Handler. The concrete value of fn must
// be a function accepted by Positional. The resulting Func will handle JSON
// be a function accepted by Positional. The resulting handler will handle JSON
// encoding and decoding, call fn, and report appropriate errors.
//
// NewPos is intended for use during program initialization, and will panic if
// the type of fn does not have one of the accepted forms. Programs that need
// to check for possible errors should call handler.Positional directly, and
// use the Wrap method of the resulting FuncInfo to obtain the wrapper.
func NewPos(fn any, names ...string) Func {
func NewPos(fn any, names ...string) jrpc2.Handler {
fi, err := Positional(fn, names...)
if err != nil {
panic(err)
Expand Down Expand Up @@ -83,7 +85,7 @@ func structFieldNames(atype reflect.Type) (bool, []string) {
// type whose fields correspond to the non-context arguments of fn. The names
// are used as the JSON field keys for the corresponding parameters.
//
// When converted into a handler.Func, the wrapped function accepts either a
// When converted into a jrpc2.Handler, the wrapped function accepts either a
// JSON array with exactly n members, or a JSON object with the field keys
// named. For example, given:
//
Expand Down
4 changes: 2 additions & 2 deletions jrpc2_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -991,7 +991,7 @@ func TestClient_concurrentCallbacks(t *testing.T) {
}, &server.LocalOptions{
Server: &jrpc2.ServerOptions{AllowPush: true},
Client: &jrpc2.ClientOptions{
OnCallback: handler.Func(func(ctx context.Context, req *jrpc2.Request) (any, error) {
OnCallback: func(ctx context.Context, req *jrpc2.Request) (any, error) {
// A trivial callback that reports its method name.
// The name is used to select which invocation we are serving.
switch req.Method() {
Expand All @@ -1004,7 +1004,7 @@ func TestClient_concurrentCallbacks(t *testing.T) {
}
<-release
return req.Method(), nil
}),
},
},
})
defer loc.Close()
Expand Down
2 changes: 1 addition & 1 deletion opts.go
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,7 @@ type ClientOptions struct {
// report a system error back to the server describing the error.
//
// Server callbacks are a non-standard extension of JSON-RPC.
OnCallback func(context.Context, *Request) (any, error)
OnCallback Handler

// If set, this function is called when the context for a request terminates.
// The function receives the client and the response that was cancelled.
Expand Down

0 comments on commit e1278b5

Please sign in to comment.