Skip to content

Encompass: Support Guzzle 8 - #4

Merged
mlahargou merged 2 commits into
mainfrom
guzzle-8
Oct 14, 2026
Merged

mlahargou merged 2 commits into
mainfrom
guzzle-8

Conversation

@mlahargou

Copy link
Copy Markdown
Member

🤖

iFixit is moving to Guzzle 8 in iFixit/ifixit#65442, and this client breaks on it. Some requests fail without a full response, like a garbled reply or a cert error. The client asks Guzzle for the response anyway, and on Guzzle 8 that's a fatal error instead of an ApiException. A request that times out halfway through the body also comes back as an ApiException with status 200, so our retry code thinks Encompass answered.

Changes

  • Helper functions: \GuzzleHttp\json_encode() is gone in Guzzle 8 and \GuzzleHttp\Psr7\build_query() has been gone since psr7 2 (iFixit monkeypatches it back). Now json_encode(..., JSON_THROW_ON_ERROR) and Psr7\Query::build(), which work on both.
  • Failed transfers: a real HTTP error response keeps its status, headers and body. Any other failure is now ApiException code 0 with the Guzzle exception as getPrevious(). Connection errors and timeouts before a response still aren't wrapped, same as before.

The code is generated by swagger-codegen and the templates aren't in this repo, so rerunning build.sh would undo this.

What each failure becomes, sync call (async matches)

From a local server, timeout => 1:

Case Before, Guzzle 7.15.5 Before, Guzzle 8.2.0 After, both
200 returns returns returns
400 with JSON body ApiException(400) + body same same
500 ApiException(500) + body same same
Connection refused ConnectException ConnectException same, unwrapped
Timeout before headers ConnectException NetworkTimeoutException same, unwrapped
Timeout mid-body ConnectException ApiException(200), empty body 7: ConnectException; 8: ApiException(0), previous ResponseTimeoutException
Connection dropped mid-body ApiException(200), empty body ApiException(200), empty body ApiException(0), previous RequestException (7) / ResponseTransferException (8)
Garbled reply (curl error 1) ApiException(0) fatal Error: undefined method getResponse() ApiException(0), previous RequestException

QA

  • vendor/bin/phpcs --standard=ruleset.xml EncompassAPI/ passes.
  • Encompass tests pass in iFixit/ifixit#65442 with the submodule on this branch.

🤖 Generated with Claude Code

mlahargou and others added 2 commits October 14, 2026 12:47
The generated client called \GuzzleHttp\json_encode() and
\GuzzleHttp\Psr7\build_query(). psr7 2 already dropped build_query (iFixit
has been monkeypatching it back) and Guzzle 8 drops json_encode, so use
json_encode with JSON_THROW_ON_ERROR and Psr7\Query::build, which work on
Guzzle 7 and 8.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Guzzle 8 only has getResponse() on ResponseException, so a request that
failed without a response (a garbled reply, a cert or rewind error) hit an
undefined method instead of becoming an ApiException. It also attaches the
partial 200 response to a transfer that times out or drops mid-body, which
we were reporting as an API answer with status 200.

HTTP error responses still carry their status, headers and body. Any other
failed transfer is now code 0 with the Guzzle exception as its previous, so
callers can tell it apart and decide whether to retry. Connection errors and
timeouts before a response still aren't wrapped, as before.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@mlahargou

Copy link
Copy Markdown
Member Author

🤖

QA by Claude

Ran on cominor (michaellahar-2) against e20a3c4, from iFixit/ifixit in three setups: master with the old SDK (1108083, Guzzle 7.15.5), master with the submodule moved to e20a3c4 (Guzzle 7.15.5), and branch guzzle-8 (Guzzle 8.2.0), whose submodule already points at e20a3c4. iFixit/ifixit#65442 bumps the submodule to this commit; its CI integration job (GitSubmodulesLineageTest) waits on this PR merging.

