From b828915530c6f768ed8055dc88581b241320bf63 Mon Sep 17 00:00:00 2001 From: Lukasz Raczylo Date: Thu, 13 Aug 2026 03:10:15 +0100 Subject: [PATCH] fix(errors): recover retryable/status from wrapped ProxyError IsRetryable and GetStatusCode used a direct type assertion, so a ProxyError wrapped with %w was never detected (status fell back to 500, retryable to false). Use errors.As, which dereferences the existing Unwrap chain and matches the file's own Unwrap method. Test: Test_IsRetryable_GetStatusCode_Wrapped (wrapped error preserves both flags). Note: these helpers currently have no production callers (test-only surface), so this is a correctness hardening of the error API. --- errors.go | 7 +++++-- errors_test.go | 19 +++++++++++++++++++ 2 files changed, 24 insertions(+), 2 deletions(-) diff --git a/errors.go b/errors.go index b910d50..a9e3ad0 100644 --- a/errors.go +++ b/errors.go @@ -2,6 +2,7 @@ package main import ( "encoding/json" + "errors" "fmt" "time" ) @@ -121,7 +122,8 @@ func IsRetryable(err error) bool { return false } - if proxyErr, ok := err.(*ProxyError); ok { + var proxyErr *ProxyError + if errors.As(err, &proxyErr) { return proxyErr.Retryable } @@ -134,7 +136,8 @@ func GetStatusCode(err error) int { return 200 } - if proxyErr, ok := err.(*ProxyError); ok { + var proxyErr *ProxyError + if errors.As(err, &proxyErr) { return proxyErr.StatusCode } diff --git a/errors_test.go b/errors_test.go index 58731ab..6b4ba43 100644 --- a/errors_test.go +++ b/errors_test.go @@ -2,6 +2,7 @@ package main import ( "errors" + "fmt" "net/http" "testing" @@ -241,3 +242,21 @@ func TestProxyError_Retryable(t *testing.T) { }) } } + +// Test_IsRetryable_GetStatusCode_Wrapped verifies that retryability and status +// are correctly recovered from a ProxyError that has been wrapped (e.g. via +// %w), using errors.As rather than a direct type assertion. +func Test_IsRetryable_GetStatusCode_Wrapped(t *testing.T) { + base := NewProxyError(ErrCodeBackendError, "boom", 503, true) + wrapped := fmt.Errorf("request failed: %w", base) + assert.True(t, IsRetryable(wrapped), "retryable flag must survive wrapping") + assert.Equal(t, 503, GetStatusCode(wrapped), "status code must survive wrapping") + + nonRetryable := NewProxyError(ErrCodeInvalidRequest, "bad", 400, false) + wrapped2 := fmt.Errorf("wrapped: %w", nonRetryable) + assert.False(t, IsRetryable(wrapped2)) + assert.Equal(t, 400, GetStatusCode(wrapped2)) + + assert.False(t, IsRetryable(nil)) + assert.Equal(t, 200, GetStatusCode(nil)) +}