Skip to content

Commit

Permalink
Merge pull request #3 from lcobucci/implement-basic-formatters
Browse files Browse the repository at this point in the history
Implement basic formatters
  • Loading branch information
lcobucci authored Mar 26, 2018
2 parents 5f81517 + ff5f2a0 commit e1ef09c
Show file tree
Hide file tree
Showing 5 changed files with 271 additions and 15 deletions.
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);
}
}

0 comments on commit e1ef09c

Please sign in to comment.