Skip to content
This repository was archived by the owner on Jul 6, 2024. It is now read-only.

Commit 48f6f2d

Browse files
author
Dominik Zogg
committed
Align/Enhance AcceptAndContentTypeMiddleware (detail, value, supportedValue)
1 parent 719ccca commit 48f6f2d

File tree

2 files changed

+152
-17
lines changed

2 files changed

+152
-17
lines changed

src/Middleware/AcceptAndContentTypeMiddleware.php

Lines changed: 39 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -25,27 +25,35 @@ public function __construct(
2525
public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
2626
{
2727
if (null === $accept = $this->acceptNegotiator->negotiate($request)) {
28-
$supportedAccepts = $this->acceptNegotiator->getSupportedMediaTypes();
28+
$supportedValues = $this->acceptNegotiator->getSupportedMediaTypes();
2929

3030
return $this->responseManager->createFromHttpException(
31-
HttpException::createNotAcceptable([
32-
'accept' => $request->getHeaderLine('Accept'),
33-
'supportedAccepts' => $supportedAccepts,
34-
]),
35-
$supportedAccepts[0],
31+
HttpException::createNotAcceptable(
32+
$this->aggregateData(
33+
'accept',
34+
$request->getHeaderLine('Accept'),
35+
$supportedValues,
36+
)
37+
),
38+
$supportedValues[0],
3639
);
3740
}
3841

39-
$request = $request->withAttribute('accept', $accept->getValue());
42+
$acceptValue = $accept->getValue();
43+
44+
$request = $request->withAttribute('accept', $acceptValue);
4045

4146
if (\in_array($request->getMethod(), ['POST', 'PUT', 'PATCH'], true)) {
4247
if (null === $contentType = $this->contentTypeNegotiator->negotiate($request)) {
4348
return $this->responseManager->createFromHttpException(
44-
HttpException::createUnsupportedMediaType([
45-
'contentType' => $request->getHeaderLine('Content-Type'),
46-
'supportedContentTypes' => $this->contentTypeNegotiator->getSupportedMediaTypes(),
47-
]),
48-
$accept->getValue(),
49+
HttpException::createUnsupportedMediaType(
50+
$this->aggregateData(
51+
'content-type',
52+
$request->getHeaderLine('Content-Type'),
53+
$this->contentTypeNegotiator->getSupportedMediaTypes()
54+
)
55+
),
56+
$acceptValue,
4957
);
5058
}
5159

@@ -54,4 +62,23 @@ public function process(ServerRequestInterface $request, RequestHandlerInterface
5462

5563
return $handler->handle($request);
5664
}
65+
66+
/**
67+
* @param array<string> $supportedValues
68+
*
69+
* @return array<string, array<string>|string>
70+
*/
71+
private function aggregateData(string $header, string $value, array $supportedValues): array
72+
{
73+
return [
74+
'detail' => sprintf(
75+
'%s %s, supportedValues: "%s"',
76+
'' !== $value ? 'Not supported' : 'Missing',
77+
$header,
78+
implode('", ', $supportedValues)
79+
),
80+
'value' => $value,
81+
'supportedValues' => $supportedValues,
82+
];
83+
}
5784
}

tests/Unit/Middleware/AcceptAndContentTypeMiddlewareTest.php

Lines changed: 113 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,55 @@ final class AcceptAndContentTypeMiddlewareTest extends TestCase
2929
use MockByCallsTrait;
3030

3131
public function testWithoutAccept(): void
32+
{
33+
/** @var MockObject|ServerRequestInterface $request */
34+
$request = $this->getMockByCalls(ServerRequestInterface::class, [
35+
Call::create('getHeaderLine')->with('Accept')->willReturn(''),
36+
]);
37+
38+
/** @var MockObject|ResponseInterface $response */
39+
$response = $this->getMockByCalls(ResponseInterface::class, []);
40+
41+
$requestHandler = new class() implements RequestHandlerInterface {
42+
public function handle(ServerRequestInterface $request): ResponseInterface
43+
{
44+
TestCase::fail('should not be called');
45+
}
46+
};
47+
48+
/** @var AcceptNegotiatorInterface|MockObject $acceptNegotiator */
49+
$acceptNegotiator = $this->getMockByCalls(AcceptNegotiatorInterface::class, [
50+
Call::create('negotiate')->with($request)->willReturn(null),
51+
Call::create('getSupportedMediaTypes')->with()->willReturn(['application/json']),
52+
]);
53+
54+
/** @var ContentTypeNegotiatorInterface|MockObject $contentTypeNegotiator */
55+
$contentTypeNegotiator = $this->getMockByCalls(ContentTypeNegotiatorInterface::class, []);
56+
57+
/** @var MockObject|ResponseManagerInterface $responseManager */
58+
$responseManager = $this->getMockByCalls(ResponseManagerInterface::class, [
59+
Call::create('createFromHttpException')
60+
->with(
61+
new ArgumentCallback(static function (HttpExceptionInterface $httpException): void {
62+
self::assertSame(406, $httpException->getStatus());
63+
64+
$data = $httpException->jsonSerialize();
65+
66+
self::assertSame('Missing accept, supportedValues: "application/json"', $data['detail']);
67+
self::assertSame('', $data['value']);
68+
self::assertSame(['application/json'], $data['supportedValues']);
69+
}),
70+
'application/json',
71+
)
72+
->willReturn($response),
73+
]);
74+
75+
$middleware = new AcceptAndContentTypeMiddleware($acceptNegotiator, $contentTypeNegotiator, $responseManager);
76+
77+
self::assertSame($response, $middleware->process($request, $requestHandler));
78+
}
79+
80+
public function testWithoutMatchingAccept(): void
3281
{
3382
/** @var MockObject|ServerRequestInterface $request */
3483
$request = $this->getMockByCalls(ServerRequestInterface::class, [
@@ -63,8 +112,9 @@ public function handle(ServerRequestInterface $request): ResponseInterface
63112

64113
$data = $httpException->jsonSerialize();
65114

66-
self::assertSame('application/xml', $data['accept']);
67-
self::assertSame(['application/json'], $data['supportedAccepts']);
115+
self::assertSame('Not supported accept, supportedValues: "application/json"', $data['detail']);
116+
self::assertSame('application/xml', $data['value']);
117+
self::assertSame(['application/json'], $data['supportedValues']);
68118
}),
69119
'application/json',
70120
)
@@ -125,7 +175,7 @@ public function testWithoutContentType(): void
125175
$request = $this->getMockByCalls(ServerRequestInterface::class, [
126176
Call::create('withAttribute')->with('accept', 'application/json')->willReturnSelf(),
127177
Call::create('getMethod')->with()->willReturn('POST'),
128-
Call::create('getHeaderLine')->with('Content-Type')->willReturn('application/xml'),
178+
Call::create('getHeaderLine')->with('Content-Type')->willReturn(''),
129179
]);
130180

131181
/** @var MockObject|ResponseInterface $response */
@@ -141,6 +191,63 @@ public function handle(ServerRequestInterface $request): ResponseInterface
141191
/** @var MockObject|NegotiatedValueInterface $accept */
142192
$accept = $this->getMockByCalls(NegotiatedValueInterface::class, [
143193
Call::create('getValue')->with()->willReturn('application/json'),
194+
]);
195+
196+
/** @var AcceptNegotiatorInterface $acceptNegotiator */
197+
$acceptNegotiator = $this->getMockByCalls(AcceptNegotiatorInterface::class, [
198+
Call::create('negotiate')->with($request)->willReturn($accept),
199+
]);
200+
201+
/** @var ContentTypeNegotiatorInterface|MockObject $contentTypeNegotiator */
202+
$contentTypeNegotiator = $this->getMockByCalls(ContentTypeNegotiatorInterface::class, [
203+
Call::create('negotiate')->with($request)->willReturn(null),
204+
Call::create('getSupportedMediaTypes')->with()->willReturn(['application/json']),
205+
]);
206+
207+
/** @var MockObject|ResponseManagerInterface $responseManager */
208+
$responseManager = $this->getMockByCalls(ResponseManagerInterface::class, [
209+
Call::create('createFromHttpException')
210+
->with(
211+
new ArgumentCallback(static function (HttpExceptionInterface $httpException): void {
212+
self::assertSame(415, $httpException->getStatus());
213+
214+
$data = $httpException->jsonSerialize();
215+
216+
self::assertSame('Missing content-type, supportedValues: "application/json"', $data['detail']);
217+
self::assertSame('', $data['value']);
218+
self::assertSame(['application/json'], $data['supportedValues']);
219+
}),
220+
'application/json',
221+
)
222+
->willReturn($response),
223+
]);
224+
225+
$middleware = new AcceptAndContentTypeMiddleware($acceptNegotiator, $contentTypeNegotiator, $responseManager);
226+
227+
self::assertSame($response, $middleware->process($request, $requestHandler));
228+
}
229+
230+
public function testWithoutMatchingContentType(): void
231+
{
232+
/** @var MockObject|ServerRequestInterface $request */
233+
$request = $this->getMockByCalls(ServerRequestInterface::class, [
234+
Call::create('withAttribute')->with('accept', 'application/json')->willReturnSelf(),
235+
Call::create('getMethod')->with()->willReturn('POST'),
236+
Call::create('getHeaderLine')->with('Content-Type')->willReturn('application/xml'),
237+
]);
238+
239+
/** @var MockObject|ResponseInterface $response */
240+
$response = $this->getMockByCalls(ResponseInterface::class, []);
241+
242+
$requestHandler = new class() implements RequestHandlerInterface {
243+
public function handle(ServerRequestInterface $request): ResponseInterface
244+
{
245+
TestCase::fail('should not be called');
246+
}
247+
};
248+
249+
/** @var MockObject|NegotiatedValueInterface $accept */
250+
$accept = $this->getMockByCalls(NegotiatedValueInterface::class, [
144251
Call::create('getValue')->with()->willReturn('application/json'),
145252
]);
146253

@@ -164,8 +271,9 @@ public function handle(ServerRequestInterface $request): ResponseInterface
164271

165272
$data = $httpException->jsonSerialize();
166273

167-
self::assertSame('application/xml', $data['contentType']);
168-
self::assertSame(['application/json'], $data['supportedContentTypes']);
274+
self::assertSame('Not supported content-type, supportedValues: "application/json"', $data['detail']);
275+
self::assertSame('application/xml', $data['value']);
276+
self::assertSame(['application/json'], $data['supportedValues']);
169277
}),
170278
'application/json',
171279
)

0 commit comments

Comments
 (0)