Skip to content

Commit d047d85

Browse files
GregoireHebertdunglas
authored andcommitted
Handle activation of swagger through versions (#2998)
* Add swagger version configuration option * deprecate enable_swagger option in favour of swagger.versions option * use first swagger version defined as default * fix optional parameter * Set nullable versions for DocumentationAction * fix phpstan review * fix php-cs-fixer review * fix code review * fix phpstan * fix code review * fix SwaggerCommand default value for spec-version option * fix swagger Command cast int option * fix phpdoc
1 parent 23d0f8f commit d047d85

File tree

13 files changed

+140
-28
lines changed

13 files changed

+140
-28
lines changed

src/Bridge/Symfony/Bundle/Action/SwaggerUiAction.php

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -56,8 +56,12 @@ final class SwaggerUiAction
5656
private $graphqlEnabled;
5757
private $graphiQlEnabled;
5858
private $graphQlPlaygroundEnabled;
59+
private $swaggerVersions;
5960

60-
public function __construct(ResourceNameCollectionFactoryInterface $resourceNameCollectionFactory, ResourceMetadataFactoryInterface $resourceMetadataFactory, NormalizerInterface $normalizer, TwigEnvironment $twig, UrlGeneratorInterface $urlGenerator, string $title = '', string $description = '', string $version = '', $formats = [], $oauthEnabled = false, $oauthClientId = '', $oauthClientSecret = '', $oauthType = '', $oauthFlow = '', $oauthTokenUrl = '', $oauthAuthorizationUrl = '', $oauthScopes = [], bool $showWebby = true, bool $swaggerUiEnabled = false, bool $reDocEnabled = false, bool $graphqlEnabled = false, bool $graphiQlEnabled = false, bool $graphQlPlaygroundEnabled = false)
61+
/**
62+
* @param int[] $swaggerVersions
63+
*/
64+
public function __construct(ResourceNameCollectionFactoryInterface $resourceNameCollectionFactory, ResourceMetadataFactoryInterface $resourceMetadataFactory, NormalizerInterface $normalizer, TwigEnvironment $twig, UrlGeneratorInterface $urlGenerator, string $title = '', string $description = '', string $version = '', $formats = [], $oauthEnabled = false, $oauthClientId = '', $oauthClientSecret = '', $oauthType = '', $oauthFlow = '', $oauthTokenUrl = '', $oauthAuthorizationUrl = '', $oauthScopes = [], bool $showWebby = true, bool $swaggerUiEnabled = false, bool $reDocEnabled = false, bool $graphqlEnabled = false, bool $graphiQlEnabled = false, bool $graphQlPlaygroundEnabled = false, array $swaggerVersions = [2, 3])
6165
{
6266
$this->resourceNameCollectionFactory = $resourceNameCollectionFactory;
6367
$this->resourceMetadataFactory = $resourceMetadataFactory;
@@ -81,6 +85,7 @@ public function __construct(ResourceNameCollectionFactoryInterface $resourceName
8185
$this->graphqlEnabled = $graphqlEnabled;
8286
$this->graphiQlEnabled = $graphiQlEnabled;
8387
$this->graphQlPlaygroundEnabled = $graphQlPlaygroundEnabled;
88+
$this->swaggerVersions = $swaggerVersions;
8489

8590
if (\is_array($formats)) {
8691
$this->formats = $formats;
@@ -127,7 +132,7 @@ private function getContext(Request $request, Documentation $documentation): arr
127132
'graphQlPlaygroundEnabled' => $this->graphQlPlaygroundEnabled,
128133
];
129134

130-
$swaggerContext = ['spec_version' => $request->query->getInt('spec_version', 2)];
135+
$swaggerContext = ['spec_version' => $request->query->getInt('spec_version', $this->swaggerVersions[0] ?? 2)];
131136
if ('' !== $baseUrl = $request->getBaseUrl()) {
132137
$swaggerContext['base_url'] = $baseUrl;
133138
}

src/Bridge/Symfony/Bundle/Command/SwaggerCommand.php

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -39,15 +39,20 @@ final class SwaggerCommand extends Command
3939
private $apiDescription;
4040
private $apiVersion;
4141
private $apiFormats;
42+
private $swaggerVersions;
4243

43-
public function __construct(NormalizerInterface $normalizer, ResourceNameCollectionFactoryInterface $resourceNameCollection, string $apiTitle, string $apiDescription, string $apiVersion, array $apiFormats = null)
44+
/**
45+
* @param int[] $swaggerVersions
46+
*/
47+
public function __construct(NormalizerInterface $normalizer, ResourceNameCollectionFactoryInterface $resourceNameCollection, string $apiTitle, string $apiDescription, string $apiVersion, array $apiFormats = null, array $swaggerVersions = [2, 3])
4448
{
4549
$this->normalizer = $normalizer;
4650
$this->resourceNameCollectionFactory = $resourceNameCollection;
4751
$this->apiTitle = $apiTitle;
4852
$this->apiDescription = $apiDescription;
4953
$this->apiVersion = $apiVersion;
5054
$this->apiFormats = $apiFormats;
55+
$this->swaggerVersions = $swaggerVersions;
5156

5257
if (null !== $apiFormats) {
5358
@trigger_error(sprintf('Passing a 6th parameter to the constructor of "%s" is deprecated since API Platform 2.5', __CLASS__), E_USER_DEPRECATED);
@@ -66,7 +71,7 @@ protected function configure()
6671
->setAliases(['api:swagger:export'])
6772
->setDescription('Dump the OpenAPI documentation')
6873
->addOption('yaml', 'y', InputOption::VALUE_NONE, 'Dump the documentation in YAML')
69-
->addOption('spec-version', null, InputOption::VALUE_OPTIONAL, 'OpenAPI version to use ("2" or "3")', '2')
74+
->addOption('spec-version', null, InputOption::VALUE_OPTIONAL, sprintf('OpenAPI version to use (%s)', implode(' or ', $this->swaggerVersions)), $this->swaggerVersions[0] ?? 2)
7075
->addOption('output', 'o', InputOption::VALUE_OPTIONAL, 'Write output to file')
7176
->addOption('api-gateway', null, InputOption::VALUE_NONE, 'API Gateway compatibility');
7277
}
@@ -77,11 +82,12 @@ protected function configure()
7782
protected function execute(InputInterface $input, OutputInterface $output)
7883
{
7984
$io = new SymfonyStyle($input, $output);
85+
8086
/** @var string $version */
8187
$version = $input->getOption('spec-version');
8288

83-
if (!\in_array($version, ['2', '3'], true)) {
84-
throw new InvalidOptionException(sprintf('This tool only supports version 2 and 3 of the OpenAPI specification ("%s" given).', $version));
89+
if (!\in_array((int) $version, $this->swaggerVersions, true)) {
90+
throw new InvalidOptionException(sprintf('This tool only supports versions %s of the OpenAPI specification ("%s" given).', implode(', ', $this->swaggerVersions), $version));
8591
}
8692

8793
$documentation = new Documentation($this->resourceNameCollectionFactory->create(), $this->apiTitle, $this->apiDescription, $this->apiVersion, $this->apiFormats);

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -307,7 +307,7 @@ private function registerOAuthConfiguration(ContainerBuilder $container, array $
307307
*/
308308
private function registerSwaggerConfiguration(ContainerBuilder $container, array $config, XmlFileLoader $loader): void
309309
{
310-
if (!$config['enable_swagger']) {
310+
if (empty($config['swagger']['versions'])) {
311311
return;
312312
}
313313

@@ -320,7 +320,7 @@ private function registerSwaggerConfiguration(ContainerBuilder $container, array
320320
$container->setParameter('api_platform.enable_re_doc', $config['enable_re_doc']);
321321
}
322322

323-
$container->setParameter('api_platform.enable_swagger', $config['enable_swagger']);
323+
$container->setParameter('api_platform.swagger.versions', $config['swagger']['versions']);
324324
$container->setParameter('api_platform.swagger.api_keys', $config['swagger']['api_keys']);
325325
}
326326

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

Lines changed: 40 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,16 @@ public function getConfigTreeBuilder()
5656
}
5757

5858
$rootNode
59+
->beforeNormalization()
60+
->ifTrue(static function ($v) {
61+
return false === ($v['enable_swagger'] ?? null);
62+
})
63+
->then(static function ($v) {
64+
$v['swagger']['versions'] = [];
65+
66+
return $v;
67+
})
68+
->end()
5969
->children()
6070
->scalarNode('title')
6171
->info('The title of the API.')
@@ -236,14 +246,40 @@ private function addGraphQlSection(ArrayNodeDefinition $rootNode): void
236246

237247
private function addSwaggerSection(ArrayNodeDefinition $rootNode): void
238248
{
249+
$defaultVersions = [2, 3];
250+
239251
$rootNode
240252
->children()
241253
->arrayNode('swagger')
242254
->addDefaultsIfNotSet()
243255
->children()
244-
->arrayNode('api_keys')
245-
->prototype('array')
246-
->children()
256+
->arrayNode('versions')
257+
->info('The active versions of OpenAPI to be exported or used in the swagger_ui. The first value is the default.')
258+
->defaultValue($defaultVersions)
259+
->beforeNormalization()
260+
->always(static function ($v) {
261+
if (!\is_array($v)) {
262+
$v = [$v];
263+
}
264+
265+
foreach ($v as &$version) {
266+
$version = (int) $version;
267+
}
268+
269+
return $v;
270+
})
271+
->end()
272+
->validate()
273+
->ifTrue(function ($v) use ($defaultVersions) {
274+
return $v !== array_intersect($v, $defaultVersions);
275+
})
276+
->thenInvalid(sprintf('Only the versions %s are supported. Got %s.', implode(' and ', $defaultVersions), '%s'))
277+
->end()
278+
->prototype('scalar')->end()
279+
->end()
280+
->arrayNode('api_keys')
281+
->prototype('array')
282+
->children()
247283
->scalarNode('name')
248284
->info('The name of the header or query parameter containing the api key.')
249285
->end()
@@ -252,7 +288,7 @@ private function addSwaggerSection(ArrayNodeDefinition $rootNode): void
252288
->values(['query', 'header'])
253289
->end()
254290
->end()
255-
->end()
291+
->end()
256292
->end()
257293
->end()
258294
->end()

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -240,6 +240,8 @@
240240
<argument>%api_platform.title%</argument>
241241
<argument>%api_platform.description%</argument>
242242
<argument>%api_platform.version%</argument>
243+
<argument>null</argument>
244+
<argument on-invalid="null">%api_platform.swagger.versions%</argument>
243245
</service>
244246

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

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@
3434
<argument>%api_platform.graphql.enabled%</argument>
3535
<argument>%api_platform.graphql.graphiql.enabled%</argument>
3636
<argument>%api_platform.graphql.graphql_playground.enabled%</argument>
37+
<argument>%api_platform.swagger.versions%</argument>
3738
</service>
3839

3940
</services>

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

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
<argument>%api_platform.formats%</argument>
3232
<argument>%api_platform.collection.pagination.client_enabled%</argument>
3333
<argument>%api_platform.collection.pagination.enabled_parameter_name%</argument>
34+
<argument>%api_platform.swagger.versions%</argument>
3435
<tag name="serializer.normalizer" priority="-790" />
3536
</service>
3637

@@ -45,6 +46,8 @@
4546
<argument>%api_platform.title%</argument>
4647
<argument>%api_platform.description%</argument>
4748
<argument>%api_platform.version%</argument>
49+
<argument>null</argument>
50+
<argument>%api_platform.swagger.versions%</argument>
4851
<tag name="console.command" />
4952
</service>
5053

src/Documentation/Action/DocumentationAction.php

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -32,14 +32,19 @@ final class DocumentationAction
3232
private $version;
3333
private $formats;
3434
private $formatsProvider;
35-
private $resourceMetadataFactory;
35+
private $swaggerVersions;
3636

37-
public function __construct(ResourceNameCollectionFactoryInterface $resourceNameCollectionFactory, string $title = '', string $description = '', string $version = '', $formatsProvider = null)
37+
/**
38+
* @param int[] $swaggerVersions
39+
* @param mixed|array|FormatsProviderInterface $formatsProvider
40+
*/
41+
public function __construct(ResourceNameCollectionFactoryInterface $resourceNameCollectionFactory, string $title = '', string $description = '', string $version = '', $formatsProvider = null, array $swaggerVersions = [2, 3])
3842
{
3943
$this->resourceNameCollectionFactory = $resourceNameCollectionFactory;
4044
$this->title = $title;
4145
$this->description = $description;
4246
$this->version = $version;
47+
$this->swaggerVersions = $swaggerVersions;
4348

4449
if (null === $formatsProvider) {
4550
return;
@@ -57,7 +62,7 @@ public function __construct(ResourceNameCollectionFactoryInterface $resourceName
5762
public function __invoke(Request $request = null): Documentation
5863
{
5964
if (null !== $request) {
60-
$context = ['base_url' => $request->getBaseUrl(), 'spec_version' => $request->query->getInt('spec_version', 2)];
65+
$context = ['base_url' => $request->getBaseUrl(), 'spec_version' => $request->query->getInt('spec_version', $this->swaggerVersions[0] ?? 2)];
6166
if ($request->query->getBoolean('api_gateway')) {
6267
$context['api_gateway'] = true;
6368
}

src/Swagger/Serializer/DocumentationNormalizer.php

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,6 @@ final class DocumentationNormalizer implements NormalizerInterface, CacheableSup
9595
private $jsonSchemaTypeFactory;
9696
private $defaultContext = [
9797
self::BASE_URL => '/',
98-
self::SPEC_VERSION => 2,
9998
ApiGatewayNormalizer::API_GATEWAY => false,
10099
];
101100

@@ -104,8 +103,9 @@ final class DocumentationNormalizer implements NormalizerInterface, CacheableSup
104103
* @param ContainerInterface|FilterCollection|null $filterLocator
105104
* @param array|OperationAwareFormatsProviderInterface $formats
106105
* @param mixed|null $jsonSchemaTypeFactory
106+
* @param int[] $swaggerVersions
107107
*/
108-
public function __construct(ResourceMetadataFactoryInterface $resourceMetadataFactory, PropertyNameCollectionFactoryInterface $propertyNameCollectionFactory, PropertyMetadataFactoryInterface $propertyMetadataFactory, $jsonSchemaFactory = null, $jsonSchemaTypeFactory = null, OperationPathResolverInterface $operationPathResolver, UrlGeneratorInterface $urlGenerator = null, $filterLocator = null, NameConverterInterface $nameConverter = null, bool $oauthEnabled = false, string $oauthType = '', string $oauthFlow = '', string $oauthTokenUrl = '', string $oauthAuthorizationUrl = '', array $oauthScopes = [], array $apiKeys = [], SubresourceOperationFactoryInterface $subresourceOperationFactory = null, bool $paginationEnabled = true, string $paginationPageParameterName = 'page', bool $clientItemsPerPage = false, string $itemsPerPageParameterName = 'itemsPerPage', $formats = [], bool $paginationClientEnabled = false, string $paginationClientEnabledParameterName = 'pagination', array $defaultContext = [])
108+
public function __construct(ResourceMetadataFactoryInterface $resourceMetadataFactory, PropertyNameCollectionFactoryInterface $propertyNameCollectionFactory, PropertyMetadataFactoryInterface $propertyMetadataFactory, $jsonSchemaFactory = null, $jsonSchemaTypeFactory = null, OperationPathResolverInterface $operationPathResolver, UrlGeneratorInterface $urlGenerator = null, $filterLocator = null, NameConverterInterface $nameConverter = null, bool $oauthEnabled = false, string $oauthType = '', string $oauthFlow = '', string $oauthTokenUrl = '', string $oauthAuthorizationUrl = '', array $oauthScopes = [], array $apiKeys = [], SubresourceOperationFactoryInterface $subresourceOperationFactory = null, bool $paginationEnabled = true, string $paginationPageParameterName = 'page', bool $clientItemsPerPage = false, string $itemsPerPageParameterName = 'itemsPerPage', $formats = [], bool $paginationClientEnabled = false, string $paginationClientEnabledParameterName = 'pagination', array $defaultContext = [], array $swaggerVersions = [2, 3])
109109
{
110110
if ($jsonSchemaTypeFactory instanceof OperationMethodResolverInterface) {
111111
@trigger_error(sprintf('Passing an instance of %s to %s() is deprecated since version 2.5 and will be removed in 3.0.', OperationMethodResolverInterface::class, __METHOD__), E_USER_DEPRECATED);
@@ -163,6 +163,8 @@ public function __construct(ResourceMetadataFactoryInterface $resourceMetadataFa
163163
$this->paginationClientEnabled = $paginationClientEnabled;
164164
$this->paginationClientEnabledParameterName = $paginationClientEnabledParameterName;
165165

166+
$this->defaultContext[self::SPEC_VERSION] = $swaggerVersions[0] ?? 2;
167+
166168
$this->defaultContext = array_merge($this->defaultContext, $defaultContext);
167169
}
168170

tests/Bridge/Symfony/Bundle/Command/SwaggerCommandTest.php

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ protected function setUp(): void
4343

4444
public function testExecuteWithAliasVersion3()
4545
{
46-
$this->tester->run(['command' => 'api:swagger:export', '--spec-version' => '3']);
46+
$this->tester->run(['command' => 'api:swagger:export', '--spec-version' => 3]);
4747

4848
$this->assertJson($this->tester->getDisplay());
4949
}
@@ -57,7 +57,7 @@ public function testExecuteOpenApiVersion2()
5757

5858
public function testExecuteWithYamlVersion3()
5959
{
60-
$this->tester->run(['command' => 'api:swagger:export', '--yaml' => true, '--spec-version' => '3']);
60+
$this->tester->run(['command' => 'api:swagger:export', '--yaml' => true, '--spec-version' => 3]);
6161

6262
$result = $this->tester->getDisplay();
6363
$this->assertYaml($result);
@@ -105,7 +105,7 @@ public function testExecuteOpenApiVersion2WithYaml()
105105
public function testExecuteWithBadArguments()
106106
{
107107
$this->expectException(InvalidOptionException::class);
108-
$this->expectExceptionMessage('This tool only supports version 2 and 3 of the OpenAPI specification ("foo" given).');
108+
$this->expectExceptionMessage('This tool only supports versions 2, 3 of the OpenAPI specification ("foo" given).');
109109
$this->tester->run(['command' => 'api:openapi:export', '--spec-version' => 'foo', '--yaml' => true]);
110110
}
111111

0 commit comments

Comments
 (0)