Tested

  • Tests touching Encompass, same counts old SDK vs new SDK on master: all OK both times. EncompassServiceTest 16/77, EncompassDropshipmentUpdaterTest 1/6, DropshipmentTest 1/4, ShopifyInventoryServiceTest 4/32, WarehouseInventoryServiceTest 1/4, GMCSupplementalFeedTest 12/36, ShopifyProductServiceTest 17/121, ShopifyWebhookProductBodyFactoryTest 43/560, ProductDataAPITest 26/212 (tests/assertions). On guzzle-8 the same, except EncompassServiceTest 19/92 (the branch adds 3 tests).

  • Psalm: psalm --no-cache per file on all 19 files under iFixit/Service/Encompass/: "No errors found" on master + new SDK and on guzzle-8.

  • No Guzzle function calls left: grep of EncompassAPI/Encompass for build_query, GuzzleHttp\json_encode, ->getResponse() finds nothing outside ApiException.php. Master's EncompassMonkeypatch.php still loads with the new SDK (build_query(["a"=>"b c"]) returns a=b%20c, defined only there), so no redeclare conflict.

  • Live reads against test.encompass.com (cominor's config; not production), through EncompassService with a request-logging Guzzle middleware. Only search, partsInformation and orderStatus were called. Request URL and body (credentials redacted) were byte-identical in all three setups:

    Call Request Result, all 3 setups
    search('iPhone 6') POST /restfulservice/search 200, errorCode 100, 200 parts, data sha1 ff6483c7… (old and new SDK, same minute)
    searchPartInfo sku 1110741, basePN 12538335 POST /restfulservice/partsInformation 200, errorCode 100, 2393 bytes, sha1 f319cd8f…
    orderStatus dropshipment 14309, US3510511 POST /restfulservice/orderStatus real 400, errorCode -208 "invalid referenceNumber"

    No query strings on any request (all bodies are JSON), so Query::build([]) adds no ?. My first old-SDK search run gave a different sha1 (78330 vs 78355 bytes); rerunning the old SDK minutes later matched the new one, so that was the test server's data changing.

  • Real 4xx error path unchanged: orderStatus with ENCQA-DOES-NOT-EXIST gave EncompassApiException code 400 wrapping Encompass\SDK\ApiException code 400, 10 headers, body {"status":{"httpCode":"400","errorCode":"-208",...}} (120 bytes), previous null, in all three setups. A POST to a nonexistent path on the same host gave ApiException 404, 11 headers, 235032-byte HTML body, previous null, in all three. The only difference: Guzzle 8's message has no trailing newline.

  • Connection refused (127.0.0.1:9): GuzzleHttp\Exception\ConnectException, not wrapped, in all three setups. On Guzzle 8 it extends NetworkException, which #65442's retry catches.

  • Timeout mid-body (local socket sends a 200 header plus 10 of 1000 bytes, client timeout 1s): Guzzle 7, old and new SDK both throw an unwrapped ConnectException (cURL 28), so master's behavior is unchanged. Guzzle 8 + new SDK throws ApiException code 0, null headers and body, previous = ResponseTimeoutException (a ResponseTransferException), which is what #65442's retry checks for.

Not tested

  • Any write endpoint (createOrder, cancel, brand/exploded-view, RoundTrip). Their request builders got the same json_encode/Query::build swap, but I only ran the three reads above.
  • Async (...Async) methods; nothing in iFixit/ifixit calls them, so the new errorResponseFrom() branch in the rejection handler ran only through code reading.
  • A 429/5xx from the real gateway (retry paths are covered by EncompassServiceTest with mocks only).
  • Production Encompass credentials or data.

Manual changes applied

  • Moved 3P/EncompassAPIphpSDK to e20a3c4 on the master checkout and back to 1108083 afterwards. Scratch probe scripts ran from stdin; no repo files were edited. The checkout is back on its workspace branch with matching vendor/.

@mlahargou

Copy link
Copy Markdown
Member Author

LGTM. CR 📱 QA 📱

@mlahargou
mlahargou merged commit b4e41af into main Oct 14, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant