Skip to content

Commit

Permalink
Merge 7a22189 into 80c50c8
Browse files Browse the repository at this point in the history
  • Loading branch information
soyuka committed Jul 23, 2020
2 parents 80c50c8 + 7a22189 commit 02f9fcd
Show file tree
Hide file tree
Showing 44 changed files with 4,275 additions and 27 deletions.
2 changes: 1 addition & 1 deletion composer.json
Expand Up @@ -22,7 +22,7 @@
"symfony/http-kernel": "^4.3.7 || ^5.0",
"symfony/property-access": "^3.4 || ^4.0 || ^5.0",
"symfony/property-info": "^3.4 || ^4.0 || ^5.0",
"symfony/serializer": "^4.3 || ^5.0",
"symfony/serializer": "^4.4 || ^5.0",
"symfony/web-link": "^4.1 || ^5.0",
"willdurand/negotiation": "^2.0.3"
},
Expand Down
93 changes: 93 additions & 0 deletions src/Bridge/Symfony/Bundle/Command/OpenApiCommand.php
@@ -0,0 +1,93 @@
<?php

/*
* This file is part of the API Platform project.
*
* (c) Kévin Dunglas <dunglas@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

declare(strict_types=1);

namespace ApiPlatform\Core\Bridge\Symfony\Bundle\Command;

use ApiPlatform\Core\OpenApi\Factory\OpenApiFactoryInterface;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\ArrayInput;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\SymfonyStyle;
use Symfony\Component\Filesystem\Filesystem;
use Symfony\Component\Serializer\Normalizer\NormalizerInterface;
use Symfony\Component\Yaml\Yaml;

/**
* Dumps Open API documentation.
*/
final class OpenApiCommand extends Command
{
private $openApiFactory;
private $normalizer;

public function __construct(OpenApiFactoryInterface $openApiFactory, NormalizerInterface $normalizer)
{
parent::__construct();
$this->openApiFactory = $openApiFactory;
$this->normalizer = $normalizer;
}

/**
* {@inheritdoc}
*/
protected function configure()
{
$this
->setName('api:openapi:export')
->setDescription('Dump the Open API documentation')
->addOption('yaml', 'y', InputOption::VALUE_NONE, 'Dump the documentation in YAML')
->addOption('output', 'o', InputOption::VALUE_OPTIONAL, 'Write output to file')
->addOption('spec-version', null, InputOption::VALUE_OPTIONAL, 'Open API version to use (2 or 3) (2 is deprecated)', 3)
->addOption('api-gateway', null, InputOption::VALUE_NONE, 'Enable the Amazon API Gateway compatibility mode');
}

/**
* {@inheritdoc}
*/
protected function execute(InputInterface $input, OutputInterface $output)
{
// Backwards compatibility
if (2 === $specVersion = (int) $input->getOption('spec-version')) {
$command = $this->getApplication()->find('api:swagger:export');

return $command->run(new ArrayInput([
'command' => 'api:swagger:export',
'--spec-version' => $specVersion,
'--yaml' => $input->getOption('yaml'),
'--output' => $input->getOption('output'),
'--api-gateway' => $input->getOption('api-gateway'),
]), $output);
}

$filesystem = new Filesystem();
$io = new SymfonyStyle($input, $output);
$data = $this->normalizer->normalize($this->openApiFactory->__invoke(), 'json');
$content = $input->getOption('yaml')
? Yaml::dump($data, 10, 2, Yaml::DUMP_OBJECT_AS_MAP | Yaml::DUMP_EMPTY_ARRAY_AS_SEQUENCE | Yaml::DUMP_MULTI_LINE_LITERAL_BLOCK)
: (json_encode($data, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES) ?: '');

$filename = $input->getOption('output');
if ($filename && \is_string($filename)) {
$filesystem->dumpFile($filename, $content);
$io->success(sprintf('Data written to %s.', $filename));

return 0;
}

$output->writeln($content);

return 0;
}
}
9 changes: 6 additions & 3 deletions src/Bridge/Symfony/Bundle/Command/SwaggerCommand.php
Expand Up @@ -67,9 +67,8 @@ public function __construct(NormalizerInterface $normalizer, ResourceNameCollect
protected function configure()
{
$this
->setName('api:openapi:export')
->setAliases(['api:swagger:export'])
->setDescription('Dump the OpenAPI documentation')
->setName('api:swagger:export')
->setDescription('Dump the Swagger v2 documentation')
->addOption('yaml', 'y', InputOption::VALUE_NONE, 'Dump the documentation in YAML')
->addOption('spec-version', null, InputOption::VALUE_OPTIONAL, sprintf('OpenAPI version to use (%s)', implode(' or ', $this->swaggerVersions)), $this->swaggerVersions[0] ?? 2)
->addOption('output', 'o', InputOption::VALUE_OPTIONAL, 'Write output to file')
Expand All @@ -90,6 +89,10 @@ protected function execute(InputInterface $input, OutputInterface $output)
throw new InvalidOptionException(sprintf('This tool only supports versions %s of the OpenAPI specification ("%s" given).', implode(', ', $this->swaggerVersions), $version));
}

if (3 === (int) $version) {
@trigger_error('The command "api:swagger:export" is deprecated for the spec version 3 use "api:openapi:export".', E_USER_DEPRECATED);
}

$documentation = new Documentation($this->resourceNameCollectionFactory->create(), $this->apiTitle, $this->apiDescription, $this->apiVersion, $this->apiFormats);
$data = $this->normalizer->normalize($documentation, DocumentationNormalizer::FORMAT, ['spec_version' => (int) $version, ApiGatewayNormalizer::API_GATEWAY => $input->getOption('api-gateway')]);
$content = $input->getOption('yaml')
Expand Down
Expand Up @@ -365,6 +365,7 @@ private function registerOAuthConfiguration(ContainerBuilder $container, array $
$container->setParameter('api_platform.oauth.flow', $config['oauth']['flow']);
$container->setParameter('api_platform.oauth.tokenUrl', $config['oauth']['tokenUrl']);
$container->setParameter('api_platform.oauth.authorizationUrl', $config['oauth']['authorizationUrl']);
$container->setParameter('api_platform.oauth.refreshUrl', $config['oauth']['refreshUrl']);
$container->setParameter('api_platform.oauth.scopes', $config['oauth']['scopes']);
}

Expand All @@ -381,6 +382,7 @@ private function registerSwaggerConfiguration(ContainerBuilder $container, array

$loader->load('json_schema.xml');
$loader->load('swagger.xml');
$loader->load('openapi.xml');
$loader->load('swagger-ui.xml');

if (!$config['enable_swagger_ui'] && !$config['enable_re_doc']) {
Expand Down
Expand Up @@ -249,6 +249,7 @@ private function addOAuthSection(ArrayNodeDefinition $rootNode): void
->scalarNode('flow')->defaultValue('application')->info('The oauth flow grant type.')->end()
->scalarNode('tokenUrl')->defaultValue('/oauth/v2/token')->info('The oauth token url.')->end()
->scalarNode('authorizationUrl')->defaultValue('/oauth/v2/auth')->info('The oauth authentication url.')->end()
->scalarNode('refreshUrl')->defaultValue('/oauth/v2/refresh')->info('The oauth refresh url.')->end()
->arrayNode('scopes')
->prototype('scalar')->end()
->end()
Expand Down Expand Up @@ -296,7 +297,7 @@ private function addSwaggerSection(ArrayNodeDefinition $rootNode): void
->addDefaultsIfNotSet()
->children()
->arrayNode('versions')
->info('The active versions of OpenAPI to be exported or used in the swagger_ui. The first value is the default.')
->info('The active versions of Open API to be exported or used in the swagger_ui. The first value is the default.')
->defaultValue($defaultVersions)
->beforeNormalization()
->always(static function ($v) {
Expand Down
11 changes: 11 additions & 0 deletions src/Bridge/Symfony/Bundle/Resources/config/data_provider.xml
Expand Up @@ -33,6 +33,17 @@
<argument>%api_platform.graphql.collection.pagination%</argument>
</service>
<service id="ApiPlatform\Core\DataProvider\Pagination" alias="api_platform.pagination" />

<service id="api_platform.pagination_options" class="ApiPlatform\Core\DataProvider\PaginationOptions">
<argument>%api_platform.collection.pagination.enabled%</argument>
<argument>%api_platform.collection.pagination.page_parameter_name%</argument>
<argument>%api_platform.collection.pagination.client_items_per_page%</argument>
<argument>%api_platform.collection.pagination.items_per_page_parameter_name%</argument>
<argument>%api_platform.collection.pagination.client_enabled%</argument>
<argument>%api_platform.collection.pagination.enabled_parameter_name%</argument>
</service>
<service id="ApiPlatform\Core\DataProvider\PaginationOptions" alias="api_platform.pagination_options" />

</services>

</container>
57 changes: 57 additions & 0 deletions src/Bridge/Symfony/Bundle/Resources/config/openapi.xml
@@ -0,0 +1,57 @@
<?xml version="1.0" ?>

<container xmlns="http://symfony.com/schema/dic/services"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd">

<services>
<service id="api_platform.openapi.normalizer" class="ApiPlatform\Core\OpenApi\Serializer\OpenApiNormalizer" public="false">
<argument type="service" id="serializer.normalizer.object" />
<tag name="serializer.normalizer" priority="-785" />
</service>
<service id="ApiPlatform\Core\OpenApi\Serializer\OpenApiNormalizer" alias="api_platform.openapi.normalizer" />

<service id="api_platform.openapi.options" class="ApiPlatform\Core\OpenApi\Options">
<argument>%api_platform.title%</argument>
<argument>%api_platform.description%</argument>
<argument>%api_platform.version%</argument>
<argument>%api_platform.oauth.enabled%</argument>
<argument>%api_platform.oauth.type%</argument>
<argument>%api_platform.oauth.flow%</argument>
<argument>%api_platform.oauth.tokenUrl%</argument>
<argument>%api_platform.oauth.authorizationUrl%</argument>
<argument>%api_platform.oauth.refreshUrl%</argument>
<argument>%api_platform.oauth.scopes%</argument>
<argument>%api_platform.swagger.api_keys%</argument>
</service>
<service id="ApiPlatform\Core\OpenApi\Options" alias="api_platform.openapi.options" />

<service id="api_platform.openapi.factory" class="ApiPlatform\Core\OpenApi\Factory\OpenApiFactory" public="false">
<argument type="service" id="api_platform.metadata.resource.name_collection_factory" />
<argument type="service" id="api_platform.metadata.resource.metadata_factory" />
<argument type="service" id="api_platform.metadata.property.name_collection_factory" />
<argument type="service" id="api_platform.metadata.property.metadata_factory" />
<argument type="service" id="api_platform.json_schema.schema_factory" />
<argument type="service" id="api_platform.json_schema.type_factory" />
<argument type="service" id="api_platform.operation_path_resolver" />
<argument type="service" id="api_platform.filter_locator" />
<argument type="service" id="api_platform.subresource_operation_factory" />
<argument>%api_platform.formats%</argument>
<argument type="service" id="api_platform.openapi.options" />
<argument type="service" id="api_platform.pagination_options" />
</service>
<service id="ApiPlatform\Core\OpenApi\Factory\OpenApiFactoryInterface" alias="api_platform.openapi.factory" />

<service id="api_platform.openapi.command" class="ApiPlatform\Core\Bridge\Symfony\Bundle\Command\OpenApiCommand">
<argument type="service" id="api_platform.openapi.factory" />
<argument type="service" id="api_platform.serializer" />
<tag name="console.command" />
</service>

<service id="api_platform.openapi.normalizer.api_gateway" class="ApiPlatform\Core\Swagger\Serializer\ApiGatewayNormalizer" public="false" decorates="api_platform.openapi.normalizer" decoration-priority="-1">
<argument type="service" id="api_platform.openapi.normalizer.api_gateway.inner" />
<tag name="serializer.normalizer" priority="-780" />
</service>
</services>

</container>
64 changes: 64 additions & 0 deletions src/DataProvider/PaginationOptions.php
@@ -0,0 +1,64 @@
<?php

/*
* This file is part of the API Platform project.
*
* (c) Kévin Dunglas <dunglas@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

declare(strict_types=1);

namespace ApiPlatform\Core\DataProvider;

final class PaginationOptions
{
private $paginationEnabled;
private $paginationPageParameterName;
private $clientItemsPerPage;
private $itemsPerPageParameterName;
private $paginationClientEnabled;
private $paginationClientEnabledParameterName;

public function __construct(bool $paginationEnabled = true, string $paginationPageParameterName = 'page', bool $clientItemsPerPage = false, string $itemsPerPageParameterName = 'itemsPerPage', bool $paginationClientEnabled = false, string $paginationClientEnabledParameterName = 'pagination')
{
$this->paginationEnabled = $paginationEnabled;
$this->paginationPageParameterName = $paginationPageParameterName;
$this->clientItemsPerPage = $clientItemsPerPage;
$this->itemsPerPageParameterName = $itemsPerPageParameterName;
$this->paginationClientEnabled = $paginationClientEnabled;
$this->paginationClientEnabledParameterName = $paginationClientEnabledParameterName;
}

public function isPaginationEnabled(): bool
{
return $this->paginationEnabled;
}

public function getPaginationPageParameterName(): string
{
return $this->paginationPageParameterName;
}

public function getClientItemsPerPage(): bool
{
return $this->clientItemsPerPage;
}

public function getItemsPerPageParameterName(): string
{
return $this->itemsPerPageParameterName;
}

public function getPaginationClientEnabled(): bool
{
return $this->paginationClientEnabled;
}

public function getPaginationClientEnabledParameterName(): string
{
return $this->paginationClientEnabledParameterName;
}
}
20 changes: 17 additions & 3 deletions src/Documentation/Action/DocumentationAction.php
Expand Up @@ -15,7 +15,9 @@

use ApiPlatform\Core\Api\FormatsProviderInterface;
use ApiPlatform\Core\Documentation\Documentation;
use ApiPlatform\Core\Documentation\DocumentationInterface;
use ApiPlatform\Core\Metadata\Resource\Factory\ResourceNameCollectionFactoryInterface;
use ApiPlatform\Core\OpenApi\Factory\OpenApiFactoryInterface;
use ApiPlatform\Core\Util\RequestAttributesExtractor;
use Symfony\Component\HttpFoundation\Request;

Expand All @@ -33,18 +35,24 @@ final class DocumentationAction
private $formats;
private $formatsProvider;
private $swaggerVersions;
private $openApiFactory;

/**
* @param int[] $swaggerVersions
* @param mixed|array|FormatsProviderInterface $formatsProvider
*/
public function __construct(ResourceNameCollectionFactoryInterface $resourceNameCollectionFactory, string $title = '', string $description = '', string $version = '', $formatsProvider = null, array $swaggerVersions = [2, 3])
public function __construct(ResourceNameCollectionFactoryInterface $resourceNameCollectionFactory, string $title = '', string $description = '', string $version = '', $formatsProvider = null, array $swaggerVersions = [2, 3], OpenApiFactoryInterface $openApiFactory = null)
{
$this->resourceNameCollectionFactory = $resourceNameCollectionFactory;
$this->title = $title;
$this->description = $description;
$this->version = $version;
$this->swaggerVersions = $swaggerVersions;
$this->openApiFactory = $openApiFactory;

if (null === $openApiFactory) {
@trigger_error(sprintf('Not passing an instance of "%s" as 7th parameter of the constructor of "%s" is deprecated since API Platform 2.6', OpenApiFactoryInterface::class, __CLASS__), E_USER_DEPRECATED);
}

if (null === $formatsProvider) {
return;
Expand All @@ -56,25 +64,31 @@ public function __construct(ResourceNameCollectionFactoryInterface $resourceName

return;
}

$this->formatsProvider = $formatsProvider;
}

public function __invoke(Request $request = null): Documentation
public function __invoke(Request $request = null): DocumentationInterface
{
if (null !== $request) {
$context = ['base_url' => $request->getBaseUrl(), 'spec_version' => $request->query->getInt('spec_version', $this->swaggerVersions[0] ?? 2)];
$context = ['base_url' => $request->getBaseUrl(), 'spec_version' => $request->query->getInt('spec_version', $this->swaggerVersions[0] ?? 3)];
if ($request->query->getBoolean('api_gateway')) {
$context['api_gateway'] = true;
}
$request->attributes->set('_api_normalization_context', $request->attributes->get('_api_normalization_context', []) + $context);

$attributes = RequestAttributesExtractor::extractAttributes($request);
}

// BC check to be removed in 3.0
if (null !== $this->formatsProvider) {
$this->formats = $this->formatsProvider->getFormatsFromAttributes($attributes ?? []);
}

if (null !== $this->openApiFactory && isset($context) && 3 === $context['spec_version']) {
return $this->openApiFactory->__invoke($context ?? []);
}

return new Documentation($this->resourceNameCollectionFactory->create(), $this->title, $this->description, $this->version, $this->formats);
}
}
2 changes: 1 addition & 1 deletion src/Documentation/Documentation.php
Expand Up @@ -20,7 +20,7 @@
*
* @author Amrouche Hamza <hamza.simperfit@gmail.com>
*/
final class Documentation
final class Documentation implements DocumentationInterface
{
private $resourceNameCollection;
private $title;
Expand Down

0 comments on commit 02f9fcd

Please sign in to comment.