diff --git a/src/Symfony/Action/DocumentationAction.php b/src/Symfony/Action/DocumentationAction.php index fd5ea76b2d..891d5d83d4 100644 --- a/src/Symfony/Action/DocumentationAction.php +++ b/src/Symfony/Action/DocumentationAction.php @@ -51,6 +51,7 @@ public function __construct( private readonly array $documentationFormats = [OpenApiNormalizer::JSON_FORMAT => ['application/vnd.openapi+json'], OpenApiNormalizer::FORMAT => ['application/json']], private readonly bool $swaggerUiEnabled = true, private readonly bool $docsEnabled = true, + private readonly bool $reDocEnabled = true, ) { $this->negotiator = $negotiator ?? new Negotiator(); } @@ -90,8 +91,8 @@ public function __invoke(?Request $request = null) */ private function getOpenApiDocumentation(array $context, string $format, Request $request): OpenApi|Response { - if ('html' === $format && !$this->swaggerUiEnabled) { - throw new NotFoundHttpException('Swagger UI is disabled.'); + if ('html' === $format && !$this->swaggerUiEnabled && !$this->reDocEnabled) { + throw new NotFoundHttpException('Swagger UI and ReDoc are disabled.'); } if ($this->provider && $this->processor) { @@ -104,7 +105,7 @@ class: OpenApi::class, outputFormats: $this->documentationFormats ); - if ('html' === $format && $this->swaggerUiEnabled) { + if ('html' === $format && ($this->swaggerUiEnabled || $this->reDocEnabled)) { $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 405af850fc..2d23932693 100644 --- a/src/Symfony/Bundle/DependencyInjection/ApiPlatformExtension.php +++ b/src/Symfony/Bundle/DependencyInjection/ApiPlatformExtension.php @@ -604,7 +604,7 @@ private function registerSwaggerConfiguration(ContainerBuilder $container, array $loader->load('openapi/yaml.php'); } - if ($config['enable_swagger_ui']) { + if ($config['enable_swagger_ui'] || $config['enable_re_doc']) { $loader->load('swagger_ui.php'); if ($config['use_symfony_listeners']) { diff --git a/src/Symfony/Bundle/Resources/config/symfony/controller.php b/src/Symfony/Bundle/Resources/config/symfony/controller.php index ad1a9a9e50..4fb1681354 100644 --- a/src/Symfony/Bundle/Resources/config/symfony/controller.php +++ b/src/Symfony/Bundle/Resources/config/symfony/controller.php @@ -49,5 +49,6 @@ '%api_platform.docs_formats%', '%api_platform.enable_swagger_ui%', '%api_platform.enable_docs%', + '%api_platform.enable_re_doc%', ]); }; diff --git a/src/Symfony/Bundle/Resources/config/symfony/events.php b/src/Symfony/Bundle/Resources/config/symfony/events.php index 32885d4714..0ec1a1daaf 100644 --- a/src/Symfony/Bundle/Resources/config/symfony/events.php +++ b/src/Symfony/Bundle/Resources/config/symfony/events.php @@ -181,6 +181,7 @@ '%api_platform.docs_formats%', '%api_platform.enable_swagger_ui%', '%api_platform.enable_docs%', + '%api_platform.enable_re_doc%', ]); $services->set('api_platform.action.placeholder', 'ApiPlatform\Symfony\Action\PlaceholderAction') diff --git a/src/Symfony/Tests/Action/DocumentationActionTest.php b/src/Symfony/Tests/Action/DocumentationActionTest.php index e98b7658ae..5117c8d868 100644 --- a/src/Symfony/Tests/Action/DocumentationActionTest.php +++ b/src/Symfony/Tests/Action/DocumentationActionTest.php @@ -36,10 +36,10 @@ class DocumentationActionTest extends TestCase { use ProphecyTrait; - public function testHtmlFormatWhenSwaggerUiDisabledThrows404(): void + public function testHtmlFormatWhenSwaggerUiAndReDocDisabledThrows404(): void { $this->expectException(NotFoundHttpException::class); - $this->expectExceptionMessage('Swagger UI is disabled.'); + $this->expectExceptionMessage('Swagger UI and ReDoc are disabled.'); $request = new Request(); $request->attributes->set('_format', 'html'); @@ -55,11 +55,70 @@ public function testHtmlFormatWhenSwaggerUiDisabledThrows404(): void 'html' => ['text/html'], ], swaggerUiEnabled: false, + reDocEnabled: false, ); $documentation($request); } + public function testHtmlFormatWhenReDocEnabledAndSwaggerUiDisabled(): void + { + $request = new Request(); + $request->attributes->set('_format', 'html'); + + $openApiFactory = $this->createMock(OpenApiFactoryInterface::class); + $resourceNameCollectionFactory = $this->createMock(ResourceNameCollectionFactoryInterface::class); + $provider = $this->createMock(ProviderInterface::class); + $provider->expects($this->once())->method('provide')->willReturn(new OpenApi(new Info('title', '1.0.0'), [], new Paths())); + $processor = $this->createMock(ProcessorInterface::class); + $processor->expects($this->once())->method('process')->willReturnArgument(0); + + $documentation = new DocumentationAction( + $resourceNameCollectionFactory, + openApiFactory: $openApiFactory, + provider: $provider, + processor: $processor, + documentationFormats: [ + 'json' => ['application/json'], + 'html' => ['text/html'], + ], + swaggerUiEnabled: false, + reDocEnabled: true, + ); + + $result = $documentation($request); + $this->assertInstanceOf(OpenApi::class, $result); + } + + public function testHtmlFormatWhenSwaggerUiEnabledAndReDocDisabled(): void + { + $request = new Request(); + $request->attributes->set('_format', 'html'); + + $openApiFactory = $this->createMock(OpenApiFactoryInterface::class); + $resourceNameCollectionFactory = $this->createMock(ResourceNameCollectionFactoryInterface::class); + $provider = $this->createMock(ProviderInterface::class); + $provider->expects($this->once())->method('provide')->willReturn(new OpenApi(new Info('title', '1.0.0'), [], new Paths())); + $processor = $this->createMock(ProcessorInterface::class); + $processor->expects($this->once())->method('process')->willReturnArgument(0); + + $documentation = new DocumentationAction( + $resourceNameCollectionFactory, + openApiFactory: $openApiFactory, + provider: $provider, + processor: $processor, + documentationFormats: [ + 'json' => ['application/json'], + 'html' => ['text/html'], + ], + swaggerUiEnabled: true, + reDocEnabled: false, + ); + + $result = $documentation($request); + $this->assertInstanceOf(OpenApi::class, $result); + } + public function testJsonFormatWhenSwaggerUiDisabledIsAccessible(): void { $request = new Request(); diff --git a/src/Symfony/Tests/Bundle/DependencyInjection/ApiPlatformExtensionTest.php b/src/Symfony/Tests/Bundle/DependencyInjection/ApiPlatformExtensionTest.php index 29e184fd13..8cb15c4624 100644 --- a/src/Symfony/Tests/Bundle/DependencyInjection/ApiPlatformExtensionTest.php +++ b/src/Symfony/Tests/Bundle/DependencyInjection/ApiPlatformExtensionTest.php @@ -268,6 +268,7 @@ public function testSwaggerUiDisabledConfiguration(): void $config = self::DEFAULT_CONFIG; $config['api_platform']['enable_swagger'] = true; $config['api_platform']['enable_swagger_ui'] = false; + $config['api_platform']['enable_re_doc'] = false; $config['api_platform']['use_symfony_listeners'] = true; (new ApiPlatformExtension())->load($config, $this->container); @@ -300,6 +301,26 @@ public function testSwaggerUiEnabledConfiguration(): void $this->assertContainerHas($services); } + public function testReDocEnabledWithSwaggerUiDisabledConfiguration(): void + { + $config = self::DEFAULT_CONFIG; + $config['api_platform']['enable_swagger'] = true; + $config['api_platform']['enable_swagger_ui'] = false; + $config['api_platform']['enable_re_doc'] = true; + $config['api_platform']['use_symfony_listeners'] = true; + + (new ApiPlatformExtension())->load($config, $this->container); + + $services = [ + 'api_platform.swagger_ui.processor', + 'api_platform.swagger_ui.context', + 'api_platform.swagger_ui.provider', + 'api_platform.swagger_ui.documentation.provider', + ]; + + $this->assertContainerHas($services); + } + public function testEventListenersConfiguration(): void { $config = self::DEFAULT_CONFIG; diff --git a/tests/Functional/DocumentationActionTest.php b/tests/Functional/DocumentationActionTest.php index 707759866e..74e80a27f1 100644 --- a/tests/Functional/DocumentationActionTest.php +++ b/tests/Functional/DocumentationActionTest.php @@ -23,17 +23,18 @@ class DocumentationActionAppKernel extends \AppKernel { public static bool $swaggerUiEnabled = true; + public static bool $reDocEnabled = true; public function getCacheDir(): string { - $suffix = self::$swaggerUiEnabled ? 'ui_enabled' : 'ui_disabled'; + $suffix = (self::$swaggerUiEnabled ? 'ui_' : 'no_ui_').(self::$reDocEnabled ? 'redoc' : 'no_redoc'); return parent::getCacheDir().'/'.$suffix; } public function getLogDir(): string { - $suffix = self::$swaggerUiEnabled ? 'ui_enabled' : 'ui_disabled'; + $suffix = (self::$swaggerUiEnabled ? 'ui_' : 'no_ui_').(self::$reDocEnabled ? 'redoc' : 'no_redoc'); return parent::getLogDir().'/'.$suffix; } @@ -45,6 +46,7 @@ protected function configureContainer(ContainerBuilder $c, LoaderInterface $load $loader->load(static function (ContainerBuilder $container) { $container->loadFromExtension('api_platform', [ 'enable_swagger_ui' => DocumentationActionAppKernel::$swaggerUiEnabled, + 'enable_re_doc' => DocumentationActionAppKernel::$reDocEnabled, ]); }); } @@ -59,28 +61,32 @@ protected static function getKernelClass(): string return DocumentationActionAppKernel::class; } - public function testHtmlDocumentationIsNotAccessibleWhenSwaggerUiIsDisabled(): void + public function testHtmlDocumentationIsNotAccessibleWhenSwaggerUiAndReDocAreDisabled(): void { DocumentationActionAppKernel::$swaggerUiEnabled = false; + DocumentationActionAppKernel::$reDocEnabled = 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')); $client->request('GET', '/docs', ['headers' => ['Accept' => 'text/html']]); $this->assertResponseStatusCodeSame(404); - $this->assertStringContainsString('Swagger UI is disabled.', $client->getResponse()->getContent(false)); + $this->assertStringContainsString('Swagger UI and ReDoc are disabled.', $client->getResponse()->getContent(false)); } public function testJsonDocumentationIsAccessibleWhenSwaggerUiIsDisabled(): void { DocumentationActionAppKernel::$swaggerUiEnabled = false; + DocumentationActionAppKernel::$reDocEnabled = 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')); $client->request('GET', '/docs.jsonopenapi', ['headers' => ['Accept' => 'application/vnd.openapi+json']]); $this->assertResponseIsSuccessful(); @@ -88,28 +94,64 @@ public function testJsonDocumentationIsAccessibleWhenSwaggerUiIsDisabled(): void $this->assertJsonContains(['info' => ['title' => 'My Dummy API']]); } + public function testHtmlDocumentationIsAccessibleWhenReDocEnabledAndSwaggerUiDisabled(): void + { + DocumentationActionAppKernel::$swaggerUiEnabled = false; + DocumentationActionAppKernel::$reDocEnabled = true; + + $client = self::createClient(); + + $container = static::getContainer(); + $this->assertFalse($container->getParameter('api_platform.enable_swagger_ui')); + $this->assertTrue($container->getParameter('api_platform.enable_re_doc')); + + $client->request('GET', '/docs', ['headers' => ['Accept' => 'text/html']]); + $this->assertResponseIsSuccessful(); + $this->assertStringNotContainsString('Swagger UI and ReDoc are disabled.', $client->getResponse()->getContent(false)); + } + + public function testHtmlDocumentationIsAccessibleWhenSwaggerUiEnabledAndReDocDisabled(): void + { + DocumentationActionAppKernel::$swaggerUiEnabled = true; + DocumentationActionAppKernel::$reDocEnabled = false; + + $client = self::createClient(); + + $container = static::getContainer(); + $this->assertTrue($container->getParameter('api_platform.enable_swagger_ui')); + $this->assertFalse($container->getParameter('api_platform.enable_re_doc')); + + $client->request('GET', '/docs', ['headers' => ['Accept' => 'text/html']]); + $this->assertResponseIsSuccessful(); + $this->assertStringNotContainsString('Swagger UI and ReDoc are disabled.', $client->getResponse()->getContent(false)); + } + public function testHtmlDocumentationIsAccessibleWhenSwaggerUiIsEnabled(): void { DocumentationActionAppKernel::$swaggerUiEnabled = true; + DocumentationActionAppKernel::$reDocEnabled = true; $client = self::createClient(); $container = static::getContainer(); $this->assertTrue($container->getParameter('api_platform.enable_swagger_ui')); + $this->assertTrue($container->getParameter('api_platform.enable_re_doc')); $client->request('GET', '/docs', ['headers' => ['Accept' => 'text/html']]); $this->assertResponseIsSuccessful(); - $this->assertStringNotContainsString('Swagger UI is disabled.', $client->getResponse()->getContent(false)); + $this->assertStringNotContainsString('Swagger UI and ReDoc are disabled.', $client->getResponse()->getContent(false)); } public function testJsonDocumentationIsAccessibleWhenSwaggerUiIsEnabled(): void { DocumentationActionAppKernel::$swaggerUiEnabled = true; + DocumentationActionAppKernel::$reDocEnabled = true; $client = self::createClient(); $container = static::getContainer(); $this->assertTrue($container->getParameter('api_platform.enable_swagger_ui')); + $this->assertTrue($container->getParameter('api_platform.enable_re_doc')); $client->request('GET', '/docs.jsonopenapi', ['headers' => ['Accept' => 'application/vnd.openapi+json']]); $this->assertResponseIsSuccessful();