From 932946429cfd8f7f0d17c4ae6ebc2f6bcd7670ab Mon Sep 17 00:00:00 2001 From: Michael Dyrynda Date: Sun, 7 Apr 2024 21:42:45 +0930 Subject: [PATCH 1/5] add generic types for dto and dtoOrFail --- src/Http/Response.php | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/Http/Response.php b/src/Http/Response.php index 2d84ae3a..854c4a95 100644 --- a/src/Http/Response.php +++ b/src/Http/Response.php @@ -287,8 +287,11 @@ public function collect(string|int|null $key = null): Collection /** * Cast the response to a DTO. + * + * @template T of object + * @return ($dto is class-string ? T : object) */ - public function dto(): mixed + public function dto(?string $dto = null): mixed { $request = $this->pendingRequest->getRequest(); $connector = $this->pendingRequest->getConnector(); @@ -304,8 +307,11 @@ public function dto(): mixed /** * Convert the response into a DTO or throw a LogicException if the response failed + * + * @template T of object + * @return ($dto is class-string ? T : object) */ - public function dtoOrFail(): mixed + public function dtoOrFail(?string $dto = null): mixed { if ($this->failed()) { throw new LogicException('Unable to create data transfer object as the response has failed.', 0, $this->toException()); From 1c989ef31e697b607cb143ce90d6392f1ec1424c Mon Sep 17 00:00:00 2001 From: Michael Dyrynda Date: Sat, 13 Apr 2024 13:14:50 +0930 Subject: [PATCH 2/5] run php-cs-fixer --- src/Http/Response.php | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/src/Http/Response.php b/src/Http/Response.php index 854c4a95..ca2a6d01 100644 --- a/src/Http/Response.php +++ b/src/Http/Response.php @@ -76,7 +76,7 @@ class Response /** * Create a new response instance. */ - public function __construct(ResponseInterface $psrResponse, PendingRequest $pendingRequest, RequestInterface $psrRequest, Throwable $senderException = null) + public function __construct(ResponseInterface $psrResponse, PendingRequest $pendingRequest, RequestInterface $psrRequest, ?Throwable $senderException = null) { $this->psrRequest = $psrRequest; $this->psrResponse = $psrResponse; @@ -193,7 +193,7 @@ public function getSenderException(): ?Throwable /** * Get the JSON decoded body of the response as an array or scalar value. * - * @param array-key|null $key + * @param array-key|null $key * @return ($key is null ? array : mixed) */ public function json(string|int|null $key = null, mixed $default = null): mixed @@ -214,7 +214,7 @@ public function json(string|int|null $key = null, mixed $default = null): mixed * * Alias of json() * - * @param array-key|null $key + * @param array-key|null $key * @return ($key is null ? array : mixed) */ public function array(int|string|null $key = null, mixed $default = null): mixed @@ -265,9 +265,10 @@ public function xmlReader(): XmlReader * Get the JSON decoded body of the response as a collection. * * Requires Laravel Collections (composer require illuminate/collections) + * * @see https://github.com/illuminate/collections * - * @param array-key|null $key + * @param array-key|null $key * @return \Illuminate\Support\Collection */ public function collect(string|int|null $key = null): Collection @@ -289,6 +290,7 @@ public function collect(string|int|null $key = null): Collection * Cast the response to a DTO. * * @template T of object + * * @return ($dto is class-string ? T : object) */ public function dto(?string $dto = null): mixed @@ -309,6 +311,7 @@ public function dto(?string $dto = null): mixed * Convert the response into a DTO or throw a LogicException if the response failed * * @template T of object + * * @return ($dto is class-string ? T : object) */ public function dtoOrFail(?string $dto = null): mixed @@ -400,7 +403,7 @@ public function serverError(): bool /** * Execute the given callback if there was a server or client error. * - * @param callable($this): (void) $callback + * @param callable($this): (void) $callback * @return $this */ public function onError(callable $callback): static @@ -460,6 +463,7 @@ protected function createException(): Throwable * Throw an exception if a server or client error occurred. * * @return $this + * * @throws \Throwable */ public function throw(): static @@ -504,7 +508,7 @@ public function getRawStream(): mixed /** * Save the body to a file * - * @param string|resource $resourceOrPath + * @param string|resource $resourceOrPath */ public function saveBodyToFile(mixed $resourceOrPath, bool $closeResource = true): void { From 0f2ac03ffc0066f1e2e7de650de76b9cde5b0d22 Mon Sep 17 00:00:00 2001 From: Michael Dyrynda Date: Sat, 13 Apr 2024 13:16:16 +0930 Subject: [PATCH 3/5] dtoOrFail() should pass $dto to dto() --- src/Http/Response.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Http/Response.php b/src/Http/Response.php index ca2a6d01..b7fb84e6 100644 --- a/src/Http/Response.php +++ b/src/Http/Response.php @@ -320,7 +320,7 @@ public function dtoOrFail(?string $dto = null): mixed throw new LogicException('Unable to create data transfer object as the response has failed.', 0, $this->toException()); } - return $this->dto(); + return $this->dto($dto); } /** From c8c348c518bd6842d63fd4e340bef871eb70ef55 Mon Sep 17 00:00:00 2001 From: Sammyjo20 <29132017+Sammyjo20@users.noreply.github.com> Date: Sun, 9 Jun 2024 10:48:55 +0100 Subject: [PATCH 4/5] Added type checks --- src/Http/Response.php | 17 ++++++++++----- tests/Feature/DataObjectWrapperTest.php | 29 ++++++++++++++++++++++++- 2 files changed, 40 insertions(+), 6 deletions(-) diff --git a/src/Http/Response.php b/src/Http/Response.php index b7fb84e6..97e4bd4b 100644 --- a/src/Http/Response.php +++ b/src/Http/Response.php @@ -291,15 +291,21 @@ public function collect(string|int|null $key = null): Collection * * @template T of object * - * @return ($dto is class-string ? T : object) + * @return ($type is class-string ? T : object) */ - public function dto(?string $dto = null): mixed + public function dto(?string $type = null): mixed { $request = $this->pendingRequest->getRequest(); $connector = $this->pendingRequest->getConnector(); $dataObject = $request->createDtoFromResponse($this) ?? $connector->createDtoFromResponse($this); + if (! is_null($type) && ! is_null($dataObject) && $dataObject::class !== $type) { + throw new InvalidArgumentException( + message: sprintf('The class-string provided [%s] must match the class-string returned by the connector/request [%s].', $type, $dataObject::class), + ); + } + if ($dataObject instanceof WithResponse) { $dataObject->setResponse($this); } @@ -312,15 +318,16 @@ public function dto(?string $dto = null): mixed * * @template T of object * - * @return ($dto is class-string ? T : object) + * @param class-string|null $type + * @return ($type is class-string ? T : object) */ - public function dtoOrFail(?string $dto = null): mixed + public function dtoOrFail(?string $type = null): mixed { if ($this->failed()) { throw new LogicException('Unable to create data transfer object as the response has failed.', 0, $this->toException()); } - return $this->dto($dto); + return $this->dto($type); } /** diff --git a/tests/Feature/DataObjectWrapperTest.php b/tests/Feature/DataObjectWrapperTest.php index a33fe83d..77c2359a 100644 --- a/tests/Feature/DataObjectWrapperTest.php +++ b/tests/Feature/DataObjectWrapperTest.php @@ -9,6 +9,7 @@ use Saloon\Tests\Fixtures\Requests\DTORequest; use Saloon\Tests\Fixtures\Data\UserWithResponse; use Saloon\Tests\Fixtures\Requests\DTOWithResponseRequest; +use Saloon\Tests\Fixtures\Requests\UserRequest; test('if a dto does not implement the WithResponse interface and HasResponse trait Saloon will not add the original response', function () { $mockClient = new MockClient([ @@ -16,7 +17,8 @@ ]); $response = connector()->send(new DTORequest, $mockClient); - $dto = $response->dto(); + + $dto = $response->dto(User::class); expect($dto)->toBeInstanceOf(User::class); expect($dto)->not->toBeInstanceOf(WithResponse::class); @@ -37,3 +39,28 @@ expect($dto)->toBeInstanceOf(WithResponse::class); expect($dto->getResponse())->toBe($response); }); + +test('if a dto type is provided and the class does not support a dto null is still returned', function () { + $mockClient = new MockClient([ + new MockResponse(['name' => 'Sammyjo20', 'actual_name' => 'Sam', 'twitter' => '@carre_sam']), + ]); + + $response = connector()->send(new UserRequest, $mockClient); + + $dto = $response->dto(User::class); + + expect($dto)->toBeNull(); +}); + +test('if a dto type is provided and the class returned doesnt match an exception is thrown', function () { + $mockClient = new MockClient([ + new MockResponse(['name' => 'Sammyjo20', 'actual_name' => 'Sam', 'twitter' => '@carre_sam']), + ]); + + $response = connector()->send(new DTORequest, $mockClient); + + $this->expectException(InvalidArgumentException::class); + $this->expectExceptionMessage('The class-string provided [Saloon\Tests\Fixtures\Data\UserWithResponse] must match the class-string returned by the connector/request [Saloon\Tests\Fixtures\Data\User].'); + + $response->dto(UserWithResponse::class); +}); From e6cc0d6587b83afc62778db80d0ec9c810a1be30 Mon Sep 17 00:00:00 2001 From: Sammyjo20 Date: Sun, 9 Jun 2024 09:49:23 +0000 Subject: [PATCH 5/5] =?UTF-8?q?=F0=9F=AA=84=20Code=20Style=20Fixes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- tests/Feature/DataObjectWrapperTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/Feature/DataObjectWrapperTest.php b/tests/Feature/DataObjectWrapperTest.php index 77c2359a..554519d6 100644 --- a/tests/Feature/DataObjectWrapperTest.php +++ b/tests/Feature/DataObjectWrapperTest.php @@ -7,9 +7,9 @@ use Saloon\Tests\Fixtures\Data\User; use Saloon\Contracts\DataObjects\WithResponse; use Saloon\Tests\Fixtures\Requests\DTORequest; +use Saloon\Tests\Fixtures\Requests\UserRequest; use Saloon\Tests\Fixtures\Data\UserWithResponse; use Saloon\Tests\Fixtures\Requests\DTOWithResponseRequest; -use Saloon\Tests\Fixtures\Requests\UserRequest; test('if a dto does not implement the WithResponse interface and HasResponse trait Saloon will not add the original response', function () { $mockClient = new MockClient([