Skip to content

feat: preserve HTTP status and response body on client errors#3

Merged
JasonHolderness-bl merged 1 commit into
masterfrom
feature/preserve-client-exception
Apr 21, 2026
Merged

feat: preserve HTTP status and response body on client errors#3
JasonHolderness-bl merged 1 commit into
masterfrom
feature/preserve-client-exception

Conversation

@JasonHolderness-bl

@JasonHolderness-bl JasonHolderness-bl commented Apr 21, 2026

Copy link
Copy Markdown

Summary

  • Introduces LeverApiException (extends RuntimeException) that carries statusCode, responseBody, and isClientError()/isServerError() helpers.
  • handleException now throws this typed exception with the original Guzzle exception chained as $previous.
  • Callers can now distinguish transient 5xx errors (retryable) from permanent 4xx errors (fail fast) and access the Lever error payload for user-facing messaging.

Why

Downstream apps (e.g. spot) were retrying 4xx errors seven times under exponential backoff because the rewrapped RuntimeException carried no status. This produced hundreds of Bugsnag events for a single malformed posting and gave admins no actionable error message.

Backward compatibility

Safe:

  • catch (\RuntimeException $e) still catches — LeverApiException extends RuntimeException.
  • Exception message string is unchanged.
  • Log output is unchanged.
  • catch (\Exception $e) / catch (\Throwable $e) still works.

Minor behavior changes (suggest tagging as v1.1.0, not a patch):

  • $e->getCode() — was always 0; now returns the HTTP status (e.g. 400, 503) for ClientException paths.
  • $e->getPrevious() — was always null; now returns the underlying Guzzle ClientException.

Real-world impact should be zero unless a consumer explicitly checks getCode() === 0 or getPrevious() === null, but these are observable changes, hence minor (not patch) bump.

Test plan

  • vendor/bin/phpunit — all 16 tests pass
  • New test asserts status code, response body, isClientError(), and chained previous exception on a 400 response
  • Pre-existing fail_to_create_opportunity_when_no_perform_as_parameter_included still passes

…se body

Callers could previously only catch a generic RuntimeException with no
way to distinguish transient 5xx from permanent 4xx errors, or to read
the Lever error payload. Introduce LeverApiException (extends
RuntimeException for backward compatibility) that carries statusCode,
responseBody, and the original Guzzle exception as the previous.

Also calls parent::setUp() in the test case so the Log facade is
available during request error paths.
@JasonHolderness-bl
JasonHolderness-bl merged commit 168829b into master Apr 21, 2026
3 checks passed
@JasonHolderness-bl
JasonHolderness-bl deleted the feature/preserve-client-exception branch May 20, 2026 00:15
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