Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement basic formatters #3

Merged
merged 3 commits into from
Mar 26, 2018
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
57 changes: 57 additions & 0 deletions src/Formatter/Json.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
<?php
declare(strict_types=1);

namespace Lcobucci\ContentNegotiation\Formatter;

use Lcobucci\ContentNegotiation\ContentCouldNotBeFormatted;
use Lcobucci\ContentNegotiation\Formatter;
use Throwable;
use const JSON_HEX_AMP;
use const JSON_HEX_APOS;
use const JSON_HEX_QUOT;
use const JSON_HEX_TAG;
use const JSON_UNESCAPED_SLASHES;
use function json_encode;
use function json_last_error;
use function json_last_error_msg;
use function sprintf;

final class Json implements Formatter
{
private const DEFAULT_FLAGS = JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX_AMP | JSON_HEX_QUOT | JSON_UNESCAPED_SLASHES;

/**
* @var int
*/
private $flags;

public function __construct(int $flags = self::DEFAULT_FLAGS)
{
$this->flags = $flags;
}

/**
* {@inheritdoc}
*/
public function format($content): string
{
try {
$encoded = json_encode($content, $this->flags);
} catch (Throwable $exception) {
throw new ContentCouldNotBeFormatted(
'An exception was thrown during JSON formatting',
$exception->getCode(),
$exception
);
}

if ($encoded === false) {
throw new ContentCouldNotBeFormatted(
sprintf('Given data cannot be formatted as JSON: %s', json_last_error_msg()),
json_last_error()
);
}

return $encoded;
}
}
24 changes: 24 additions & 0 deletions src/Formatter/StringCast.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
<?php
declare(strict_types=1);

namespace Lcobucci\ContentNegotiation\Formatter;

use Lcobucci\ContentNegotiation\ContentCouldNotBeFormatted;
use Lcobucci\ContentNegotiation\Formatter;
use function is_object;
use function method_exists;

final class StringCast implements Formatter
{
/**
* {@inheritdoc}
*/
public function format($content): string
{
if (is_object($content) && ! method_exists($content, '__toString')) {
throw new ContentCouldNotBeFormatted('Given data could not be cast to string');
}

return (string) $content;
}
}
23 changes: 8 additions & 15 deletions tests/ContentTypeMiddlewareTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@
use Zend\Diactoros\Response;
use Zend\Diactoros\Response\EmptyResponse;
use Zend\Diactoros\ServerRequest;
use function json_encode;

/**
* @coversDefaultClass \Lcobucci\ContentNegotiation\ContentTypeMiddleware
Expand All @@ -27,6 +26,8 @@ final class ContentTypeMiddlewareTest extends TestCase
* @covers ::__construct()
* @covers ::fromRecommendedSettings()
* @covers ::process()
*
* @uses \Lcobucci\ContentNegotiation\Formatter\Json
*/
public function processShouldReturnFormattedResponseDirectly(): void
{
Expand All @@ -48,6 +49,7 @@ public function processShouldReturnFormattedResponseDirectly(): void
* @covers ::formatResponse()
*
* @uses \Lcobucci\ContentNegotiation\UnformattedResponse
* @uses \Lcobucci\ContentNegotiation\Formatter\Json
*/
public function processShouldReturnAResponseWithErrorWhenFormatterWasNotFound(): void
{
Expand Down Expand Up @@ -75,6 +77,7 @@ public function processShouldReturnAResponseWithErrorWhenFormatterWasNotFound():
* @covers ::formatResponse()
*
* @uses \Lcobucci\ContentNegotiation\UnformattedResponse
* @uses \Lcobucci\ContentNegotiation\Formatter\Json
*/
public function processShouldReturnAResponseWithFormattedContent(): void
{
Expand All @@ -90,7 +93,7 @@ public function processShouldReturnAResponseWithFormattedContent(): void
self::assertInstanceOf(UnformattedResponse::class, $response);
self::assertSame(StatusCodeInterface::STATUS_OK, $response->getStatusCode());
self::assertSame('application/json; charset=UTF-8', $response->getHeaderLine('Content-Type'));
self::assertSame('{"id":1,"name":"Testing"}', (string) $response->getBody());
self::assertJsonStringEqualsJsonString('{"id":1,"name":"Testing"}', (string) $response->getBody());
}

/**
Expand All @@ -103,6 +106,7 @@ public function processShouldReturnAResponseWithFormattedContent(): void
* @covers ::formatResponse()
*
* @uses \Lcobucci\ContentNegotiation\UnformattedResponse
* @uses \Lcobucci\ContentNegotiation\Formatter\Json
*/
public function processShouldReturnAResponseWithFormattedContentEvenWithoutForcingTheCharset(): void
{
Expand All @@ -118,7 +122,7 @@ public function processShouldReturnAResponseWithFormattedContentEvenWithoutForci
self::assertInstanceOf(UnformattedResponse::class, $response);
self::assertSame(StatusCodeInterface::STATUS_OK, $response->getStatusCode());
self::assertSame('application/json', $response->getHeaderLine('Content-Type'));
self::assertSame('{"id":1,"name":"Testing"}', (string) $response->getBody());
self::assertJsonStringEqualsJsonString('{"id":1,"name":"Testing"}', (string) $response->getBody());
}

private function createRequestHandler(ResponseInterface $response): RequestHandlerInterface
Expand Down Expand Up @@ -160,18 +164,7 @@ private function createMiddleware(bool $forceCharset = true): ContentTypeMiddlew
'charset' => $forceCharset,
],
],
[
'application/json' => new class implements Formatter
{
/**
* {@inheritdoc}
*/
public function format($content): string
{
return (string) json_encode($content);
}
},
]
['application/json' => new Formatter\Json()]
);
}
}
111 changes: 111 additions & 0 deletions tests/Formatter/JsonTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
<?php
declare(strict_types=1);

