mirror of
https://github.com/retailcrm/NelmioApiDocBundle.git
synced 2025-02-09 02:59:27 +03:00
Merge pull request #1842 from zdenekdrahos/html-dump
Dump documentation to a static HTML and YAML file
This commit is contained in:
commit
b53edda10c
@ -11,9 +11,9 @@
|
||||
|
||||
namespace Nelmio\ApiDocBundle\Command;
|
||||
|
||||
use Psr\Container\ContainerInterface;
|
||||
use Nelmio\ApiDocBundle\Render\Html\AssetsMode;
|
||||
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 +21,21 @@ use Symfony\Component\Console\Output\OutputInterface;
|
||||
class DumpCommand extends Command
|
||||
{
|
||||
/**
|
||||
* @var ContainerInterface
|
||||
* @var RenderOpenApi
|
||||
*/
|
||||
private $generatorLocator;
|
||||
private $renderOpenApi;
|
||||
|
||||
/**
|
||||
* DumpCommand constructor.
|
||||
* @var mixed[]
|
||||
*/
|
||||
public function __construct(ContainerInterface $generatorLocator)
|
||||
private $defaultHtmlConfig = [
|
||||
'assets_mode' => AssetsMode::CDN,
|
||||
'swagger_ui_config' => [],
|
||||
];
|
||||
|
||||
public function __construct(RenderOpenApi $renderOpenApi)
|
||||
{
|
||||
$this->generatorLocator = $generatorLocator;
|
||||
$this->renderOpenApi = $renderOpenApi;
|
||||
|
||||
parent::__construct();
|
||||
}
|
||||
@ -40,34 +45,48 @@ class DumpCommand extends Command
|
||||
*/
|
||||
protected function configure()
|
||||
{
|
||||
$availableFormats = $this->renderOpenApi->getAvailableFormats();
|
||||
$this
|
||||
->setDescription('Dumps documentation in OpenAPI JSON format')
|
||||
->setDescription('Dumps documentation in OpenAPI format to: '.implode(', ', $availableFormats))
|
||||
->addOption('area', '', InputOption::VALUE_OPTIONAL, '', 'default')
|
||||
->addOption('no-pretty', '', InputOption::VALUE_NONE, 'Do not pretty format output')
|
||||
->addOption(
|
||||
'format',
|
||||
'',
|
||||
InputOption::VALUE_REQUIRED,
|
||||
'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 JSON output')
|
||||
;
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws InvalidArgumentException If the area to dump is not valid
|
||||
*
|
||||
* @return int|void
|
||||
*/
|
||||
protected function execute(InputInterface $input, OutputInterface $output)
|
||||
{
|
||||
$area = $input->getOption('area');
|
||||
$format = $input->getOption('format');
|
||||
|
||||
if (!$this->generatorLocator->has($area)) {
|
||||
throw new InvalidArgumentException(sprintf('Area "%s" is not supported.', $area));
|
||||
$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']),
|
||||
];
|
||||
}
|
||||
|
||||
$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));
|
||||
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);
|
||||
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
@ -11,35 +11,31 @@
|
||||
|
||||
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->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));
|
||||
}
|
||||
|
||||
/** @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);
|
||||
}
|
||||
}
|
||||
|
@ -11,33 +11,38 @@
|
||||
|
||||
namespace Nelmio\ApiDocBundle\Controller;
|
||||
|
||||
use OpenApi\Annotations\OpenApi;
|
||||
use OpenApi\Annotations\Server;
|
||||
use Psr\Container\ContainerInterface;
|
||||
use InvalidArgumentException;
|
||||
use Nelmio\ApiDocBundle\Render\Html\AssetsMode;
|
||||
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->renderFromRequest($request, RenderOpenApi::HTML, $area, [
|
||||
'assets_mode' => AssetsMode::BUNDLE,
|
||||
]),
|
||||
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 +50,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');
|
||||
}
|
||||
}
|
||||
|
@ -11,37 +11,36 @@
|
||||
|
||||
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->renderFromRequest($request, RenderOpenApi::YAML, $area),
|
||||
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',
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
19
Render/Html/AssetsMode.php
Normal file
19
Render/Html/AssetsMode.php
Normal file
@ -0,0 +1,19 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the NelmioApiDocBundle package.
|
||||
*
|
||||
* (c) Nelmio
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Nelmio\ApiDocBundle\Render\Html;
|
||||
|
||||
class AssetsMode
|
||||
{
|
||||
public const BUNDLE = 'bundle';
|
||||
public const CDN = 'cdn';
|
||||
public const OFFLINE = 'offline';
|
||||
}
|
96
Render/Html/GetNelmioAsset.php
Normal file
96
Render/Html/GetNelmioAsset.php
Normal file
@ -0,0 +1,96 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the NelmioApiDocBundle package.
|
||||
*
|
||||
* (c) Nelmio
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Nelmio\ApiDocBundle\Render\Html;
|
||||
|
||||
use Symfony\Bridge\Twig\Extension\AssetExtension;
|
||||
use Twig\TwigFunction;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
class GetNelmioAsset
|
||||
{
|
||||
private $assetExtension;
|
||||
private $resourcesDir;
|
||||
private $cdnUrl;
|
||||
private $assetsMode = AssetsMode::BUNDLE;
|
||||
|
||||
public function __construct(AssetExtension $assetExtension)
|
||||
{
|
||||
$this->assetExtension = $assetExtension;
|
||||
$this->cdnUrl = 'https://cdn.jsdelivr.net/gh/nelmio/NelmioApiDocBundle/Resources/public';
|
||||
$this->resourcesDir = __DIR__.'/../../Resources/public';
|
||||
}
|
||||
|
||||
public function toTwigFunction($assetsMode): TwigFunction
|
||||
{
|
||||
$this->assetsMode = $assetsMode;
|
||||
|
||||
return new TwigFunction('nelmioAsset', $this, ['is_safe' => ['html']]);
|
||||
}
|
||||
|
||||
public function __invoke($asset)
|
||||
{
|
||||
[$extension, $mode] = $this->getExtension($asset);
|
||||
[$resource, $isInline] = $this->getResource($asset, $mode);
|
||||
if ('js' == $extension) {
|
||||
return $this->renderJavascript($resource, $isInline);
|
||||
} elseif ('css' == $extension) {
|
||||
return $this->renderCss($resource, $isInline);
|
||||
} else {
|
||||
return $resource;
|
||||
}
|
||||
}
|
||||
|
||||
private function getExtension($asset)
|
||||
{
|
||||
$extension = mb_substr($asset, -3, 3, 'utf-8');
|
||||
if ('.js' === $extension) {
|
||||
return ['js', $this->assetsMode];
|
||||
} elseif ('png' === $extension) {
|
||||
return ['png', AssetsMode::OFFLINE == $this->assetsMode ? AssetsMode::CDN : $this->assetsMode];
|
||||
} else {
|
||||
return ['css', $this->assetsMode];
|
||||
}
|
||||
}
|
||||
|
||||
private function getResource($asset, $mode)
|
||||
{
|
||||
if (filter_var($asset, FILTER_VALIDATE_URL)) {
|
||||
return [$asset, false];
|
||||
} elseif (AssetsMode::OFFLINE === $mode) {
|
||||
return [file_get_contents($this->resourcesDir.'/'.$asset), true];
|
||||
} elseif (AssetsMode::CDN === $mode) {
|
||||
return [$this->cdnUrl.'/'.$asset, false];
|
||||
} else {
|
||||
return [$this->assetExtension->getAssetUrl(sprintf('bundles/nelmioapidoc/%s', $asset)), false];
|
||||
}
|
||||
}
|
||||
|
||||
private function renderJavascript(string $script, bool $isInline)
|
||||
{
|
||||
if ($isInline) {
|
||||
return sprintf('<script>%s</script>', $script);
|
||||
} else {
|
||||
return sprintf('<script src="%s"></script>', $script);
|
||||
}
|
||||
}
|
||||
|
||||
private function renderCss(string $stylesheet, bool $isInline)
|
||||
{
|
||||
if ($isInline) {
|
||||
return sprintf('<style>%s</style>', $stylesheet);
|
||||
} else {
|
||||
return sprintf('<link rel="stylesheet" href="%s">', $stylesheet);
|
||||
}
|
||||
}
|
||||
}
|
63
Render/Html/HtmlOpenApiRenderer.php
Normal file
63
Render/Html/HtmlOpenApiRenderer.php
Normal file
@ -0,0 +1,63 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the NelmioApiDocBundle package.
|
||||
*
|
||||
* (c) Nelmio
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Nelmio\ApiDocBundle\Render\Html;
|
||||
|
||||
use InvalidArgumentException;
|
||||
use Nelmio\ApiDocBundle\Render\OpenApiRenderer;
|
||||
use Nelmio\ApiDocBundle\Render\RenderOpenApi;
|
||||
use OpenApi\Annotations\OpenApi;
|
||||
use Twig\Environment;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
class HtmlOpenApiRenderer implements OpenApiRenderer
|
||||
{
|
||||
/** @var Environment|\Twig_Environment */
|
||||
private $twig;
|
||||
|
||||
/** @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->getNelmioAsset = $getNelmioAsset;
|
||||
}
|
||||
|
||||
public function getFormat(): string
|
||||
{
|
||||
return RenderOpenApi::HTML;
|
||||
}
|
||||
|
||||
public function render(OpenApi $spec, array $options = []): string
|
||||
{
|
||||
$options += [
|
||||
'assets_mode' => AssetsMode::CDN,
|
||||
'swagger_ui_config' => [],
|
||||
];
|
||||
|
||||
$this->twig->addFunction($this->getNelmioAsset->toTwigFunction($options['assets_mode']));
|
||||
|
||||
return $this->twig->render(
|
||||
'@NelmioApiDoc/SwaggerUi/index.html.twig',
|
||||
[
|
||||
'swagger_data' => ['spec' => json_decode($spec->toJson(), true)],
|
||||
'assets_mode' => $options['assets_mode'],
|
||||
'swagger_ui_config' => $options['swagger_ui_config'],
|
||||
]
|
||||
);
|
||||
}
|
||||
}
|
37
Render/Json/JsonOpenApiRenderer.php
Normal file
37
Render/Json/JsonOpenApiRenderer.php
Normal file
@ -0,0 +1,37 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the NelmioApiDocBundle package.
|
||||
*
|
||||
* (c) Nelmio
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Nelmio\ApiDocBundle\Render\Json;
|
||||
|
||||
use Nelmio\ApiDocBundle\Render\OpenApiRenderer;
|
||||
use Nelmio\ApiDocBundle\Render\RenderOpenApi;
|
||||
use OpenApi\Annotations\OpenApi;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
class JsonOpenApiRenderer implements OpenApiRenderer
|
||||
{
|
||||
public function getFormat(): string
|
||||
{
|
||||
return RenderOpenApi::JSON;
|
||||
}
|
||||
|
||||
public function render(OpenApi $spec, array $options = []): string
|
||||
{
|
||||
$options += [
|
||||
'no-pretty' => false,
|
||||
];
|
||||
$flags = $options['no-pretty'] ? 0 : JSON_PRETTY_PRINT;
|
||||
|
||||
return json_encode($spec, $flags);
|
||||
}
|
||||
}
|
24
Render/OpenApiRenderer.php
Normal file
24
Render/OpenApiRenderer.php
Normal file
@ -0,0 +1,24 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the NelmioApiDocBundle package.
|
||||
*
|
||||
* (c) Nelmio
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Nelmio\ApiDocBundle\Render;
|
||||
|
||||
use OpenApi\Annotations\OpenApi;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
interface OpenApiRenderer
|
||||
{
|
||||
public function getFormat(): string;
|
||||
|
||||
public function render(OpenApi $spec, array $options = []): string;
|
||||
}
|
78
Render/RenderOpenApi.php
Normal file
78
Render/RenderOpenApi.php
Normal file
@ -0,0 +1,78 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the NelmioApiDocBundle package.
|
||||
*
|
||||
* (c) Nelmio
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
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
|
||||
{
|
||||
public const HTML = 'html';
|
||||
public const JSON = 'json';
|
||||
public const YAML = 'yaml';
|
||||
|
||||
/** @var ContainerInterface */
|
||||
private $generatorLocator;
|
||||
|
||||
/** @var array<string, OpenApiRenderer> */
|
||||
private $openApiRenderers = [];
|
||||
|
||||
public function __construct(ContainerInterface $generatorLocator, OpenApiRenderer ...$openApiRenderers)
|
||||
{
|
||||
$this->generatorLocator = $generatorLocator;
|
||||
foreach ($openApiRenderers as $openApiRenderer) {
|
||||
$this->openApiRenderers[$openApiRenderer->getFormat()] = $openApiRenderer;
|
||||
}
|
||||
}
|
||||
|
||||
public function getAvailableFormats(): array
|
||||
{
|
||||
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
|
||||
*/
|
||||
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();
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
32
Render/Yaml/YamlOpenApiRenderer.php
Normal file
32
Render/Yaml/YamlOpenApiRenderer.php
Normal file
@ -0,0 +1,32 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the NelmioApiDocBundle package.
|
||||
*
|
||||
* (c) Nelmio
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Nelmio\ApiDocBundle\Render\Yaml;
|
||||
|
||||
use Nelmio\ApiDocBundle\Render\OpenApiRenderer;
|
||||
use Nelmio\ApiDocBundle\Render\RenderOpenApi;
|
||||
use OpenApi\Annotations\OpenApi;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
class YamlOpenApiRenderer implements OpenApiRenderer
|
||||
{
|
||||
public function getFormat(): string
|
||||
{
|
||||
return RenderOpenApi::YAML;
|
||||
}
|
||||
|
||||
public function render(OpenApi $spec, array $options = []): string
|
||||
{
|
||||
return $spec->toYaml();
|
||||
}
|
||||
}
|
@ -6,24 +6,42 @@
|
||||
<services>
|
||||
<!-- Commands -->
|
||||
<service id="nelmio_api_doc.command.dump" class="Nelmio\ApiDocBundle\Command\DumpCommand" public="true">
|
||||
<argument type="service" id="nelmio_api_doc.generator_locator" />
|
||||
<argument type="service" id="nelmio_api_doc.render_docs" />
|
||||
<tag name="console.command" command="nelmio:apidoc:dump" />
|
||||
</service>
|
||||
|
||||
<!-- Controllers -->
|
||||
<service id="nelmio_api_doc.controller.swagger_ui" class="Nelmio\ApiDocBundle\Controller\SwaggerUiController" public="true">
|
||||
<argument type="service" id="nelmio_api_doc.generator_locator" />
|
||||
<argument type="service" id="twig" />
|
||||
<argument type="service" id="nelmio_api_doc.render_docs" />
|
||||
</service>
|
||||
|
||||
<service id="nelmio_api_doc.controller.swagger" alias="nelmio_api_doc.controller.swagger_json" public="true" />
|
||||
|
||||
<service id="nelmio_api_doc.controller.swagger_json" class="Nelmio\ApiDocBundle\Controller\DocumentationController" public="true">
|
||||
<argument type="service" id="nelmio_api_doc.generator_locator" />
|
||||
<argument type="service" id="nelmio_api_doc.render_docs" />
|
||||
</service>
|
||||
|
||||
<service id="nelmio_api_doc.controller.swagger_yaml" class="Nelmio\ApiDocBundle\Controller\YamlDocumentationController" public="true">
|
||||
<argument type="service" id="nelmio_api_doc.render_docs" />
|
||||
</service>
|
||||
|
||||
<!-- Render -->
|
||||
<service id="nelmio_api_doc.render_docs" class="Nelmio\ApiDocBundle\Render\RenderOpenApi" public="true">
|
||||
<argument type="service" id="nelmio_api_doc.generator_locator" />
|
||||
<argument type="service" id="nelmio_api_doc.render_docs.html" />
|
||||
<argument type="service" id="nelmio_api_doc.render_docs.json" />
|
||||
<argument type="service" id="nelmio_api_doc.render_docs.yaml" />
|
||||
</service>
|
||||
<service id="nelmio_api_doc.render_docs.html" class="Nelmio\ApiDocBundle\Render\Html\HtmlOpenApiRenderer" public="false">
|
||||
<argument type="service" id="twig" />
|
||||
<argument type="service" id="nelmio_api_doc.render_docs.html.asset" />
|
||||
</service>
|
||||
<service id="nelmio_api_doc.render_docs.html.asset" class="Nelmio\ApiDocBundle\Render\Html\GetNelmioAsset" public="false">
|
||||
<argument type="service" id="twig.extension.assets" />
|
||||
</service>
|
||||
<service id="nelmio_api_doc.render_docs.json" class="Nelmio\ApiDocBundle\Render\Json\JsonOpenApiRenderer" public="false">
|
||||
</service>
|
||||
<service id="nelmio_api_doc.render_docs.yaml" class="Nelmio\ApiDocBundle\Render\Yaml\YamlOpenApiRenderer" public="false">
|
||||
</service>
|
||||
|
||||
<!-- Swagger Spec Generator -->
|
||||
|
45
Resources/doc/commands.rst
Normal file
45
Resources/doc/commands.rst
Normal file
@ -0,0 +1,45 @@
|
||||
Commands
|
||||
========
|
||||
|
||||
A command is provided in order to dump the documentation in ``json``, ``yaml`` 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
|
||||
|
||||
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
|
||||
|
||||
$ 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/
|
@ -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
|
||||
|
@ -14,8 +14,8 @@ file that was distributed with this source code. #}
|
||||
<title>{% block title %}{{ swagger_data.spec.info.title }}{% endblock title %}</title>
|
||||
|
||||
{% block stylesheets %}
|
||||
<link rel="stylesheet" href="{{ asset('bundles/nelmioapidoc/swagger-ui/swagger-ui.css') }}">
|
||||
<link rel="stylesheet" href="{{ asset('bundles/nelmioapidoc/style.css') }}">
|
||||
{{ nelmioAsset('swagger-ui/swagger-ui.css') }}
|
||||
{{ nelmioAsset('style.css') }}
|
||||
{% endblock stylesheets %}
|
||||
|
||||
{% block swagger_data %}
|
||||
@ -53,7 +53,9 @@ file that was distributed with this source code. #}
|
||||
{% endblock svg_icons %}
|
||||
<header>
|
||||
{% block header %}
|
||||
<a id="logo" href="https://github.com/nelmio/NelmioApiDocBundle"><img src="{{ asset('bundles/nelmioapidoc/logo.png') }}" alt="NelmioApiDocBundle"></a>
|
||||
<a id="logo" href="https://github.com/nelmio/NelmioApiDocBundle">
|
||||
<img src="{{ nelmioAsset('logo.png') }}" alt="NelmioApiDocBundle">
|
||||
</a>
|
||||
{% endblock header %}
|
||||
</header>
|
||||
|
||||
@ -62,14 +64,18 @@ file that was distributed with this source code. #}
|
||||
{% endblock %}
|
||||
|
||||
{% block javascripts %}
|
||||
<script src="{{ asset('bundles/nelmioapidoc/swagger-ui/swagger-ui-bundle.js') }}"></script>
|
||||
<script src="{{ asset('bundles/nelmioapidoc/swagger-ui/swagger-ui-standalone-preset.js') }}"></script>
|
||||
{{ nelmioAsset('swagger-ui/swagger-ui-bundle.js') }}
|
||||
{{ nelmioAsset('swagger-ui/swagger-ui-standalone-preset.js') }}
|
||||
{% endblock javascripts %}
|
||||
|
||||
<script src="{{ asset('bundles/nelmioapidoc/init-swagger-ui.js') }}"></script>
|
||||
{{ nelmioAsset('init-swagger-ui.js') }}
|
||||
|
||||
{% block swagger_initialization %}
|
||||
<script type="text/javascript">
|
||||
window.onload = loadSwaggerUI();
|
||||
(function () {
|
||||
var swaggerUI = {{ swagger_ui_config|json_encode(65)|raw }};
|
||||
window.onload = loadSwaggerUI(swaggerUI);
|
||||
})();
|
||||
</script>
|
||||
{% endblock swagger_initialization %}
|
||||
</body>
|
||||
|
@ -11,26 +11,104 @@
|
||||
|
||||
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;
|
||||
|
||||
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],
|
||||
];
|
||||
}
|
||||
|
||||
public function testYaml()
|
||||
{
|
||||
$output = $this->executeDumpCommand([
|
||||
'--format' => 'yaml',
|
||||
'--server-url' => 'http://example.com/api',
|
||||
]);
|
||||
$expectedYaml = <<<YAML
|
||||
servers:
|
||||
-
|
||||
url: 'http://example.com/api'
|
||||
YAML;
|
||||
self::assertStringContainsString($expectedYaml, $output);
|
||||
}
|
||||
|
||||
/** @dataProvider provideAssetsMode */
|
||||
public function testHtml($htmlConfig, string $expectedHtml)
|
||||
{
|
||||
$output = $this->executeDumpCommand([
|
||||
'--area' => 'test',
|
||||
'--format' => 'html',
|
||||
'--html-config' => json_encode($htmlConfig),
|
||||
]);
|
||||
self::assertStringContainsString('<body>', $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],
|
||||
'<style>',
|
||||
],
|
||||
'configure swagger ui' => [
|
||||
[
|
||||
'swagger_ui_config' => [
|
||||
'supportedSubmitMethods' => ['get'],
|
||||
],
|
||||
],
|
||||
'"supportedSubmitMethods":["get"]',
|
||||
],
|
||||
'configure server url' => [
|
||||
[
|
||||
'server_url' => 'http://example.com/api',
|
||||
],
|
||||
'[{"url":"http://example.com/api"}]',
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
112
Tests/Render/Html/GetNelmioAssetTest.php
Normal file
112
Tests/Render/Html/GetNelmioAssetTest.php
Normal file
@ -0,0 +1,112 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the NelmioApiDocBundle package.
|
||||
*
|
||||
* (c) Nelmio
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Nelmio\ApiDocBundle\Tests\Render\Html;
|
||||
|
||||
use Nelmio\ApiDocBundle\Render\Html\AssetsMode;
|
||||
use Nelmio\ApiDocBundle\Render\Html\GetNelmioAsset;
|
||||
use Nelmio\ApiDocBundle\Tests\Functional\WebTestCase;
|
||||
|
||||
class GetNelmioAssetTest extends WebTestCase
|
||||
{
|
||||
/** @dataProvider provideAsset */
|
||||
public function test($mode, $asset, $expectedContent)
|
||||
{
|
||||
static::bootKernel();
|
||||
/** @var GetNelmioAsset $getNelmioAsset */
|
||||
$getNelmioAsset = static::$container->get('nelmio_api_doc.render_docs.html.asset');
|
||||
$twigFunction = $getNelmioAsset->toTwigFunction($mode);
|
||||
self::assertSame($expectedContent, $twigFunction->getCallable()->__invoke($asset, $mode));
|
||||
}
|
||||
|
||||
public function provideAsset()
|
||||
{
|
||||
$cdnDir = 'https://cdn.jsdelivr.net/gh/nelmio/NelmioApiDocBundle/Resources/public';
|
||||
$resourceDir = __DIR__.'/../../../Resources/public';
|
||||
|
||||
return $this->provideCss($cdnDir, $resourceDir)
|
||||
+ $this->provideJs($cdnDir, $resourceDir)
|
||||
+ $this->provideImage($cdnDir);
|
||||
}
|
||||
|
||||
private function provideCss($cdnDir, $resourceDir)
|
||||
{
|
||||
return [
|
||||
'bundled css' => [
|
||||
AssetsMode::BUNDLE,
|
||||
'style.css',
|
||||
'<link rel="stylesheet" href="/bundles/nelmioapidoc/style.css">',
|
||||
],
|
||||
'cdn css' => [
|
||||
AssetsMode::CDN,
|
||||
'style.css',
|
||||
'<link rel="stylesheet" href="'.$cdnDir.'/style.css">',
|
||||
],
|
||||
'offline css' => [
|
||||
AssetsMode::OFFLINE,
|
||||
'style.css',
|
||||
'<style>'.file_get_contents($resourceDir.'/style.css').'</style>',
|
||||
],
|
||||
'external css' => [
|
||||
AssetsMode::BUNDLE,
|
||||
'https://cdn.com/my.css',
|
||||
'<link rel="stylesheet" href="https://cdn.com/my.css">',
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
private function provideJs($cdnDir, $resourceDir)
|
||||
{
|
||||
return [
|
||||
'bundled js' => [
|
||||
AssetsMode::BUNDLE,
|
||||
'init-swagger-ui.js',
|
||||
'<script src="/bundles/nelmioapidoc/init-swagger-ui.js"></script>',
|
||||
],
|
||||
'cdn js' => [
|
||||
AssetsMode::CDN,
|
||||
'init-swagger-ui.js',
|
||||
'<script src="'.$cdnDir.'/init-swagger-ui.js"></script>',
|
||||
],
|
||||
'offline js' => [
|
||||
AssetsMode::OFFLINE,
|
||||
'init-swagger-ui.js',
|
||||
'<script>'.file_get_contents($resourceDir.'/init-swagger-ui.js').'</script>',
|
||||
],
|
||||
'external js' => [
|
||||
AssetsMode::BUNDLE,
|
||||
'https://cdn.com/my.js',
|
||||
'<script src="https://cdn.com/my.js"></script>',
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
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',
|
||||
],
|
||||
];
|
||||
}
|
||||
}
|
75
Tests/Render/RenderOpenApiTest.php
Normal file
75
Tests/Render/RenderOpenApiTest.php
Normal file
@ -0,0 +1,75 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the NelmioApiDocBundle package.
|
||||
*
|
||||
* (c) Nelmio
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Nelmio\ApiDocBundle\Tests\Render;
|
||||
|
||||
use InvalidArgumentException;
|
||||
use Nelmio\ApiDocBundle\Render\OpenApiRenderer;
|
||||
use Nelmio\ApiDocBundle\Render\RenderOpenApi;
|
||||
use OpenApi\Annotations\OpenApi;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Psr\Container\ContainerInterface;
|
||||
|
||||
class RenderOpenApiTest extends TestCase
|
||||
{
|
||||
private $area = 'irrelevant area';
|
||||
private $format = 'irrelevant format';
|
||||
private $hasArea = true;
|
||||
|
||||
public function testRender()
|
||||
{
|
||||
$openApiRenderer = $this->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, []);
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user