Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Dominik Zogg
committed
May 11, 2019
1 parent
675d657
commit f3d3c0e
Showing
4 changed files
with
356 additions
and
5 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,86 @@ | ||
<?php | ||
|
||
declare(strict_types=1); | ||
|
||
namespace Chubbyphp\ApiHttp\Middleware; | ||
|
||
use Chubbyphp\ApiHttp\ApiProblem\ClientError\NotAcceptable; | ||
use Chubbyphp\ApiHttp\ApiProblem\ClientError\UnsupportedMediaType; | ||
use Chubbyphp\ApiHttp\Manager\ResponseManagerInterface; | ||
use Chubbyphp\Negotiation\AcceptNegotiatorInterface; | ||
use Chubbyphp\Negotiation\ContentTypeNegotiatorInterface; | ||
use Psr\Http\Message\ResponseInterface; | ||
use Psr\Http\Message\ServerRequestInterface; | ||
use Psr\Http\Server\MiddlewareInterface; | ||
use Psr\Http\Server\RequestHandlerInterface; | ||
|
||
final class AcceptAndContentTypeMiddleware implements MiddlewareInterface | ||
{ | ||
/** | ||
* @var AcceptNegotiatorInterface | ||
*/ | ||
private $acceptNegotiator; | ||
|
||
/** | ||
* @var ContentTypeNegotiatorInterface | ||
*/ | ||
private $contentTypeNegotiator; | ||
|
||
/** | ||
* @var ResponseManagerInterface | ||
*/ | ||
private $responseManager; | ||
|
||
/** | ||
* @param AcceptNegotiatorInterface $acceptNegotiator | ||
* @param ContentTypeNegotiatorInterface $contentTypeNegotiator | ||
* @param ResponseManagerInterface $responseManager | ||
*/ | ||
public function __construct( | ||
AcceptNegotiatorInterface $acceptNegotiator, | ||
ContentTypeNegotiatorInterface $contentTypeNegotiator, | ||
ResponseManagerInterface $responseManager | ||
) { | ||
$this->acceptNegotiator = $acceptNegotiator; | ||
$this->contentTypeNegotiator = $contentTypeNegotiator; | ||
$this->responseManager = $responseManager; | ||
} | ||
|
||
/** | ||
* @param ServerRequestInterface $request | ||
* @param RequestHandlerInterface $handler | ||
* | ||
* @return ResponseInterface | ||
*/ | ||
public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface | ||
{ | ||
if (null === $accept = $this->acceptNegotiator->negotiate($request)) { | ||
$supportedMediaTypes = $this->acceptNegotiator->getSupportedMediaTypes(); | ||
return $this->responseManager->createFromApiProblem( | ||
new NotAcceptable( | ||
$request->getHeaderLine('Accept'), | ||
$supportedMediaTypes | ||
), | ||
$supportedMediaTypes[0] | ||
); | ||
} | ||
|
||
$request = $request->withAttribute('accept', $accept->getValue()); | ||
|
||
if (in_array($request->getMethod(), ['POST', 'PUT', 'PATCH'], true)) { | ||
if (null === $contentType = $this->contentTypeNegotiator->negotiate($request)) { | ||
return $this->responseManager->createFromApiProblem( | ||
new UnsupportedMediaType( | ||
$request->getHeaderLine('Content-Type'), | ||
$this->contentTypeNegotiator->getSupportedMediaTypes() | ||
), | ||
$accept->getValue() | ||
); | ||
} | ||
|
||
$request = $request->withAttribute('contentType', $contentType->getValue()); | ||
} | ||
|
||
return $handler->handle($request); | ||
} | ||
} |
261 changes: 261 additions & 0 deletions
261
tests/Middleware/AcceptAndContentTypeMiddlewareTest.php
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,261 @@ | ||
<?php | ||
|
||
declare(strict_types=1); | ||
|
||
namespace Chubbyphp\Tests\ApiHttp\Middleware; | ||
|
||
use Chubbyphp\ApiHttp\ApiProblem\ClientError\NotAcceptable; | ||
use Chubbyphp\ApiHttp\ApiProblem\ClientError\UnsupportedMediaType; | ||
use Chubbyphp\ApiHttp\Manager\ResponseManagerInterface; | ||
use Chubbyphp\ApiHttp\Middleware\AcceptAndContentTypeMiddleware; | ||
use Chubbyphp\Mock\Argument\ArgumentCallback; | ||
use Chubbyphp\Mock\Call; | ||
use Chubbyphp\Mock\MockByCallsTrait; | ||
use Chubbyphp\Negotiation\AcceptNegotiatorInterface; | ||
use Chubbyphp\Negotiation\ContentTypeNegotiatorInterface; | ||
use Chubbyphp\Negotiation\NegotiatedValueInterface; | ||
use PHPUnit\Framework\MockObject\MockObject; | ||
use PHPUnit\Framework\TestCase; | ||
use Psr\Http\Message\ResponseInterface; | ||
use Psr\Http\Message\ServerRequestInterface; | ||
use Psr\Http\Server\RequestHandlerInterface; | ||
|
||
/** | ||
* @covers \Chubbyphp\ApiHttp\Middleware\AcceptAndContentTypeMiddleware | ||
*/ | ||
class AcceptAndContentTypeMiddlewareTest extends TestCase | ||
{ | ||
use MockByCallsTrait; | ||
|
||
public function testWithoutAccept(): void | ||
{ | ||
/** @var ServerRequestInterface|MockObject $request */ | ||
$request = $this->getMockByCalls(ServerRequestInterface::class, [ | ||
Call::create('getHeaderLine')->with('Accept')->willReturn('application/xml'), | ||
]); | ||
|
||
/** @var ResponseInterface|MockObject $response */ | ||
$response = $this->getMockByCalls(ResponseInterface::class, []); | ||
|
||
$requestHandler = new class() implements RequestHandlerInterface { | ||
/** | ||
* @param ServerRequestInterface $request | ||
* | ||
* @return ResponseInterface | ||
*/ | ||
public function handle(ServerRequestInterface $request): ResponseInterface | ||
{ | ||
self::fail('should not be called'); | ||
} | ||
}; | ||
|
||
/** @var AcceptNegotiatorInterface|MockObject $acceptNegotiator */ | ||
$acceptNegotiator = $this->getMockByCalls(AcceptNegotiatorInterface::class, [ | ||
Call::create('negotiate')->with($request)->willReturn(null), | ||
Call::create('getSupportedMediaTypes')->with()->willReturn(['application/json']), | ||
]); | ||
|
||
/** @var ContentTypeNegotiatorInterface|MockObject $contentTypeNegotiator */ | ||
$contentTypeNegotiator = $this->getMockByCalls(ContentTypeNegotiatorInterface::class, []); | ||
|
||
/** @var ResponseManagerInterface|MockObject $responseManager */ | ||
$responseManager = $this->getMockByCalls(ResponseManagerInterface::class, [ | ||
Call::create('createFromApiProblem') | ||
->with( | ||
new ArgumentCallback(function (NotAcceptable $apiProblem) { | ||
self::assertSame('application/xml', $apiProblem->getAccept()); | ||
self::assertSame(['application/json'], $apiProblem->getAcceptables()); | ||
}), | ||
'application/json', | ||
null | ||
) | ||
->willReturn($response), | ||
]); | ||
|
||
$middleware = new AcceptAndContentTypeMiddleware($acceptNegotiator, $contentTypeNegotiator, $responseManager); | ||
|
||
self::assertSame($response, $middleware->process($request, $requestHandler)); | ||
} | ||
|
||
public function testWithAccept(): void | ||
{ | ||
/** @var ServerRequestInterface|MockObject $request */ | ||
$request = $this->getMockByCalls(ServerRequestInterface::class, [ | ||
Call::create('withAttribute')->with('accept', 'application/json')->willReturnSelf(), | ||
Call::create('getMethod')->with()->willReturn('GET'), | ||
]); | ||
|
||
/** @var ResponseInterface|MockObject $response */ | ||
$response = $this->getMockByCalls(ResponseInterface::class, []); | ||
|
||
$requestHandler = new class($response) implements RequestHandlerInterface { | ||
/** | ||
* @var ResponseInterface | ||
*/ | ||
private $response; | ||
|
||
/** | ||
* @param ResponseInterface $response | ||
*/ | ||
public function __construct(ResponseInterface $response) | ||
{ | ||
$this->response = $response; | ||
} | ||
|
||
/** | ||
* @param ServerRequestInterface $request | ||
* | ||
* @return ResponseInterface | ||
*/ | ||
public function handle(ServerRequestInterface $request): ResponseInterface | ||
{ | ||
return $this->response; | ||
} | ||
}; | ||
|
||
/** @var NegotiatedValueInterface|MockObject $accept */ | ||
$accept = $this->getMockByCalls(NegotiatedValueInterface::class, [ | ||
Call::create('getValue')->with()->willReturn('application/json'), | ||
]); | ||
|
||
/** @var AcceptNegotiatorInterface|MockObject $acceptNegotiator */ | ||
$acceptNegotiator = $this->getMockByCalls(AcceptNegotiatorInterface::class, [ | ||
Call::create('negotiate')->with($request)->willReturn($accept), | ||
]); | ||
|
||
/** @var ContentTypeNegotiatorInterface|MockObject $contentTypeNegotiator */ | ||
$contentTypeNegotiator = $this->getMockByCalls(ContentTypeNegotiatorInterface::class, []); | ||
|
||
/** @var ResponseManagerInterface|MockObject $responseManager */ | ||
$responseManager = $this->getMockByCalls(ResponseManagerInterface::class, []); | ||
|
||
$middleware = new AcceptAndContentTypeMiddleware($acceptNegotiator, $contentTypeNegotiator, $responseManager); | ||
|
||
self::assertSame($response, $middleware->process($request, $requestHandler)); | ||
} | ||
|
||
public function testWithoutContentType(): void | ||
{ | ||
/** @var ServerRequestInterface|MockObject $request */ | ||
$request = $this->getMockByCalls(ServerRequestInterface::class, [ | ||
Call::create('withAttribute')->with('accept', 'application/json')->willReturnSelf(), | ||
Call::create('getMethod')->with()->willReturn('POST'), | ||
Call::create('getHeaderLine')->with('Content-Type')->willReturn('application/xml'), | ||
]); | ||
|
||
/** @var ResponseInterface|MockObject $response */ | ||
$response = $this->getMockByCalls(ResponseInterface::class, []); | ||
|
||
$requestHandler = new class() implements RequestHandlerInterface { | ||
/** | ||
* @param ServerRequestInterface $request | ||
* | ||
* @return ResponseInterface | ||
*/ | ||
public function handle(ServerRequestInterface $request): ResponseInterface | ||
{ | ||
self::fail('should not be called'); | ||
} | ||
}; | ||
|
||
/** @var NegotiatedValueInterface|MockObject $accept */ | ||
$accept = $this->getMockByCalls(NegotiatedValueInterface::class, [ | ||
Call::create('getValue')->with()->willReturn('application/json'), | ||
Call::create('getValue')->with()->willReturn('application/json'), | ||
]); | ||
|
||
/** @var AcceptNegotiatorInterface $acceptNegotiator */ | ||
$acceptNegotiator = $this->getMockByCalls(AcceptNegotiatorInterface::class, [ | ||
Call::create('negotiate')->with($request)->willReturn($accept), | ||
]); | ||
|
||
/** @var ContentTypeNegotiatorInterface|MockObject $contentTypeNegotiator */ | ||
$contentTypeNegotiator = $this->getMockByCalls(ContentTypeNegotiatorInterface::class, [ | ||
Call::create('negotiate')->with($request)->willReturn(null), | ||
Call::create('getSupportedMediaTypes')->with()->willReturn(['application/json']), | ||
]); | ||
|
||
/** @var ResponseManagerInterface|MockObject $responseManager */ | ||
$responseManager = $this->getMockByCalls(ResponseManagerInterface::class, [ | ||
Call::create('createFromApiProblem') | ||
->with( | ||
new ArgumentCallback(function (UnsupportedMediaType $apiProblem) { | ||
self::assertSame('application/xml', $apiProblem->getMediaType()); | ||
self::assertSame(['application/json'], $apiProblem->getSupportedMediaTypes()); | ||
}), | ||
'application/json', | ||
null | ||
) | ||
->willReturn($response), | ||
]); | ||
|
||
$middleware = new AcceptAndContentTypeMiddleware($acceptNegotiator, $contentTypeNegotiator, $responseManager); | ||
|
||
self::assertSame($response, $middleware->process($request, $requestHandler)); | ||
} | ||
|
||
public function testWithContentType(): void | ||
{ | ||
/** @var ServerRequestInterface|MockObject $request */ | ||
$request = $this->getMockByCalls(ServerRequestInterface::class, [ | ||
Call::create('withAttribute')->with('accept', 'application/json')->willReturnSelf(), | ||
Call::create('getMethod')->with()->willReturn('POST'), | ||
Call::create('withAttribute')->with('contentType', 'application/json')->willReturnSelf(), | ||
]); | ||
|
||
/** @var ResponseInterface|MockObject $response */ | ||
$response = $this->getMockByCalls(ResponseInterface::class, []); | ||
|
||
$requestHandler = new class($response) implements RequestHandlerInterface { | ||
/** | ||
* @var ResponseInterface | ||
*/ | ||
private $response; | ||
|
||
/** | ||
* @param ResponseInterface $response | ||
*/ | ||
public function __construct(ResponseInterface $response) | ||
{ | ||
$this->response = $response; | ||
} | ||
|
||
/** | ||
* @param ServerRequestInterface $request | ||
* | ||
* @return ResponseInterface | ||
*/ | ||
public function handle(ServerRequestInterface $request): ResponseInterface | ||
{ | ||
return $this->response; | ||
} | ||
}; | ||
|
||
/** @var NegotiatedValueInterface|MockObject $accept */ | ||
$accept = $this->getMockByCalls(NegotiatedValueInterface::class, [ | ||
Call::create('getValue')->with()->willReturn('application/json'), | ||
]); | ||
|
||
/** @var AcceptNegotiatorInterface|MockObject $acceptNegotiator */ | ||
$acceptNegotiator = $this->getMockByCalls(AcceptNegotiatorInterface::class, [ | ||
Call::create('negotiate')->with($request)->willReturn($accept), | ||
]); | ||
|
||
/** @var NegotiatedValueInterface|MockObject $contentType */ | ||
$contentType = $this->getMockByCalls(NegotiatedValueInterface::class, [ | ||
Call::create('getValue')->with()->willReturn('application/json'), | ||
]); | ||
|
||
/** @var ContentTypeNegotiatorInterface|MockObject $contentTypeNegotiator */ | ||
$contentTypeNegotiator = $this->getMockByCalls(ContentTypeNegotiatorInterface::class, [ | ||
Call::create('negotiate')->with($request)->willReturn($contentType), | ||
]); | ||
|
||
/** @var ResponseManagerInterface|MockObject $responseManager */ | ||
$responseManager = $this->getMockByCalls(ResponseManagerInterface::class, []); | ||
|
||
$middleware = new AcceptAndContentTypeMiddleware($acceptNegotiator, $contentTypeNegotiator, $responseManager); | ||
|
||
self::assertSame($response, $middleware->process($request, $requestHandler)); | ||
} | ||
} |