diff --git a/src/Laravel/ApiPlatformProvider.php b/src/Laravel/ApiPlatformProvider.php index 893f5406b66..412edbbc126 100644 --- a/src/Laravel/ApiPlatformProvider.php +++ b/src/Laravel/ApiPlatformProvider.php @@ -405,7 +405,7 @@ public function register(): void /** @var ConfigRepository */ $config = $app['config']; - return new SwaggerUiProvider($app->make(ReadProvider::class), $app->make(OpenApiFactoryInterface::class), $config->get('api-platform.swagger_ui.enabled', false)); + return new SwaggerUiProvider($app->make(ReadProvider::class), $app->make(OpenApiFactoryInterface::class), $config->get('api-platform.swagger_ui.enabled', false), $config->get('api-platform.scalar.enabled', false)); }); $this->app->singleton(DeserializeProvider::class, static function (Application $app) { @@ -746,6 +746,8 @@ public function register(): void oauthClientId: $config->get('api-platform.swagger_ui.oauth.clientId'), oauthClientSecret: $config->get('api-platform.swagger_ui.oauth.clientSecret'), oauthPkce: $config->get('api-platform.swagger_ui.oauth.pkce', false), + scalarEnabled: $config->get('api-platform.scalar.enabled', false), + scalarExtraConfiguration: $config->get('api-platform.scalar.extra_configuration', []), ); }); @@ -759,7 +761,7 @@ public function register(): void /** @var ConfigRepository */ $config = $app['config']; - return new DocumentationController($app->make(ResourceNameCollectionFactoryInterface::class), $config->get('api-platform.title') ?? '', $config->get('api-platform.description') ?? '', $config->get('api-platform.version') ?? '', $app->make(OpenApiFactoryInterface::class), $app->make(ProviderInterface::class), $app->make(ProcessorInterface::class), $app->make(Negotiator::class), $config->get('api-platform.docs_formats'), $config->get('api-platform.swagger_ui.enabled', false)); + return new DocumentationController($app->make(ResourceNameCollectionFactoryInterface::class), $config->get('api-platform.title') ?? '', $config->get('api-platform.description') ?? '', $config->get('api-platform.version') ?? '', $app->make(OpenApiFactoryInterface::class), $app->make(ProviderInterface::class), $app->make(ProcessorInterface::class), $app->make(Negotiator::class), $config->get('api-platform.docs_formats'), $config->get('api-platform.swagger_ui.enabled', false), $config->get('api-platform.scalar.enabled', false)); }); $this->app->singleton(EntrypointController::class, static function (Application $app) { diff --git a/src/Laravel/Controller/DocumentationController.php b/src/Laravel/Controller/DocumentationController.php index 0b3b1809b74..33f44b22e59 100644 --- a/src/Laravel/Controller/DocumentationController.php +++ b/src/Laravel/Controller/DocumentationController.php @@ -53,6 +53,7 @@ public function __construct( ?Negotiator $negotiator = null, private readonly array $documentationFormats = [OpenApiNormalizer::JSON_FORMAT => ['application/vnd.openapi+json'], OpenApiNormalizer::FORMAT => ['application/json']], private readonly bool $swaggerUiEnabled = true, + private readonly bool $scalarEnabled = true, ) { $this->negotiator = $negotiator ?? new Negotiator(); } @@ -94,7 +95,7 @@ class: OpenApi::class, outputFormats: $this->documentationFormats ); - if ('html' === $format && $this->swaggerUiEnabled) { + if ('html' === $format && ($this->swaggerUiEnabled || $this->scalarEnabled)) { $operation = $operation->withProcessor('api_platform.swagger_ui.processor')->withWrite(true); } diff --git a/src/Laravel/State/SwaggerUiProcessor.php b/src/Laravel/State/SwaggerUiProcessor.php index 6a90a23fdc9..2818517d2ca 100644 --- a/src/Laravel/State/SwaggerUiProcessor.php +++ b/src/Laravel/State/SwaggerUiProcessor.php @@ -34,6 +34,7 @@ final class SwaggerUiProcessor implements ProcessorInterface /** * @param array $formats + * @param array $scalarExtraConfiguration */ public function __construct( private readonly UrlGeneratorInterface $urlGenerator, @@ -43,6 +44,8 @@ public function __construct( private readonly ?string $oauthClientId = null, private readonly ?string $oauthClientSecret = null, private readonly bool $oauthPkce = false, + private readonly bool $scalarEnabled = false, + private readonly array $scalarExtraConfiguration = [], ) { } @@ -92,7 +95,9 @@ public function process(mixed $openApi, Operation $operation, array $uriVariable $status = $requestedOperation->getStatus() ?? $status; } - return new Response(view('api-platform::swagger-ui', $swaggerContext + ['swagger_data' => $swaggerData]), 200); + $swaggerData['scalarExtraConfiguration'] = $this->scalarExtraConfiguration; + + return new Response(view('api-platform::swagger-ui', $swaggerContext + ['swagger_data' => $swaggerData, 'scalar_enabled' => $this->scalarEnabled]), 200); } /** diff --git a/src/Laravel/State/SwaggerUiProvider.php b/src/Laravel/State/SwaggerUiProvider.php index a465e5c1748..dd8d0fbae76 100644 --- a/src/Laravel/State/SwaggerUiProvider.php +++ b/src/Laravel/State/SwaggerUiProvider.php @@ -38,6 +38,7 @@ public function __construct( private readonly ProviderInterface $decorated, private readonly OpenApiFactoryInterface $openApiFactory, private readonly bool $swaggerUiEnabled = true, + private readonly bool $scalarEnabled = false, ) { } @@ -52,7 +53,7 @@ public function provide(Operation $operation, array $uriVariables = [], array $c !($operation instanceof HttpOperation) || !($request = $context['request'] ?? null) || 'html' !== $request->getRequestFormat() - || !$this->swaggerUiEnabled + || (!$this->swaggerUiEnabled && !$this->scalarEnabled) || true === ($operation->getExtraProperties()['_api_disable_swagger_provider'] ?? false) ) { return $this->decorated->provide($operation, $uriVariables, $context); diff --git a/src/Laravel/config/api-platform.php b/src/Laravel/config/api-platform.php index 81411333ba8..e5117655726 100644 --- a/src/Laravel/config/api-platform.php +++ b/src/Laravel/config/api-platform.php @@ -97,6 +97,11 @@ AuthorizationException::class => 403, ], + 'scalar' => [ + 'enabled' => true, + 'extra_configuration' => [], + ], + 'swagger_ui' => [ 'enabled' => true, // 'apiKeys' => [ diff --git a/src/Laravel/resources/views/swagger-ui.blade.php b/src/Laravel/resources/views/swagger-ui.blade.php index 4a9436c6e0c..4fec36e76fc 100644 --- a/src/Laravel/resources/views/swagger-ui.blade.php +++ b/src/Laravel/resources/views/swagger-ui.blade.php @@ -213,8 +213,13 @@ @endif
- - - + @if (($scalar_enabled ?? false) && request()->query('ui') === 'scalar') + + + @else + + + + @endif diff --git a/src/Symfony/Action/DocumentationAction.php b/src/Symfony/Action/DocumentationAction.php index 891d5d83d4c..50981993b69 100644 --- a/src/Symfony/Action/DocumentationAction.php +++ b/src/Symfony/Action/DocumentationAction.php @@ -52,6 +52,7 @@ public function __construct( private readonly bool $swaggerUiEnabled = true, private readonly bool $docsEnabled = true, private readonly bool $reDocEnabled = true, + private readonly bool $scalarEnabled = true, ) { $this->negotiator = $negotiator ?? new Negotiator(); } @@ -91,8 +92,8 @@ public function __invoke(?Request $request = null) */ private function getOpenApiDocumentation(array $context, string $format, Request $request): OpenApi|Response { - if ('html' === $format && !$this->swaggerUiEnabled && !$this->reDocEnabled) { - throw new NotFoundHttpException('Swagger UI and ReDoc are disabled.'); + if ('html' === $format && !$this->swaggerUiEnabled && !$this->reDocEnabled && !$this->scalarEnabled) { + throw new NotFoundHttpException('Swagger UI, ReDoc and Scalar are disabled.'); } if ($this->provider && $this->processor) { @@ -105,7 +106,7 @@ class: OpenApi::class, outputFormats: $this->documentationFormats ); - if ('html' === $format && ($this->swaggerUiEnabled || $this->reDocEnabled)) { + if ('html' === $format && ($this->swaggerUiEnabled || $this->reDocEnabled || $this->scalarEnabled)) { $operation = $operation->withProcessor('api_platform.swagger_ui.processor')->withWrite(true); } diff --git a/src/Symfony/Bundle/DependencyInjection/ApiPlatformExtension.php b/src/Symfony/Bundle/DependencyInjection/ApiPlatformExtension.php index a2e1d698cf4..4b465bfc975 100644 --- a/src/Symfony/Bundle/DependencyInjection/ApiPlatformExtension.php +++ b/src/Symfony/Bundle/DependencyInjection/ApiPlatformExtension.php @@ -151,6 +151,7 @@ public function load(array $configs, ContainerBuilder $container): void // to prevent HTML documentation from being served on resource endpoints. $config['enable_swagger_ui'] = false; $config['enable_re_doc'] = false; + $config['enable_scalar'] = false; } $jsonSchemaFormats = $config['jsonschema_formats']; @@ -647,6 +648,7 @@ private function registerSwaggerConfiguration(ContainerBuilder $container, array if (!$config['enable_swagger']) { $container->setParameter('api_platform.enable_swagger_ui', false); $container->setParameter('api_platform.enable_re_doc', false); + $container->setParameter('api_platform.enable_scalar', false); return; } @@ -657,7 +659,7 @@ private function registerSwaggerConfiguration(ContainerBuilder $container, array $loader->load('openapi/yaml.php'); } - if ($config['enable_swagger_ui'] || $config['enable_re_doc']) { + if ($config['enable_swagger_ui'] || $config['enable_re_doc'] || $config['enable_scalar']) { $loader->load('swagger_ui.php'); if ($config['use_symfony_listeners']) { @@ -667,13 +669,14 @@ private function registerSwaggerConfiguration(ContainerBuilder $container, array $loader->load('state/swagger_ui.php'); } - if (!$config['enable_swagger_ui'] && !$config['enable_re_doc']) { + if (!$config['enable_swagger_ui'] && !$config['enable_re_doc'] && !$config['enable_scalar']) { // Remove the listener but keep the controller to allow customizing the path of the UI $container->removeDefinition('api_platform.swagger.listener.ui'); } $container->setParameter('api_platform.enable_swagger_ui', $config['enable_swagger_ui']); $container->setParameter('api_platform.enable_re_doc', $config['enable_re_doc']); + $container->setParameter('api_platform.enable_scalar', $config['enable_scalar']); $container->setParameter('api_platform.swagger.api_keys', $config['swagger']['api_keys']); $container->setParameter('api_platform.swagger.persist_authorization', $config['swagger']['persist_authorization']); $container->setParameter('api_platform.swagger.http_auth', $config['swagger']['http_auth']); @@ -681,6 +684,7 @@ private function registerSwaggerConfiguration(ContainerBuilder $container, array throw new RuntimeException('You can not set "swagger_ui_extra_configuration" twice - in "openapi" and "swagger" section.'); } $container->setParameter('api_platform.swagger_ui.extra_configuration', $config['openapi']['swagger_ui_extra_configuration'] ?: $config['swagger']['swagger_ui_extra_configuration']); + $container->setParameter('api_platform.scalar.extra_configuration', $config['openapi']['scalar_extra_configuration']); } private function registerJsonApiConfiguration(ContainerBuilder $container, array $formats, PhpFileLoader $loader, array $config): void diff --git a/src/Symfony/Bundle/DependencyInjection/Configuration.php b/src/Symfony/Bundle/DependencyInjection/Configuration.php index 45fd8bebd0c..89f72c7bf94 100644 --- a/src/Symfony/Bundle/DependencyInjection/Configuration.php +++ b/src/Symfony/Bundle/DependencyInjection/Configuration.php @@ -124,6 +124,7 @@ public function getConfigTreeBuilder(): TreeBuilder ->booleanNode('enable_json_streamer')->defaultValue(class_exists(ControllerHelper::class) && class_exists(JsonStreamWriter::class))->info('Enable json streamer.')->end() ->booleanNode('enable_swagger_ui')->defaultValue(class_exists(TwigBundle::class))->info('Enable Swagger UI')->end() ->booleanNode('enable_re_doc')->defaultValue(class_exists(TwigBundle::class))->info('Enable ReDoc')->end() + ->booleanNode('enable_scalar')->defaultValue(class_exists(TwigBundle::class))->info('Enable Scalar API Reference')->end() ->booleanNode('enable_entrypoint')->defaultTrue()->info('Enable the entrypoint')->end() ->booleanNode('enable_docs')->defaultTrue()->info('Enable the docs')->end() ->booleanNode('enable_profiler')->defaultTrue()->info('Enable the data collector and the WebProfilerBundle integration.')->end() @@ -590,6 +591,14 @@ private function addOpenApiSection(ArrayNodeDefinition $rootNode): void ->end() ->info('To pass extra configuration to Swagger UI, like docExpansion or filter.') ->end() + ->variableNode('scalar_extra_configuration') + ->defaultValue([]) + ->validate() + ->ifTrue(static fn ($v): bool => false === \is_array($v)) + ->thenInvalid('The scalar_extra_configuration parameter must be an array.') + ->end() + ->info('To pass extra configuration to Scalar API Reference, like theme or darkMode.') + ->end() ->booleanNode('overrideResponses')->defaultTrue()->info('Whether API Platform adds automatic responses to the OpenAPI documentation.')->end() ->scalarNode('error_resource_class')->defaultNull()->info('The class used to represent errors in the OpenAPI documentation.')->end() ->scalarNode('validation_error_resource_class')->defaultNull()->info('The class used to represent validation errors in the OpenAPI documentation.')->end() diff --git a/src/Symfony/Bundle/Resources/config/swagger_ui.php b/src/Symfony/Bundle/Resources/config/swagger_ui.php index 4d4b756af6e..312e12227af 100644 --- a/src/Symfony/Bundle/Resources/config/swagger_ui.php +++ b/src/Symfony/Bundle/Resources/config/swagger_ui.php @@ -28,6 +28,8 @@ '%api_platform.graphql.graphiql.enabled%', '%api_platform.asset_package%', '%api_platform.swagger_ui.extra_configuration%', + '%api_platform.enable_scalar%', + '%api_platform.scalar.extra_configuration%', ]); $services->set('api_platform.swagger_ui.processor', SwaggerUiProcessor::class) diff --git a/src/Symfony/Bundle/Resources/config/symfony/controller.php b/src/Symfony/Bundle/Resources/config/symfony/controller.php index 203afa69245..2ac24e613e0 100644 --- a/src/Symfony/Bundle/Resources/config/symfony/controller.php +++ b/src/Symfony/Bundle/Resources/config/symfony/controller.php @@ -54,5 +54,6 @@ '%api_platform.enable_swagger_ui%', '%api_platform.enable_docs%', '%api_platform.enable_re_doc%', + '%api_platform.enable_scalar%', ]); }; diff --git a/src/Symfony/Bundle/Resources/config/symfony/events.php b/src/Symfony/Bundle/Resources/config/symfony/events.php index 12488624e08..c8c2c833e70 100644 --- a/src/Symfony/Bundle/Resources/config/symfony/events.php +++ b/src/Symfony/Bundle/Resources/config/symfony/events.php @@ -202,6 +202,7 @@ '%api_platform.enable_swagger_ui%', '%api_platform.enable_docs%', '%api_platform.enable_re_doc%', + '%api_platform.enable_scalar%', ]); $services->set('api_platform.action.placeholder', PlaceholderAction::class) diff --git a/src/Symfony/Bundle/Resources/public/init-scalar-ui.js b/src/Symfony/Bundle/Resources/public/init-scalar-ui.js new file mode 100644 index 00000000000..ba8c2091117 --- /dev/null +++ b/src/Symfony/Bundle/Resources/public/init-scalar-ui.js @@ -0,0 +1,12 @@ +'use strict'; + +window.onload = function() { + var data = JSON.parse(document.getElementById('swagger-data').innerText); + + var config = Object.assign({ + content: data.spec, + theme: 'default', + }, data.scalarExtraConfiguration || {}); + + Scalar.createApiReference('#swagger-ui', config); +}; diff --git a/src/Symfony/Bundle/Resources/views/SwaggerUi/index.html.twig b/src/Symfony/Bundle/Resources/views/SwaggerUi/index.html.twig index 3405232fbaa..52dfca324ee 100644 --- a/src/Symfony/Bundle/Resources/views/SwaggerUi/index.html.twig +++ b/src/Symfony/Bundle/Resources/views/SwaggerUi/index.html.twig @@ -3,17 +3,23 @@ {% block head_metas %} + {% endblock %} {% block title %} {% if title %}{{ title }} - {% endif %}API Platform {% endblock %} + {% set active_ui = app.request.query.get('ui', 'swagger_ui') %} + {% set is_scalar = (scalarEnabled and not swaggerUiEnabled and not reDocEnabled) or (scalarEnabled and 'scalar' == active_ui) %} + {% block stylesheet %} - - - - + {% if not is_scalar %} + + + + + {% endif %} {% endblock %} {% set oauth_data = {'oauth': swagger_data.oauth|merge({'redirectUrl' : absolute_url(asset('bundles/apiplatform/swagger-ui/oauth2-redirect.html', assetPackage)) })} %} @@ -25,6 +31,7 @@ +{% if not is_scalar %} @@ -69,9 +76,11 @@
{% endif %} +{% endif %}
+{% if not is_scalar %}
@@ -81,16 +90,20 @@ {% endfor %}
Other API docs: - {% set active_ui = app.request.query.get('ui', 'swagger_ui') %} {% if swaggerUiEnabled and active_ui != 'swagger_ui' %}Swagger UI{% endif %} {% if reDocEnabled and active_ui != 're_doc' %}ReDoc{% endif %} + {% if scalarEnabled and active_ui != 'scalar' %}Scalar{% endif %} {% if not graphQlEnabled or graphiQlEnabled %}GraphiQL{% endif %}
+{% endif %} {% block javascript %} - {% if (reDocEnabled and not swaggerUiEnabled) or (reDocEnabled and 're_doc' == active_ui) %} + {% if is_scalar %} + + + {% elseif (reDocEnabled and not swaggerUiEnabled) or (reDocEnabled and 're_doc' == active_ui) %} {% else %} @@ -98,7 +111,9 @@ {% endif %} - + {% if not is_scalar %} + + {% endif %} {% endblock %} diff --git a/src/Symfony/Bundle/SwaggerUi/SwaggerUiContext.php b/src/Symfony/Bundle/SwaggerUi/SwaggerUiContext.php index e0240c9c717..1c5e29db84f 100644 --- a/src/Symfony/Bundle/SwaggerUi/SwaggerUiContext.php +++ b/src/Symfony/Bundle/SwaggerUi/SwaggerUiContext.php @@ -18,7 +18,7 @@ final class SwaggerUiContext /** * @param string|null $assetPackage */ - public function __construct(private readonly bool $swaggerUiEnabled = false, private readonly bool $showWebby = true, private readonly bool $reDocEnabled = false, private readonly bool $graphQlEnabled = false, private readonly bool $graphiQlEnabled = false, private $assetPackage = null, private readonly array $extraConfiguration = []) + public function __construct(private readonly bool $swaggerUiEnabled = false, private readonly bool $showWebby = true, private readonly bool $reDocEnabled = false, private readonly bool $graphQlEnabled = false, private readonly bool $graphiQlEnabled = false, private $assetPackage = null, private readonly array $extraConfiguration = [], private readonly bool $scalarEnabled = false, private readonly array $scalarExtraConfiguration = []) { } @@ -56,4 +56,14 @@ public function getExtraConfiguration(): array { return $this->extraConfiguration; } + + public function isScalarEnabled(): bool + { + return $this->scalarEnabled; + } + + public function getScalarExtraConfiguration(): array + { + return $this->scalarExtraConfiguration; + } } diff --git a/src/Symfony/Bundle/SwaggerUi/SwaggerUiProcessor.php b/src/Symfony/Bundle/SwaggerUi/SwaggerUiProcessor.php index 844f64d9c5c..eba9d89fed8 100644 --- a/src/Symfony/Bundle/SwaggerUi/SwaggerUiProcessor.php +++ b/src/Symfony/Bundle/SwaggerUi/SwaggerUiProcessor.php @@ -52,6 +52,7 @@ public function process(mixed $openApi, Operation $operation, array $uriVariable 'showWebby' => $this->swaggerUiContext->isWebbyShown(), 'swaggerUiEnabled' => $this->swaggerUiContext->isSwaggerUiEnabled(), 'reDocEnabled' => $this->swaggerUiContext->isRedocEnabled(), + 'scalarEnabled' => $this->swaggerUiContext->isScalarEnabled(), 'graphQlEnabled' => $this->swaggerUiContext->isGraphQlEnabled(), 'graphiQlEnabled' => $this->swaggerUiContext->isGraphiQlEnabled(), 'assetPackage' => $this->swaggerUiContext->getAssetPackage(), @@ -75,6 +76,7 @@ public function process(mixed $openApi, Operation $operation, array $uriVariable 'pkce' => $this->oauthPkce, ], 'extraConfiguration' => $this->swaggerUiContext->getExtraConfiguration(), + 'scalarExtraConfiguration' => $this->swaggerUiContext->getScalarExtraConfiguration(), ]; $status = 200; diff --git a/src/Symfony/Tests/Action/DocumentationActionTest.php b/src/Symfony/Tests/Action/DocumentationActionTest.php index 2595e33b232..802f4ceb3e9 100644 --- a/src/Symfony/Tests/Action/DocumentationActionTest.php +++ b/src/Symfony/Tests/Action/DocumentationActionTest.php @@ -37,10 +37,10 @@ class DocumentationActionTest extends TestCase { use ProphecyTrait; - public function testHtmlFormatWhenSwaggerUiAndReDocDisabledThrows404(): void + public function testHtmlFormatWhenSwaggerUiAndReDocAndScalarDisabledThrows404(): void { $this->expectException(NotFoundHttpException::class); - $this->expectExceptionMessage('Swagger UI and ReDoc are disabled.'); + $this->expectExceptionMessage('Swagger UI, ReDoc and Scalar are disabled.'); $request = new Request(); $request->attributes->set('_format', 'html'); @@ -57,6 +57,7 @@ public function testHtmlFormatWhenSwaggerUiAndReDocDisabledThrows404(): void ], swaggerUiEnabled: false, reDocEnabled: false, + scalarEnabled: false, ); $documentation($request); diff --git a/tests/Functional/DocumentationActionTest.php b/tests/Functional/DocumentationActionTest.php index b31dca0c763..69d8ba90fde 100644 --- a/tests/Functional/DocumentationActionTest.php +++ b/tests/Functional/DocumentationActionTest.php @@ -24,18 +24,19 @@ class DocumentationActionAppKernel extends \AppKernel { public static bool $swaggerUiEnabled = true; public static bool $reDocEnabled = true; + public static bool $scalarEnabled = true; public static bool $docsEnabled = true; public function getCacheDir(): string { - $suffix = (self::$swaggerUiEnabled ? 'ui_' : 'no_ui_').(self::$reDocEnabled ? 'redoc' : 'no_redoc').(self::$docsEnabled ? '' : '_no_docs'); + $suffix = (self::$swaggerUiEnabled ? 'ui_' : 'no_ui_').(self::$reDocEnabled ? 'redoc' : 'no_redoc').(self::$scalarEnabled ? '_scalar' : '_no_scalar').(self::$docsEnabled ? '' : '_no_docs'); return parent::getCacheDir().'/'.$suffix; } public function getLogDir(): string { - $suffix = (self::$swaggerUiEnabled ? 'ui_' : 'no_ui_').(self::$reDocEnabled ? 'redoc' : 'no_redoc').(self::$docsEnabled ? '' : '_no_docs'); + $suffix = (self::$swaggerUiEnabled ? 'ui_' : 'no_ui_').(self::$reDocEnabled ? 'redoc' : 'no_redoc').(self::$scalarEnabled ? '_scalar' : '_no_scalar').(self::$docsEnabled ? '' : '_no_docs'); return parent::getLogDir().'/'.$suffix; } @@ -48,6 +49,7 @@ protected function configureContainer(ContainerBuilder $c, LoaderInterface $load $container->loadFromExtension('api_platform', [ 'enable_swagger_ui' => DocumentationActionAppKernel::$swaggerUiEnabled, 'enable_re_doc' => DocumentationActionAppKernel::$reDocEnabled, + 'enable_scalar' => DocumentationActionAppKernel::$scalarEnabled, 'enable_docs' => DocumentationActionAppKernel::$docsEnabled, ]); }); @@ -63,32 +65,36 @@ protected static function getKernelClass(): string return DocumentationActionAppKernel::class; } - public function testHtmlDocumentationIsNotAccessibleWhenSwaggerUiAndReDocAreDisabled(): void + public function testHtmlDocumentationIsNotAccessibleWhenSwaggerUiAndReDocAndScalarAreDisabled(): void { DocumentationActionAppKernel::$swaggerUiEnabled = false; DocumentationActionAppKernel::$reDocEnabled = false; + DocumentationActionAppKernel::$scalarEnabled = false; $client = self::createClient(); $container = static::getContainer(); $this->assertFalse($container->getParameter('api_platform.enable_swagger_ui')); $this->assertFalse($container->getParameter('api_platform.enable_re_doc')); + $this->assertFalse($container->getParameter('api_platform.enable_scalar')); $client->request('GET', '/docs', ['headers' => ['Accept' => 'text/html']]); $this->assertResponseStatusCodeSame(404); - $this->assertStringContainsString('Swagger UI and ReDoc are disabled.', $client->getResponse()->getContent(false)); + $this->assertStringContainsString('Swagger UI, ReDoc and Scalar are disabled.', $client->getResponse()->getContent(false)); } public function testJsonDocumentationIsAccessibleWhenSwaggerUiIsDisabled(): void { DocumentationActionAppKernel::$swaggerUiEnabled = false; DocumentationActionAppKernel::$reDocEnabled = false; + DocumentationActionAppKernel::$scalarEnabled = false; $client = self::createClient(); $container = static::getContainer(); $this->assertFalse($container->getParameter('api_platform.enable_swagger_ui')); $this->assertFalse($container->getParameter('api_platform.enable_re_doc')); + $this->assertFalse($container->getParameter('api_platform.enable_scalar')); $client->request('GET', '/docs.jsonopenapi', ['headers' => ['Accept' => 'application/vnd.openapi+json']]); $this->assertResponseIsSuccessful(); @@ -161,10 +167,47 @@ public function testJsonDocumentationIsAccessibleWhenSwaggerUiIsEnabled(): void $this->assertJsonContains(['info' => ['title' => 'My Dummy API']]); } - public function testEnableDocsFalseDisablesSwaggerUiAndReDoc(): void + public function testHtmlDocumentationIsAccessibleWhenOnlyScalarIsEnabled(): void + { + DocumentationActionAppKernel::$swaggerUiEnabled = false; + DocumentationActionAppKernel::$reDocEnabled = false; + DocumentationActionAppKernel::$scalarEnabled = true; + + $client = self::createClient(); + + $container = static::getContainer(); + $this->assertFalse($container->getParameter('api_platform.enable_swagger_ui')); + $this->assertFalse($container->getParameter('api_platform.enable_re_doc')); + $this->assertTrue($container->getParameter('api_platform.enable_scalar')); + + $client->request('GET', '/docs', ['headers' => ['Accept' => 'text/html']]); + $this->assertResponseIsSuccessful(); + $content = $client->getResponse()->getContent(); + $this->assertStringContainsString('cdn.jsdelivr.net/npm/@scalar/api-reference', $content); + $this->assertStringContainsString('init-scalar-ui.js', $content); + } + + public function testScalarUiIsAccessibleWithUiQueryParameter(): void + { + DocumentationActionAppKernel::$swaggerUiEnabled = true; + DocumentationActionAppKernel::$reDocEnabled = true; + DocumentationActionAppKernel::$scalarEnabled = true; + + $client = self::createClient(); + + $client->request('GET', '/docs?ui=scalar', ['headers' => ['Accept' => 'text/html']]); + $this->assertResponseIsSuccessful(); + $content = $client->getResponse()->getContent(); + $this->assertStringContainsString('cdn.jsdelivr.net/npm/@scalar/api-reference', $content); + $this->assertStringContainsString('init-scalar-ui.js', $content); + $this->assertStringNotContainsString('swagger-ui-bundle.js', $content); + } + + public function testEnableDocsFalseDisablesSwaggerUiAndReDocAndScalar(): void { DocumentationActionAppKernel::$swaggerUiEnabled = true; DocumentationActionAppKernel::$reDocEnabled = true; + DocumentationActionAppKernel::$scalarEnabled = true; DocumentationActionAppKernel::$docsEnabled = false; $client = self::createClient(); @@ -174,6 +217,7 @@ public function testEnableDocsFalseDisablesSwaggerUiAndReDoc(): void // enable_docs: false acts as a master switch, forcing these to false $this->assertFalse($container->getParameter('api_platform.enable_swagger_ui')); $this->assertFalse($container->getParameter('api_platform.enable_re_doc')); + $this->assertFalse($container->getParameter('api_platform.enable_scalar')); $client->request('GET', '/docs', ['headers' => ['Accept' => 'text/html']]); $this->assertResponseStatusCodeSame(404); diff --git a/tests/Symfony/Bundle/DependencyInjection/ConfigurationTest.php b/tests/Symfony/Bundle/DependencyInjection/ConfigurationTest.php index def95beb140..233bc358287 100644 --- a/tests/Symfony/Bundle/DependencyInjection/ConfigurationTest.php +++ b/tests/Symfony/Bundle/DependencyInjection/ConfigurationTest.php @@ -231,6 +231,7 @@ private function runDefaultConfigTests(array $doctrineIntegrationsToLoad = ['orm 'tags' => [], 'error_resource_class' => null, 'validation_error_resource_class' => null, + 'scalar_extra_configuration' => [], ], 'maker' => [ 'enabled' => true, @@ -250,6 +251,7 @@ private function runDefaultConfigTests(array $doctrineIntegrationsToLoad = ['orm 'jsonapi' => [ 'use_iri_as_id' => true, ], + 'enable_scalar' => true, ], $config); }