diff --git a/src/Symfony/Bundle/Resources/config/openapi.php b/src/Symfony/Bundle/Resources/config/openapi.php index 8e51f6418a..a45f051f9f 100644 --- a/src/Symfony/Bundle/Resources/config/openapi.php +++ b/src/Symfony/Bundle/Resources/config/openapi.php @@ -29,8 +29,11 @@ return function (ContainerConfigurator $container) { $services = $container->services(); + $services->set('api_platform.openapi.name_converter') + ->parent('serializer.name_converter.metadata_aware.abstract'); + $services->set('api_platform.openapi.normalizer', OpenApiNormalizer::class) - ->args([inline_service(Serializer::class)->arg(0, [inline_service(ObjectNormalizer::class)->arg(0, service('serializer.mapping.class_metadata_factory'))->arg(1, service('serializer.name_converter.metadata_aware'))->arg(2, service('api_platform.property_accessor'))->arg(3, service('api_platform.property_info'))])->arg(1, [service('serializer.encoder.json')])]) + ->args([inline_service(Serializer::class)->arg(0, [inline_service(ObjectNormalizer::class)->arg(0, service('serializer.mapping.class_metadata_factory'))->arg(1, service('api_platform.openapi.name_converter'))->arg(2, service('api_platform.property_accessor'))->arg(3, service('api_platform.property_info'))])->arg(1, [service('serializer.encoder.json')])]) ->tag('serializer.normalizer', ['priority' => -795]); $services->alias(OpenApiNormalizer::class, 'api_platform.openapi.normalizer'); diff --git a/tests/Functional/OpenApiNameConverterTest.php b/tests/Functional/OpenApiNameConverterTest.php new file mode 100644 index 0000000000..2ff0828ca3 --- /dev/null +++ b/tests/Functional/OpenApiNameConverterTest.php @@ -0,0 +1,90 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace ApiPlatform\Tests\Functional; + +use ApiPlatform\Symfony\Bundle\Test\ApiTestCase; +use ApiPlatform\Tests\Fixtures\TestBundle\ApiResource\Issue8143\ReferenceResponse; +use ApiPlatform\Tests\SetupClassResourcesTrait; +use Symfony\Component\Config\Loader\LoaderInterface; +use Symfony\Component\DependencyInjection\ContainerBuilder; + +class OpenApiNameConverterAppKernel extends \AppKernel +{ + public function getCacheDir(): string + { + return parent::getCacheDir().'/openapi_name_converter'; + } + + public function getLogDir(): string + { + return parent::getLogDir().'/openapi_name_converter'; + } + + protected function configureContainer(ContainerBuilder $c, LoaderInterface $loader): void + { + parent::configureContainer($c, $loader); + + $loader->load(static function (ContainerBuilder $container): void { + $container->loadFromExtension('framework', [ + 'serializer' => [ + 'name_converter' => 'serializer.name_converter.camel_case_to_snake_case', + ], + ]); + }); + } +} + +final class OpenApiNameConverterTest extends ApiTestCase +{ + use SetupClassResourcesTrait; + + protected static ?bool $alwaysBootKernel = true; + + /** + * @return class-string[] + */ + public static function getResources(): array + { + return [ReferenceResponse::class]; + } + + protected static function getKernelClass(): string + { + return OpenApiNameConverterAppKernel::class; + } + + public function testGlobalNameConverterDoesNotLeakIntoOpenApiDocument(): void + { + $response = self::createClient()->request('GET', '/docs', [ + 'headers' => ['Accept' => 'application/vnd.openapi+json'], + ]); + + $this->assertResponseIsSuccessful(); + $json = $response->toArray(); + $content = $response->getContent(); + + // OpenAPI keys must stay camelCase even when a global name converter is configured. + $this->assertStringContainsString('"operationId"', $content); + $this->assertStringNotContainsString('"operation_id"', $content); + $this->assertStringNotContainsString('"extension_properties"', $content); + $this->assertStringNotContainsString('"external_docs"', $content); + $this->assertStringNotContainsString('"request_bodies"', $content); + $this->assertStringNotContainsString('"security_schemes"', $content); + + // The #[SerializedName('$ref')] metadata must still be honored. + $responses = $json['paths']['/issue8143_reference_response']['post']['responses']; + $this->assertArrayHasKey('$ref', $responses['401']); + $this->assertSame('#/components/responses/401', $responses['401']['$ref']); + } +}