Skip to content

Commit

Permalink
Merge pull request #11 from farzai/add-response-builder
Browse files Browse the repository at this point in the history
Add response builder
  • Loading branch information
parsilver authored Mar 8, 2024
2 parents 5e189dd + f5936ca commit bf32591
Show file tree
Hide file tree
Showing 10 changed files with 411 additions and 39 deletions.
4 changes: 2 additions & 2 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,8 @@
],
"require": {
"php": "^8.1",
"farzai/support": "^1.1",
"guzzlehttp/guzzle": "^7.7",
"farzai/support": "^1.2",
"guzzlehttp/guzzle": "^7.8",
"guzzlehttp/psr7": "^2.5",
"psr/http-client": "^1.0",
"psr/http-message": "^2.0"
Expand Down
2 changes: 1 addition & 1 deletion src/Contracts/ResponseInterface.php
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ public function json(?string $key = null): mixed;
/**
* Throw an exception if the response is not successfull.
*
* @param callable|null $callback Custom callback to throw an exception.
* @param callable|null $callback Custom callback to throw an exception.
*
* @throws \Psr\Http\Client\ClientExceptionInterface
*/
Expand Down
10 changes: 10 additions & 0 deletions src/Exceptions/ClientException.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<?php

namespace Farzai\Transport\Exceptions;

use Psr\Http\Client\ClientExceptionInterface;

class ClientException extends \RuntimeException implements ClientExceptionInterface
{
//
}
2 changes: 1 addition & 1 deletion src/Response.php
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ public function json(?string $key = null): mixed
/**
* Throw an exception if the response is not successfull.
*
* @param callable|null $callback Custom callback to throw an exception.
* @param callable|null $callback Custom callback to throw an exception.
* @return $this
*
* @throws \Psr\Http\Client\ClientExceptionInterface
Expand Down
121 changes: 121 additions & 0 deletions src/ResponseBuilder.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
<?php

namespace Farzai\Transport;

use Psr\Http\Message\ResponseInterface as PsrResponseInterface;

class ResponseBuilder
{
protected int $statusCode = 200;

/**
* @var array<string, array<string>>
*/
protected array $headers = [];

/**
* @var mixed
*/
protected $body;

protected string $version = '1.1';

protected ?string $reason = null;

/**
* Create a new response builder instance.
*/
public static function create(): ResponseBuilder
{
return new static();
}

/**
* Set the response status code.
*/
public function statusCode(int $statusCode): self
{
$this->statusCode = $statusCode;

return $this;
}

/**
* Set the response headers.
*
* @param array<string, array<string>> $headers
*/
public function withHeaders(array $headers): self
{
foreach ($headers as $name => $value) {
$this->withHeader($name, is_array($value) ? $value : [$value]);
}

return $this;
}

/**
* Add a header to the response.
*
* @param mixed $value
*/
public function withHeader(string $name, $value): self
{
if (! isset($this->headers[$name])) {
$this->headers[$name] = [];
}

$this->headers[$name] = array_merge(
$this->headers[$name],
(array) $value
);

return $this;
}

/**
* Set the response body.
*
* @param mixed $body
*/
public function withBody($body): self
{
$this->body = $body;

return $this;
}

/**
* Set the response version.
*/
public function withVersion(string $version): self
{
$this->version = $version;

return $this;
}

/**
* Set the response reason.
*/
public function withReason(string $reason): self
{
$this->reason = $reason;

return $this;
}

/**
* Build the response.
*/
public function build(): PsrResponseInterface
{
return ResponseFactory::create(
$this->statusCode,
$this->headers,
$this->body,
$this->version,
$this->reason
);
}
}
22 changes: 22 additions & 0 deletions src/ResponseFactory.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
<?php

namespace Farzai\Transport;

use GuzzleHttp\Psr7\Response;
use Psr\Http\Message\ResponseInterface as PsrResponseInterface;

class ResponseFactory
{
/**
* Create a new response instance.
*/
public static function create(
int $statusCode,
array $headers = [],
$body = null,
string $version = '1.1',
?string $reason = null
): PsrResponseInterface {
return new Response($statusCode, $headers, $body, $version, $reason);
}
}
101 changes: 101 additions & 0 deletions tests/HttpClient/MockHttpClient.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
<?php

namespace Farzai\Transport\Tests\HttpClient;

