Skip to content

Commit ef22230

Browse files
ernadoclaude
andcommitted
feat(errors): pass context cancellation through asAPIError
Surface context.Canceled/DeadlineExceeded unchanged (even when wrapped by an RPC error) so callers can errors.Is on ctx.Err() instead of seeing a synthetic Bot API error. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
1 parent 0fdff64 commit ef22230

2 files changed

Lines changed: 20 additions & 0 deletions

File tree

errors_map.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package botapi
22

33
import (
4+
"context"
45
"errors"
56
"net/http"
67
"strconv"
@@ -120,6 +121,11 @@ func asAPIError(err error) error {
120121
if errors.As(err, &already) {
121122
return err
122123
}
124+
// Context cancellation/deadline always passes through unchanged (even if it
125+
// happens to be wrapped by an RPC error) so callers can errors.Is on it.
126+
if errors.Is(err, context.Canceled) || errors.Is(err, context.DeadlineExceeded) {
127+
return err
128+
}
123129
rpcErr, ok := tgerr.As(err)
124130
if !ok {
125131
return err

errors_map_test.go

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package botapi
33
import (
44
"context"
55
"errors"
6+
"fmt"
67
"testing"
78

89
"github.com/gotd/td/tgerr"
@@ -19,6 +20,19 @@ func TestAsAPIError(t *testing.T) {
1920
t.Fatalf("non-RPC error should pass through, got %v", got)
2021
}
2122
})
23+
t.Run("PassThroughDeadline", func(t *testing.T) {
24+
if got := asAPIError(context.DeadlineExceeded); !errors.Is(got, context.DeadlineExceeded) {
25+
t.Fatalf("deadline error should pass through, got %v", got)
26+
}
27+
})
28+
t.Run("ContextWrappedByRPCStillPasses", func(t *testing.T) {
29+
// An RPC-typed error that wraps a cancellation must still surface the
30+
// cancellation so callers can branch on ctx.Err().
31+
wrapped := fmt.Errorf("rpc: %w", context.Canceled)
32+
if got := asAPIError(wrapped); !errors.Is(got, context.Canceled) {
33+
t.Fatalf("wrapped cancellation should pass through, got %v", got)
34+
}
35+
})
2236
t.Run("AlreadyAPIError", func(t *testing.T) {
2337
in := &Error{Code: 400, Description: "x"}
2438
if got := asAPIError(in); got != error(in) {

0 commit comments

Comments
 (0)