Skip to content

Commit 9bf6450

Browse files
authored
Add DoValue, which requires generics and bumps to Go 1.21 (#26)
Closes #22
1 parent a99f7cd commit 9bf6450

File tree

9 files changed

+91
-116
lines changed

9 files changed

+91
-116
lines changed

.github/workflows/test.yml

Lines changed: 15 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,12 @@ name: 'Test'
33
on:
44
push:
55
branches:
6-
- 'main'
6+
- 'main'
77
tags:
8-
- '*'
8+
- '*'
99
pull_request:
1010
branches:
11-
- 'main'
11+
- 'main'
1212
workflow_dispatch:
1313

1414
concurrency:
@@ -20,11 +20,17 @@ jobs:
2020
runs-on: 'ubuntu-latest'
2121

2222
steps:
23-
- uses: 'actions/checkout@v3'
23+
- uses: 'actions/checkout@v4'
2424

25-
- uses: actions/setup-go@v3
26-
with:
27-
go-version: '1.16'
25+
- uses: actions/setup-go@v5
26+
with:
27+
go-version-file: 'go.mod'
2828

29-
- name: 'Test'
30-
run: 'make test'
29+
- name: 'Test'
30+
run: |-
31+
go test \
32+
-count=1 \
33+
-race \
34+
-short \
35+
-timeout=5m \
36+
./...

Makefile

Lines changed: 0 additions & 8 deletions
This file was deleted.

benchmark/benchmark_test.go

Lines changed: 0 additions & 52 deletions
This file was deleted.

benchmark/go.mod

Lines changed: 0 additions & 10 deletions
This file was deleted.

benchmark/go.sum

Lines changed: 0 additions & 25 deletions
This file was deleted.

go.mod

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
module github.com/sethvargo/go-retry
22

3-
go 1.16
3+
go 1.21
44

55
// Something weird happened with the v0.2.0 tag where the commit in the module
66
// registry doesn't match the commit on GitHub.

go.sum

Whitespace-only changes.

retry.go

Lines changed: 23 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,12 @@ import (
1818
"time"
1919
)
2020

21-
// RetryFunc is a function passed to retry.
21+
// RetryFunc is a function passed to [Do].
2222
type RetryFunc func(ctx context.Context) error
2323

24+
// RetryFuncValue is a function passed to [Do] which returns a value.
25+
type RetryFuncValue[T any] func(ctx context.Context) (T, error)
26+
2427
type retryableError struct {
2528
err error
2629
}
@@ -46,47 +49,56 @@ func (e *retryableError) Error() string {
4649
return "retryable: " + e.err.Error()
4750
}
4851

49-
// Do wraps a function with a backoff to retry. The provided context is the same
50-
// context passed to the RetryFunc.
51-
func Do(ctx context.Context, b Backoff, f RetryFunc) error {
52+
func DoValue[T any](ctx context.Context, b Backoff, f RetryFuncValue[T]) (T, error) {
53+
var nilT T
54+
5255
for {
5356
// Return immediately if ctx is canceled
5457
select {
5558
case <-ctx.Done():
56-
return ctx.Err()
59+
return nilT, ctx.Err()
5760
default:
5861
}
5962

60-
err := f(ctx)
63+
v, err := f(ctx)
6164
if err == nil {
62-
return nil
65+
return v, nil
6366
}
6467

6568
// Not retryable
6669
var rerr *retryableError
6770
if !errors.As(err, &rerr) {
68-
return err
71+
return nilT, err
6972
}
7073

7174
next, stop := b.Next()
7275
if stop {
73-
return rerr.Unwrap()
76+
return nilT, rerr.Unwrap()
7477
}
7578

7679
// ctx.Done() has priority, so we test it alone first
7780
select {
7881
case <-ctx.Done():
79-
return ctx.Err()
82+
return nilT, ctx.Err()
8083
default:
8184
}
8285

8386
t := time.NewTimer(next)
8487
select {
8588
case <-ctx.Done():
8689
t.Stop()
87-
return ctx.Err()
90+
return nilT, ctx.Err()
8891
case <-t.C:
8992
continue
9093
}
9194
}
9295
}
96+
97+
// Do wraps a function with a backoff to retry. The provided context is the same
98+
// context passed to the [RetryFunc].
99+
func Do(ctx context.Context, b Backoff, f RetryFunc) error {
100+
_, err := DoValue(ctx, b, func(ctx context.Context) (*struct{}, error) {
101+
return nil, f(ctx)
102+
})
103+
return err
104+
}

retry_test.go

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,30 @@ func TestRetryableError(t *testing.T) {
2222
}
2323
}
2424

25+
func TestDoValue(t *testing.T) {
26+
t.Parallel()
27+
28+
t.Run("returns_value", func(t *testing.T) {
29+
t.Parallel()
30+
31+
ctx := context.Background()
32+
b := retry.WithMaxRetries(3, retry.BackoffFunc(func() (time.Duration, bool) {
33+
return 1 * time.Nanosecond, false
34+
}))
35+
36+
v, err := retry.DoValue(ctx, b, func(_ context.Context) (string, error) {
37+
return "foo", nil
38+
})
39+
if err != nil {
40+
t.Fatal("expected err")
41+
}
42+
43+
if got, want := v, "foo"; got != want {
44+
t.Errorf("expected %v to be %v", got, want)
45+
}
46+
})
47+
}
48+
2549
func TestDo(t *testing.T) {
2650
t.Parallel()
2751

@@ -175,6 +199,34 @@ func ExampleDo_customRetry() {
175199
}
176200
}
177201

202+
func ExampleDoValue() {
203+
ctx := context.Background()
204+
205+
b := retry.NewFibonacci(1 * time.Nanosecond)
206+
207+
body, err := retry.DoValue(ctx, retry.WithMaxRetries(3, b), func(ctx context.Context) ([]byte, error) {
208+
resp, err := http.Get("https://google.com/")
209+
if err != nil {
210+
return nil, err
211+
}
212+
defer resp.Body.Close()
213+
214+
switch resp.StatusCode / 100 {
215+
case 4:
216+
return nil, fmt.Errorf("bad response: %v", resp.StatusCode)
217+
case 5:
218+
return nil, retry.RetryableError(fmt.Errorf("bad response: %v", resp.StatusCode))
219+
default:
220+
b, _ := io.ReadAll(resp.Body)
221+
return b, nil
222+
}
223+
})
224+
if err != nil {
225+
// handle error
226+
}
227+
_ = body
228+
}
229+
178230
func TestCancel(t *testing.T) {
179231
for i := 0; i < 100000; i++ {
180232
ctx, cancel := context.WithCancel(context.Background())

0 commit comments

Comments
 (0)