From 4ebee933c422b77647afaadb56179d04e8de85f4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zdene=CC=8Ck=20Drahos=CC=8C?= Date: Sun, 27 Jun 2021 08:41:33 +0200 Subject: [PATCH 1/9] Extract rendering docs from command and controller --- Command/DumpCommand.php | 32 ++++-------- Controller/SwaggerUiController.php | 52 ++++++++------------ Render/Html/HtmlOpenApiRenderer.php | 56 +++++++++++++++++++++ Render/Json/JsonOpenApiRenderer.php | 34 +++++++++++++ Render/OpenApiRenderer.php | 21 ++++++++ Render/RenderOpenApi.php | 53 ++++++++++++++++++++ Resources/config/services.xml | 17 +++++-- Tests/Command/DumpCommandTest.php | 31 +++++++++--- Tests/Render/RenderOpenApiTest.php | 75 +++++++++++++++++++++++++++++ 9 files changed, 305 insertions(+), 66 deletions(-) create mode 100644 Render/Html/HtmlOpenApiRenderer.php create mode 100644 Render/Json/JsonOpenApiRenderer.php create mode 100644 Render/OpenApiRenderer.php create mode 100644 Render/RenderOpenApi.php create mode 100644 Tests/Render/RenderOpenApiTest.php diff --git a/Command/DumpCommand.php b/Command/DumpCommand.php index f1b6271..b2438da 100644 --- a/Command/DumpCommand.php +++ b/Command/DumpCommand.php @@ -11,9 +11,8 @@ namespace Nelmio\ApiDocBundle\Command; -use Psr\Container\ContainerInterface; +use Nelmio\ApiDocBundle\Render\RenderOpenApi; use Symfony\Component\Console\Command\Command; -use Symfony\Component\Console\Exception\InvalidArgumentException; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Output\OutputInterface; @@ -21,16 +20,13 @@ use Symfony\Component\Console\Output\OutputInterface; class DumpCommand extends Command { /** - * @var ContainerInterface + * @var RenderOpenApi */ - private $generatorLocator; + private $renderOpenApi; - /** - * DumpCommand constructor. - */ - public function __construct(ContainerInterface $generatorLocator) + public function __construct(RenderOpenApi $renderOpenApi) { - $this->generatorLocator = $generatorLocator; + $this->renderOpenApi = $renderOpenApi; parent::__construct(); } @@ -48,25 +44,17 @@ class DumpCommand extends Command } /** - * @throws InvalidArgumentException If the area to dump is not valid - * * @return int|void */ protected function execute(InputInterface $input, OutputInterface $output) { $area = $input->getOption('area'); - if (!$this->generatorLocator->has($area)) { - throw new InvalidArgumentException(sprintf('Area "%s" is not supported.', $area)); - } - - $spec = $this->generatorLocator->get($area)->generate(); - - if ($input->hasParameterOption(['--no-pretty'])) { - $output->writeln(json_encode($spec)); - } else { - $output->writeln(json_encode($spec, JSON_PRETTY_PRINT)); - } + $options = [ + 'no-pretty' => $input->hasParameterOption(['--no-pretty']), + ]; + $docs = $this->renderOpenApi->render(RenderOpenApi::JSON, $area, $options); + $output->writeln($docs, OutputInterface::OUTPUT_RAW); return 0; } diff --git a/Controller/SwaggerUiController.php b/Controller/SwaggerUiController.php index 2b3d962..4edad7f 100644 --- a/Controller/SwaggerUiController.php +++ b/Controller/SwaggerUiController.php @@ -11,33 +11,37 @@ namespace Nelmio\ApiDocBundle\Controller; -use OpenApi\Annotations\OpenApi; -use OpenApi\Annotations\Server; -use Psr\Container\ContainerInterface; +use InvalidArgumentException; +use Nelmio\ApiDocBundle\Render\RenderOpenApi; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpKernel\Exception\BadRequestHttpException; -use Twig\Environment; final class SwaggerUiController { - private $generatorLocator; + /** + * @var RenderOpenApi + */ + private $renderOpenApi; - private $twig; - - public function __construct(ContainerInterface $generatorLocator, $twig) + public function __construct(RenderOpenApi $renderOpenApi) { - if (!$twig instanceof \Twig_Environment && !$twig instanceof Environment) { - throw new \InvalidArgumentException(sprintf('Providing an instance of "%s" as twig is not supported.', get_class($twig))); - } - - $this->generatorLocator = $generatorLocator; - $this->twig = $twig; + $this->renderOpenApi = $renderOpenApi; } public function __invoke(Request $request, $area = 'default') { - if (!$this->generatorLocator->has($area)) { + try { + $response = new Response( + $this->renderOpenApi->render(RenderOpenApi::HTML, $area, [ + 'server_url' => '' !== $request->getBaseUrl() ? $request->getSchemeAndHttpHost().$request->getBaseUrl() : null, + ]), + Response::HTTP_OK, + ['Content-Type' => 'text/html'] + ); + + return $response->setCharset('UTF-8'); + } catch (InvalidArgumentException $e) { $advice = ''; if (false !== strpos($area, '.json')) { $advice = ' Since the area provided contains `.json`, the issue is likely caused by route priorities. Try switching the Swagger UI / the json documentation routes order.'; @@ -45,23 +49,5 @@ final class SwaggerUiController throw new BadRequestHttpException(sprintf('Area "%s" is not supported as it isn\'t defined in config.%s', $area, $advice)); } - - /** @var OpenApi $spec */ - $spec = $this->generatorLocator->get($area)->generate(); - - if ('' !== $request->getBaseUrl()) { - $spec->servers = [new Server(['url' => $request->getSchemeAndHttpHost().$request->getBaseUrl()])]; - } - - return new Response( - $this->twig->render( - '@NelmioApiDoc/SwaggerUi/index.html.twig', - ['swagger_data' => ['spec' => json_decode($spec->toJson(), true)]] - ), - Response::HTTP_OK, - ['Content-Type' => 'text/html'] - ); - - return $response->setCharset('UTF-8'); } } diff --git a/Render/Html/HtmlOpenApiRenderer.php b/Render/Html/HtmlOpenApiRenderer.php new file mode 100644 index 0000000..32417be --- /dev/null +++ b/Render/Html/HtmlOpenApiRenderer.php @@ -0,0 +1,56 @@ +twig = $twig; + } + + public function getFormat(): string + { + return RenderOpenApi::HTML; + } + + public function render(OpenApi $spec, array $options = []): string + { + $options += [ + 'server_url' => null, + ]; + + if ($options['server_url']) { + $spec->servers = [new Server(['url' => $options['server_url']])]; + } + + return $this->twig->render( + '@NelmioApiDoc/SwaggerUi/index.html.twig', + ['swagger_data' => ['spec' => json_decode($spec->toJson(), true)]] + ); + } +} diff --git a/Render/Json/JsonOpenApiRenderer.php b/Render/Json/JsonOpenApiRenderer.php new file mode 100644 index 0000000..09af9e1 --- /dev/null +++ b/Render/Json/JsonOpenApiRenderer.php @@ -0,0 +1,34 @@ + false, + ]; + $flags = $options['no-pretty'] ? 0 : JSON_PRETTY_PRINT; + + return json_encode($spec, $flags); + } +} diff --git a/Render/OpenApiRenderer.php b/Render/OpenApiRenderer.php new file mode 100644 index 0000000..5a672c4 --- /dev/null +++ b/Render/OpenApiRenderer.php @@ -0,0 +1,21 @@ + */ + private $openApiRenderers = []; + + public function __construct(ContainerInterface $generatorLocator, OpenApiRenderer ...$openApiRenderers) + { + $this->generatorLocator = $generatorLocator; + foreach ($openApiRenderers as $openApiRenderer) { + $this->openApiRenderers[$openApiRenderer->getFormat()] = $openApiRenderer; + } + } + + /** + * @throws InvalidArgumentException If the area to dump is not valid + */ + public function render(string $format, string $area, array $options = []): string + { + if (!$this->generatorLocator->has($area)) { + throw new InvalidArgumentException(sprintf('Area "%s" is not supported.', $area)); + } elseif (!array_key_exists($format, $this->openApiRenderers)) { + throw new InvalidArgumentException(sprintf('Format "%s" is not supported.', $format)); + } + + /** @var OpenApi $spec */ + $spec = $this->generatorLocator->get($area)->generate(); + + return $this->openApiRenderers[$format]->render($spec, $options); + } +} diff --git a/Resources/config/services.xml b/Resources/config/services.xml index 8c888a6..47174fd 100644 --- a/Resources/config/services.xml +++ b/Resources/config/services.xml @@ -6,14 +6,13 @@ - + - - + @@ -26,6 +25,18 @@ + + + + + + + + + + + + diff --git a/Tests/Command/DumpCommandTest.php b/Tests/Command/DumpCommandTest.php index 2ccb15e..76902d2 100644 --- a/Tests/Command/DumpCommandTest.php +++ b/Tests/Command/DumpCommandTest.php @@ -17,20 +17,35 @@ use Symfony\Component\Console\Tester\CommandTester; class DumpCommandTest extends WebTestCase { - public function testExecute() + /** @dataProvider provideJsonMode */ + public function testJson(array $jsonOptions, int $expectedJsonFlags) + { + $output = $this->executeDumpCommand($jsonOptions + [ + '--area' => 'test', + ]); + $this->assertEquals( + json_encode($this->getOpenApiDefinition('test'), $expectedJsonFlags)."\n", + $output + ); + } + + public function provideJsonMode() + { + return [ + 'pretty print' => [[], JSON_PRETTY_PRINT], + 'one line' => [['--no-pretty'], 0], + ]; + } + + private function executeDumpCommand(array $options) { $kernel = static::bootKernel(); $application = new Application($kernel); $command = $application->find('nelmio:apidoc:dump'); $commandTester = new CommandTester($command); - $commandTester->execute([ - '--area' => 'test', - '--no-pretty' => '', - ]); + $commandTester->execute($options); - // the output of the command in the console - $output = $commandTester->getDisplay(); - $this->assertEquals(json_encode($this->getOpenApiDefinition('test'))."\n", $output); + return $commandTester->getDisplay(); } } diff --git a/Tests/Render/RenderOpenApiTest.php b/Tests/Render/RenderOpenApiTest.php new file mode 100644 index 0000000..d9b656b --- /dev/null +++ b/Tests/Render/RenderOpenApiTest.php @@ -0,0 +1,75 @@ +createMock(OpenApiRenderer::class); + $openApiRenderer->method('getFormat')->willReturn($this->format); + $openApiRenderer->expects($this->once())->method('render'); + $this->renderOpenApi($openApiRenderer); + } + + public function testUnknownFormat() + { + $availableOpenApiRenderers = []; + $this->expectException(InvalidArgumentException::class); + $this->expectErrorMessage(sprintf('Format "%s" is not supported.', $this->format)); + $this->renderOpenApi(...$availableOpenApiRenderers); + } + + public function testUnknownArea() + { + $this->hasArea = false; + $this->expectException(InvalidArgumentException::class); + $this->expectErrorMessage(sprintf('Area "%s" is not supported.', $this->area)); + $this->renderOpenApi(); + } + + private function renderOpenApi(...$openApiRenderer): void + { + $spec = $this->createMock(OpenApi::class); + $generator = new class($spec) { + private $spec; + + public function __construct($spec) + { + $this->spec = $spec; + } + + public function generate() + { + return $this->spec; + } + }; + + $generatorLocator = $this->createMock(ContainerInterface::class); + $generatorLocator->method('has')->willReturn($this->hasArea); + $generatorLocator->method('get')->willReturn($generator); + + $renderOpenApi = new RenderOpenApi($generatorLocator, ...$openApiRenderer); + $renderOpenApi->render($this->format, $this->area, []); + } +} From 5124f07ece91d211c53730c4b689c1d20886b6bf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zdene=CC=8Ck=20Drahos=CC=8C?= Date: Sun, 27 Jun 2021 09:24:35 +0200 Subject: [PATCH 2/9] Enable dumping html docs with cdn and offline assets --- Command/DumpCommand.php | 37 ++++++++++++++--- Controller/SwaggerUiController.php | 2 + Render/Html/AssetsMode.php | 19 +++++++++ Render/Html/GetNelmioAsset.php | 41 +++++++++++++++++++ Render/Html/HtmlOpenApiRenderer.php | 35 +++++++++++++--- Render/RenderOpenApi.php | 5 +++ Resources/config/services.xml | 1 + Resources/doc/commands.rst | 39 ++++++++++++++++++ Resources/doc/index.rst | 1 + Resources/views/SwaggerUi/index.html.twig | 38 ++++++++++++++---- Tests/Command/DumpCommandTest.php | 49 +++++++++++++++++++++++ 11 files changed, 250 insertions(+), 17 deletions(-) create mode 100644 Render/Html/AssetsMode.php create mode 100644 Render/Html/GetNelmioAsset.php create mode 100644 Resources/doc/commands.rst diff --git a/Command/DumpCommand.php b/Command/DumpCommand.php index b2438da..481c682 100644 --- a/Command/DumpCommand.php +++ b/Command/DumpCommand.php @@ -11,6 +11,7 @@ namespace Nelmio\ApiDocBundle\Command; +use Nelmio\ApiDocBundle\Render\Html\AssetsMode; use Nelmio\ApiDocBundle\Render\RenderOpenApi; use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Input\InputInterface; @@ -24,6 +25,15 @@ class DumpCommand extends Command */ private $renderOpenApi; + /** + * @var mixed[] + */ + private $defaultHtmlConfig = [ + 'assets_mode' => AssetsMode::CDN, + 'swagger_ui_config' => [], + 'server_url' => null, + ]; + public function __construct(RenderOpenApi $renderOpenApi) { $this->renderOpenApi = $renderOpenApi; @@ -36,9 +46,18 @@ class DumpCommand extends Command */ protected function configure() { + $availableFormats = $this->renderOpenApi->getAvailableFormats(); $this - ->setDescription('Dumps documentation in OpenAPI JSON format') + ->setDescription('Dumps documentation in OpenAPI JSON format or HTML') ->addOption('area', '', InputOption::VALUE_OPTIONAL, '', 'default') + ->addOption( + 'format', + '', + InputOption::VALUE_REQUIRED, + 'Output format like: '.implode(', ', $availableFormats), + RenderOpenApi::JSON + ) + ->addOption('html-config', '', InputOption::VALUE_REQUIRED, '', json_encode($this->defaultHtmlConfig)) ->addOption('no-pretty', '', InputOption::VALUE_NONE, 'Do not pretty format output') ; } @@ -49,11 +68,19 @@ class DumpCommand extends Command protected function execute(InputInterface $input, OutputInterface $output) { $area = $input->getOption('area'); + $format = $input->getOption('format'); - $options = [ - 'no-pretty' => $input->hasParameterOption(['--no-pretty']), - ]; - $docs = $this->renderOpenApi->render(RenderOpenApi::JSON, $area, $options); + $options = []; + if (RenderOpenApi::HTML === $format) { + $rawHtmlConfig = json_decode($input->getOption('html-config'), true); + $options = is_array($rawHtmlConfig) ? $rawHtmlConfig : $this->defaultHtmlConfig; + } elseif (RenderOpenApi::JSON === $format) { + $options = [ + 'no-pretty' => $input->hasParameterOption(['--no-pretty']), + ]; + } + + $docs = $this->renderOpenApi->render($format, $area, $options); $output->writeln($docs, OutputInterface::OUTPUT_RAW); return 0; diff --git a/Controller/SwaggerUiController.php b/Controller/SwaggerUiController.php index 4edad7f..1d4cca4 100644 --- a/Controller/SwaggerUiController.php +++ b/Controller/SwaggerUiController.php @@ -12,6 +12,7 @@ namespace Nelmio\ApiDocBundle\Controller; use InvalidArgumentException; +use Nelmio\ApiDocBundle\Render\Html\AssetsMode; use Nelmio\ApiDocBundle\Render\RenderOpenApi; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; @@ -35,6 +36,7 @@ final class SwaggerUiController $response = new Response( $this->renderOpenApi->render(RenderOpenApi::HTML, $area, [ 'server_url' => '' !== $request->getBaseUrl() ? $request->getSchemeAndHttpHost().$request->getBaseUrl() : null, + 'assets_mode' => AssetsMode::BUNDLE, ]), Response::HTTP_OK, ['Content-Type' => 'text/html'] diff --git a/Render/Html/AssetsMode.php b/Render/Html/AssetsMode.php new file mode 100644 index 0000000..2bbdf5a --- /dev/null +++ b/Render/Html/AssetsMode.php @@ -0,0 +1,19 @@ +assetExtension = $assetExtension; + $this->defaultAssetsMode = $defaultAssetsMode; + } + + public function __invoke($asset, $forcedMode = null) + { + $mode = $forcedMode ?: $this->defaultAssetsMode; + if (AssetsMode::CDN === $mode) { + return sprintf( + 'https://cdn.jsdelivr.net/gh/nelmio/NelmioApiDocBundle@4.1/Resources/public/%s', + $asset + ); + } elseif (AssetsMode::OFFLINE === $mode) { + return file_get_contents(__DIR__.sprintf('/../../Resources/public/%s', $asset)); + } else { + return $this->assetExtension->getAssetUrl(sprintf('bundles/nelmioapidoc/%s', $asset)); + } + } +} diff --git a/Render/Html/HtmlOpenApiRenderer.php b/Render/Html/HtmlOpenApiRenderer.php index 32417be..5bb8645 100644 --- a/Render/Html/HtmlOpenApiRenderer.php +++ b/Render/Html/HtmlOpenApiRenderer.php @@ -16,7 +16,9 @@ use Nelmio\ApiDocBundle\Render\OpenApiRenderer; use Nelmio\ApiDocBundle\Render\RenderOpenApi; use OpenApi\Annotations\OpenApi; use OpenApi\Annotations\Server; +use Symfony\Bridge\Twig\Extension\AssetExtension; use Twig\Environment; +use Twig\TwigFunction; class HtmlOpenApiRenderer implements OpenApiRenderer { @@ -24,13 +26,18 @@ class HtmlOpenApiRenderer implements OpenApiRenderer * @var Environment|\Twig_Environment */ private $twig; + /** + * @var AssetExtension + */ + private $assetExtension; - public function __construct($twig) + public function __construct($twig, AssetExtension $assetExtension) { if (!$twig instanceof \Twig_Environment && !$twig instanceof Environment) { throw new InvalidArgumentException(sprintf('Providing an instance of "%s" as twig is not supported.', get_class($twig))); } $this->twig = $twig; + $this->assetExtension = $assetExtension; } public function getFormat(): string @@ -42,15 +49,33 @@ class HtmlOpenApiRenderer implements OpenApiRenderer { $options += [ 'server_url' => null, + 'assets_mode' => AssetsMode::CDN, + 'swagger_ui_config' => [], ]; - if ($options['server_url']) { - $spec->servers = [new Server(['url' => $options['server_url']])]; - } + $this->twig->addFunction( + new TwigFunction( + 'nelmioAsset', + new GetNelmioAsset($this->assetExtension, $options['assets_mode']) + ) + ); return $this->twig->render( '@NelmioApiDoc/SwaggerUi/index.html.twig', - ['swagger_data' => ['spec' => json_decode($spec->toJson(), true)]] + [ + 'swagger_data' => ['spec' => $this->createJsonSpec($spec, $options['server_url'])], + 'assets_mode' => $options['assets_mode'], + 'swagger_ui_config' => $options['swagger_ui_config'], + ] ); } + + private function createJsonSpec(OpenApi $spec, $serverUrl) + { + if ($serverUrl) { + $spec->servers = [new Server(['url' => $serverUrl])]; + } + + return json_decode($spec->toJson(), true); + } } diff --git a/Render/RenderOpenApi.php b/Render/RenderOpenApi.php index aace14f..1407b67 100644 --- a/Render/RenderOpenApi.php +++ b/Render/RenderOpenApi.php @@ -34,6 +34,11 @@ class RenderOpenApi } } + public function getAvailableFormats(): array + { + return array_keys($this->openApiRenderers); + } + /** * @throws InvalidArgumentException If the area to dump is not valid */ diff --git a/Resources/config/services.xml b/Resources/config/services.xml index 47174fd..e0127ae 100644 --- a/Resources/config/services.xml +++ b/Resources/config/services.xml @@ -33,6 +33,7 @@ + diff --git a/Resources/doc/commands.rst b/Resources/doc/commands.rst new file mode 100644 index 0000000..fe4df38 --- /dev/null +++ b/Resources/doc/commands.rst @@ -0,0 +1,39 @@ +Commands +======== + +A command is provided in order to dump the documentation in ``json`` or ``html``. + +.. code-block:: bash + + $ php app/console api:doc:dump [--format="..."] + +The ``--format`` option allows to choose the format (default is: ``json``). + +By default, the generated JSON will be pretty-formatted. If you want to generate a json +without whitespace, use the ``--no-pretty`` option. + +.. code-block:: bash + + $ php app/console api:doc:dump --format=json > json-pretty-formatted.json + $ php app/console api:doc:dump --format=json --no-pretty > json-no-pretty.json + +For example to generate a static version of your documentation you can use: + +.. code-block:: bash + + $ php app/console api:doc:dump --format=html > api.html + +By default, the generated HTML will add the sandbox feature. +If you want to generate a static version of your documentation without sandbox, +or configure UI configuration, use the ``--html-config`` option. + +- ``assets_mode`` - `cdn` loads assets from CDN, `offline` inlines assets +- ``server_url`` - API url, useful if static documentation is not hosted on API url +- ``swagger_ui_config`` - `configure Swagger UI`_ + - ``"supportedSubmitMethods":[]`` disables the sandbox + +.. code-block:: bash + + $ php app/console api:doc:dump --format=html --html-config '{"assets_mode":"offline","server_url":"https://example.com","swagger_ui_config":{"supportedSubmitMethods":[]}}' > api.html + +.. _`configure Swagger UI`: https://swagger.io/docs/open-source-tools/swagger-ui/usage/configuration/ diff --git a/Resources/doc/index.rst b/Resources/doc/index.rst index fdc7c0e..9daa3ea 100644 --- a/Resources/doc/index.rst +++ b/Resources/doc/index.rst @@ -339,6 +339,7 @@ If you need more complex features, take a look at: areas alternative_names customization + commands faq .. _`Symfony PropertyInfo component`: https://symfony.com/doc/current/components/property_info.html diff --git a/Resources/views/SwaggerUi/index.html.twig b/Resources/views/SwaggerUi/index.html.twig index 82e1332..0db7f8b 100644 --- a/Resources/views/SwaggerUi/index.html.twig +++ b/Resources/views/SwaggerUi/index.html.twig @@ -14,8 +14,13 @@ file that was distributed with this source code. #} {% block title %}{{ swagger_data.spec.info.title }}{% endblock title %} {% block stylesheets %} - - + {% if assets_mode == 'offline' %} + + + {% else %} + + + {% endif %} {% endblock stylesheets %} {% block swagger_data %} @@ -53,7 +58,13 @@ file that was distributed with this source code. #} {% endblock svg_icons %}
{% block header %} - + {% endblock header %}
@@ -62,14 +73,27 @@ file that was distributed with this source code. #} {% endblock %} {% block javascripts %} - - + {% if assets_mode == 'offline' %} + + + {% else %} + + + {% endif %} {% endblock javascripts %} - + {% if assets_mode == 'offline' %} + + {% else %} + + {% endif %} + {% block swagger_initialization %} {% endblock swagger_initialization %} diff --git a/Tests/Command/DumpCommandTest.php b/Tests/Command/DumpCommandTest.php index 76902d2..23e6f3a 100644 --- a/Tests/Command/DumpCommandTest.php +++ b/Tests/Command/DumpCommandTest.php @@ -11,6 +11,7 @@ namespace Nelmio\ApiDocBundle\Tests\Command; +use Nelmio\ApiDocBundle\Render\Html\AssetsMode; use Nelmio\ApiDocBundle\Tests\Functional\WebTestCase; // for the creation of the kernel use Symfony\Bundle\FrameworkBundle\Console\Application; use Symfony\Component\Console\Tester\CommandTester; @@ -37,6 +38,54 @@ class DumpCommandTest extends WebTestCase ]; } + /** @dataProvider provideAssetsMode */ + public function testHtml($htmlConfig, string $expectedHtml) + { + $output = $this->executeDumpCommand([ + '--area' => 'test', + '--format' => 'html', + '--html-config' => json_encode($htmlConfig), + ]); + self::assertStringContainsString('', $output); + self::assertStringContainsString($expectedHtml, $output); + } + + public function provideAssetsMode() + { + return [ + 'default mode is cdn' => [ + null, + 'https://cdn.jsdelivr.net', + ], + 'invalid mode fallbacks to cdn' => [ + 'invalid', + 'https://cdn.jsdelivr.net', + ], + 'select cdn mode' => [ + ['assets_mode' => AssetsMode::CDN], + 'https://cdn.jsdelivr.net', + ], + 'select offline mode' => [ + ['assets_mode' => AssetsMode::OFFLINE], + '', $stylesheet); + } else { + return sprintf('', $stylesheet); } } } diff --git a/Render/Html/HtmlOpenApiRenderer.php b/Render/Html/HtmlOpenApiRenderer.php index 5bb8645..bedf14e 100644 --- a/Render/Html/HtmlOpenApiRenderer.php +++ b/Render/Html/HtmlOpenApiRenderer.php @@ -16,28 +16,23 @@ use Nelmio\ApiDocBundle\Render\OpenApiRenderer; use Nelmio\ApiDocBundle\Render\RenderOpenApi; use OpenApi\Annotations\OpenApi; use OpenApi\Annotations\Server; -use Symfony\Bridge\Twig\Extension\AssetExtension; use Twig\Environment; -use Twig\TwigFunction; class HtmlOpenApiRenderer implements OpenApiRenderer { - /** - * @var Environment|\Twig_Environment - */ + /** @var Environment|\Twig_Environment */ private $twig; - /** - * @var AssetExtension - */ - private $assetExtension; - public function __construct($twig, AssetExtension $assetExtension) + /** @var GetNelmioAsset */ + private $getNelmioAsset; + + public function __construct($twig, GetNelmioAsset $getNelmioAsset) { if (!$twig instanceof \Twig_Environment && !$twig instanceof Environment) { throw new InvalidArgumentException(sprintf('Providing an instance of "%s" as twig is not supported.', get_class($twig))); } $this->twig = $twig; - $this->assetExtension = $assetExtension; + $this->getNelmioAsset = $getNelmioAsset; } public function getFormat(): string @@ -53,12 +48,7 @@ class HtmlOpenApiRenderer implements OpenApiRenderer 'swagger_ui_config' => [], ]; - $this->twig->addFunction( - new TwigFunction( - 'nelmioAsset', - new GetNelmioAsset($this->assetExtension, $options['assets_mode']) - ) - ); + $this->twig->addFunction($this->getNelmioAsset->toTwigFunction($options['assets_mode'])); return $this->twig->render( '@NelmioApiDoc/SwaggerUi/index.html.twig', diff --git a/Resources/config/services.xml b/Resources/config/services.xml index e0127ae..d125a10 100644 --- a/Resources/config/services.xml +++ b/Resources/config/services.xml @@ -33,6 +33,9 @@ + + + diff --git a/Resources/views/SwaggerUi/index.html.twig b/Resources/views/SwaggerUi/index.html.twig index 0db7f8b..a1b3129 100644 --- a/Resources/views/SwaggerUi/index.html.twig +++ b/Resources/views/SwaggerUi/index.html.twig @@ -14,13 +14,8 @@ file that was distributed with this source code. #} {% block title %}{{ swagger_data.spec.info.title }}{% endblock title %} {% block stylesheets %} - {% if assets_mode == 'offline' %} - - - {% else %} - - - {% endif %} + {{ nelmioAsset('swagger-ui/swagger-ui.css') }} + {{ nelmioAsset('style.css') }} {% endblock stylesheets %} {% block swagger_data %} @@ -59,11 +54,7 @@ file that was distributed with this source code. #}
{% block header %} {% endblock header %}
@@ -73,20 +64,11 @@ file that was distributed with this source code. #} {% endblock %} {% block javascripts %} - {% if assets_mode == 'offline' %} - - - {% else %} - - - {% endif %} + {{ nelmioAsset('swagger-ui/swagger-ui-bundle.js') }} + {{ nelmioAsset('swagger-ui/swagger-ui-standalone-preset.js') }} {% endblock javascripts %} - {% if assets_mode == 'offline' %} - - {% else %} - - {% endif %} + {{ nelmioAsset('init-swagger-ui.js') }} {% block swagger_initialization %} ', + ], + 'cdn js' => [ + AssetsMode::CDN, + 'init-swagger-ui.js', + '', + ], + 'offline js' => [ + AssetsMode::OFFLINE, + 'init-swagger-ui.js', + '', + ], + 'external js' => [ + AssetsMode::BUNDLE, + 'https://cdn.com/my.js', + '', + ], + ]; + } + + private function provideImage($cdnDir) + { + return [ + 'bundled image' => [ + AssetsMode::BUNDLE, + 'logo.png', + '/bundles/nelmioapidoc/logo.png', + ], + 'cdn image' => [ + AssetsMode::CDN, + 'logo.png', + $cdnDir.'/logo.png', + ], + 'offline image fallbacks to cdn' => [ + AssetsMode::OFFLINE, + 'logo.png', + $cdnDir.'/logo.png', + ], + ]; + } +} From 2c890ff93b58496ab1139ec4ad6fc7942e7f7584 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zdene=CC=8Ck=20Drahos=CC=8C?= Date: Sat, 10 Jul 2021 18:00:11 +0200 Subject: [PATCH 4/9] Hotfix testing private service in Symfony < 5.3 https://github.com/nelmio/NelmioApiDocBundle/runs/3031148906?check_suite_focus=true#step:7:106 Error: Call to undefined method Nelmio\ApiDocBundle\Tests\Render\Html\GetNelmioAssetTest::getContainer() https://symfony.com/blog/new-in-symfony-4-1-simpler-service-testing In practice, tests based on WebTestCase and KernelTestCase now access to a special container via the static::$container property that allows fetching non-removed private services: https://github.com/symfony/symfony/blob/5.3/CHANGELOG-5.3.md#changelog-for-53x feature #40366 [FrameworkBundle] Add KernelTestCase::getContainer() (Nyholm) static $container @deprecated since Symfony 5.3, use static::getContainer() instead --- Tests/Render/Html/GetNelmioAssetTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Tests/Render/Html/GetNelmioAssetTest.php b/Tests/Render/Html/GetNelmioAssetTest.php index 2077a11..ce33be5 100644 --- a/Tests/Render/Html/GetNelmioAssetTest.php +++ b/Tests/Render/Html/GetNelmioAssetTest.php @@ -22,7 +22,7 @@ class GetNelmioAssetTest extends WebTestCase { static::bootKernel(); /** @var GetNelmioAsset $getNelmioAsset */ - $getNelmioAsset = self::getContainer()->get('nelmio_api_doc.render_docs.html.asset'); + $getNelmioAsset = static::$container->get('nelmio_api_doc.render_docs.html.asset'); $twigFunction = $getNelmioAsset->toTwigFunction($mode); self::assertSame($expectedContent, $twigFunction->getCallable()->__invoke($asset, $mode)); } From 1b9be28ad69d4cfedd5fa8424c5727ff19d0a93b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zdene=CC=8Ck=20Drahos=CC=8C?= Date: Thu, 29 Jul 2021 11:57:00 +0200 Subject: [PATCH 5/9] Enable dumping docs to yaml --- Command/DumpCommand.php | 2 +- Controller/YamlDocumentationController.php | 37 +++++++++++---------- Render/RenderOpenApi.php | 1 + Render/Yaml/YamlOpenApiRenderer.php | 38 ++++++++++++++++++++++ Resources/config/services.xml | 5 ++- Resources/doc/commands.rst | 2 +- Tests/Command/DumpCommandTest.php | 8 +++++ 7 files changed, 72 insertions(+), 21 deletions(-) create mode 100644 Render/Yaml/YamlOpenApiRenderer.php diff --git a/Command/DumpCommand.php b/Command/DumpCommand.php index 481c682..47f80be 100644 --- a/Command/DumpCommand.php +++ b/Command/DumpCommand.php @@ -48,7 +48,7 @@ class DumpCommand extends Command { $availableFormats = $this->renderOpenApi->getAvailableFormats(); $this - ->setDescription('Dumps documentation in OpenAPI JSON format or HTML') + ->setDescription('Dumps documentation in OpenAPI format to: '.implode(', ', $availableFormats)) ->addOption('area', '', InputOption::VALUE_OPTIONAL, '', 'default') ->addOption( 'format', diff --git a/Controller/YamlDocumentationController.php b/Controller/YamlDocumentationController.php index 7557fc8..ef9bc36 100644 --- a/Controller/YamlDocumentationController.php +++ b/Controller/YamlDocumentationController.php @@ -11,37 +11,38 @@ namespace Nelmio\ApiDocBundle\Controller; -use OpenApi\Annotations\OpenApi; -use OpenApi\Annotations\Server; -use Psr\Container\ContainerInterface; +use InvalidArgumentException; +use Nelmio\ApiDocBundle\Render\RenderOpenApi; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpKernel\Exception\BadRequestHttpException; final class YamlDocumentationController { - private $generatorLocator; + /** + * @var RenderOpenApi + */ + private $renderOpenApi; - public function __construct(ContainerInterface $generatorLocator) + public function __construct(RenderOpenApi $renderOpenApi) { - $this->generatorLocator = $generatorLocator; + $this->renderOpenApi = $renderOpenApi; } public function __invoke(Request $request, $area = 'default') { - if (!$this->generatorLocator->has($area)) { + try { + $response = new Response( + $this->renderOpenApi->render(RenderOpenApi::YAML, $area, [ + 'server_url' => '' !== $request->getBaseUrl() ? $request->getSchemeAndHttpHost().$request->getBaseUrl() : null, + ]), + Response::HTTP_OK, + ['Content-Type' => 'text/x-yaml'] + ); + + return $response->setCharset('UTF-8'); + } catch (InvalidArgumentException $e) { throw new BadRequestHttpException(sprintf('Area "%s" is not supported as it isn\'t defined in config.', $area)); } - - /** @var OpenApi $spec */ - $spec = $this->generatorLocator->get($area)->generate(); - - if ('' !== $request->getBaseUrl()) { - $spec->servers = [new Server(['url' => $request->getSchemeAndHttpHost().$request->getBaseUrl()])]; - } - - return new Response($spec->toYaml(), 200, [ - 'Content-Type' => 'text/x-yaml', - ]); } } diff --git a/Render/RenderOpenApi.php b/Render/RenderOpenApi.php index 1407b67..8076d49 100644 --- a/Render/RenderOpenApi.php +++ b/Render/RenderOpenApi.php @@ -19,6 +19,7 @@ class RenderOpenApi { public const HTML = 'html'; public const JSON = 'json'; + public const YAML = 'yaml'; /** @var ContainerInterface */ private $generatorLocator; diff --git a/Render/Yaml/YamlOpenApiRenderer.php b/Render/Yaml/YamlOpenApiRenderer.php new file mode 100644 index 0000000..025216c --- /dev/null +++ b/Render/Yaml/YamlOpenApiRenderer.php @@ -0,0 +1,38 @@ + null, + ]; + + if ($options['server_url']) { + $spec->servers = [new Server(['url' => $options['server_url']])]; + } + + return $spec->toYaml(); + } +} diff --git a/Resources/config/services.xml b/Resources/config/services.xml index d125a10..d452034 100644 --- a/Resources/config/services.xml +++ b/Resources/config/services.xml @@ -22,7 +22,7 @@
- + @@ -30,6 +30,7 @@ + @@ -40,6 +41,8 @@ + + diff --git a/Resources/doc/commands.rst b/Resources/doc/commands.rst index fe4df38..93bb6e1 100644 --- a/Resources/doc/commands.rst +++ b/Resources/doc/commands.rst @@ -1,7 +1,7 @@ Commands ======== -A command is provided in order to dump the documentation in ``json`` or ``html``. +A command is provided in order to dump the documentation in ``json``, ``yaml`` or ``html``. .. code-block:: bash diff --git a/Tests/Command/DumpCommandTest.php b/Tests/Command/DumpCommandTest.php index 23e6f3a..eb2bfbe 100644 --- a/Tests/Command/DumpCommandTest.php +++ b/Tests/Command/DumpCommandTest.php @@ -38,6 +38,14 @@ class DumpCommandTest extends WebTestCase ]; } + public function testYaml() + { + $output = $this->executeDumpCommand([ + '--format' => 'yaml', + ]); + self::assertStringContainsString($this->getOpenApiDefinition()->toYaml(), $output); + } + /** @dataProvider provideAssetsMode */ public function testHtml($htmlConfig, string $expectedHtml) { From 1f29be85f4cfd61427d407f4dc49ad673d1ec26a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zdene=CC=8Ck=20Drahos=CC=8C?= Date: Thu, 29 Jul 2021 12:01:54 +0200 Subject: [PATCH 6/9] Use json renderer in controller --- Controller/DocumentationController.php | 30 ++++++++++++-------------- Render/Json/JsonOpenApiRenderer.php | 6 ++++++ Resources/config/services.xml | 2 +- 3 files changed, 21 insertions(+), 17 deletions(-) diff --git a/Controller/DocumentationController.php b/Controller/DocumentationController.php index 0450d3a..4374bb2 100644 --- a/Controller/DocumentationController.php +++ b/Controller/DocumentationController.php @@ -11,35 +11,33 @@ namespace Nelmio\ApiDocBundle\Controller; -use OpenApi\Annotations\OpenApi; -use OpenApi\Annotations\Server; -use Psr\Container\ContainerInterface; +use Nelmio\ApiDocBundle\Render\RenderOpenApi; use Symfony\Component\HttpFoundation\JsonResponse; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpKernel\Exception\BadRequestHttpException; final class DocumentationController { - private $generatorLocator; + /** + * @var RenderOpenApi + */ + private $renderOpenApi; - public function __construct(ContainerInterface $generatorLocator) + public function __construct(RenderOpenApi $renderOpenApi) { - $this->generatorLocator = $generatorLocator; + $this->renderOpenApi = $renderOpenApi; } public function __invoke(Request $request, $area = 'default') { - if (!$this->generatorLocator->has($area)) { + try { + return JsonResponse::fromJsonString( + $this->renderOpenApi->render(RenderOpenApi::JSON, $area, [ + 'server_url' => '' !== $request->getBaseUrl() ? $request->getSchemeAndHttpHost().$request->getBaseUrl() : null, + ]) + ); + } catch (InvalidArgumentException $e) { throw new BadRequestHttpException(sprintf('Area "%s" is not supported as it isn\'t defined in config.', $area)); } - - /** @var OpenApi $spec */ - $spec = $this->generatorLocator->get($area)->generate(); - - if ('' !== $request->getBaseUrl()) { - $spec->servers = [new Server(['url' => $request->getSchemeAndHttpHost().$request->getBaseUrl()])]; - } - - return new JsonResponse($spec); } } diff --git a/Render/Json/JsonOpenApiRenderer.php b/Render/Json/JsonOpenApiRenderer.php index 09af9e1..a5da3c9 100644 --- a/Render/Json/JsonOpenApiRenderer.php +++ b/Render/Json/JsonOpenApiRenderer.php @@ -14,6 +14,7 @@ namespace Nelmio\ApiDocBundle\Render\Json; use Nelmio\ApiDocBundle\Render\OpenApiRenderer; use Nelmio\ApiDocBundle\Render\RenderOpenApi; use OpenApi\Annotations\OpenApi; +use OpenApi\Annotations\Server; class JsonOpenApiRenderer implements OpenApiRenderer { @@ -25,10 +26,15 @@ class JsonOpenApiRenderer implements OpenApiRenderer public function render(OpenApi $spec, array $options = []): string { $options += [ + 'server_url' => null, 'no-pretty' => false, ]; $flags = $options['no-pretty'] ? 0 : JSON_PRETTY_PRINT; + if ($options['server_url']) { + $spec->servers = [new Server(['url' => $options['server_url']])]; + } + return json_encode($spec, $flags); } } diff --git a/Resources/config/services.xml b/Resources/config/services.xml index d452034..3caaba4 100644 --- a/Resources/config/services.xml +++ b/Resources/config/services.xml @@ -18,7 +18,7 @@ - + From 181f4b276356f34ea307b5d44ec7ff6cc189ea27 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zdene=CC=8Ck=20Drahos=CC=8C?= Date: Thu, 29 Jul 2021 12:11:10 +0200 Subject: [PATCH 7/9] Keep server_url from Request only in RenderOpenApi --- Controller/DocumentationController.php | 4 +--- Controller/SwaggerUiController.php | 3 +-- Controller/YamlDocumentationController.php | 4 +--- Render/Html/HtmlOpenApiRenderer.php | 13 +------------ Render/Json/JsonOpenApiRenderer.php | 6 ------ Render/RenderOpenApi.php | 19 +++++++++++++++++++ Render/Yaml/YamlOpenApiRenderer.php | 9 --------- 7 files changed, 23 insertions(+), 35 deletions(-) diff --git a/Controller/DocumentationController.php b/Controller/DocumentationController.php index 4374bb2..020451a 100644 --- a/Controller/DocumentationController.php +++ b/Controller/DocumentationController.php @@ -32,9 +32,7 @@ final class DocumentationController { try { return JsonResponse::fromJsonString( - $this->renderOpenApi->render(RenderOpenApi::JSON, $area, [ - 'server_url' => '' !== $request->getBaseUrl() ? $request->getSchemeAndHttpHost().$request->getBaseUrl() : null, - ]) + $this->renderOpenApi->renderFromRequest($request, RenderOpenApi::JSON, $area) ); } catch (InvalidArgumentException $e) { throw new BadRequestHttpException(sprintf('Area "%s" is not supported as it isn\'t defined in config.', $area)); diff --git a/Controller/SwaggerUiController.php b/Controller/SwaggerUiController.php index 1d4cca4..c5a3201 100644 --- a/Controller/SwaggerUiController.php +++ b/Controller/SwaggerUiController.php @@ -34,8 +34,7 @@ final class SwaggerUiController { try { $response = new Response( - $this->renderOpenApi->render(RenderOpenApi::HTML, $area, [ - 'server_url' => '' !== $request->getBaseUrl() ? $request->getSchemeAndHttpHost().$request->getBaseUrl() : null, + $this->renderOpenApi->renderFromRequest($request, RenderOpenApi::HTML, $area, [ 'assets_mode' => AssetsMode::BUNDLE, ]), Response::HTTP_OK, diff --git a/Controller/YamlDocumentationController.php b/Controller/YamlDocumentationController.php index ef9bc36..a935a2d 100644 --- a/Controller/YamlDocumentationController.php +++ b/Controller/YamlDocumentationController.php @@ -33,9 +33,7 @@ final class YamlDocumentationController { try { $response = new Response( - $this->renderOpenApi->render(RenderOpenApi::YAML, $area, [ - 'server_url' => '' !== $request->getBaseUrl() ? $request->getSchemeAndHttpHost().$request->getBaseUrl() : null, - ]), + $this->renderOpenApi->renderFromRequest($request, RenderOpenApi::YAML, $area), Response::HTTP_OK, ['Content-Type' => 'text/x-yaml'] ); diff --git a/Render/Html/HtmlOpenApiRenderer.php b/Render/Html/HtmlOpenApiRenderer.php index bedf14e..88cb7d1 100644 --- a/Render/Html/HtmlOpenApiRenderer.php +++ b/Render/Html/HtmlOpenApiRenderer.php @@ -15,7 +15,6 @@ use InvalidArgumentException; use Nelmio\ApiDocBundle\Render\OpenApiRenderer; use Nelmio\ApiDocBundle\Render\RenderOpenApi; use OpenApi\Annotations\OpenApi; -use OpenApi\Annotations\Server; use Twig\Environment; class HtmlOpenApiRenderer implements OpenApiRenderer @@ -43,7 +42,6 @@ class HtmlOpenApiRenderer implements OpenApiRenderer public function render(OpenApi $spec, array $options = []): string { $options += [ - 'server_url' => null, 'assets_mode' => AssetsMode::CDN, 'swagger_ui_config' => [], ]; @@ -53,19 +51,10 @@ class HtmlOpenApiRenderer implements OpenApiRenderer return $this->twig->render( '@NelmioApiDoc/SwaggerUi/index.html.twig', [ - 'swagger_data' => ['spec' => $this->createJsonSpec($spec, $options['server_url'])], + 'swagger_data' => ['spec' => json_decode($spec->toJson(), true)], 'assets_mode' => $options['assets_mode'], 'swagger_ui_config' => $options['swagger_ui_config'], ] ); } - - private function createJsonSpec(OpenApi $spec, $serverUrl) - { - if ($serverUrl) { - $spec->servers = [new Server(['url' => $serverUrl])]; - } - - return json_decode($spec->toJson(), true); - } } diff --git a/Render/Json/JsonOpenApiRenderer.php b/Render/Json/JsonOpenApiRenderer.php index a5da3c9..09af9e1 100644 --- a/Render/Json/JsonOpenApiRenderer.php +++ b/Render/Json/JsonOpenApiRenderer.php @@ -14,7 +14,6 @@ namespace Nelmio\ApiDocBundle\Render\Json; use Nelmio\ApiDocBundle\Render\OpenApiRenderer; use Nelmio\ApiDocBundle\Render\RenderOpenApi; use OpenApi\Annotations\OpenApi; -use OpenApi\Annotations\Server; class JsonOpenApiRenderer implements OpenApiRenderer { @@ -26,15 +25,10 @@ class JsonOpenApiRenderer implements OpenApiRenderer public function render(OpenApi $spec, array $options = []): string { $options += [ - 'server_url' => null, 'no-pretty' => false, ]; $flags = $options['no-pretty'] ? 0 : JSON_PRETTY_PRINT; - if ($options['server_url']) { - $spec->servers = [new Server(['url' => $options['server_url']])]; - } - return json_encode($spec, $flags); } } diff --git a/Render/RenderOpenApi.php b/Render/RenderOpenApi.php index 8076d49..46b1ae1 100644 --- a/Render/RenderOpenApi.php +++ b/Render/RenderOpenApi.php @@ -13,7 +13,9 @@ namespace Nelmio\ApiDocBundle\Render; use InvalidArgumentException; use OpenApi\Annotations\OpenApi; +use OpenApi\Annotations\Server; use Psr\Container\ContainerInterface; +use Symfony\Component\HttpFoundation\Request; class RenderOpenApi { @@ -40,6 +42,19 @@ class RenderOpenApi return array_keys($this->openApiRenderers); } + public function renderFromRequest(Request $request, string $format, $area, array $extraOptions = []) + { + $options = []; + if ('' !== $request->getBaseUrl()) { + $options += [ + 'server_url' => $request->getSchemeAndHttpHost().$request->getBaseUrl(), + ]; + } + $options += $extraOptions; + + return $this->render($format, $area, $options); + } + /** * @throws InvalidArgumentException If the area to dump is not valid */ @@ -54,6 +69,10 @@ class RenderOpenApi /** @var OpenApi $spec */ $spec = $this->generatorLocator->get($area)->generate(); + if (array_key_exists('server_url', $options) && $options['server_url']) { + $spec->servers = [new Server(['url' => $options['server_url']])]; + } + return $this->openApiRenderers[$format]->render($spec, $options); } } diff --git a/Render/Yaml/YamlOpenApiRenderer.php b/Render/Yaml/YamlOpenApiRenderer.php index 025216c..a8e7c5d 100644 --- a/Render/Yaml/YamlOpenApiRenderer.php +++ b/Render/Yaml/YamlOpenApiRenderer.php @@ -14,7 +14,6 @@ namespace Nelmio\ApiDocBundle\Render\Yaml; use Nelmio\ApiDocBundle\Render\OpenApiRenderer; use Nelmio\ApiDocBundle\Render\RenderOpenApi; use OpenApi\Annotations\OpenApi; -use OpenApi\Annotations\Server; class YamlOpenApiRenderer implements OpenApiRenderer { @@ -25,14 +24,6 @@ class YamlOpenApiRenderer implements OpenApiRenderer public function render(OpenApi $spec, array $options = []): string { - $options += [ - 'server_url' => null, - ]; - - if ($options['server_url']) { - $spec->servers = [new Server(['url' => $options['server_url']])]; - } - return $spec->toYaml(); } } From 1a21f1855ed9866e512aa59f3fd4d3981f1d8a4d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zdene=CC=8Ck=20Drahos=CC=8C?= Date: Thu, 29 Jul 2021 12:22:54 +0200 Subject: [PATCH 8/9] Enable overriding server url for yaml and json export from console --- Command/DumpCommand.php | 8 ++++++-- Resources/doc/commands.rst | 6 ++++++ Tests/Command/DumpCommandTest.php | 8 +++++++- 3 files changed, 19 insertions(+), 3 deletions(-) diff --git a/Command/DumpCommand.php b/Command/DumpCommand.php index 47f80be..c2d929f 100644 --- a/Command/DumpCommand.php +++ b/Command/DumpCommand.php @@ -31,7 +31,6 @@ class DumpCommand extends Command private $defaultHtmlConfig = [ 'assets_mode' => AssetsMode::CDN, 'swagger_ui_config' => [], - 'server_url' => null, ]; public function __construct(RenderOpenApi $renderOpenApi) @@ -57,8 +56,9 @@ class DumpCommand extends Command 'Output format like: '.implode(', ', $availableFormats), RenderOpenApi::JSON ) + ->addOption('server-url', '', InputOption::VALUE_REQUIRED, 'URL where live api doc is served') ->addOption('html-config', '', InputOption::VALUE_REQUIRED, '', json_encode($this->defaultHtmlConfig)) - ->addOption('no-pretty', '', InputOption::VALUE_NONE, 'Do not pretty format output') + ->addOption('no-pretty', '', InputOption::VALUE_NONE, 'Do not pretty format JSON output') ; } @@ -80,6 +80,10 @@ class DumpCommand extends Command ]; } + if ($input->getOption('server-url')) { + $options['server_url'] = $input->getOption('server-url'); + } + $docs = $this->renderOpenApi->render($format, $area, $options); $output->writeln($docs, OutputInterface::OUTPUT_RAW); diff --git a/Resources/doc/commands.rst b/Resources/doc/commands.rst index 93bb6e1..839a122 100644 --- a/Resources/doc/commands.rst +++ b/Resources/doc/commands.rst @@ -17,6 +17,12 @@ without whitespace, use the ``--no-pretty`` option. $ php app/console api:doc:dump --format=json > json-pretty-formatted.json $ php app/console api:doc:dump --format=json --no-pretty > json-no-pretty.json +Every format can override API url. Useful if static documentation is not hosted on API url: + +.. code-block:: bash + + $ php app/console api:doc:dump --format=yaml --server-url "http://example.com/api" > api.yaml + For example to generate a static version of your documentation you can use: .. code-block:: bash diff --git a/Tests/Command/DumpCommandTest.php b/Tests/Command/DumpCommandTest.php index eb2bfbe..9d82f38 100644 --- a/Tests/Command/DumpCommandTest.php +++ b/Tests/Command/DumpCommandTest.php @@ -42,8 +42,14 @@ class DumpCommandTest extends WebTestCase { $output = $this->executeDumpCommand([ '--format' => 'yaml', + '--server-url' => 'http://example.com/api', ]); - self::assertStringContainsString($this->getOpenApiDefinition()->toYaml(), $output); + $expectedYaml = << Date: Sat, 28 Aug 2021 08:29:07 +0200 Subject: [PATCH 9/9] Add @internal to renderer classes --- Render/Html/GetNelmioAsset.php | 3 +++ Render/Html/HtmlOpenApiRenderer.php | 3 +++ Render/Json/JsonOpenApiRenderer.php | 3 +++ Render/OpenApiRenderer.php | 3 +++ Render/Yaml/YamlOpenApiRenderer.php | 3 +++ 5 files changed, 15 insertions(+) diff --git a/Render/Html/GetNelmioAsset.php b/Render/Html/GetNelmioAsset.php index 38e85a6..a312899 100644 --- a/Render/Html/GetNelmioAsset.php +++ b/Render/Html/GetNelmioAsset.php @@ -14,6 +14,9 @@ namespace Nelmio\ApiDocBundle\Render\Html; use Symfony\Bridge\Twig\Extension\AssetExtension; use Twig\TwigFunction; +/** + * @internal + */ class GetNelmioAsset { private $assetExtension; diff --git a/Render/Html/HtmlOpenApiRenderer.php b/Render/Html/HtmlOpenApiRenderer.php index 88cb7d1..78577f1 100644 --- a/Render/Html/HtmlOpenApiRenderer.php +++ b/Render/Html/HtmlOpenApiRenderer.php @@ -17,6 +17,9 @@ use Nelmio\ApiDocBundle\Render\RenderOpenApi; use OpenApi\Annotations\OpenApi; use Twig\Environment; +/** + * @internal + */ class HtmlOpenApiRenderer implements OpenApiRenderer { /** @var Environment|\Twig_Environment */ diff --git a/Render/Json/JsonOpenApiRenderer.php b/Render/Json/JsonOpenApiRenderer.php index 09af9e1..62b0a7f 100644 --- a/Render/Json/JsonOpenApiRenderer.php +++ b/Render/Json/JsonOpenApiRenderer.php @@ -15,6 +15,9 @@ use Nelmio\ApiDocBundle\Render\OpenApiRenderer; use Nelmio\ApiDocBundle\Render\RenderOpenApi; use OpenApi\Annotations\OpenApi; +/** + * @internal + */ class JsonOpenApiRenderer implements OpenApiRenderer { public function getFormat(): string diff --git a/Render/OpenApiRenderer.php b/Render/OpenApiRenderer.php index 5a672c4..73edb66 100644 --- a/Render/OpenApiRenderer.php +++ b/Render/OpenApiRenderer.php @@ -13,6 +13,9 @@ namespace Nelmio\ApiDocBundle\Render; use OpenApi\Annotations\OpenApi; +/** + * @internal + */ interface OpenApiRenderer { public function getFormat(): string; diff --git a/Render/Yaml/YamlOpenApiRenderer.php b/Render/Yaml/YamlOpenApiRenderer.php index a8e7c5d..b96a284 100644 --- a/Render/Yaml/YamlOpenApiRenderer.php +++ b/Render/Yaml/YamlOpenApiRenderer.php @@ -15,6 +15,9 @@ use Nelmio\ApiDocBundle\Render\OpenApiRenderer; use Nelmio\ApiDocBundle\Render\RenderOpenApi; use OpenApi\Annotations\OpenApi; +/** + * @internal + */ class YamlOpenApiRenderer implements OpenApiRenderer { public function getFormat(): string