112 lines
2.4 KiB
Go
112 lines
2.4 KiB
Go
|
package jsonrpc2
|
||
|
|
||
|
import (
|
||
|
"context"
|
||
|
"encoding/json"
|
||
|
"errors"
|
||
|
"fmt"
|
||
|
"reflect"
|
||
|
)
|
||
|
|
||
|
var (
|
||
|
ctxT = reflect.TypeOf((*context.Context)(nil)).Elem()
|
||
|
errT = reflect.TypeOf((*error)(nil)).Elem()
|
||
|
)
|
||
|
|
||
|
type methodDispatchFunc func(context.Context, Request) (any, error)
|
||
|
|
||
|
func newMethodDispatchFunc(
|
||
|
method reflect.Value,
|
||
|
) methodDispatchFunc {
|
||
|
paramT := method.Type().In(1)
|
||
|
return func(ctx context.Context, req Request) (any, error) {
|
||
|
var (
|
||
|
ctxV = reflect.ValueOf(ctx)
|
||
|
paramPtrV = reflect.New(paramT)
|
||
|
)
|
||
|
|
||
|
err := json.Unmarshal(req.Params, paramPtrV.Interface())
|
||
|
if err != nil {
|
||
|
// The JSON has already been validated, so this is not an
|
||
|
// errCodeParse situation. We assume it's an invalid param then,
|
||
|
// unless the error says otherwise via an UnmarshalJSON method
|
||
|
// returning an Error of its own.
|
||
|
if !errors.As(err, new(Error)) {
|
||
|
err = NewInvalidParamsError(
|
||
|
"JSON unmarshaling params into %T: %v", paramT, err,
|
||
|
)
|
||
|
}
|
||
|
return nil, err
|
||
|
}
|
||
|
|
||
|
var (
|
||
|
callResV = method.Call([]reflect.Value{ctxV, paramPtrV.Elem()})
|
||
|
resV = callResV[0]
|
||
|
errV = callResV[1]
|
||
|
)
|
||
|
|
||
|
if errV.IsNil() {
|
||
|
return resV.Interface(), nil
|
||
|
}
|
||
|
|
||
|
return nil, errV.Interface().(error)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
type dispatcher struct {
|
||
|
methods map[string]methodDispatchFunc
|
||
|
}
|
||
|
|
||
|
// NewDispatchHandler returns a Handler which will use methods on the passed in
|
||
|
// value to dispatch RPC calls. The passed in value must be a pointer. All
|
||
|
// exported methods which look like:
|
||
|
//
|
||
|
// MethodName(context.Context, ParamType) (ResponseType, error)
|
||
|
//
|
||
|
// will be available via RPC calls.
|
||
|
func NewDispatchHandler(i any) Handler {
|
||
|
v := reflect.ValueOf(i)
|
||
|
if v.Kind() != reflect.Pointer {
|
||
|
panic(fmt.Sprintf("expected pointer but got type %T", i))
|
||
|
}
|
||
|
|
||
|
v = v.Elem()
|
||
|
|
||
|
var (
|
||
|
t = v.Type()
|
||
|
numMethods = t.NumMethod()
|
||
|
methods = make(map[string]methodDispatchFunc, numMethods)
|
||
|
)
|
||
|
|
||
|
for i := range numMethods {
|
||
|
var (
|
||
|
method = t.Method(i)
|
||
|
methodV = v.Method(i)
|
||
|
methodT = methodV.Type()
|
||
|
)
|
||
|
|
||
|
if !method.IsExported() ||
|
||
|
methodT.NumIn() != 2 ||
|
||
|
methodT.In(0) != ctxT ||
|
||
|
methodT.NumOut() != 2 ||
|
||
|
methodT.Out(1) != errT {
|
||
|
continue
|
||
|
}
|
||
|
|
||
|
methods[method.Name] = newMethodDispatchFunc(methodV)
|
||
|
}
|
||
|
|
||
|
return &dispatcher{methods}
|
||
|
}
|
||
|
|
||
|
func (d *dispatcher) ServeRPC(ctx context.Context, req Request) (any, error) {
|
||
|
fn, ok := d.methods[req.Method]
|
||
|
if !ok {
|
||
|
return nil, NewError(
|
||
|
errCodeMethodNotFound, "unknown method %q", req.Method,
|
||
|
)
|
||
|
}
|
||
|
|
||
|
return fn(ctx, req)
|
||
|
}
|