Skip to content

Commit 6a335f5

Browse files
committed
feat(DebugInfo): Add sensitive header redaction and sanitization methods
1 parent 650429d commit 6a335f5

File tree

8 files changed

+1014
-273
lines changed

8 files changed

+1014
-273
lines changed

src/Fetch/Concerns/ManagesRetries.php

Lines changed: 72 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,7 @@
99
use Fetch\Interfaces\Response as ResponseInterface;
1010
use Fetch\Support\RetryStrategy;
1111
use GuzzleHttp\Exception\ConnectException;
12-
use InvalidArgumentException;
1312
use Psr\Log\NullLogger;
14-
use RuntimeException;
15-
use Throwable;
1613

1714
trait ManagesRetries
1815
{
@@ -40,7 +37,7 @@ trait ManagesRetries
4037
/**
4138
* The exceptions that should be retried.
4239
*
43-
* @var array<class-string<Throwable>>
40+
* @var array<class-string<\Throwable>>
4441
*/
4542
protected array $retryableExceptions = [
4643
ConnectException::class,
@@ -49,20 +46,21 @@ trait ManagesRetries
4946
/**
5047
* Set the retry logic for the request.
5148
*
52-
* @param int $retries Maximum number of retry attempts
53-
* @param int $delay Initial delay in milliseconds
49+
* @param int $retries Maximum number of retry attempts
50+
* @param int $delay Initial delay in milliseconds
51+
*
5452
* @return $this
5553
*
56-
* @throws InvalidArgumentException If the parameters are invalid
54+
* @throws \InvalidArgumentException If the parameters are invalid
5755
*/
5856
public function retry(int $retries, int $delay = 100): ClientHandler
5957
{
6058
if ($retries < 0) {
61-
throw new InvalidArgumentException('Retries must be a non-negative integer');
59+
throw new \InvalidArgumentException('Retries must be a non-negative integer');
6260
}
6361

6462
if ($delay < 0) {
65-
throw new InvalidArgumentException('Delay must be a non-negative integer');
63+
throw new \InvalidArgumentException('Delay must be a non-negative integer');
6664
}
6765

6866
$this->maxRetries = $retries;
@@ -74,7 +72,8 @@ public function retry(int $retries, int $delay = 100): ClientHandler
7472
/**
7573
* Set the status codes that should be retried.
7674
*
77-
* @param array<int> $statusCodes HTTP status codes
75+
* @param array<int> $statusCodes HTTP status codes
76+
*
7877
* @return $this
7978
*/
8079
public function retryStatusCodes(array $statusCodes): ClientHandler
@@ -87,7 +86,8 @@ public function retryStatusCodes(array $statusCodes): ClientHandler
8786
/**
8887
* Set the exception types that should be retried.
8988
*
90-
* @param array<class-string<Throwable>> $exceptions Exception class names
89+
* @param array<class-string<\Throwable>> $exceptions Exception class names
90+
*
9191
* @return $this
9292
*/
9393
public function retryExceptions(array $exceptions): ClientHandler
@@ -130,7 +130,7 @@ public function getRetryableStatusCodes(): array
130130
/**
131131
* Get the retryable exception types.
132132
*
133-
* @return array<class-string<Throwable>> The retryable exception classes
133+
* @return array<class-string<\Throwable>> The retryable exception classes
134134
*/
135135
public function getRetryableExceptions(): array
136136
{
@@ -140,45 +140,77 @@ public function getRetryableExceptions(): array
140140
/**
141141
* Implement retry logic for the request with exponential backoff.
142142
*
143-
* This method now accepts an optional RequestContext to read retry configuration
143+
* This method accepts an optional RequestContext to read retry configuration
144144
* from per-request context instead of handler state, making it safe for concurrent usage.
145+
* All retry settings (maxRetries, retryDelay, retryableStatusCodes, retryableExceptions)
146+
* are read from the context when provided, falling back to handler state otherwise.
147+
*
148+
* @param \Fetch\Support\RequestContext|null $context The request context (optional)
149+
* @param callable $request The request to execute
145150
*
146-
* @param \Fetch\Support\RequestContext|null $context The request context (optional)
147-
* @param callable $request The request to execute
148151
* @return ResponseInterface The response after successful execution
149152
*
150153
* @throws FetchRequestException If the request fails after all retries
151-
* @throws RuntimeException If something unexpected happens
154+
* @throws \RuntimeException If something unexpected happens
152155
*/
153156
protected function retryRequest(?\Fetch\Support\RequestContext $context, callable $request): ResponseInterface
154157
{
155158
// Read retry config from context if provided, otherwise fall back to handler state
156159
$maxRetries = $context?->getMaxRetries() ?? $this->getMaxRetries();
157160
$baseDelayMs = $context?->getRetryDelay() ?? $this->getRetryDelay();
158161

162+
// Read retryable status codes and exceptions from context, falling back to handler state
163+
$retryableStatusCodes = null !== $context
164+
? $context->getRetryableStatusCodes()
165+
: $this->retryableStatusCodes;
166+
167+
$retryableExceptions = null !== $context
168+
? $context->getRetryableExceptions()
169+
: $this->retryableExceptions;
170+
159171
$strategy = new RetryStrategy(
160172
maxRetries: $maxRetries,
161173
baseDelayMs: $baseDelayMs,
162-
retryableStatusCodes: $this->retryableStatusCodes,
163-
retryableExceptions: $this->retryableExceptions,
164-
logger: $this->logger ?? new NullLogger
174+
retryableStatusCodes: $retryableStatusCodes,
175+
retryableExceptions: $retryableExceptions,
176+
logger: $this->logger ?? new NullLogger()
165177
);
166178

167179
return $strategy->execute(
168180
$request,
169-
function (int $attempt, int $maxAttempts, Throwable $exception, int $delayMs): void {
170-
if (method_exists($this, 'logRetry')) {
171-
$this->logRetry($attempt, $maxAttempts, $exception);
172-
}
181+
function (int $attempt, int $maxAttempts, \Throwable $exception, int $delayMs) use ($context): void {
182+
$this->logRetryAttempt($attempt, $maxAttempts, $exception, $delayMs, $context);
173183
}
174184
);
175185
}
176186

187+
/**
188+
* Log a retry attempt with context information.
189+
*
190+
* @param int $attempt Current attempt number
191+
* @param int $maxAttempts Maximum number of attempts
192+
* @param \Throwable $exception The exception that triggered the retry
193+
* @param int $delayMs The delay before the next attempt in milliseconds
194+
* @param \Fetch\Support\RequestContext|null $context Optional request context for additional info
195+
*/
196+
protected function logRetryAttempt(
197+
int $attempt,
198+
int $maxAttempts,
199+
\Throwable $exception,
200+
int $delayMs,
201+
?\Fetch\Support\RequestContext $context = null,
202+
): void {
203+
if (method_exists($this, 'logRetry')) {
204+
$this->logRetry($attempt, $maxAttempts, $exception);
205+
}
206+
}
207+
177208
/**
178209
* Calculate backoff delay with exponential growth and jitter.
179210
*
180-
* @param int $baseDelay The base delay in milliseconds
181-
* @param int $attempt The current attempt number (0-based)
211+
* @param int $baseDelay The base delay in milliseconds
212+
* @param int $attempt The current attempt number (0-based)
213+
*
182214
* @return int The calculated delay in milliseconds
183215
*/
184216
protected function calculateBackoffDelay(int $baseDelay, int $attempt): int
@@ -197,11 +229,22 @@ protected function calculateBackoffDelay(int $baseDelay, int $attempt): int
197229
/**
198230
* Determine if an error is retryable.
199231
*
200-
* @param Throwable $e The exception to check
232+
* @param \Throwable $e The exception to check
233+
* @param \Fetch\Support\RequestContext|null $context Optional context for per-request retry config
234+
*
201235
* @return bool Whether the error is retryable
202236
*/
203-
protected function isRetryableError(Throwable $e): bool
237+
protected function isRetryableError(\Throwable $e, ?\Fetch\Support\RequestContext $context = null): bool
204238
{
239+
// Use context values if provided, otherwise fall back to handler state
240+
$retryableStatusCodes = null !== $context
241+
? $context->getRetryableStatusCodes()
242+
: $this->retryableStatusCodes;
243+
244+
$retryableExceptions = null !== $context
245+
? $context->getRetryableExceptions()
246+
: $this->retryableExceptions;
247+
205248
$statusCode = null;
206249

207250
// Try to extract status code from a Fetch RequestException
@@ -217,14 +260,14 @@ protected function isRetryableError(Throwable $e): bool
217260
}
218261

219262
// Check if the status code is in our list of retryable codes
220-
$isRetryableStatusCode = $statusCode !== null && in_array($statusCode, $this->retryableStatusCodes, true);
263+
$isRetryableStatusCode = null !== $statusCode && in_array($statusCode, $retryableStatusCodes, true);
221264

222265
// Check if the exception or its previous is one of our retryable exception types
223266
$isRetryableException = false;
224267
$exception = $e;
225268

226269
while ($exception) {
227-
if (in_array(get_class($exception), $this->retryableExceptions, true)) {
270+
if (in_array(get_class($exception), $retryableExceptions, true)) {
228271
$isRetryableException = true;
229272
break;
230273
}

0 commit comments

Comments
 (0)