Skip to content

Commit 0d2f94e

Browse files
authored
Merge pull request #13 from J7mbo/feature/#4-Use-anonymous-func-for-type-safety
[#4] - Added `ExecuteFuncWithRetry()` for userland type safety
2 parents b84e6e9 + 58e4541 commit 0d2f94e

File tree

4 files changed

+109
-2
lines changed

4 files changed

+109
-2
lines changed

MethodCallRetrier.go

+35
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,41 @@ func New(waitTime int64, maxRetries int64, exponent *int64) *MethodCallRetrier {
3838
return &MethodCallRetrier{waitTime: waitTime, maxRetries: maxRetries, exponent: *exponent}
3939
}
4040

41+
/*
42+
Retries a function with a maximum number of retries and a wait time. Functionally equivalent to ExecuteWithRetry() but
43+
accepts a function to maintain type safety in userland instead and removes the requirement of a user type assertion.
44+
*/
45+
func (r *MethodCallRetrier) ExecuteFuncWithRetry(function func() error) []error {
46+
defer func() {
47+
r.resetCurrentRetries()
48+
r.resetErrorList()
49+
}()
50+
51+
if r.currentRetries >= r.maxRetries {
52+
r.errorList = append(
53+
r.errorList, &MaxRetriesError{
54+
methodName: reflect.TypeOf(function).String(),
55+
waitTime: r.waitTime,
56+
maxRetries: r.maxRetries,
57+
},
58+
)
59+
60+
return r.errorList
61+
}
62+
63+
err := function()
64+
65+
if err != nil {
66+
r.errorList = append(r.errorList, err)
67+
68+
r.sleepAndIncrementRetries()
69+
70+
return r.ExecuteFuncWithRetry(function)
71+
}
72+
73+
return r.errorList
74+
}
75+
4176
/* Retries the call to object.methodName(...args) with a maximum number of retries and a wait time. */
4277
func (r *MethodCallRetrier) ExecuteWithRetry(
4378
object interface{}, methodName string, args ...interface{},

README.md

+35-2
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,13 @@ Call `ExecuteWithRetry` with your object and method you want to retry:
3030
```
3131
ExecuteWithRetry(
3232
object interface{}, methodName string, args ...interface{},
33-
) ([]reflect.Value, []error) {
33+
) ([]reflect.Value, []error)
34+
```
35+
36+
Alternatively, call `ExecuteFuncWithRetry` and pass in a function that returns `error` to retry.
37+
38+
```
39+
ExecuteFuncWithRetry(func() error) []error
3440
```
3541

3642
You can use it as follows:
@@ -44,4 +50,31 @@ To use the results, you must typecast the result to the expected type. In the ca
4450

4551
```
4652
myInt := results[0].Interface().(int64)
47-
```
53+
```
54+
55+
Or, to maintain type safety in userland, you can pass a function in instead and do all your retriable work in there:
56+
57+
```
58+
var json string
59+
60+
functionToRetry := func() error {
61+
json, err = DoSomeFlakeyHttpCall()
62+
63+
if err != nil {
64+
return err
65+
}
66+
67+
return nil
68+
}
69+
70+
if errs := retrier.ExecuteWithRetry(funcToRetry); len(errs) > 0 {
71+
/* Do something because we failed 3 times */
72+
return
73+
}
74+
75+
fmt.Println(json)
76+
```
77+
78+
Be aware that this choice now requires you to work within the function scope, and that if you want to use the results of
79+
the call outside of the scope of the function then you must declare it using `var result type` as shown above; but this
80+
does remove the requirement of a type assertion that you have to do with `ExecuteWithRetry`.

Retrier.go

+2
Original file line numberDiff line numberDiff line change
@@ -7,4 +7,6 @@ type Retrier interface {
77
ExecuteWithRetry(
88
maxRetries int64, waitTime int64, object interface{}, methodName string, args ...interface{},
99
) ([]reflect.Value, error)
10+
11+
ExecuteFuncWithRetry(function func() error) []error
1012
}

Retrier_test.go

+37
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,35 @@ func (s *RetrierTestSuite) TestRetrierWorksWhenMultipleReturnParamsAreErrors() {
103103
s.Assert().Len(errs, 11)
104104
}
105105

106+
func (s *RetrierTestSuite) TestRetrierWorksWithUserFunction() {
107+
var num int
108+
109+
errs := New(0, 3, nil).ExecuteFuncWithRetry(func() error {
110+
num = 42
111+
112+
return nil
113+
})
114+
115+
s.Assert().Equal(42, num)
116+
s.Assert().Len(errs, 0)
117+
}
118+
119+
func (s *RetrierTestSuite) TestRetrierWithUserFunctionReturnsCorrectNumberOfErrors() {
120+
errs := New(0, 3, nil).ExecuteFuncWithRetry(func() error {
121+
return errors.New("")
122+
})
123+
124+
s.Assert().Equal(4, len(errs))
125+
}
126+
127+
func (s *RetrierTestSuite) TestRetrierWorksWithUserFunctionCalledCorrectNumberOfTimes() {
128+
testObj := RetryMockObject{}
129+
130+
New(0, 3, nil).ExecuteFuncWithRetry(testObj.MethodToBeCalledToReturnResultAndError)
131+
132+
s.Assert().Equal(3, testObj.timesCalled)
133+
}
134+
106135
type RetryObject struct{}
107136

108137
func (m *RetryObject) MethodReturningNoValues() {}
@@ -125,8 +154,16 @@ func (m *RetryObject) MethodReturningMultipleErrors() (string, error, error) {
125154

126155
type RetryMockObject struct {
127156
mock.Mock
157+
158+
timesCalled int
128159
}
129160

130161
func (m *RetryMockObject) MethodReturningError(anArgument string) error {
131162
return m.Called(anArgument).Error(0)
132163
}
164+
165+
func (m *RetryMockObject) MethodToBeCalledToReturnResultAndError() error {
166+
m.timesCalled += 1
167+
168+
return errors.New("")
169+
}

0 commit comments

Comments
 (0)