use PHPUnit\Framework\TestCase as PHPUnitTestCase;
use Psr\Http\Client\ClientInterface as PsrClientInterface;
use Psr\Http\Message\RequestInterface as PsrRequestInterface;
use Psr\Http\Message\ResponseInterface as PsrResponseInterface;
use Psr\Http\Message\StreamInterface as PsrStreamInterface;

class MockHttpClient extends PHPUnitTestCase implements PsrClientInterface
{
/**
* The sequence of responses.
*
* @var array<int, PsrResponseInterface>
*/
private array $sequence = [];

/**
* Create a new mock http client instance.
*/
public static function new(): self
{
return new static('mock-http-client');
}

/**
* Add a sequence of responses.
*
* @param PsrResponseInterface|callable<PsrResponseInterface> ...$responses
*/
public function addSequence(
PsrResponseInterface|callable ...$responses
): self {
foreach ($responses as $response) {
if (is_callable($response)) {
$response = $response($this);
}

$this->sequence[] = $response;
}

return $this;
}

/**
* Send a PSR-7 request and return a PSR-7 response.
*/
public function sendRequest(
PsrRequestInterface $request
): PsrResponseInterface {
return array_shift($this->sequence);
}

/**
* Create a stream with the given contents.
*/
public function createStream(string $contents): PsrStreamInterface
{
$stream = $this->createMock(PsrStreamInterface::class);
$stream->method('getContents')->willReturn($contents);

return $stream;
}

/**
* Create a response with the given status code and contents.
*/
public function createResponse(
int $statusCode,
string $contents,
array $headers = []
): PsrResponseInterface {
$stream = $this->createStream($contents);

$response = $this->createMock(PsrResponseInterface::class);
$response->method('getStatusCode')->willReturn($statusCode);
$response->method('getBody')->willReturn($stream);
$response->method('getHeaders')->willReturn($headers);

return $response;
}

/**
* Create a response with the given status code and contents.
*/
public static function response(
int $statusCode,
array|string $contents,
array $headers = []
): PsrResponseInterface {
$client = static::new();

if (is_array($contents)) {
$contents = json_encode($contents);
}

return $client->createResponse($statusCode, $contents, $headers);
}
}
53 changes: 53 additions & 0 deletions tests/ResponseExceptionTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
<?php

use Farzai\Transport\Exceptions\ResponseExceptionFactory;
use Farzai\Transport\Response;
use GuzzleHttp\Exception\BadResponseException;
use GuzzleHttp\Exception\ServerException;
use Psr\Http\Message\RequestInterface as PsrRequestInterface;
use Psr\Http\Message\ResponseInterface as PsrResponseInterface;
use Psr\Http\Message\StreamInterface as PsrStreamInterface;

it('should throw bad request', function () {
$stream = $this->createMock(PsrStreamInterface::class);
$stream->method('getContents')->willReturn('{"message":"Bad Request"}');

$response = $this->createMock(PsrResponseInterface::class);
$response->method('getStatusCode')->willReturn(400);
$response->method('getBody')->willReturn($stream);
$response
->method('getHeaders')
->willReturn(['Content-Type' => ['application/json']]);

$psrRequest = $this->createMock(PsrRequestInterface::class);

$exception = ResponseExceptionFactory::create(
new Response($psrRequest, $response)
);

expect($exception)->toBeInstanceOf(BadResponseException::class);
expect($exception->getMessage())->toBe('Bad Request');
expect($exception->getCode())->toBe(400);
});

it('should throw server error', function () {
$stream = $this->createMock(PsrStreamInterface::class);
$stream->method('getContents')->willReturn('{"message":"Internal Server Error"}');

$response = $this->createMock(PsrResponseInterface::class);
$response->method('getStatusCode')->willReturn(500);
$response->method('getBody')->willReturn($stream);
$response
->method('getHeaders')
->willReturn(['Content-Type' => ['application/json']]);

$psrRequest = $this->createMock(PsrRequestInterface::class);

$exception = ResponseExceptionFactory::create(
new Response($psrRequest, $response)
);

expect($exception)->toBeInstanceOf(ServerException::class);
expect($exception->getMessage())->toBe('Internal Server Error');
expect($exception->getCode())->toBe(500);
});
Loading

0 comments on commit bf32591

Please sign in to comment.