feat: support recursive injection of provider parameters
This allows provider functions to accept parameters that are injected by other
bindings or binding providers, eg. call the provider function with the root CLI
struct (which is automatically bound by Kong):
kong.BindToProvider(func(cli *CLI) (*Injected, error) { ... })
This commit is contained in:
+20
-22
@@ -6,7 +6,10 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
type bindings map[reflect.Type]func() (reflect.Value, error)
|
// A map of type to function that returns a value of that type.
|
||||||
|
//
|
||||||
|
// The function should have the signature func(...) (T, error). Arguments are recursively resolved.
|
||||||
|
type bindings map[reflect.Type]any
|
||||||
|
|
||||||
func (b bindings) String() string {
|
func (b bindings) String() string {
|
||||||
out := []string{}
|
out := []string{}
|
||||||
@@ -19,32 +22,23 @@ func (b bindings) String() string {
|
|||||||
func (b bindings) add(values ...interface{}) bindings {
|
func (b bindings) add(values ...interface{}) bindings {
|
||||||
for _, v := range values {
|
for _, v := range values {
|
||||||
v := v
|
v := v
|
||||||
b[reflect.TypeOf(v)] = func() (reflect.Value, error) { return reflect.ValueOf(v), nil }
|
b[reflect.TypeOf(v)] = func() (any, error) { return v, nil }
|
||||||
}
|
}
|
||||||
return b
|
return b
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b bindings) addTo(impl, iface interface{}) {
|
func (b bindings) addTo(impl, iface interface{}) {
|
||||||
valueOf := reflect.ValueOf(impl)
|
b[reflect.TypeOf(iface).Elem()] = func() (any, error) { return impl, nil }
|
||||||
b[reflect.TypeOf(iface).Elem()] = func() (reflect.Value, error) { return valueOf, nil }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b bindings) addProvider(provider interface{}) error {
|
func (b bindings) addProvider(provider interface{}) error {
|
||||||
pv := reflect.ValueOf(provider)
|
pv := reflect.ValueOf(provider)
|
||||||
t := pv.Type()
|
t := pv.Type()
|
||||||
if t.Kind() != reflect.Func || t.NumIn() != 0 || t.NumOut() != 2 || t.Out(1) != reflect.TypeOf((*error)(nil)).Elem() {
|
if t.Kind() != reflect.Func || t.NumOut() != 2 || t.Out(1) != reflect.TypeOf((*error)(nil)).Elem() {
|
||||||
return fmt.Errorf("%T must be a function with the signature func()(T, error)", provider)
|
return fmt.Errorf("%T must be a function with the signature func(...)(T, error)", provider)
|
||||||
}
|
}
|
||||||
rt := pv.Type().Out(0)
|
rt := pv.Type().Out(0)
|
||||||
b[rt] = func() (reflect.Value, error) {
|
b[rt] = provider
|
||||||
out := pv.Call(nil)
|
|
||||||
errv := out[1]
|
|
||||||
var err error
|
|
||||||
if !errv.IsNil() {
|
|
||||||
err = errv.Interface().(error) //nolint
|
|
||||||
}
|
|
||||||
return out[0], err
|
|
||||||
}
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -101,15 +95,19 @@ func callAnyFunction(f reflect.Value, bindings bindings) (out []any, err error)
|
|||||||
t := f.Type()
|
t := f.Type()
|
||||||
for i := 0; i < t.NumIn(); i++ {
|
for i := 0; i < t.NumIn(); i++ {
|
||||||
pt := t.In(i)
|
pt := t.In(i)
|
||||||
if argf, ok := bindings[pt]; ok {
|
argf, ok := bindings[pt]
|
||||||
argv, err := argf()
|
if !ok {
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
in = append(in, argv)
|
|
||||||
} else {
|
|
||||||
return nil, fmt.Errorf("couldn't find binding of type %s for parameter %d of %s(), use kong.Bind(%s)", pt, i, t, pt)
|
return nil, fmt.Errorf("couldn't find binding of type %s for parameter %d of %s(), use kong.Bind(%s)", pt, i, t, pt)
|
||||||
}
|
}
|
||||||
|
// Recursively resolve binding functions.
|
||||||
|
argv, err := callAnyFunction(reflect.ValueOf(argf), bindings)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("%s: %w", pt, err)
|
||||||
|
}
|
||||||
|
if ferrv := reflect.ValueOf(argv[len(argv)-1]); ferrv.IsValid() && !ferrv.IsNil() {
|
||||||
|
return nil, ferrv.Interface().(error) //nolint:forcetypeassert
|
||||||
|
}
|
||||||
|
in = append(in, reflect.ValueOf(argv[0]))
|
||||||
}
|
}
|
||||||
outv := f.Call(in)
|
outv := f.Call(in)
|
||||||
out = make([]any, len(outv))
|
out = make([]any, len(outv))
|
||||||
|
|||||||
+5
-1
@@ -208,7 +208,11 @@ func BindTo(impl, iface interface{}) Option {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// BindToProvider allows binding of provider functions.
|
// BindToProvider binds an injected value to a provider function.
|
||||||
|
//
|
||||||
|
// The provider function must have the signature:
|
||||||
|
//
|
||||||
|
// func() (interface{}, error)
|
||||||
//
|
//
|
||||||
// This is useful when the Run() function of different commands require different values that may
|
// This is useful when the Run() function of different commands require different values that may
|
||||||
// not all be initialisable from the main() function.
|
// not all be initialisable from the main() function.
|
||||||
|
|||||||
+6
-1
@@ -89,11 +89,13 @@ func TestCallbackCustomError(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type bindToProviderCLI struct {
|
type bindToProviderCLI struct {
|
||||||
|
Filled bool `default:"true"`
|
||||||
Called bool
|
Called bool
|
||||||
Cmd bindToProviderCmd `cmd:""`
|
Cmd bindToProviderCmd `cmd:""`
|
||||||
}
|
}
|
||||||
|
|
||||||
type boundThing struct {
|
type boundThing struct {
|
||||||
|
Filled bool
|
||||||
}
|
}
|
||||||
|
|
||||||
type bindToProviderCmd struct{}
|
type bindToProviderCmd struct{}
|
||||||
@@ -105,7 +107,10 @@ func (*bindToProviderCmd) Run(cli *bindToProviderCLI, b *boundThing) error {
|
|||||||
|
|
||||||
func TestBindToProvider(t *testing.T) {
|
func TestBindToProvider(t *testing.T) {
|
||||||
var cli bindToProviderCLI
|
var cli bindToProviderCLI
|
||||||
app, err := New(&cli, BindToProvider(func() (*boundThing, error) { return &boundThing{}, nil }))
|
app, err := New(&cli, BindToProvider(func(cli *bindToProviderCLI) (*boundThing, error) {
|
||||||
|
assert.True(t, cli.Filled, "CLI struct should have already been populated by Kong")
|
||||||
|
return &boundThing{Filled: cli.Filled}, nil
|
||||||
|
}))
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
ctx, err := app.Parse([]string{"cmd"})
|
ctx, err := app.Parse([]string{"cmd"})
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
|
|||||||
Reference in New Issue
Block a user