-
Notifications
You must be signed in to change notification settings - Fork 4
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Implement invoker service with invocation result access APIs. Signed-off-by: Pavel Patrin <[email protected]>
- Loading branch information
1 parent
aab11ca
commit 4b045bc
Showing
5 changed files
with
220 additions
and
34 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,83 @@ | ||
package gontainer | ||
|
||
import ( | ||
"fmt" | ||
"reflect" | ||
) | ||
|
||
// Invoker defines invoker interface. | ||
type Invoker interface { | ||
// Invoke invokes specified function. | ||
Invoke(fn any) (InvokeResult, error) | ||
} | ||
|
||
// invoker implements invoker interface. | ||
type invoker struct { | ||
resolver Resolver | ||
} | ||
|
||
// Invoke invokes specified function. | ||
func (i *invoker) Invoke(fn any) (InvokeResult, error) { | ||
// Get reflection of the fn. | ||
fnValue := reflect.ValueOf(fn) | ||
if fnValue.Kind() != reflect.Func { | ||
return nil, fmt.Errorf("fn must be a function") | ||
} | ||
|
||
// Resolve function arguments. | ||
fnInArgs := make([]reflect.Value, 0, fnValue.Type().NumIn()) | ||
for index := 0; index < fnValue.Type().NumIn(); index++ { | ||
fnArgPtrValue := reflect.New(fnValue.Type().In(index)) | ||
if err := i.resolver.Resolve(fnArgPtrValue.Interface()); err != nil { | ||
return nil, fmt.Errorf("failed to resolve dependency: %w", err) | ||
} | ||
fnInArgs = append(fnInArgs, fnArgPtrValue.Elem()) | ||
} | ||
|
||
// Convert function results. | ||
fnOutArgs := fnValue.Call(fnInArgs) | ||
result := &invokeResult{ | ||
values: make([]any, 0, len(fnOutArgs)), | ||
err: nil, | ||
} | ||
for index, fnOut := range fnOutArgs { | ||
// If it is the last return value. | ||
if index == len(fnOutArgs)-1 { | ||
// And type of the value is the error. | ||
if fnOut.Type().Implements(errorType) { | ||
// Use the value as an error. | ||
result.err = fnOut.Interface().(error) | ||
} | ||
} | ||
|
||
// Add value to the results slice. | ||
result.values = append(result.values, fnOut.Interface()) | ||
} | ||
|
||
return result, nil | ||
} | ||
|
||
// InvokeResult provides access to the invocation result. | ||
type InvokeResult interface { | ||
// Values returns a slice of function result values. | ||
Values() []any | ||
|
||
// Error returns function result error, if any. | ||
Error() error | ||
} | ||
|
||
// invokeResult implements corresponding interface. | ||
type invokeResult struct { | ||
values []any | ||
err error | ||
} | ||
|
||
// Values implements corresponding interface method. | ||
func (r *invokeResult) Values() []any { | ||
return r.values | ||
} | ||
|
||
// Error implements corresponding interface method. | ||
func (r *invokeResult) Error() error { | ||
return r.err | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,92 @@ | ||
package gontainer | ||
|
||
import ( | ||
"errors" | ||
"testing" | ||
) | ||
|
||
// TestInvokerService tests invoker service. | ||
func TestInvokerService(t *testing.T) { | ||
tests := []struct { | ||
name string | ||
haveFn any | ||
wantFn func(t *testing.T, value InvokeResult) | ||
wantErr bool | ||
}{ | ||
{ | ||
name: "ReturnNothing", | ||
haveFn: func(var1 string, var2 int) {}, | ||
wantFn: func(t *testing.T, value InvokeResult) { | ||
equal(t, len(value.Values()), 0) | ||
equal(t, value.Error(), nil) | ||
}, | ||
wantErr: false, | ||
}, | ||
{ | ||
name: "ReturnValuesNoError", | ||
haveFn: func(var1 string, var2 int) (string, int, bool) { | ||
return var1 + "-X", var2 + 100, true | ||
}, | ||
wantFn: func(t *testing.T, value InvokeResult) { | ||
equal(t, len(value.Values()), 3) | ||
equal(t, value.Values()[0], "string-X") | ||
equal(t, value.Values()[1], 223) | ||
equal(t, value.Values()[2], true) | ||
equal(t, value.Error(), nil) | ||
}, | ||
wantErr: false, | ||
}, | ||
{ | ||
name: "ReturnNoValuesWithError", | ||
haveFn: func(var1 string, var2 int) (string, int, error) { | ||
return var1 + "-X", var2 + 100, errors.New("failed") | ||
}, | ||
wantFn: func(t *testing.T, value InvokeResult) { | ||
equal(t, len(value.Values()), 3) | ||
equal(t, value.Values()[0], "string-X") | ||
equal(t, value.Values()[1], 223) | ||
equal(t, value.Values()[2].(error).Error(), "failed") | ||
equal(t, value.Error().Error(), "failed") | ||
equal(t, value.Error(), value.Values()[2]) | ||
}, | ||
wantErr: false, | ||
}, | ||
{ | ||
name: "ReturnMultipleError", | ||
haveFn: func(var1 string, var2 int) (error, error, error) { | ||
return nil, errors.New("error-1"), errors.New("error-2") | ||
}, | ||
wantFn: func(t *testing.T, value InvokeResult) { | ||
equal(t, len(value.Values()), 3) | ||
equal(t, value.Values()[0], nil) | ||
equal(t, value.Values()[1].(error).Error(), "error-1") | ||
equal(t, value.Values()[2].(error).Error(), "error-2") | ||
equal(t, value.Error().Error(), "error-2") | ||
equal(t, value.Error(), value.Values()[2]) | ||
}, | ||
wantErr: false, | ||
}, | ||
} | ||
for _, tt := range tests { | ||
t.Run(tt.name, func(t *testing.T) { | ||
container, err := New( | ||
NewFactory(func() string { return "string" }), | ||
NewFactory(func() int { return 123 }), | ||
) | ||
equal(t, err, nil) | ||
equal(t, container == nil, false) | ||
defer func() { | ||
equal(t, container.Close(), nil) | ||
}() | ||
|
||
result, err := container.Invoker().Invoke(tt.haveFn) | ||
if (err != nil) != tt.wantErr { | ||
t.Errorf("Invoke() error = %v, wantErr %v", err, tt.wantErr) | ||
return | ||
} | ||
if tt.wantFn != nil { | ||
tt.wantFn(t, result) | ||
} | ||
}) | ||
} | ||
} |