Repository navigation
fix: wrap transport failures (DNS/connect/TLS/timeout) in a catchable NetworkException - #20
Merged
Merged
Conversation
LiyanaAmil
approved these changes
Oct 23, 2026
A DNS failure, refused connection, TLS error or timeout raised the raw GuzzleHttp exception, so catch (ChipApiException $e) never saw it and the caller had to know the transport library. GuzzleClient now catches TransferException and throws Chip\Exception\NetworkException (code 0, getResponseBody() null, transport exception preserved as the previous). Network failures are deliberately not retried: an error raised before a response existed does not prove the request never reached the gateway, so retrying a POST could duplicate it. Tests cover the mapping, the no-retry contract (one attempt), and that 4xx/5xx keep their own exception types.
wzul
force-pushed
the
fix/network-exception
branch
from
October 23, 2026 17:05
c6e601f to
4242cea
Compare
This file contains hidden or 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
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
A request that fails before any HTTP response exists — DNS failure, connection refused, TLS handshake error, timeout — raises the raw Guzzle transport exception:
GuzzleClient::request()only catchesGuzzleClientException(4xx) andGuzzleServerException(5xx), which both extendBadResponseException.ConnectExceptionextendsTransferExceptionand therefore bypasses the SDK's exception mapping entirely: callers must know the transport library to handle a plainly network-shaped failure, andcatch (ChipApiException $e)gives a false sense of coverage.Verified before the fix (
MockHandlerfeeding aConnectException):escaped as: GuzzleHttp\Exception\ConnectException (is ChipApiException? NO).Changes
lib/Exception/NetworkException.php(new) — extendsChipApiException;getCode()is0(no HTTP status),getResponseBody()isnull, and the transport exception is preserved viagetPrevious().lib/Http/GuzzleClient.php— a thirdcatch (TransferException $e)after the 4xx/5xx handlers maps it toNetworkExceptionwith'Network error: ' . $e->getMessage()and logs at error level. Ordering matters:ClientException/ServerExceptionare caught first, so 4xx/5xx mapping is unchanged.tests/NetworkExceptionTest.php(new) — three cases: the mapping (class, message, code, body, previous), the no-retry contract (exactly one attempt), and a guard that 4xx still raises its own exception type.README.md—NetworkExceptionadded to the error-handling example;CHANGELOG.md—### Addedentry under[Unreleased].Verification
composer test— 130 tests, 382 assertions, OK.composer phpstan— no errors (level 8 + bleedingEdge).composer cs-check— clean.lib/Http/GuzzleClient.phpreverted,testConnectFailureBecomesCatchableChipApiExceptionandtestNetworkFailureIsNotRetriedfail withGuzzleHttp\Exception\ConnectException: Connection timed out/Connection refusedescaping — exactly the reported leak.Potential Risks & Review Items
catch (\GuzzleHttp\Exception\ConnectException $e)around an SDK call will no longer catch it, because the SDK now throwsChip\Exception\NetworkException(the Guzzle exception remains reachable throughgetPrevious()). That is the point of the change, but it is a visible surface change — the reason I would cut this as a minor (2.2.0), not a patch.RetryClient::shouldRetry()keys off the status code (5xx/429);NetworkExceptioncarries0, so network failures still fail on the first attempt. This is deliberate: an error raised before a response existed does not prove the request never reached the gateway, so retrying aPOST(create purchase, refund, capture) could duplicate it. Making network failures retryable is a separate decision that needs per-method idempotency analysis.TransferExceptionis broader thanConnectException. It also catchesTooManyRedirectsExceptionand any future Guzzle transport exception, so those are reported asNetworkExceptionwith code0rather than leaking.RequestExceptionwith anullresponse lands there too. I think that is the right bucket, but a reviewer may prefer an explicit allow-list.CURLE_OPERATION_TIMEOUTEDtoConnectException), so the SDK'stimeoutconfig option now produces a catchableNetworkExceptioninstead of a raw transport error.[Unreleased].