359 lines
12 KiB
Go
359 lines
12 KiB
Go
// Copyright (C) 2017 Michael J. Fromberger. All Rights Reserved.
|
|
|
|
// Package handler provides implementations of the jrpc2.Assigner interface,
|
|
// and support for adapting functions to the jrpc2.Handler interface.
|
|
package handler
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"encoding/json"
|
|
"errors"
|
|
"reflect"
|
|
"sort"
|
|
"strings"
|
|
|
|
"github.com/creachadair/jrpc2"
|
|
"github.com/creachadair/jrpc2/code"
|
|
)
|
|
|
|
// A Func adapts a function having the correct signature to a jrpc2.Handler.
|
|
type Func func(context.Context, *jrpc2.Request) (interface{}, error)
|
|
|
|
// Handle implements the jrpc2.Handler interface by calling m.
|
|
func (m Func) Handle(ctx context.Context, req *jrpc2.Request) (interface{}, error) {
|
|
return m(ctx, req)
|
|
}
|
|
|
|
// A Map is a trivial implementation of the jrpc2.Assigner interface that looks
|
|
// up method names in a map of static jrpc2.Handler values.
|
|
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] }
|
|
|
|
// Names implements the optional jrpc2.Namer extension interface.
|
|
func (m Map) Names() []string {
|
|
var names []string
|
|
for name := range m {
|
|
names = append(names, name)
|
|
}
|
|
sort.Strings(names)
|
|
return names
|
|
}
|
|
|
|
// A ServiceMap combines multiple assigners into one, permitting a server to
|
|
// export multiple services under different names.
|
|
type ServiceMap map[string]jrpc2.Assigner
|
|
|
|
// Assign splits the inbound method name as Service.Method, and passes the
|
|
// Method portion to the corresponding Service assigner. If method does not
|
|
// have the form Service.Method, or if Service is not set in m, the lookup
|
|
// fails and returns nil.
|
|
func (m ServiceMap) Assign(ctx context.Context, method string) jrpc2.Handler {
|
|
parts := strings.SplitN(method, ".", 2)
|
|
if len(parts) == 1 {
|
|
return nil
|
|
} else if ass, ok := m[parts[0]]; ok {
|
|
return ass.Assign(ctx, parts[1])
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// Names reports the composed names of all the methods in the service, each
|
|
// having the form Service.Method.
|
|
func (m ServiceMap) Names() []string {
|
|
var all []string
|
|
for svc, assigner := range m {
|
|
namer, ok := assigner.(jrpc2.Namer)
|
|
if !ok {
|
|
all = append(all, svc+".*")
|
|
continue
|
|
}
|
|
for _, name := range namer.Names() {
|
|
all = append(all, svc+"."+name)
|
|
}
|
|
}
|
|
sort.Strings(all)
|
|
return all
|
|
}
|
|
|
|
// 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.
|
|
//
|
|
// 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 interface{}) Func {
|
|
fi, err := Check(fn)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
return fi.Wrap()
|
|
}
|
|
|
|
// NewStrict acts as New, but enforces strict field checking on an argument of
|
|
// struct type.
|
|
func NewStrict(fn interface{}) Func {
|
|
fi, err := Check(fn)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
fi.strictFields = true
|
|
return fi.Wrap()
|
|
}
|
|
|
|
var (
|
|
ctxType = reflect.TypeOf((*context.Context)(nil)).Elem() // type context.Context
|
|
errType = reflect.TypeOf((*error)(nil)).Elem() // type error
|
|
reqType = reflect.TypeOf((*jrpc2.Request)(nil)) // type *jrpc2.Request
|
|
|
|
strictType = reflect.TypeOf((*interface{ DisallowUnknownFields() })(nil)).Elem()
|
|
|
|
errNoParameters = &jrpc2.Error{Code: code.InvalidParams, Message: "no parameters accepted"}
|
|
)
|
|
|
|
// FuncInfo captures type signature information from a valid handler function.
|
|
type FuncInfo struct {
|
|
Type reflect.Type // the complete function type
|
|
Argument reflect.Type // the non-context argument type, or nil
|
|
Result reflect.Type // the non-error result type, or nil
|
|
ReportsError bool // true if the function reports an error
|
|
strictFields bool // enforce strict field checking
|
|
posNames []string // positional field names (requires strictFields)
|
|
|
|
fn interface{} // the original function value
|
|
}
|
|
|
|
// Wrap adapts the function represented by fi in a Func that satisfies the
|
|
// jrpc2.Handler interface. 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 {
|
|
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.
|
|
//
|
|
// To do this, we "pre-compile" helper functions to unmarshal JSON into the
|
|
// input arguments (newInput) and to convert the results from reflectors
|
|
// back into values (decodeOut). We pre-check the function signature and
|
|
// types, so that the helpers do only as much reflection as is necessary:
|
|
// for example, we won't allocate a parameter value if the function does not
|
|
// accept a parameter, nor decode a return value if the function returns
|
|
// only an error.
|
|
|
|
// 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) (interface{}, error)); ok {
|
|
return Func(f)
|
|
}
|
|
|
|
// If strict field checking is desired, ensure arguments are wrapped.
|
|
arg := fi.Argument
|
|
wrapArg := func(v reflect.Value) interface{} { return v.Interface() }
|
|
if fi.strictFields && arg != nil && !arg.Implements(strictType) {
|
|
names := fi.posNames
|
|
wrapArg = func(v reflect.Value) interface{} {
|
|
return &strict{v: v.Interface(), posNames: names}
|
|
}
|
|
}
|
|
|
|
// Construct a function to unpack the parameters from the request message,
|
|
// based on the signature of the user's callback.
|
|
var newInput func(ctx reflect.Value, req *jrpc2.Request) ([]reflect.Value, error)
|
|
|
|
if arg == nil {
|
|
// Case 1: The function does not want any request parameters.
|
|
// Nothing needs to be decoded, but verify no parameters were passed.
|
|
newInput = func(ctx reflect.Value, req *jrpc2.Request) ([]reflect.Value, error) {
|
|
if req.HasParams() {
|
|
return nil, errNoParameters
|
|
}
|
|
return []reflect.Value{ctx}, nil
|
|
}
|
|
|
|
} else if arg == reqType {
|
|
// Case 2: The function wants the underlying *jrpc2.Request value.
|
|
newInput = func(ctx reflect.Value, req *jrpc2.Request) ([]reflect.Value, error) {
|
|
return []reflect.Value{ctx, reflect.ValueOf(req)}, nil
|
|
}
|
|
|
|
} else if arg.Kind() == reflect.Ptr {
|
|
// Case 3a: The function wants a pointer to its argument value.
|
|
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 []reflect.Value{ctx, in}, nil
|
|
}
|
|
} else {
|
|
// Case 3b: The function wants a bare argument value.
|
|
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)
|
|
}
|
|
// Indirect the pointer back off for the callee.
|
|
return []reflect.Value{ctx, in.Elem()}, nil
|
|
}
|
|
}
|
|
|
|
// Construct a function to decode the result values.
|
|
var decodeOut func([]reflect.Value) (interface{}, error)
|
|
|
|
if fi.Result == nil {
|
|
// The function returns only an error, the result is always nil.
|
|
decodeOut = func(vals []reflect.Value) (interface{}, error) {
|
|
oerr := vals[0].Interface()
|
|
if oerr != nil {
|
|
return nil, oerr.(error)
|
|
}
|
|
return nil, nil
|
|
}
|
|
} else if !fi.ReportsError {
|
|
// The function returns only single non-error: err is always nil.
|
|
decodeOut = func(vals []reflect.Value) (interface{}, error) {
|
|
return vals[0].Interface(), nil
|
|
}
|
|
} else {
|
|
// The function returns both a value and an error.
|
|
decodeOut = func(vals []reflect.Value) (interface{}, error) {
|
|
if oerr := vals[1].Interface(); oerr != nil {
|
|
return nil, oerr.(error)
|
|
}
|
|
return vals[0].Interface(), nil
|
|
}
|
|
}
|
|
|
|
call := reflect.ValueOf(fi.fn).Call
|
|
return Func(func(ctx context.Context, req *jrpc2.Request) (interface{}, 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
|
|
// fn must be a function with one of the following type signature schemes, for
|
|
// JSON-marshalable types X and Y:
|
|
//
|
|
// func(context.Context) error
|
|
// func(context.Context) Y
|
|
// func(context.Context) (Y, error)
|
|
// func(context.Context, X) error
|
|
// func(context.Context, X) Y
|
|
// func(context.Context, X) (Y, error)
|
|
// func(context.Context, *jrpc2.Request) error
|
|
// func(context.Context, *jrpc2.Request) Y
|
|
// func(context.Context, *jrpc2.Request) (Y, error)
|
|
// func(context.Context, *jrpc2.Request) (interface{}, error)
|
|
//
|
|
// If fn does not have one of these forms, Check reports an error.
|
|
//
|
|
// Note that the JSON-RPC standard restricts encoded parameter values to arrays
|
|
// and objects. Check will accept argument types that do not encode to arrays
|
|
// or objects, but the wrapper will report an error when decoding the request.
|
|
// The recommended solution is to define a struct type for your parameters.
|
|
//
|
|
// For a single arbitrary type, another approach is to use a 1-element array:
|
|
//
|
|
// func(ctx context.Context, sp [1]string) error {
|
|
// s := sp[0] // pull the actual argument out of the array
|
|
// // ...
|
|
// }
|
|
//
|
|
// For more complex positional signatures, see also handler.Positional.
|
|
//
|
|
func Check(fn interface{}) (*FuncInfo, error) {
|
|
if fn == nil {
|
|
return nil, errors.New("nil function")
|
|
}
|
|
|
|
info := &FuncInfo{Type: reflect.TypeOf(fn), fn: fn}
|
|
if info.Type.Kind() != reflect.Func {
|
|
return nil, errors.New("not a function")
|
|
}
|
|
|
|
// Check argument values.
|
|
if np := info.Type.NumIn(); np == 0 || np > 2 {
|
|
return nil, errors.New("wrong number of parameters")
|
|
} else if info.Type.In(0) != ctxType {
|
|
return nil, errors.New("first parameter is not context.Context")
|
|
} else if info.Type.IsVariadic() {
|
|
return nil, errors.New("variadic functions are not supported")
|
|
} else if np == 2 {
|
|
info.Argument = info.Type.In(1)
|
|
}
|
|
|
|
// Check return values.
|
|
no := info.Type.NumOut()
|
|
if no < 1 || no > 2 {
|
|
return nil, errors.New("wrong number of results")
|
|
} else if no == 2 && info.Type.Out(1) != errType {
|
|
return nil, errors.New("result is not of type error")
|
|
}
|
|
info.ReportsError = info.Type.Out(no-1) == errType
|
|
if no == 2 || !info.ReportsError {
|
|
info.Result = info.Type.Out(0)
|
|
}
|
|
return info, nil
|
|
}
|
|
|
|
// strict is a wrapper for an arbitrary value that enforces strict field
|
|
// checking when unmarshaling from JSON, and handles translation of array
|
|
// format into object format.
|
|
type strict struct {
|
|
v interface{}
|
|
posNames []string
|
|
}
|
|
|
|
// translate translates the raw JSON data into the correct format for
|
|
// unmarshaling into s.v.
|
|
//
|
|
// If s.posNames is set and data encodes an array, the array is rewritten to an
|
|
// equivalent object with field names assigned by the positional names.
|
|
// Otherwise, data is returned as-is without error.
|
|
func (s *strict) translate(data []byte) ([]byte, error) {
|
|
if len(s.posNames) == 0 || firstByte(data) != '[' {
|
|
return data, nil // no names, or not an array
|
|
}
|
|
|
|
// Decode the array wrapper and verify it has the correct length.
|
|
var arr []json.RawMessage
|
|
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",
|
|
len(arr), len(s.posNames))
|
|
}
|
|
|
|
// Rewrite the array into an object.
|
|
obj := make(map[string]json.RawMessage, len(s.posNames))
|
|
for i, name := range s.posNames {
|
|
obj[name] = arr[i]
|
|
}
|
|
return json.Marshal(obj)
|
|
}
|
|
|
|
func (s *strict) UnmarshalJSON(data []byte) error {
|
|
actual, err := s.translate(data)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
dec := json.NewDecoder(bytes.NewReader(actual))
|
|
dec.DisallowUnknownFields()
|
|
return dec.Decode(s.v)
|
|
}
|