Skip to content

Commit f1ad101

Browse files
committed
add a specific "documentation_formats" that may differ from the API "formats". Fixes #1854
1 parent 7a94ef0 commit f1ad101

File tree

8 files changed

+84
-21
lines changed

8 files changed

+84
-21
lines changed

src/Bridge/Symfony/Bundle/DependencyInjection/ApiPlatformExtension.php

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,8 @@ public function load(array $configs, ContainerBuilder $container)
8888
$config = $this->processConfiguration($configuration, $configs);
8989
$formats = $this->getFormats($config['formats']);
9090
$errorFormats = $this->getFormats($config['error_formats']);
91-
$this->handleConfig($container, $config, $formats, $errorFormats);
91+
$documentationFormats = $this->getFormats($config['documentation_formats']);
92+
$this->handleConfig($container, $config, $formats, $errorFormats, $documentationFormats);
9293

9394
$loader = new XmlFileLoader($container, new FileLocator(__DIR__.'/../Resources/config'));
9495
$loader->load('api.xml');
@@ -153,8 +154,9 @@ public function load(array $configs, ContainerBuilder $container)
153154
* @param array $config
154155
* @param array $formats
155156
* @param array $errorFormats
157+
* @param array $documentationFormats
156158
*/
157-
private function handleConfig(ContainerBuilder $container, array $config, array $formats, array $errorFormats)
159+
private function handleConfig(ContainerBuilder $container, array $config, array $formats, array $errorFormats, array $documentationFormats)
158160
{
159161
$container->setParameter('api_platform.enable_entrypoint', $config['enable_entrypoint']);
160162
$container->setParameter('api_platform.enable_docs', $config['enable_docs']);
@@ -164,6 +166,7 @@ private function handleConfig(ContainerBuilder $container, array $config, array
164166
$container->setParameter('api_platform.exception_to_status', $config['exception_to_status']);
165167
$container->setParameter('api_platform.formats', $formats);
166168
$container->setParameter('api_platform.error_formats', $errorFormats);
169+
$container->setParameter('api_platform.documentation_formats', $documentationFormats);
167170
$container->setParameter('api_platform.allow_plain_identifiers', $config['allow_plain_identifiers']);
168171
$container->setParameter('api_platform.eager_loading.enabled', $config['eager_loading']['enabled']);
169172
$container->setParameter('api_platform.eager_loading.max_joins', $config['eager_loading']['max_joins']);

src/Bridge/Symfony/Bundle/DependencyInjection/Configuration.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -222,6 +222,8 @@ public function getConfigTreeBuilder()
222222

223223
$this->addFormatSection($rootNode, 'formats', [
224224
'jsonld' => ['mime_types' => ['application/ld+json']],
225+
]);
226+
$this->addFormatSection($rootNode, 'documentation_formats', [
225227
'json' => ['mime_types' => ['application/json']], // Swagger support
226228
'html' => ['mime_types' => ['text/html']], // Swagger UI support
227229
]);

src/Bridge/Symfony/Bundle/Resources/config/api.xml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,7 @@
130130
<service id="api_platform.listener.request.add_format" class="ApiPlatform\Core\EventListener\AddFormatListener">
131131
<argument type="service" id="api_platform.negotiator" />
132132
<argument>%api_platform.formats%</argument>
133+
<argument>%api_platform.documentation_formats%</argument>
133134

134135
<tag name="kernel.event_listener" event="kernel.request" method="onKernelRequest" priority="7" />
135136
</service>
@@ -204,7 +205,7 @@
204205
<argument>%api_platform.title%</argument>
205206
<argument>%api_platform.description%</argument>
206207
<argument>%api_platform.version%</argument>
207-
<argument>%api_platform.formats%</argument>
208+
<argument>%api_platform.documentation_formats%</argument>
208209
</service>
209210

210211
<service id="api_platform.action.exception" class="ApiPlatform\Core\Action\ExceptionAction" public="true">

src/Bridge/Symfony/Bundle/Resources/config/swagger.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@
5858
<argument>%api_platform.title%</argument>
5959
<argument>%api_platform.description%</argument>
6060
<argument>%api_platform.version%</argument>
61-
<argument>%api_platform.formats%</argument>
61+
<argument>%api_platform.documentation_formats%</argument>
6262
<argument>%api_platform.oauth.enabled%</argument>
6363
<argument>%api_platform.oauth.clientId%</argument>
6464
<argument>%api_platform.oauth.clientSecret%</argument>

src/EventListener/AddFormatListener.php

Lines changed: 26 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -28,12 +28,14 @@ final class AddFormatListener
2828
{
2929
private $negotiator;
3030
private $formats;
31+
private $documentationFormats;
3132
private $mimeTypes;
3233

33-
public function __construct(Negotiator $negotiator, array $formats)
34+
public function __construct(Negotiator $negotiator, array $formats, array $documentationFormats)
3435
{
3536
$this->negotiator = $negotiator;
3637
$this->formats = $formats;
38+
$this->documentationFormats = $documentationFormats;
3739
}
3840

3941
/**
@@ -51,13 +53,15 @@ public function onKernelRequest(GetResponseEvent $event)
5153
return;
5254
}
5355

54-
$this->populateMimeTypes();
55-
$this->addRequestFormats($request, $this->formats);
56+
$requestAcceptedFormats = $this->getRequestFormats($request);
57+
58+
$this->populateMimeTypes($requestAcceptedFormats);
59+
$this->addRequestFormats($request, $requestAcceptedFormats);
5660

5761
// Empty strings must be converted to null because the Symfony router doesn't support parameter typing before 3.2 (_format)
5862
if (null === $routeFormat = $request->attributes->get('_format') ?: null) {
5963
$mimeTypes = array_keys($this->mimeTypes);
60-
} elseif (!isset($this->formats[$routeFormat])) {
64+
} elseif (!isset($requestAcceptedFormats[$routeFormat])) {
6165
throw new NotFoundHttpException(sprintf('Format "%s" is not supported', $routeFormat));
6266
} else {
6367
$mimeTypes = Request::getMimeTypes($routeFormat);
@@ -88,7 +92,7 @@ public function onKernelRequest(GetResponseEvent $event)
8892
}
8993

9094
// Finally, if no Accept header nor Symfony request format is set, return the default format
91-
foreach ($this->formats as $format => $mimeType) {
95+
foreach ($requestAcceptedFormats as $format => $mimeType) {
9296
$request->setRequestFormat($format);
9397

9498
return;
@@ -110,15 +114,17 @@ private function addRequestFormats(Request $request, array $formats)
110114

111115
/**
112116
* Populates the $mimeTypes property.
117+
*
118+
* @param array $requestAcceptedFormats
113119
*/
114-
private function populateMimeTypes()
120+
private function populateMimeTypes(array $requestAcceptedFormats)
115121
{
116122
if (null !== $this->mimeTypes) {
117123
return;
118124
}
119125

120126
$this->mimeTypes = [];
121-
foreach ($this->formats as $format => $mimeTypes) {
127+
foreach ($requestAcceptedFormats as $format => $mimeTypes) {
122128
foreach ($mimeTypes as $mimeType) {
123129
$this->mimeTypes[$mimeType] = $format;
124130
}
@@ -145,4 +151,17 @@ private function getNotAcceptableHttpException(string $accept, array $mimeTypes
145151
implode('", "', $mimeTypes)
146152
));
147153
}
154+
155+
private function getRequestFormats(Request $request): array
156+
{
157+
return $this->isDocumentation($request)
158+
? $this->documentationFormats
159+
: $this->formats
160+
;
161+
}
162+
163+
private function isDocumentation(Request $request): bool
164+
{
165+
return 'api_platform.action.documentation' === $request->attributes->get('_controller');
166+
}
148167
}

tests/Bridge/Symfony/Bundle/DependencyInjection/ApiPlatformExtensionTest.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -450,6 +450,7 @@ private function getPartialContainerBuilderProphecy($test = false)
450450
'api_platform.collection.pagination.partial_parameter_name' => 'partial',
451451
'api_platform.description' => 'description',
452452
'api_platform.error_formats' => ['jsonproblem' => ['application/problem+json'], 'jsonld' => ['application/ld+json']],
453+
'api_platform.documentation_formats' => ['html' => ['text/html'], 'json' => ['application/json']],
453454
'api_platform.formats' => ['jsonld' => ['application/ld+json'], 'jsonhal' => ['application/hal+json']],
454455
'api_platform.exception_to_status' => [ExceptionInterface::class => Response::HTTP_BAD_REQUEST, InvalidArgumentException::class => Response::HTTP_BAD_REQUEST],
455456
'api_platform.title' => 'title',

tests/Bridge/Symfony/Bundle/DependencyInjection/ConfigurationTest.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,8 @@ public function testDefaultConfig()
5757
'version' => '1.0.0',
5858
'formats' => [
5959
'jsonld' => ['mime_types' => ['application/ld+json']],
60+
],
61+
'documentation_formats' => [
6062
'json' => ['mime_types' => ['application/json']],
6163
'html' => ['mime_types' => ['text/html']],
6264
],

tests/EventListener/AddFormatListenerTest.php

Lines changed: 45 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ public function testNoResourceClass()
3232
$eventProphecy->getRequest()->willReturn($request)->shouldBeCalled();
3333
$event = $eventProphecy->reveal();
3434

35-
$listener = new AddFormatListener(new Negotiator(), ['notexist' => 'application/vnd.notexist']);
35+
$listener = new AddFormatListener(new Negotiator(), ['notexist' => 'application/vnd.notexist'], []);
3636
$listener->onKernelRequest($event);
3737

3838
$this->assertNull($request->getFormat('application/vnd.notexist'));
@@ -47,7 +47,7 @@ public function testSupportedRequestFormat()
4747
$eventProphecy->getRequest()->willReturn($request)->shouldBeCalled();
4848
$event = $eventProphecy->reveal();
4949

50-
$listener = new AddFormatListener(new Negotiator(), ['xml' => ['text/xml']]);
50+
$listener = new AddFormatListener(new Negotiator(), ['xml' => ['text/xml']], []);
5151
$listener->onKernelRequest($event);
5252

5353
$this->assertSame('xml', $request->getRequestFormat());
@@ -63,7 +63,7 @@ public function testRespondFlag()
6363
$eventProphecy->getRequest()->willReturn($request)->shouldBeCalled();
6464
$event = $eventProphecy->reveal();
6565

66-
$listener = new AddFormatListener(new Negotiator(), ['xml' => ['text/xml']]);
66+
$listener = new AddFormatListener(new Negotiator(), ['xml' => ['text/xml']], []);
6767
$listener->onKernelRequest($event);
6868

6969
$this->assertSame('xml', $request->getRequestFormat());
@@ -83,7 +83,7 @@ public function testUnsupportedRequestFormat()
8383
$eventProphecy->getRequest()->willReturn($request)->shouldBeCalled();
8484
$event = $eventProphecy->reveal();
8585

86-
$listener = new AddFormatListener(new Negotiator(), ['json' => ['application/json']]);
86+
$listener = new AddFormatListener(new Negotiator(), ['json' => ['application/json']], []);
8787
$listener->onKernelRequest($event);
8888

8989
$this->assertSame('json', $request->getRequestFormat());
@@ -98,7 +98,7 @@ public function testSupportedAcceptHeader()
9898
$eventProphecy->getRequest()->willReturn($request)->shouldBeCalled();
9999
$event = $eventProphecy->reveal();
100100

101-
$listener = new AddFormatListener(new Negotiator(), ['binary' => ['application/octet-stream'], 'json' => ['application/json']]);
101+
$listener = new AddFormatListener(new Negotiator(), ['binary' => ['application/octet-stream'], 'json' => ['application/json']], []);
102102
$listener->onKernelRequest($event);
103103

104104
$this->assertSame('json', $request->getRequestFormat());
@@ -113,7 +113,7 @@ public function testAcceptAllHeader()
113113
$eventProphecy->getRequest()->willReturn($request)->shouldBeCalled();
114114
$event = $eventProphecy->reveal();
115115

116-
$listener = new AddFormatListener(new Negotiator(), ['binary' => ['application/octet-stream'], 'json' => ['application/json']]);
116+
$listener = new AddFormatListener(new Negotiator(), ['binary' => ['application/octet-stream'], 'json' => ['application/json']], []);
117117
$listener->onKernelRequest($event);
118118

119119
$this->assertSame('binary', $request->getRequestFormat());
@@ -133,7 +133,7 @@ public function testUnsupportedAcceptHeader()
133133
$eventProphecy->getRequest()->willReturn($request)->shouldBeCalled();
134134
$event = $eventProphecy->reveal();
135135

136-
$listener = new AddFormatListener(new Negotiator(), ['binary' => ['application/octet-stream'], 'json' => ['application/json']]);
136+
$listener = new AddFormatListener(new Negotiator(), ['binary' => ['application/octet-stream'], 'json' => ['application/json']], []);
137137
$listener->onKernelRequest($event);
138138
}
139139

@@ -150,7 +150,7 @@ public function testInvalidAcceptHeader()
150150
$eventProphecy->getRequest()->willReturn($request)->shouldBeCalled();
151151
$event = $eventProphecy->reveal();
152152

153-
$listener = new AddFormatListener(new Negotiator(), ['json' => ['application/json']]);
153+
$listener = new AddFormatListener(new Negotiator(), ['json' => ['application/json']], []);
154154
$listener->onKernelRequest($event);
155155
}
156156

@@ -164,7 +164,7 @@ public function testAcceptHeaderTakePrecedenceOverRequestFormat()
164164
$eventProphecy->getRequest()->willReturn($request)->shouldBeCalled();
165165
$event = $eventProphecy->reveal();
166166

167-
$listener = new AddFormatListener(new Negotiator(), ['xml' => ['application/xml'], 'json' => ['application/json']]);
167+
$listener = new AddFormatListener(new Negotiator(), ['xml' => ['application/xml'], 'json' => ['application/json']], []);
168168
$listener->onKernelRequest($event);
169169

170170
$this->assertSame('json', $request->getRequestFormat());
@@ -182,7 +182,42 @@ public function testInvalidRouteFormat()
182182
$eventProphecy->getRequest()->willReturn($request)->shouldBeCalled();
183183
$event = $eventProphecy->reveal();
184184

185-
$listener = new AddFormatListener(new Negotiator(), ['json' => ['application/json']]);
185+
$listener = new AddFormatListener(new Negotiator(), ['json' => ['application/json']], []);
186186
$listener->onKernelRequest($event);
187187
}
188+
189+
public function testSupportedDocumentationAcceptHeader()
190+
{
191+
$request = new Request([], [], ['_api_respond' => '1', '_controller' => 'api_platform.action.documentation']);
192+
$request->setRequestFormat('json');
193+
194+
$eventProphecy = $this->prophesize(GetResponseEvent::class);
195+
$eventProphecy->getRequest()->willReturn($request)->shouldBeCalled();
196+
$event = $eventProphecy->reveal();
197+
198+
$listener = new AddFormatListener(new Negotiator(), ['xml' => ['text/xml']], ['json' => ['application/json']]);
199+
$listener->onKernelRequest($event);
200+
201+
$this->assertSame('json', $request->getRequestFormat());
202+
$this->assertSame('application/json', $request->getMimeType($request->getRequestFormat()));
203+
}
204+
205+
/**
206+
* @expectedException \Symfony\Component\HttpKernel\Exception\NotAcceptableHttpException
207+
* @expectedExceptionMessage Requested format "application/ld+json" is not supported. Supported MIME types are "application/json".
208+
*/
209+
public function testUnsupportedDocumentationRequestFormat()
210+
{
211+
$request = new Request([], [], ['_api_respond' => '1', '_controller' => 'api_platform.action.documentation']);
212+
$request->setRequestFormat('jsonld');
213+
214+
$eventProphecy = $this->prophesize(GetResponseEvent::class);
215+
$eventProphecy->getRequest()->willReturn($request)->shouldBeCalled();
216+
$event = $eventProphecy->reveal();
217+
218+
$listener = new AddFormatListener(new Negotiator(), ['xml' => ['text/xml']], ['json' => ['application/json']]);
219+
$listener->onKernelRequest($event);
220+
221+
$this->assertSame('json', $request->getRequestFormat());
222+
}
188223
}

0 commit comments

Comments
 (0)