127 lines
3.4 KiB
Go
127 lines
3.4 KiB
Go
package config
|
|
|
|
import (
|
|
"fmt"
|
|
"reflect"
|
|
"runtime"
|
|
|
|
"github.com/libp2p/go-libp2p-core/host"
|
|
tptu "github.com/libp2p/go-libp2p-transport-upgrader"
|
|
)
|
|
|
|
var errorType = reflect.TypeOf((*error)(nil)).Elem()
|
|
|
|
// checks if a function returns either the specified type or the specified type
|
|
// and an error.
|
|
func checkReturnType(fnType, tptType reflect.Type) error {
|
|
switch fnType.NumOut() {
|
|
case 2:
|
|
if fnType.Out(1) != errorType {
|
|
return fmt.Errorf("expected (optional) second return value from transport constructor to be an error")
|
|
}
|
|
|
|
fallthrough
|
|
case 1:
|
|
if !fnType.Out(0).Implements(tptType) {
|
|
return fmt.Errorf("transport constructor returns %s which doesn't implement %s", fnType.Out(0), tptType)
|
|
}
|
|
default:
|
|
return fmt.Errorf("expected transport constructor to return a transport and, optionally, an error")
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// Handles return values with optional errors. That is, return values of the
|
|
// form `(something, error)` or just `something`.
|
|
//
|
|
// Panics if the return value isn't of the correct form.
|
|
func handleReturnValue(out []reflect.Value) (interface{}, error) {
|
|
switch len(out) {
|
|
case 2:
|
|
err := out[1]
|
|
if err != (reflect.Value{}) && !err.IsNil() {
|
|
return nil, err.Interface().(error)
|
|
}
|
|
fallthrough
|
|
case 1:
|
|
tpt := out[0]
|
|
|
|
// Check for nil value and nil error.
|
|
if tpt == (reflect.Value{}) {
|
|
return nil, fmt.Errorf("unspecified error")
|
|
}
|
|
switch tpt.Kind() {
|
|
case reflect.Ptr, reflect.Interface, reflect.Func:
|
|
if tpt.IsNil() {
|
|
return nil, fmt.Errorf("unspecified error")
|
|
}
|
|
}
|
|
|
|
return tpt.Interface(), nil
|
|
default:
|
|
panic("expected 1 or 2 return values from transport constructor")
|
|
}
|
|
}
|
|
|
|
// calls the transport constructor and annotates the error with the name of the constructor.
|
|
func callConstructor(c reflect.Value, args []reflect.Value) (interface{}, error) {
|
|
val, err := handleReturnValue(c.Call(args))
|
|
if err != nil {
|
|
name := runtime.FuncForPC(c.Pointer()).Name()
|
|
if name != "" {
|
|
// makes debugging easier
|
|
return nil, fmt.Errorf("transport constructor %s failed: %s", name, err)
|
|
}
|
|
}
|
|
return val, err
|
|
}
|
|
|
|
type constructor func(h host.Host, u *tptu.Upgrader) interface{}
|
|
|
|
func makeArgumentConstructors(fnType reflect.Type, argTypes map[reflect.Type]constructor) ([]constructor, error) {
|
|
out := make([]constructor, fnType.NumIn())
|
|
for i := range out {
|
|
argType := fnType.In(i)
|
|
c, ok := argTypes[argType]
|
|
if !ok {
|
|
return nil, fmt.Errorf("argument %d has an unexpected type %s", i, argType.Name())
|
|
}
|
|
out[i] = c
|
|
}
|
|
return out, nil
|
|
}
|
|
|
|
// makes a transport constructor.
|
|
func makeConstructor(
|
|
tpt interface{},
|
|
tptType reflect.Type,
|
|
argTypes map[reflect.Type]constructor,
|
|
) (func(host.Host, *tptu.Upgrader) (interface{}, error), error) {
|
|
v := reflect.ValueOf(tpt)
|
|
// avoid panicing on nil/zero value.
|
|
if v == (reflect.Value{}) {
|
|
return nil, fmt.Errorf("expected a transport or transport constructor, got a %T", tpt)
|
|
}
|
|
t := v.Type()
|
|
if t.Kind() != reflect.Func {
|
|
return nil, fmt.Errorf("expected a transport or transport constructor, got a %T", tpt)
|
|
}
|
|
|
|
if err := checkReturnType(t, tptType); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
argConstructors, err := makeArgumentConstructors(t, argTypes)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return func(h host.Host, u *tptu.Upgrader) (interface{}, error) {
|
|
arguments := make([]reflect.Value, len(argConstructors))
|
|
for i, makeArg := range argConstructors {
|
|
arguments[i] = reflect.ValueOf(makeArg(h, u))
|
|
}
|
|
return callConstructor(v, arguments)
|
|
}, nil
|
|
}
|