namespace Lcobucci\ContentNegotiation\Tests\Formatter;

use JsonSerializable;
use Lcobucci\ContentNegotiation\ContentCouldNotBeFormatted;
use Lcobucci\ContentNegotiation\Formatter\Json;
use Lcobucci\ContentNegotiation\Tests\PersonDto;
use PHPUnit\Framework\TestCase;
use RuntimeException;
use const JSON_HEX_AMP;
use const JSON_HEX_APOS;
use const JSON_HEX_QUOT;
use const JSON_HEX_TAG;
use const JSON_UNESCAPED_SLASHES;
use function acos;

/**
* @coversDefaultClass \Lcobucci\ContentNegotiation\Formatter\Json
*/
final class JsonTest extends TestCase
{
/**
* @test
*
* @covers ::__construct()
*/
public function constructorShouldAllowTheConfigurationOfEncodingFlags(): void
{
self::assertAttributeSame(JSON_UNESCAPED_SLASHES, 'flags', new Json(JSON_UNESCAPED_SLASHES));
}

/**
* @test
*
* @covers ::__construct()
*/
public function constructorShouldUseDefaultFlagsWhenNothingWasSet(): void
{
self::assertAttributeSame(
JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX_AMP | JSON_HEX_QUOT | JSON_UNESCAPED_SLASHES,
'flags',
new Json()
);
}

/**
* @test
*
* @covers ::format()
*
* @uses \Lcobucci\ContentNegotiation\Formatter\Json::__construct()
*
*/
public function formatShouldReturnAJsonEncodedValue(): void
{
self::assertJsonStringEqualsJsonString(
'{"id":1,"name":"Test"}',
$this->format(new PersonDto(1, 'Test'))
);
}

/**
* @test
*
* @covers ::format()
*
* @uses \Lcobucci\ContentNegotiation\Formatter\Json::__construct()
*/
public function formatShouldRaiseExceptionWhenContentCouldNotBeEncoded(): void
{
$this->expectException(ContentCouldNotBeFormatted::class);
$this->expectExceptionMessage('Inf and NaN cannot be JSON encoded');

$this->format(acos(8));
}

/**
* @test
*
* @covers ::format()
*
* @uses \Lcobucci\ContentNegotiation\Formatter\Json::__construct()
*/
public function formatShouldConvertAnyExceptionDuringJsonSerialization(): void
{
$this->expectException(ContentCouldNotBeFormatted::class);
$this->expectExceptionMessage('An exception was thrown during JSON formatting');

$this->format(
new class implements JsonSerializable
{
public function jsonSerialize(): void
{
throw new RuntimeException('This should be converted');
}
}
);
}

/**
* @param mixed $content
*/
private function format($content): string
{
$formatter = new Json();

return $formatter->format($content);
}
}
71 changes: 71 additions & 0 deletions tests/Formatter/StringCastTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
<?php
declare(strict_types=1);

namespace Lcobucci\ContentNegotiation\Tests\Formatter;

use Lcobucci\ContentNegotiation\ContentCouldNotBeFormatted;
use Lcobucci\ContentNegotiation\Formatter\StringCast;
use PHPUnit\Framework\TestCase;

/**
* @coversDefaultClass \Lcobucci\ContentNegotiation\Formatter\StringCast
*/
final class StringCastTest extends TestCase
{
/**
* @test
* @dataProvider validData
*
* @covers ::format()
*
* @param mixed $content
*/
public function formatShouldSimplyReturnTheStringRepresentationOfTheContent(
string $expected,
$content
): void {
$formatter = new StringCast();

self::assertSame($expected, $formatter->format($content));
}

/**
* @return mixed[][]
*/
public function validData(): array
{
$test = new class
{
public function __toString(): string
{
return 'test';
}
};

return [
['test', 'test'],
['test', $test],
['1', 1],
['1', true],
['', false],
['', null],
];
}

/**
* @test
*
* @covers ::format()
*/
public function formatShouldRaiseExceptionWhenContentCouldNotBeCastToString(): void
{
$this->expectException(ContentCouldNotBeFormatted::class);

$content = new class
{
};

$formatter = new StringCast();
$formatter->format($content);
}
}