Added new handled error for when request body is not valid JSON

This commit is contained in:
Alejandro Celaya 2023-01-02 13:33:24 +01:00
parent 921f303404
commit 812c5f4993
4 changed files with 90 additions and 12 deletions

View file

@ -0,0 +1,29 @@
<?php
declare(strict_types=1);
namespace Shlinkio\Shlink\Core\Exception;
use Fig\Http\Message\StatusCodeInterface;
use JsonException;
use Mezzio\ProblemDetails\Exception\CommonProblemDetailsExceptionTrait;
use Mezzio\ProblemDetails\Exception\ProblemDetailsExceptionInterface;
use function Shlinkio\Shlink\Core\toProblemDetailsType;
class MalformedBodyException extends InvalidArgumentException implements ProblemDetailsExceptionInterface
{
use CommonProblemDetailsExceptionTrait;
public static function forInvalidJson(JsonException $prev): self
{
$e = new self('Provided request does not contain a valid JSON body.', previous: $prev);
$e->detail = $e->getMessage();
$e->title = 'Malformed request body';
$e->type = toProblemDetailsType('malformed-request-body');
$e->status = StatusCodeInterface::STATUS_BAD_REQUEST;
return $e;
}
}

View file

@ -0,0 +1,27 @@
<?php
declare(strict_types=1);
namespace ShlinkioTest\Shlink\Core\Exception;
use Fig\Http\Message\StatusCodeInterface;
use JsonException;
use PHPUnit\Framework\TestCase;
use Shlinkio\Shlink\Core\Exception\MalformedBodyException;
class MalformedBodyExceptionTest extends TestCase
{
/** @test */
public function createsExpectedException(): void
{
$prev = new JsonException();
$e = MalformedBodyException::forInvalidJson($prev);
self::assertEquals($prev, $e->getPrevious());
self::assertEquals('Provided request does not contain a valid JSON body.', $e->getMessage());
self::assertEquals('Provided request does not contain a valid JSON body.', $e->getDetail());
self::assertEquals('Malformed request body', $e->getTitle());
self::assertEquals('https://shlink.io/api/error/malformed-request-body', $e->getType());
self::assertEquals(StatusCodeInterface::STATUS_BAD_REQUEST, $e->getStatus());
}
}

View file

@ -5,10 +5,12 @@ declare(strict_types=1);
namespace Shlinkio\Shlink\Rest\Middleware; namespace Shlinkio\Shlink\Rest\Middleware;
use Fig\Http\Message\RequestMethodInterface; use Fig\Http\Message\RequestMethodInterface;
use JsonException;
use Psr\Http\Message\ResponseInterface as Response; use Psr\Http\Message\ResponseInterface as Response;
use Psr\Http\Message\ServerRequestInterface as Request; use Psr\Http\Message\ServerRequestInterface as Request;
use Psr\Http\Server\MiddlewareInterface; use Psr\Http\Server\MiddlewareInterface;
use Psr\Http\Server\RequestHandlerInterface; use Psr\Http\Server\RequestHandlerInterface;
use Shlinkio\Shlink\Core\Exception\MalformedBodyException;
use function Functional\contains; use function Functional\contains;
use function Shlinkio\Shlink\Common\json_decode; use function Shlinkio\Shlink\Common\json_decode;
@ -42,7 +44,11 @@ class BodyParserMiddleware implements MiddlewareInterface, RequestMethodInterfac
return $request; return $request;
} }
$parsedJson = json_decode($rawBody); try {
return $request->withParsedBody($parsedJson); $parsedJson = json_decode($rawBody);
return $request->withParsedBody($parsedJson);
} catch (JsonException $e) {
throw MalformedBodyException::forInvalidJson($e);
}
} }
} }

View file

@ -7,10 +7,12 @@ namespace ShlinkioTest\Shlink\Rest\Middleware;
use Laminas\Diactoros\Response; use Laminas\Diactoros\Response;
use Laminas\Diactoros\ServerRequest; use Laminas\Diactoros\ServerRequest;
use Laminas\Diactoros\Stream; use Laminas\Diactoros\Stream;
use PHPUnit\Framework\Assert;
use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\MockObject\MockObject;
use PHPUnit\Framework\TestCase; use PHPUnit\Framework\TestCase;
use Psr\Http\Message\ServerRequestInterface; use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\RequestHandlerInterface; use Psr\Http\Server\RequestHandlerInterface;
use Shlinkio\Shlink\Core\Exception\MalformedBodyException;
use Shlinkio\Shlink\Rest\Middleware\BodyParserMiddleware; use Shlinkio\Shlink\Rest\Middleware\BodyParserMiddleware;
class BodyParserMiddlewareTest extends TestCase class BodyParserMiddlewareTest extends TestCase
@ -65,7 +67,6 @@ class BodyParserMiddlewareTest extends TestCase
/** @test */ /** @test */
public function jsonRequestsAreJsonDecoded(): void public function jsonRequestsAreJsonDecoded(): void
{ {
$test = $this;
$body = new Stream('php://temp', 'wr'); $body = new Stream('php://temp', 'wr');
$body->write('{"foo": "bar", "bar": ["one", 5]}'); $body->write('{"foo": "bar", "bar": ["one", 5]}');
$request = (new ServerRequest())->withMethod('PUT') $request = (new ServerRequest())->withMethod('PUT')
@ -73,16 +74,31 @@ class BodyParserMiddlewareTest extends TestCase
$handler = $this->createMock(RequestHandlerInterface::class); $handler = $this->createMock(RequestHandlerInterface::class);
$handler->expects($this->once())->method('handle')->with( $handler->expects($this->once())->method('handle')->with(
$this->isInstanceOf(ServerRequestInterface::class), $this->isInstanceOf(ServerRequestInterface::class),
)->willReturnCallback( )->willReturnCallback(function (ServerRequestInterface $req) {
function (ServerRequestInterface $req) use ($test) { Assert::assertEquals([
$test->assertEquals([ 'foo' => 'bar',
'foo' => 'bar', 'bar' => ['one', 5],
'bar' => ['one', 5], ], $req->getParsedBody());
], $req->getParsedBody());
return new Response(); return new Response();
}, });
);
$this->middleware->process($request, $handler);
}
/** @test */
public function invalidBodyResultsInException(): void
{
$body = new Stream('php://temp', 'wr');
$body->write('{"foo": "bar", "bar": ["one');
$request = (new ServerRequest())->withMethod('PUT')
->withBody($body);
$handler = $this->createMock(RequestHandlerInterface::class);
$handler->expects($this->never())->method('handle');
$this->expectException(MalformedBodyException::class);
$this->expectExceptionMessage('Provided request does not contain a valid JSON body.');
$this->middleware->process($request, $handler); $this->middleware->process($request, $handler);
} }