From e2665e586f3f0111e1341ebff625506644afde7a Mon Sep 17 00:00:00 2001 From: Adria Casajus Date: Fri, 31 Oct 2014 14:03:09 +0100 Subject: [PATCH] Added InvokeWithArgs method to ensure that certain function arguments are used --- inject.go | 87 ++++++++++++++++++++++++++++++++++++++++---------- inject_test.go | 37 ++++++++++++++++++++- 2 files changed, 106 insertions(+), 18 deletions(-) diff --git a/inject.go b/inject.go index 3ff713c..320664c 100644 --- a/inject.go +++ b/inject.go @@ -2,6 +2,7 @@ package inject import ( + "errors" "fmt" "reflect" ) @@ -33,6 +34,14 @@ type Invoker interface { // a slice of reflect.Value representing the returned values of the function. // Returns an error if the injection fails. Invoke(interface{}) ([]reflect.Value, error) + // Invoke attempts to call the interface{} provided as a function, + // providing dependencies for function arguments based on Type. + // The passed arguments are required to be injected in the invocation, if + // Not all the arguments can be injected an error will be raised + // Returns a slice of reflect.Value representing the returned values of the function. + // Returns an error if the injection fails. + // It panics if f is not a function + InvokeWithArgs(interface{}, ...interface{}) ([]reflect.Value, error) } // TypeMapper represents an interface for mapping interface{} values based on type. @@ -52,8 +61,30 @@ type TypeMapper interface { Get(reflect.Type) reflect.Value } +type valueMap map[reflect.Type]reflect.Value + +func (m valueMap) Get(t reflect.Type) reflect.Value { + val := m[t] + + if val.IsValid() { + return val + } + + // no concrete types found, try to find implementors + // if t is an interface + if t.Kind() == reflect.Interface { + for k, v := range m { + if k.Implements(t) { + val = v + break + } + } + } + return val +} + type injector struct { - values map[reflect.Type]reflect.Value + values valueMap parent Injector } @@ -102,6 +133,43 @@ func (inj *injector) Invoke(f interface{}) ([]reflect.Value, error) { return reflect.ValueOf(f).Call(in), nil } +// Invoke attempts to call the interface{} provided as a function, +// providing dependencies for function arguments based on Type. +// The passed arguments are required to be injected in the invocation, if +// Not all the arguments can be injected an error will be raised +// Returns a slice of reflect.Value representing the returned values of the function. +// Returns an error if the injection fails. +// It panics if f is not a function +func (inj *injector) InvokeWithArgs(f interface{}, args ...interface{}) ([]reflect.Value, error) { + t := reflect.TypeOf(f) + + argsMap := valueMap(make(map[reflect.Type]reflect.Value)) + for _, a := range args { + argsMap[reflect.TypeOf(a)] = reflect.ValueOf(a) + } + + var in = make([]reflect.Value, t.NumIn()) //Panic if t is not kind of Func + for i := 0; i < t.NumIn(); i++ { + argType := t.In(i) + val := inj.Get(argType) + if !val.IsValid() { + val = argsMap.Get(argType) + if !val.IsValid() { + return nil, fmt.Errorf("Value not found for type %v", argType) + } + delete(argsMap, argType) + } + + in[i] = val + } + + if len(argsMap) > 0 { + return nil, errors.New("Not all arguments could be injected") + } + + return reflect.ValueOf(f).Call(in), nil +} + // Maps dependencies in the Type map to each field in the struct // that is tagged with 'inject'. // Returns an error if the injection fails. @@ -156,22 +224,7 @@ func (i *injector) Set(typ reflect.Type, val reflect.Value) TypeMapper { } func (i *injector) Get(t reflect.Type) reflect.Value { - val := i.values[t] - - if val.IsValid() { - return val - } - - // no concrete types found, try to find implementors - // if t is an interface - if t.Kind() == reflect.Interface { - for k, v := range i.values { - if k.Implements(t) { - val = v - break - } - } - } + val := i.values.Get(t) // Still no type found, try to look it up on the parent if !val.IsValid() && i.parent != nil { diff --git a/inject_test.go b/inject_test.go index eb94471..aec7116 100644 --- a/inject_test.go +++ b/inject_test.go @@ -2,9 +2,10 @@ package inject_test import ( "fmt" - "github.com/codegangsta/inject" "reflect" "testing" + + "github.com/acasajus/inject" ) type SpecialString interface { @@ -64,6 +65,40 @@ func Test_InjectorInvoke(t *testing.T) { expect(t, err, nil) } +func Test_InjectorInvokeWithArguments(t *testing.T) { + injector := inject.New() + expect(t, injector == nil, false) + + dep := "some dependency" + injector.Map(dep) + dep2 := 3 + injector.Map(dep2) + arg1 := make(chan *SpecialString) + arg2 := &Greeter{"Me"} + + _, err := injector.InvokeWithArgs(func(d1 string, d2 int, a1 chan *SpecialString, a2 *Greeter) { + expect(t, d1, dep) + expect(t, d2, dep2) + expect(t, reflect.TypeOf(a1).Elem(), reflect.TypeOf(arg1).Elem()) + expect(t, reflect.TypeOf(a1).ChanDir(), reflect.BothDir) + expect(t, a2, arg2) + }, arg1, arg2) + + expect(t, err, nil) + + _, err = injector.InvokeWithArgs(func(d1 string, d2 int, a1 chan *SpecialString, a2 *Greeter) { + expect(t, d1, dep) + expect(t, d2, dep2) + expect(t, reflect.TypeOf(a1).Elem(), reflect.TypeOf(arg1).Elem()) + expect(t, reflect.TypeOf(a1).ChanDir(), reflect.BothDir) + expect(t, a2, arg2) + }, arg1) + + if err == nil || err.Error() != "Value not found for type *inject_test.Greeter" { + t.Error("Did not properly fail to invoke method") + } +} + func Test_InjectorInvokeReturnValues(t *testing.T) { injector := inject.New() expect(t, injector == nil, false)