Skip to content

Commit 47b731d

Browse files
expose ResolveFunctionArgsWithInterceptor and ResolveFunctionArgs
Add "Interceptor" ability to ResolveFunctionArgs by calling ResolveFunctionArgsWithInterceptor This allows for us to define a function which we can over-ride certain args during the resolution of them... This is mainly for use with Invocables... which has a new method: CallMethodByNameWithArgInterceptor where we can define one. See the tests for a gross example
1 parent 28127aa commit 47b731d

File tree

5 files changed

+96
-39
lines changed

5 files changed

+96
-39
lines changed

container_resolver.go

Lines changed: 41 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -74,24 +74,17 @@ func (container *ContainerInstance) resolveStructFields(instanceType reflect.Typ
7474
return instance
7575
}
7676

77-
// resolveFunctionArgs - Resolves the args of our function we bound to the container
78-
// parameters is an array of values that we wish to provide which is optional
79-
// parameters will first be assigned starting at index 0 of the functions args
80-
// Then we'll look at the function args, and if we assigned a value from the parameters already
81-
// it will use that, otherwise we'll look the type up in the container and resolve it
82-
func (container *ContainerInstance) resolveFunctionArgs(function reflect.Value, parameters ...any) []reflect.Value {
77+
type FuncArgResolverInterceptor = func(index int, argType reflect.Type, typeZeroVal reflect.Value) (reflect.Value, bool)
78+
79+
func (container *ContainerInstance) ResolveFunctionArgsWithInterceptor(function reflect.Value, interceptor FuncArgResolverInterceptor, parameters ...any) []reflect.Value {
8380
inArgCount := 0
8481

8582
if !function.IsValid() || function.IsZero() {
8683
return []reflect.Value{}
8784
}
8885

89-
functionType := function.Type()
90-
if functionType.Kind() == reflect.Ptr {
91-
inArgCount = functionType.Elem().NumIn()
92-
} else {
93-
inArgCount = functionType.NumIn()
94-
}
86+
functionType := getType(function)
87+
inArgCount = functionType.NumIn()
9588

9689
// We'll put the types of all in args into this array,
9790
// so we don't have to keep running .In()
@@ -118,24 +111,36 @@ func (container *ContainerInstance) resolveFunctionArgs(function reflect.Value,
118111

119112
// Assign parameter values from the provided parameters list first
120113
if len(parameters) > 0 {
121-
for i := 0; i < len(parameters); i++ {
122-
paramVal := reflect.ValueOf(parameters[i])
114+
parametersType := reflect.TypeOf(parameters)
115+
116+
// We can provide a function as an "interceptor" instead...
117+
if parametersType.Kind() == reflect.Array || parametersType.Kind() == reflect.Slice {
118+
for i := 0; i < len(parameters); i++ {
119+
paramVal := reflect.ValueOf(parameters[i])
120+
121+
// We'll only assign the param from the provided list, if the type matches?
122+
inArg := inArgTypes[i]
123+
if inArg == paramVal.Type() {
124+
assignArg(i, paramVal)
125+
}
126+
}
123127

124-
// We'll only assign the param from the provided list, if the type matches?
125-
inArg := inArgTypes[i]
126-
if inArg == paramVal.Type() {
127-
assignArg(i, paramVal)
128+
// If our provided parameters fulfils all the function args, let's just early return
129+
if assignedCount >= inArgCount {
130+
return args
128131
}
129132
}
130133

131-
// If our provided parameters fulfils all the function args, let's just early return
132-
if assignedCount >= inArgCount {
133-
return args
134-
}
135134
}
136135

137136
// Now we'll try to resolve any other types from the container
138137
for i := 0; i < inArgCount; i++ {
138+
interceptedVal, didIntercept := interceptor(i, inArgTypes[i], args[i])
139+
if didIntercept {
140+
assignArg(i, interceptedVal)
141+
continue
142+
}
143+
139144
// We already assigned it, let's skip...
140145
if assignedArgs[i] {
141146
continue
@@ -153,9 +158,22 @@ func (container *ContainerInstance) resolveFunctionArgs(function reflect.Value,
153158
}
154159

155160
return args
161+
162+
}
163+
164+
// ResolveFunctionArgs - Resolves the args of our function we bound to the container
165+
// parameters is an array of values that we wish to provide which is optional
166+
// parameters will first be assigned starting at index 0 of the functions args
167+
// Then we'll look at the function args, and if we assigned a value from the parameters already
168+
// it will use that, otherwise we'll look the type up in the container and resolve it
169+
func (container *ContainerInstance) ResolveFunctionArgs(function reflect.Value, parameters ...any) []reflect.Value {
170+
interceptor := func(index int, argType reflect.Type, typeZeroVal reflect.Value) (reflect.Value, bool) {
171+
return typeZeroVal, false
172+
}
173+
return container.ResolveFunctionArgsWithInterceptor(function, interceptor, parameters...)
156174
}
157175

158-
// resolveFunctionArg - Used in resolveFunctionArgs, we pass an arg type and attempt to
176+
// resolveFunctionArg - Used in ResolveFunctionArgs, we pass an arg type and attempt to
159177
// resolve it from the container, if the type doesn't exist in the container
160178
// we'll return a zero value version of the type
161179
func (container *ContainerInstance) resolveFunctionArg(arg reflect.Type) (reflect.Value, bool) {

invocable.go

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -123,7 +123,21 @@ func (invocable *Invocable) CallMethodByNameWith(methodName string, container *C
123123
structInstance := invocable.InstantiateStructAndFill(container)
124124
method := structInstance.MethodByName(methodName)
125125

126-
return method.Call(container.resolveFunctionArgs(method, parameters...))
126+
return method.Call(container.ResolveFunctionArgs(method, parameters...))
127+
}
128+
129+
func (invocable *Invocable) CallMethodByNameWithArgInterceptor(methodName string, container *ContainerInstance, interceptor FuncArgResolverInterceptor, parameters ...any) []reflect.Value {
130+
if invocable.typeOfBinding != "struct" {
131+
panic("CallMethodByNameWith is only usable when the Invocable instance is created with a struct.")
132+
}
133+
if !invocable.isInstantiated {
134+
invocable.instantiate()
135+
}
136+
137+
structInstance := invocable.InstantiateStructAndFill(container)
138+
method := structInstance.MethodByName(methodName)
139+
140+
return method.Call(container.ResolveFunctionArgsWithInterceptor(method, interceptor, parameters...))
127141
}
128142

129143
// CallMethodWith - Call the method and assign its parameters from the passed parameters & container
@@ -133,6 +147,6 @@ func (invocable *Invocable) CallMethodWith(container *ContainerInstance, paramet
133147
}
134148

135149
return invocable.instance.Call(
136-
container.resolveFunctionArgs(invocable.instance, parameters...),
150+
container.ResolveFunctionArgs(invocable.instance, parameters...),
137151
)
138152
}

tests/invocable_test.go

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,3 +62,26 @@ func TestCreatingStructUsingDI(t *testing.T) {
6262
}
6363

6464
}
65+
66+
func TestCallingFunctionOnStructUsingArgInterceptor(t *testing.T) {
67+
container := Container.CreateContainer()
68+
container.Bind(createSingletonServiceOne)
69+
70+
invocable := Container.CreateInvocableStruct(reflect.ValueOf(&serviceConcrete{}))
71+
results := invocable.CallMethodByNameWithArgInterceptor(
72+
"InterceptFunc",
73+
container,
74+
func(index int, argType reflect.Type, typeZeroVal reflect.Value) (reflect.Value, bool) {
75+
if argType.Name() == "anotherServiceAbstract" {
76+
return reflect.ValueOf(&serviceConcreteTwo{
77+
message: "Hi, from interceptor",
78+
}), true
79+
}
80+
81+
return typeZeroVal, false
82+
},
83+
)
84+
85+
assert.Equal(t, results[0].Interface(), "Hi, from interceptor")
86+
87+
}

tests/mock_objects.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,10 @@ func (service *serviceConcrete) Message() string {
3030
return "Hello World!"
3131
}
3232

33+
func (service *serviceConcrete) InterceptFunc(anotherService anotherServiceAbstract) string {
34+
return anotherService.Message()
35+
}
36+
3337
func createSingletonServiceOne() *serviceConcrete {
3438
service := new(serviceConcrete)
3539
service.message = "TestBindingSingletonValueToContainer"

tests/types_test.go

Lines changed: 12 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,13 @@
11
package tests
22

33
import (
4-
"errors"
54
"testing"
65

76
container "github.com/Envuso/go-ioc-container"
8-
"github.com/modern-go/reflect2"
97
"github.com/stretchr/testify/assert"
108
)
119

12-
var pkgPath = "github.com/Envuso/go-ioc-container"
10+
var pkgPath = "github.com/Envuso/go-ioc-container/tests"
1311

1412
type TestingStr struct {
1513
Name string
@@ -31,17 +29,17 @@ func TestTypes_Of(t *testing.T) {
3129
assert.Equal(t, pkgPath, testingInter.Path)
3230
assert.Equal(t, "TestingInter", testingInter.Name)
3331

34-
testingErr := container.ContainerTypes.Of(errors.New("yeet"))
35-
assert.Equal(t, pkgPath+"/TestingInter", testingErr.FullName)
36-
assert.Equal(t, pkgPath, testingErr.Path)
37-
assert.Equal(t, "TestingInter", testingErr.Name)
38-
39-
test := reflect2.TypeByName("container.TestingStr")
40-
test.String()
41-
42-
test2 := reflect2.TypeByPackageName("github.com/Envuso/go-ioc-container", "TestingStr")
43-
test2.String()
44-
print("")
32+
// testingErr := container.ContainerTypes.Of(errors.New("yeet"))
33+
// assert.Equal(t, pkgPath+"/TestingInter", testingErr.FullName)
34+
// assert.Equal(t, pkgPath, testingErr.Path)
35+
// assert.Equal(t, "TestingInter", testingErr.Name)
36+
37+
// test := reflect2.TypeByName("container.TestingStr")
38+
// test.String()
39+
//
40+
// test2 := reflect2.TypeByPackageName("github.com/Envuso/go-ioc-container", "TestingStr")
41+
// test2.String()
42+
// print("")
4543
}
4644

4745
func TestPkgType_Save(t *testing.T) {

0 commit comments

Comments
 (0)