-
Notifications
You must be signed in to change notification settings - Fork 8
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Create client level retry functionality (#9)
Co-authored-by: Dean Oren <[email protected]> Co-authored-by: Johannes Riecken <[email protected]>
- Loading branch information
1 parent
e6f186b
commit 63c57dc
Showing
13 changed files
with
629 additions
and
1 deletion.
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,54 @@ | ||
package retry | ||
|
||
import ( | ||
"net/http" | ||
"strings" | ||
) | ||
|
||
const ( | ||
// timeout configuration constants | ||
CONFIG_IS_RETRYABLE = "IsRetryable" | ||
|
||
// requesrt errors | ||
ERR_NO_SUCH_HOST = "dial tcp: lookup" | ||
) | ||
|
||
// IsRetryable is the config struct | ||
type IsRetryable struct { | ||
fnList []func(err error) bool | ||
} | ||
|
||
// SetIsRetryable sets functions that determin if an error can be retried or not | ||
func (c *Retry) SetIsRetryable(f ...func(err error) bool) *Retry { | ||
return c.withConfig(&IsRetryable{ | ||
fnList: f, | ||
}) | ||
} | ||
|
||
var _ = Config(&IsRetryable{}) | ||
|
||
func (c *IsRetryable) String() string { | ||
return CONFIG_IS_RETRYABLE | ||
} | ||
|
||
func (c *IsRetryable) Value() interface{} { | ||
return c.fnList | ||
} | ||
|
||
// IsRetryableNoOp always retries | ||
func IsRetryableNoOp(err error) bool { | ||
return true | ||
} | ||
|
||
// IsRetryableDefault | ||
func IsRetryableDefault(err error) bool { | ||
if strings.Contains(err.Error(), http.StatusText(http.StatusBadRequest)) { | ||
return strings.Contains(err.Error(), ERR_NO_SUCH_HOST) | ||
} | ||
|
||
if strings.Contains(err.Error(), http.StatusText(http.StatusUnauthorized)) { | ||
return false | ||
} | ||
|
||
return true | ||
} |
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,88 @@ | ||
package retry | ||
|
||
import ( | ||
"errors" | ||
"net/http" | ||
"reflect" | ||
"runtime" | ||
"testing" | ||
) | ||
|
||
func GetFunctionName(i interface{}) string { | ||
return runtime.FuncForPC(reflect.ValueOf(i).Pointer()).Name() | ||
} | ||
|
||
func TestRetry_SetIsRetryable(t *testing.T) { | ||
r := New() | ||
r.IsRetryableFns = []func(err error) bool{IsRetryableNoOp} | ||
|
||
type args struct { | ||
f []func(err error) bool | ||
} | ||
tests := []struct { | ||
name string | ||
args args | ||
want *Retry | ||
}{ | ||
{"ok", args{[]func(err error) bool{IsRetryableNoOp}}, r}, | ||
} | ||
for _, tt := range tests { | ||
t.Run(tt.name, func(t *testing.T) { | ||
c := New() | ||
got := c.SetIsRetryable(tt.args.f...) | ||
if len(got.IsRetryableFns) != len(r.IsRetryableFns) { | ||
t.Error("wrong lengths") | ||
return | ||
} | ||
for k, v := range got.IsRetryableFns { | ||
if GetFunctionName(r.IsRetryableFns[k]) != GetFunctionName(v) { | ||
t.Errorf("%s != %s", GetFunctionName(r.IsRetryableFns[k]), GetFunctionName(v)) | ||
return | ||
} | ||
} | ||
}) | ||
} | ||
} | ||
|
||
func TestIsRetryableNoOp(t *testing.T) { | ||
type args struct { | ||
err error | ||
} | ||
tests := []struct { | ||
name string | ||
args args | ||
want bool | ||
}{ | ||
{"always true", args{nil}, true}, | ||
} | ||
for _, tt := range tests { | ||
t.Run(tt.name, func(t *testing.T) { | ||
if got := IsRetryableNoOp(tt.args.err); got != tt.want { | ||
t.Errorf("IsRetryableNoOp() = %v, want %v", got, tt.want) | ||
} | ||
}) | ||
} | ||
} | ||
|
||
func TestIsRetryableDefault(t *testing.T) { | ||
type args struct { | ||
err error | ||
} | ||
tests := []struct { | ||
name string | ||
args args | ||
want bool | ||
}{ | ||
{"bad request - no retry", args{errors.New(http.StatusText(http.StatusBadRequest))}, false}, | ||
{"bad request - no host - retry", args{errors.New(http.StatusText(http.StatusBadRequest) + ERR_NO_SUCH_HOST)}, true}, | ||
{"bad request - unauthorized", args{errors.New(http.StatusText(http.StatusUnauthorized))}, false}, | ||
{"other error", args{errors.New(http.StatusText(http.StatusConflict))}, true}, | ||
} | ||
for _, tt := range tests { | ||
t.Run(tt.name, func(t *testing.T) { | ||
if got := IsRetryableDefault(tt.args.err); got != tt.want { | ||
t.Errorf("IsRetryableDefault() = %v, want %v", got, tt.want) | ||
} | ||
}) | ||
} | ||
} |
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,30 @@ | ||
package retry | ||
|
||
const ( | ||
// name of the configuration | ||
CONFIG_MAX_RETRIES = "MaxRetries" | ||
) | ||
|
||
// MaxRetries is the config struct | ||
type MaxRetries struct { | ||
Retries *int | ||
} | ||
|
||
// SetMaxRetries sets the maximum retries | ||
func (c *Retry) SetMaxRetries(r int) *Retry { | ||
return c.withConfig(&MaxRetries{ | ||
Retries: &r, | ||
}) | ||
} | ||
|
||
var _ = Config(&Timeout{}) | ||
|
||
// String returns the config name | ||
func (c *MaxRetries) String() string { | ||
return CONFIG_MAX_RETRIES | ||
} | ||
|
||
// Value return the value | ||
func (c *MaxRetries) Value() interface{} { | ||
return c.Retries | ||
} |
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,30 @@ | ||
package retry | ||
|
||
import ( | ||
"reflect" | ||
"testing" | ||
) | ||
|
||
func TestRetry_SetMaxRetries(t *testing.T) { | ||
r := New() | ||
five := 5 | ||
r.MaxRetries = &five | ||
type args struct { | ||
r int | ||
} | ||
tests := []struct { | ||
name string | ||
args args | ||
want *Retry | ||
}{ | ||
{"ok", args{5}, r}, | ||
} | ||
for _, tt := range tests { | ||
t.Run(tt.name, func(t *testing.T) { | ||
c := New() | ||
if got := c.SetMaxRetries(tt.args.r); !reflect.DeepEqual(*got.MaxRetries, *tt.want.MaxRetries) { | ||
t.Errorf("Retry.SetMaxRetries() = %v, want %v", *got.MaxRetries, *tt.want.MaxRetries) | ||
} | ||
}) | ||
} | ||
} |
Oops, something went wrong.