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] 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 %}
@@ -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 %}
', $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],
+ '