Skip to content

Commit b02854f

Browse files
authored
Merge pull request #2 from Setono/idempotent-order-cancel
Made the `SalesOrderEndpoint::cancel` idempotent
2 parents 930d3cd + 5ee9c5e commit b02854f

File tree

5 files changed

+93
-4
lines changed

5 files changed

+93
-4
lines changed

src/Client/Client.php

+2
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
use Setono\PeakWMS\Client\Endpoint\StockEndpointInterface;
2424
use Setono\PeakWMS\Client\Endpoint\WebhookEndpoint;
2525
use Setono\PeakWMS\Client\Endpoint\WebhookEndpointInterface;
26+
use Setono\PeakWMS\Exception\BadRequestException;
2627
use Setono\PeakWMS\Exception\InternalServerErrorException;
2728
use Setono\PeakWMS\Exception\NotAuthorizedException;
2829
use Setono\PeakWMS\Exception\NotFoundException;
@@ -268,6 +269,7 @@ private static function assertStatusCode(ResponseInterface $response): void
268269
return;
269270
}
270271

272+
BadRequestException::assert($response);
271273
NotAuthorizedException::assert($response);
272274
NotFoundException::assert($response);
273275
TooManyRequestsException::assert($response);

src/Client/Endpoint/SalesOrderEndpoint.php

+8-1
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
namespace Setono\PeakWMS\Client\Endpoint;
66

77
use Setono\PeakWMS\DataTransferObject\SalesOrder\SalesOrder;
8+
use Setono\PeakWMS\Exception\BadRequestException;
89

910
/**
1011
* @extends Endpoint<SalesOrder>
@@ -25,7 +26,13 @@ final class SalesOrderEndpoint extends Endpoint implements SalesOrderEndpointInt
2526

2627
public function cancel(string $id): void
2728
{
28-
$this->client->put(sprintf('%s/%s/cancel', $this->endpoint, $id));
29+
try {
30+
$this->client->put(sprintf('%s/%s/cancel', $this->endpoint, $id));
31+
} catch (BadRequestException $e) {
32+
if (!str_contains($e->getMessage(), 'cannot be cancelled because it is in state CANCELLED')) {
33+
throw $e;
34+
}
35+
}
2936
}
3037

3138
protected static function getDataClass(): string

src/Exception/BadRequestException.php

+17
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Setono\PeakWMS\Exception;
6+
7+
use Psr\Http\Message\ResponseInterface;
8+
9+
final class BadRequestException extends ResponseAwareException
10+
{
11+
public static function assert(ResponseInterface $response): void
12+
{
13+
if ($response->getStatusCode() === 400) {
14+
throw new self($response);
15+
}
16+
}
17+
}

src/Exception/ResponseAwareException.php

+18-3
Original file line numberDiff line numberDiff line change
@@ -10,22 +10,37 @@ abstract class ResponseAwareException extends \RuntimeException
1010
{
1111
private ResponseInterface $response;
1212

13+
private int $statusCode;
14+
1315
public function __construct(ResponseInterface $response)
1416
{
15-
$message = sprintf('The status code was: %d.', $response->getStatusCode());
16-
1717
$body = (string) $response->getBody();
18+
$message = sprintf('The request failed with status code %d and body: %s', $response->getStatusCode(), $body);
19+
1820
if ('' !== $body) {
19-
$message .= sprintf(' The body was: %s.', $body);
21+
try {
22+
/** @var mixed $data */
23+
$data = json_decode($body, true, 512, \JSON_THROW_ON_ERROR);
24+
if (is_array($data) && isset($data['message']) && is_string($data['message'])) {
25+
$message = $data['message'];
26+
}
27+
} catch (\JsonException) {
28+
}
2029
}
2130

2231
parent::__construct($message);
2332

2433
$this->response = $response;
34+
$this->statusCode = $response->getStatusCode();
2535
}
2636

2737
public function getResponse(): ResponseInterface
2838
{
2939
return $this->response;
3040
}
41+
42+
public function getStatusCode(): int
43+
{
44+
return $this->statusCode;
45+
}
3146
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Setono\PeakWMS\Exception;
6+
7+
use PHPUnit\Framework\TestCase;
8+
use Prophecy\PhpUnit\ProphecyTrait;
9+
use Psr\Http\Message\ResponseInterface;
10+
11+
final class ResponseAwareExceptionTest extends TestCase
12+
{
13+
use ProphecyTrait;
14+
15+
/**
16+
* @test
17+
*/
18+
public function it_handles_json_response(): void
19+
{
20+
$response = $this->prophesize(ResponseInterface::class);
21+
$response->getStatusCode()->willReturn(400);
22+
$response->getBody()->willReturn('{"message":"OpenApi -> Order 1001127819 (CANCELLED) cannot be cancelled because it is in state CANCELLED","messageKey":null,"parameters":null,"errorCode":"4148","translateParams":false}');
23+
24+
$exception = new ConcreteResponseAwareException($response->reveal());
25+
26+
self::assertSame(400, $exception->getStatusCode());
27+
self::assertSame('OpenApi -> Order 1001127819 (CANCELLED) cannot be cancelled because it is in state CANCELLED', $exception->getMessage());
28+
}
29+
30+
/**
31+
* @test
32+
*/
33+
public function it_handles_non_json_response(): void
34+
{
35+
$response = $this->prophesize(ResponseInterface::class);
36+
$response->getStatusCode()->willReturn(400);
37+
$response->getBody()->willReturn('The API failed');
38+
39+
$exception = new ConcreteResponseAwareException($response->reveal());
40+
41+
self::assertSame(400, $exception->getStatusCode());
42+
self::assertSame('The request failed with status code 400 and body: The API failed', $exception->getMessage());
43+
}
44+
}
45+
46+
final class ConcreteResponseAwareException extends ResponseAwareException
47+
{
48+
}

0 commit comments

Comments
 (0)