Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 16 additions & 3 deletions src/Http/Client/LeverClient.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

namespace Bluelightco\LeverPhp\Http\Client;

use Bluelightco\LeverPhp\Http\Exceptions\LeverApiException;
use Bluelightco\LeverPhp\Http\Middleware\LeverRateStore;
use Bluelightco\LeverPhp\Http\Middleware\QueryStringCleanerMiddleware;
use Bluelightco\LeverPhp\Http\Responses\ApiResponse;
Expand All @@ -14,7 +15,6 @@
use Illuminate\Support\LazyCollection;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\StreamInterface;
use RuntimeException;
use Spatie\GuzzleRateLimiterMiddleware\RateLimiterMiddleware;
use Spatie\GuzzleRateLimiterMiddleware\Store;

Expand Down Expand Up @@ -470,18 +470,31 @@ private function checkExpandOptions(array|string $expand): bool

private function handleException(Exception $e, string $method, string $endpoint, array $options = []): void
{
$statusCode = null;
$responseBody = null;

if ($e instanceof ClientException && $e->getResponse()) {
$statusCode = $e->getResponse()->getStatusCode();
$responseBody = (string) $e->getResponse()->getBody();
}

Log::error("HTTP $method error: ".$e->getMessage(), [
'message' => $e->getMessage(),
'package_context' => [
'package' => 'Bluelightco\LeverPhp',
'method' => $method,
'endpoint' => $endpoint,
'options' => json_encode($options),
'response' => ($e instanceof ClientException ? ($e->getResponse() ? $e->getResponse()->getBody()->getContents() : null) : null),
'response' => $responseBody,
],
'exception' => $e,
]);

throw new RuntimeException("Error executing HTTP $method. Please check the logs for more details.");
throw new LeverApiException(
"Error executing HTTP $method. Please check the logs for more details.",
$statusCode,
$responseBody,
$e,
);
}
}
38 changes: 38 additions & 0 deletions src/Http/Exceptions/LeverApiException.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
<?php

namespace Bluelightco\LeverPhp\Http\Exceptions;

use RuntimeException;
use Throwable;

class LeverApiException extends RuntimeException
{
public function __construct(
string $message,
private readonly ?int $statusCode = null,
private readonly ?string $responseBody = null,
?Throwable $previous = null,
) {
parent::__construct($message, $statusCode ?? 0, $previous);
}

public function getStatusCode(): ?int
{
return $this->statusCode;
}

public function getResponseBody(): ?string
{
return $this->responseBody;
}

public function isClientError(): bool
{
return $this->statusCode !== null && $this->statusCode >= 400 && $this->statusCode < 500;
}

public function isServerError(): bool
{
return $this->statusCode !== null && $this->statusCode >= 500 && $this->statusCode < 600;
}
}
19 changes: 19 additions & 0 deletions tests/OpportunitiesTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

namespace Bluelightco\LeverPhp\Tests;

use Bluelightco\LeverPhp\Http\Exceptions\LeverApiException;
use GuzzleHttp\Psr7\Response;
use Illuminate\Support\LazyCollection;
use RuntimeException;
Expand Down Expand Up @@ -129,6 +130,24 @@ public function fail_to_create_opportunity_when_no_perform_as_parameter_included
);
}

/** @test */
public function client_error_exposes_status_and_response_body()
{
$body = '{"code":"BadRequestError","message":"Input HTML is not valid."}';
$this->mockHandler->append(new Response(400, [], $body));

try {
$this->lever->opportunities()->create(['name' => 'x']);
$this->fail('Expected LeverApiException was not thrown.');
} catch (LeverApiException $e) {
$this->assertSame(400, $e->getStatusCode());
$this->assertSame($body, $e->getResponseBody());
$this->assertTrue($e->isClientError());
$this->assertFalse($e->isServerError());
$this->assertInstanceOf(\GuzzleHttp\Exception\ClientException::class, $e->getPrevious());
}
}

/** @test */
public function fetching_offers()
{
Expand Down
2 changes: 2 additions & 0 deletions tests/TestCase.php
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ class TestCase extends \Orchestra\Testbench\TestCase

protected function setUp(): void
{
parent::setUp();

$this->mockHandler = new MockHandler();

$this->container = [];
Expand Down
Loading