Skip to content

Commit 274f4f5

Browse files
vincentchalamondunglas
authored andcommitted
Update Swagger DocumentationNormalizer for AWS API-Gateway (#1697)
* Update Swagger DocumentationNormalizer for AWS API-Gateway * Support API Gateway on query filter * Update unit tests * Fix unit tests * Add API_Gateway pattern in const * Update unit tests * Create ApiGatewayNormalizer * Update unit tests * Fix review
1 parent dab2eb7 commit 274f4f5

File tree

6 files changed

+355
-3
lines changed

6 files changed

+355
-3
lines changed

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

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,11 @@
3131
<tag name="serializer.normalizer" priority="16" />
3232
</service>
3333

34+
<service id="api_platform.swagger.normalizer.api_gateway" class="ApiPlatform\Core\Swagger\Serializer\ApiGatewayNormalizer" public="false" decorates="api_platform.swagger.normalizer.documentation">
35+
<argument type="service" id="api_platform.swagger.normalizer.api_gateway.inner" />
36+
<tag name="serializer.normalizer" priority="17" />
37+
</service>
38+
3439
<service id="api_platform.swagger.command.swagger_command" class="ApiPlatform\Core\Bridge\Symfony\Bundle\Command\SwaggerCommand">
3540
<argument type="service" id="api_platform.swagger.normalizer.documentation" />
3641
<argument type="service" id="api_platform.metadata.resource.name_collection_factory" />

src/Documentation/Action/DocumentationAction.php

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,11 @@ public function __construct(ResourceNameCollectionFactoryInterface $resourceName
4242
public function __invoke(Request $request = null): Documentation
4343
{
4444
if (null !== $request) {
45-
$request->attributes->set('_api_normalization_context', $request->attributes->get('_api_normalization_context', []) + ['base_url' => $request->getBaseUrl()]);
45+
$context = ['base_url' => $request->getBaseUrl()];
46+
if ($request->query->getBoolean('api_gateway', false)) {
47+
$context['api_gateway'] = true;
48+
}
49+
$request->attributes->set('_api_normalization_context', $request->attributes->get('_api_normalization_context', []) + $context);
4650
}
4751

4852
return new Documentation($this->resourceNameCollectionFactory->create(), $this->title, $this->description, $this->version, $this->formats);
Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the API Platform project.
5+
*
6+
* (c) Kévin Dunglas <[email protected]>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
declare(strict_types=1);
13+
14+
namespace ApiPlatform\Core\Swagger\Serializer;
15+
16+
use Symfony\Component\Serializer\Normalizer\NormalizerInterface;
17+
18+
/**
19+
* Removes features unsupported by Amazon API Gateway.
20+
*
21+
* @see https://docs.aws.amazon.com/apigateway/latest/developerguide/api-gateway-known-issues.html
22+
*
23+
* @internal
24+
*
25+
* @author Vincent Chalamon <[email protected]>
26+
*/
27+
final class ApiGatewayNormalizer implements NormalizerInterface
28+
{
29+
private $documentationNormalizer;
30+
31+
public function __construct(NormalizerInterface $documentationNormalizer)
32+
{
33+
$this->documentationNormalizer = $documentationNormalizer;
34+
}
35+
36+
/**
37+
* {@inheritdoc}
38+
*/
39+
public function normalize($object, $format = null, array $context = [])
40+
{
41+
$data = $this->documentationNormalizer->normalize($object, $format, $context);
42+
if (empty($data['basePath'])) {
43+
$data['basePath'] = '/';
44+
}
45+
46+
if (!($context['api_gateway'] ?? false)) {
47+
return $data;
48+
}
49+
50+
foreach ($data['paths'] as $path => $operations) {
51+
foreach ($operations as $operation => $options) {
52+
if (isset($options['parameters'])) {
53+
foreach ($options['parameters'] as $key => $parameter) {
54+
if (!preg_match('/^[a-zA-Z0-9._$-]+$/', $parameter['name'])) {
55+
unset($data['paths'][$path][$operation]['parameters'][$key]);
56+
}
57+
if (isset($parameter['schema']['$ref']) && !preg_match('/^#\/definitions\/[A-z]+$/', $parameter['schema']['$ref'])) {
58+
$data['paths'][$path][$operation]['parameters'][$key]['schema']['$ref'] = str_replace(['-', '_'], '', $parameter['schema']['$ref']);
59+
}
60+
}
61+
$data['paths'][$path][$operation]['parameters'] = array_values($data['paths'][$path][$operation]['parameters']);
62+
}
63+
if (isset($options['responses'])) {
64+
foreach ($options['responses'] as $statusCode => $response) {
65+
if (isset($response['schema']['items']['$ref']) && !preg_match('/^#\/definitions\/[A-z]+$/', $response['schema']['items']['$ref'])) {
66+
$data['paths'][$path][$operation]['responses'][$statusCode]['schema']['items']['$ref'] = str_replace(['-', '_'], '', $response['schema']['items']['$ref']);
67+
}
68+
if (isset($response['schema']['$ref']) && !preg_match('/^#\/definitions\/[A-z]+$/', $response['schema']['$ref'])) {
69+
$data['paths'][$path][$operation]['responses'][$statusCode]['schema']['$ref'] = str_replace(['-', '_'], '', $response['schema']['$ref']);
70+
}
71+
}
72+
}
73+
}
74+
}
75+
76+
foreach ($data['definitions'] as $definition => $options) {
77+
if (!isset($options['properties'])) {
78+
continue;
79+
}
80+
foreach ($options['properties'] as $property => $propertyOptions) {
81+
if (isset($propertyOptions['readOnly'])) {
82+
unset($data['definitions'][$definition]['properties'][$property]['readOnly']);
83+
}
84+
if (isset($propertyOptions['$ref']) && !preg_match('/^#\/definitions\/[A-z]+$/', $propertyOptions['$ref'])) {
85+
$data['definitions'][$definition]['properties'][$property]['$ref'] = str_replace(['-', '_'], '', $propertyOptions['$ref']);
86+
}
87+
if (isset($propertyOptions['items']['$ref']) && !preg_match('/^#\/definitions\/[A-z]+$/', $propertyOptions['items']['$ref'])) {
88+
$data['definitions'][$definition]['properties'][$property]['items']['$ref'] = str_replace(['-', '_'], '', $propertyOptions['items']['$ref']);
89+
}
90+
}
91+
}
92+
93+
// $data['definitions'] is an instance of \ArrayObject
94+
foreach (array_keys($data['definitions']->getArrayCopy()) as $definition) {
95+
if (!preg_match('/^[A-z]+$/', $definition)) {
96+
$data['definitions'][str_replace(['-', '_'], '', $definition)] = $data['definitions'][$definition];
97+
unset($data['definitions'][$definition]);
98+
}
99+
}
100+
101+
return $data;
102+
}
103+
104+
/**
105+
* {@inheritdoc}
106+
*/
107+
public function supportsNormalization($data, $format = null)
108+
{
109+
return $this->documentationNormalizer->supportsNormalization($data, $format);
110+
}
111+
}

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -576,6 +576,7 @@ private function getBaseContainerBuilderProphecy()
576576
'api_platform.jsonld.context_builder',
577577
'api_platform.jsonld.normalizer.item',
578578
'api_platform.swagger.normalizer.documentation',
579+
'api_platform.swagger.normalizer.api_gateway',
579580
'api_platform.swagger.command.swagger_command',
580581
'api_platform.swagger.action.ui',
581582
'api_platform.swagger.listener.ui',
@@ -613,7 +614,6 @@ private function getBaseContainerBuilderProphecy()
613614
'api_platform.problem.normalizer.error',
614615
'api_platform.swagger.action.ui',
615616
'api_platform.swagger.command.swagger_command',
616-
'api_platform.swagger.normalizer.documentation',
617617
'api_platform.http_cache.listener.response.configure',
618618
'api_platform.http_cache.purger.varnish',
619619
'api_platform.http_cache.purger.varnish_client',

tests/Documentation/Action/DocumentationActionTest.php

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
use ApiPlatform\Core\Metadata\Resource\ResourceNameCollection;
2020
use PHPUnit\Framework\TestCase;
2121
use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface;
22+
use Symfony\Component\HttpFoundation\ParameterBag;
2223
use Symfony\Component\HttpFoundation\Request;
2324

2425
/**
@@ -30,12 +31,15 @@ public function testDocumentationAction()
3031
{
3132
$requestProphecy = $this->prophesize(Request::class);
3233
$attributesProphecy = $this->prophesize(ParameterBagInterface::class);
34+
$queryProphecy = $this->prophesize(ParameterBag::class);
3335
$resourceNameCollectionFactoryProphecy = $this->prophesize(ResourceNameCollectionFactoryInterface::class);
3436
$resourceNameCollectionFactoryProphecy->create()->willReturn(new ResourceNameCollection(['dummies']));
3537
$requestProphecy->attributes = $attributesProphecy->reveal();
38+
$requestProphecy->query = $queryProphecy->reveal();
3639
$requestProphecy->getBaseUrl()->willReturn('/api')->shouldBeCalledTimes(1);
40+
$queryProphecy->getBoolean('api_gateway', false)->willReturn(true)->shouldBeCalledTimes(1);
3741
$attributesProphecy->get('_api_normalization_context', [])->willReturn(['foo' => 'bar'])->shouldBeCalledTimes(1);
38-
$attributesProphecy->set('_api_normalization_context', ['foo' => 'bar', 'base_url' => '/api'])->shouldBeCalledTimes(1);
42+
$attributesProphecy->set('_api_normalization_context', ['foo' => 'bar', 'base_url' => '/api', 'api_gateway' => true])->shouldBeCalledTimes(1);
3943
$documentation = new DocumentationAction($resourceNameCollectionFactoryProphecy->reveal(), 'My happy hippie api', 'lots of chocolate', '1.0.0', ['formats' => ['jsonld' => 'application/ld+json']]);
4044
$this->assertEquals(new Documentation(new ResourceNameCollection(['dummies']), 'My happy hippie api', 'lots of chocolate', '1.0.0', ['formats' => ['jsonld' => 'application/ld+json']]), $documentation($requestProphecy->reveal()));
4145
}

0 commit comments

Comments
 (0)