Skip to content

Commit

Permalink
fix: Adjusts response header.
Browse files Browse the repository at this point in the history
  • Loading branch information
gustavofreze committed Jun 14, 2023
1 parent 14588cf commit cce3329
Show file tree
Hide file tree
Showing 8 changed files with 164 additions and 60 deletions.
29 changes: 29 additions & 0 deletions src/HttpContentType.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
<?php

namespace TinyBlocks\Http;

use TinyBlocks\Http\Internal\Header;

/**
* The Content-Type representation header is used to indicate the original media type
* of the resource (prior to any content encoding applied for sending).
*
* @see https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Type
*/
enum HttpContentType: string implements Header
{
case TEXT_HTML = 'text/html';
case TEXT_PLAIN = 'text/plain';
case APPLICATION_PDF = 'application/pdf';
case APPLICATION_JSON = 'application/json';

public function key(): string
{
return 'Content-Type';
}

public function value(): string
{
return $this->value;
}
}
43 changes: 43 additions & 0 deletions src/HttpHeaders.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
<?php

namespace TinyBlocks\Http;

use TinyBlocks\Http\Internal\Header;

/**
* HTTP headers let the client and the server pass additional information with an HTTP request or response.
*
* @see https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers
*/
final class HttpHeaders
{
private array $values = [];

public function add(Header $header): HttpHeaders
{
$key = $header->key();
$this->values['header'][$key] = sprintf('%s: %s', $key, $header->value());

return $this;
}

public function getHeader(): array
{
return $this->values['header'] ?? [];
}

public function hasHeaders(): bool
{
return !empty($this->values);
}

public function hasHeader(string $key): bool
{
return !empty($this->getHeader()[$key]);
}

public function toArray(): array
{
return $this->values;
}
}
8 changes: 4 additions & 4 deletions src/HttpResponse.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,22 +12,22 @@
*/
final class HttpResponse
{
public static function ok(mixed $data, array $headers = []): ResponseInterface
public static function ok(mixed $data, ?HttpHeaders $headers = null): ResponseInterface
{
return Response::from(code: HttpCode::OK, data: $data, headers: $headers);
}

public static function created(mixed $data, array $headers = []): ResponseInterface
public static function created(mixed $data, HttpHeaders $headers = null): ResponseInterface
{
return Response::from(code: HttpCode::CREATED, data: $data, headers: $headers);
}

public static function accepted(mixed $data, array $headers = []): ResponseInterface
public static function accepted(mixed $data, HttpHeaders $headers = null): ResponseInterface
{
return Response::from(code: HttpCode::ACCEPTED, data: $data, headers: $headers);
}

public static function noContent(array $headers = []): ResponseInterface
public static function noContent(HttpHeaders $headers = null): ResponseInterface
{
return Response::from(code: HttpCode::NO_CONTENT, data: null, headers: $headers);
}
Expand Down
10 changes: 10 additions & 0 deletions src/Internal/Header.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<?php

namespace TinyBlocks\Http\Internal;

interface Header
{
public function key(): string;

public function value(): string;
}
16 changes: 9 additions & 7 deletions src/Internal/Response.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\StreamInterface;
use TinyBlocks\Http\HttpCode;
use TinyBlocks\Http\HttpContentType;
use TinyBlocks\Http\HttpHeaders;
use TinyBlocks\Http\Internal\Exceptions\BadMethodCall;
use TinyBlocks\Http\Internal\Stream\StreamFactory;

Expand All @@ -14,14 +16,14 @@ final class Response implements ResponseInterface
private function __construct(
private readonly HttpCode $code,
private readonly StreamInterface $body,
private readonly array $headers
private readonly HttpHeaders $headers
) {
}

public static function from(HttpCode $code, mixed $data, array $headers): ResponseInterface
public static function from(HttpCode $code, mixed $data, ?HttpHeaders $headers): ResponseInterface
{
if (empty($headers)) {
$headers[] = ['Content-Type' => 'application/json'];
if (is_null($headers) || !$headers->hasHeaders()) {
$headers = (new HttpHeaders())->add(header: HttpContentType::APPLICATION_JSON);
}

return new Response(code: $code, body: StreamFactory::from(data: $data), headers: $headers);
Expand Down Expand Up @@ -64,17 +66,17 @@ public function getProtocolVersion(): string

public function getHeaders(): array
{
return $this->headers;
return $this->headers->toArray();
}

public function hasHeader(string $name): bool
{
return isset($this->headers[$name]);
return $this->headers->hasHeader(key: $name);
}

public function getHeader(string $name): array
{
return $this->headers[$name] ?? [];
return $this->headers->getHeader();
}

public function getHeaderLine(string $name): string
Expand Down
2 changes: 1 addition & 1 deletion tests/HttpResponseTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@

class HttpResponseTest extends TestCase
{
private array $defaultHeader = [['Content-Type' => 'application/json']];
private array $defaultHeader = ['header' => ['Content-Type' => 'Content-Type: application/json']];

/**
* @dataProvider providerData
Expand Down
29 changes: 29 additions & 0 deletions tests/Internal/HeadersTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
<?php

namespace TinyBlocks\Http\Internal;

use PHPUnit\Framework\TestCase;
use TinyBlocks\Http\HttpContentType;
use TinyBlocks\Http\HttpHeaders;

class HeadersTest extends TestCase
{
public function testAddAndGetValues(): void
{
$headers = (new HttpHeaders())->add(header: HttpContentType::APPLICATION_JSON);
$expected = ['header' => ['Content-Type' => 'Content-Type: application/json']];

self::assertEquals($expected, $headers->toArray());
}

public function testAddAndGetUniqueValues(): void
{
$headers = (new HttpHeaders())
->add(header: HttpContentType::TEXT_HTML)
->add(header: HttpContentType::APPLICATION_PDF);

$expected = ['header' => ['Content-Type' => 'Content-Type: application/pdf']];

self::assertEquals($expected, $headers->toArray());
}
}
87 changes: 39 additions & 48 deletions tests/Internal/ResponseTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,125 +4,116 @@

use PHPUnit\Framework\TestCase;
use TinyBlocks\Http\HttpCode;
use TinyBlocks\Http\HttpContentType;
use TinyBlocks\Http\HttpHeaders;
use TinyBlocks\Http\Internal\Exceptions\BadMethodCall;
use TinyBlocks\Http\Internal\Stream\StreamFactory;

class ResponseTest extends TestCase
{
private Response $response;
private const TEXT_PLAIN = 'Content-Type: text/plain';
private const APPLICATION_JSON = 'Content-Type: application/json';

protected function setUp(): void
public function testDefaultHeaders(): void
{
$this->response = Response::from(code: HttpCode::OK, data: [], headers: []);
$response = Response::from(code: HttpCode::OK, data: [], headers: null);
$expected = ['header' => ['Content-Type' => self::APPLICATION_JSON]];

self::assertEquals($expected, $response->getHeaders());
}

public function testGetProtocolVersion(): void
{
self::assertEquals('1.1', $this->response->getProtocolVersion());
$response = Response::from(code: HttpCode::OK, data: [], headers: null);

self::assertEquals('1.1', $response->getProtocolVersion());
}

public function testGetHeaders(): void
{
$headers = [
'Content-Type' => 'application/json',
'X-Auth-Token' => 'abc123'
];

$headers = (new HttpHeaders())->add(header: HttpContentType::APPLICATION_JSON);
$response = Response::from(code: HttpCode::OK, data: [], headers: $headers);
$expected = ['Content-Type' => self::APPLICATION_JSON];

self::assertEquals($headers, $response->getHeaders());
self::assertEquals($headers->toArray(), $response->getHeaders());
self::assertEquals($expected, $response->getHeader(name: 'Content-Type'));
}

public function testHasHeader(): void
{
$headers = [
'Content-Type' => 'application/json',
'X-Auth-Token' => 'abc123'
];

$headers = (new HttpHeaders())->add(header: HttpContentType::TEXT_PLAIN);
$response = Response::from(code: HttpCode::OK, data: [], headers: $headers);
$expected = ['Content-Type' => self::TEXT_PLAIN];

self::assertTrue($response->hasHeader(name: 'Content-Type'));
self::assertFalse($response->hasHeader(name: 'Authorization'));
}

public function testGetHeader(): void
{
$headers = [
'Content-Type' => ['application/json'],
'X-Auth-Token' => ['abc123'],
'X-Custom-Header' => ['value1', 'value2']
];

$response = Response::from(code: HttpCode::OK, data: [], headers: $headers);

self::assertEquals(['application/json'], $response->getHeader(name: 'Content-Type'));
self::assertEquals(['abc123'], $response->getHeader(name: 'X-Auth-Token'));
self::assertEquals(['value1', 'value2'], $response->getHeader(name: 'X-Custom-Header'));
self::assertEquals([], $response->getHeader(name: 'Authorization'));
self::assertEquals($expected, $response->getHeader(name: 'Content-Type'));
}

public function testGetHeaderLine(): void
{
$headers = [
'Content-Type' => ['application/json'],
'X-Auth-Token' => ['abc123'],
'X-Custom-Header' => ['value1', 'value2']
];

$headers = (new HttpHeaders())->add(header: HttpContentType::APPLICATION_JSON);
$response = Response::from(code: HttpCode::OK, data: [], headers: $headers);

self::assertEquals('application/json', $response->getHeaderLine(name: 'Content-Type'));
self::assertEquals('abc123', $response->getHeaderLine(name: 'X-Auth-Token'));
self::assertEquals('value1, value2', $response->getHeaderLine(name: 'X-Custom-Header'));
self::assertEquals('', $response->getHeaderLine(name: 'Authorization'));
self::assertEquals(self::APPLICATION_JSON, $response->getHeaderLine(name: 'Content-Type'));
}

public function testExceptionWhenBadMethodCallOnWithBody(): void
{
$response = Response::from(code: HttpCode::OK, data: [], headers: null);

self::expectException(BadMethodCall::class);
self::expectExceptionMessage('Method <TinyBlocks\Http\Internal\Response::withBody> cannot be used.');

$this->response->withBody(body: StreamFactory::from(data: []));
$response->withBody(body: StreamFactory::from(data: []));
}

public function testExceptionWhenBadMethodCallOnWithStatus(): void
{
$response = Response::from(code: HttpCode::OK, data: [], headers: null);

self::expectException(BadMethodCall::class);
self::expectExceptionMessage('Method <TinyBlocks\Http\Internal\Response::withStatus> cannot be used.');

$this->response->withStatus(code: HttpCode::OK->value);
$response->withStatus(code: HttpCode::OK->value);
}

public function testExceptionWhenBadMethodCallOnWithHeader(): void
{
$response = Response::from(code: HttpCode::OK, data: [], headers: null);

self::expectException(BadMethodCall::class);
self::expectExceptionMessage('Method <TinyBlocks\Http\Internal\Response::withHeader> cannot be used.');

$this->response->withHeader(name: '', value: '');
$response->withHeader(name: '', value: '');
}

public function testExceptionWhenBadMethodCallOnWithoutHeader(): void
{
$response = Response::from(code: HttpCode::OK, data: [], headers: null);

self::expectException(BadMethodCall::class);
self::expectExceptionMessage('Method <TinyBlocks\Http\Internal\Response::withoutHeader> cannot be used.');

$this->response->withoutHeader(name: '');
$response->withoutHeader(name: '');
}

public function testExceptionWhenBadMethodCallOnWithAddedHeader(): void
{
$response = Response::from(code: HttpCode::OK, data: [], headers: null);

self::expectException(BadMethodCall::class);
self::expectExceptionMessage('Method <TinyBlocks\Http\Internal\Response::withAddedHeader> cannot be used.');

$this->response->withAddedHeader(name: '', value: '');
$response->withAddedHeader(name: '', value: '');
}

public function testExceptionWhenBadMethodCallOnWithProtocolVersion(): void
{
$response = Response::from(code: HttpCode::OK, data: [], headers: null);

self::expectException(BadMethodCall::class);
self::expectExceptionMessage('Method <TinyBlocks\Http\Internal\Response::withProtocolVersion> cannot be used.');

$this->response->withProtocolVersion(version: '');
$response->withProtocolVersion(version: '');
}
}

0 comments on commit cce3329

Please sign in to comment.