From 5c8df74a65b6f9c202ce3c4cac44e56990878954 Mon Sep 17 00:00:00 2001 From: meyerbaptiste Date: Fri, 23 Sep 2016 15:48:38 +0200 Subject: [PATCH] Fix #673: name converter for Swagger and NelmioApiDocBundle --- .../NelmioApiDoc/Parser/ApiPlatformParser.php | 8 +- .../Resources/config/nelmio_api_doc.xml | 1 + .../Bundle/Resources/config/swagger.xml | 1 + .../Serializer/DocumentationNormalizer.php | 10 +- .../Parser/ApiPlatformParserTest.php | 43 ++++++++ .../DocumentationNormalizerTest.php | 98 +++++++++++++++++++ 6 files changed, 156 insertions(+), 5 deletions(-) diff --git a/src/Bridge/NelmioApiDoc/Parser/ApiPlatformParser.php b/src/Bridge/NelmioApiDoc/Parser/ApiPlatformParser.php index a5a680bd9c7..9a802518252 100644 --- a/src/Bridge/NelmioApiDoc/Parser/ApiPlatformParser.php +++ b/src/Bridge/NelmioApiDoc/Parser/ApiPlatformParser.php @@ -20,6 +20,7 @@ use Nelmio\ApiDocBundle\DataTypes; use Nelmio\ApiDocBundle\Parser\ParserInterface; use Symfony\Component\PropertyInfo\Type; +use Symfony\Component\Serializer\NameConverter\NameConverterInterface; /** * Extract input and output information for the NelmioApiDocBundle. @@ -42,12 +43,14 @@ final class ApiPlatformParser implements ParserInterface private $resourceMetadataFactory; private $propertyNameCollectionFactory; private $propertyMetadataFactory; + private $nameConverter; - public function __construct(ResourceMetadataFactoryInterface $resourceMetadataFactory, PropertyNameCollectionFactoryInterface $propertyNameCollectionFactory, PropertyMetadataFactoryInterface $propertyMetadataFactory) + public function __construct(ResourceMetadataFactoryInterface $resourceMetadataFactory, PropertyNameCollectionFactoryInterface $propertyNameCollectionFactory, PropertyMetadataFactoryInterface $propertyMetadataFactory, NameConverterInterface $nameConverter = null) { $this->resourceMetadataFactory = $resourceMetadataFactory; $this->propertyNameCollectionFactory = $propertyNameCollectionFactory; $this->propertyMetadataFactory = $propertyMetadataFactory; + $this->nameConverter = $nameConverter; } /** @@ -171,7 +174,8 @@ private function getPropertyMetadata(ResourceMetadata $resourceMetadata, string ($propertyMetadata->isReadable() && self::OUT_PREFIX === $io) || ($propertyMetadata->isWritable() && self::IN_PREFIX === $io) ) { - $data[$propertyName] = $this->parseProperty($resourceMetadata, $propertyMetadata, $io, null, $visited); + $normalizedPropertyName = $this->nameConverter ? $this->nameConverter->normalize($propertyName) : $propertyName; + $data[$normalizedPropertyName] = $this->parseProperty($resourceMetadata, $propertyMetadata, $io, null, $visited); } } diff --git a/src/Bridge/Symfony/Bundle/Resources/config/nelmio_api_doc.xml b/src/Bridge/Symfony/Bundle/Resources/config/nelmio_api_doc.xml index 141b950f233..980757408e2 100644 --- a/src/Bridge/Symfony/Bundle/Resources/config/nelmio_api_doc.xml +++ b/src/Bridge/Symfony/Bundle/Resources/config/nelmio_api_doc.xml @@ -19,6 +19,7 @@ + diff --git a/src/Bridge/Symfony/Bundle/Resources/config/swagger.xml b/src/Bridge/Symfony/Bundle/Resources/config/swagger.xml index 42c535d87a2..2b8480d0c52 100644 --- a/src/Bridge/Symfony/Bundle/Resources/config/swagger.xml +++ b/src/Bridge/Symfony/Bundle/Resources/config/swagger.xml @@ -15,6 +15,7 @@ + diff --git a/src/Swagger/Serializer/DocumentationNormalizer.php b/src/Swagger/Serializer/DocumentationNormalizer.php index a50a45776d1..136013e1fe5 100644 --- a/src/Swagger/Serializer/DocumentationNormalizer.php +++ b/src/Swagger/Serializer/DocumentationNormalizer.php @@ -23,6 +23,7 @@ use ApiPlatform\Core\Metadata\Resource\ResourceMetadata; use ApiPlatform\Core\PathResolver\OperationPathResolverInterface; use Symfony\Component\PropertyInfo\Type; +use Symfony\Component\Serializer\NameConverter\NameConverterInterface; use Symfony\Component\Serializer\Normalizer\NormalizerInterface; /** @@ -45,8 +46,9 @@ final class DocumentationNormalizer implements NormalizerInterface private $operationPathResolver; private $urlGenerator; private $filterCollection; + private $nameConverter; - public function __construct(ResourceMetadataFactoryInterface $resourceMetadataFactory, PropertyNameCollectionFactoryInterface $propertyNameCollectionFactory, PropertyMetadataFactoryInterface $propertyMetadataFactory, ResourceClassResolverInterface $resourceClassResolver, OperationMethodResolverInterface $operationMethodResolver, OperationPathResolverInterface $operationPathResolver, UrlGeneratorInterface $urlGenerator, FilterCollection $filterCollection = null) + public function __construct(ResourceMetadataFactoryInterface $resourceMetadataFactory, PropertyNameCollectionFactoryInterface $propertyNameCollectionFactory, PropertyMetadataFactoryInterface $propertyMetadataFactory, ResourceClassResolverInterface $resourceClassResolver, OperationMethodResolverInterface $operationMethodResolver, OperationPathResolverInterface $operationPathResolver, UrlGeneratorInterface $urlGenerator, FilterCollection $filterCollection = null, NameConverterInterface $nameConverter = null) { $this->resourceMetadataFactory = $resourceMetadataFactory; $this->propertyNameCollectionFactory = $propertyNameCollectionFactory; @@ -56,6 +58,7 @@ public function __construct(ResourceMetadataFactoryInterface $resourceMetadataFa $this->operationPathResolver = $operationPathResolver; $this->urlGenerator = $urlGenerator; $this->filterCollection = $filterCollection; + $this->nameConverter = $nameConverter; } /** @@ -378,12 +381,13 @@ private function getDefinitionSchema(string $resourceClass, ResourceMetadata $re $options = isset($serializerContext['groups']) ? ['serializer_groups' => $serializerContext['groups']] : []; foreach ($this->propertyNameCollectionFactory->create($resourceClass, $options) as $propertyName) { $propertyMetadata = $this->propertyMetadataFactory->create($resourceClass, $propertyName); + $normalizedPropertyName = $this->nameConverter ? $this->nameConverter->normalize($propertyName) : $propertyName; if ($propertyMetadata->isRequired()) { - $definitionSchema['required'][] = $propertyName; + $definitionSchema['required'][] = $normalizedPropertyName; } - $definitionSchema['properties'][$propertyName] = $this->getPropertySchema($propertyMetadata); + $definitionSchema['properties'][$normalizedPropertyName] = $this->getPropertySchema($propertyMetadata); } return $definitionSchema; diff --git a/tests/Bridge/NelmioApiDoc/Parser/ApiPlatformParserTest.php b/tests/Bridge/NelmioApiDoc/Parser/ApiPlatformParserTest.php index a66ef1551f1..297788fb1d6 100644 --- a/tests/Bridge/NelmioApiDoc/Parser/ApiPlatformParserTest.php +++ b/tests/Bridge/NelmioApiDoc/Parser/ApiPlatformParserTest.php @@ -26,6 +26,7 @@ use Nelmio\ApiDocBundle\Parser\ParserInterface; use Prophecy\Argument; use Symfony\Component\PropertyInfo\Type; +use Symfony\Component\Serializer\NameConverter\NameConverterInterface; /** * @author Teoh Han Hui @@ -359,4 +360,46 @@ public function testParseRelation() ], ], $actual); } + + public function testParseWithNameConverter() + { + $resourceMetadataFactoryProphecy = $this->prophesize(ResourceMetadataFactoryInterface::class); + $resourceMetadataFactoryProphecy->create(Dummy::class)->willReturn(new ResourceMetadata('dummy', 'dummy', null, [], []))->shouldBeCalled(); + $resourceMetadataFactory = $resourceMetadataFactoryProphecy->reveal(); + + $propertyNameCollectionFactoryProphecy = $this->prophesize(PropertyNameCollectionFactoryInterface::class); + $propertyNameCollectionFactoryProphecy->create(Dummy::class, Argument::cetera())->willReturn(new PropertyNameCollection([ + 'nameConverted', + ]))->shouldBeCalled(); + $propertyNameCollectionFactory = $propertyNameCollectionFactoryProphecy->reveal(); + + $propertyMetadataFactoryProphecy = $this->prophesize(PropertyMetadataFactoryInterface::class); + $nameConvertedPropertyMetadata = (new PropertyMetadata()) + ->withType(new Type(Type::BUILTIN_TYPE_STRING, true)) + ->withDescription('A converted name') + ->withReadable(true) + ->withWritable(true) + ->withRequired(false); + $propertyMetadataFactoryProphecy->create(Dummy::class, 'nameConverted')->willReturn($nameConvertedPropertyMetadata)->shouldBeCalled(); + $propertyMetadataFactory = $propertyMetadataFactoryProphecy->reveal(); + + $nameConverterProphecy = $this->prophesize(NameConverterInterface::class); + $nameConverterProphecy->normalize('nameConverted')->willReturn('name_converted')->shouldBeCalled(); + $nameConverter = $nameConverterProphecy->reveal(); + + $apiPlatformParser = new ApiPlatformParser($resourceMetadataFactory, $propertyNameCollectionFactory, $propertyMetadataFactory, $nameConverter); + + $actual = $apiPlatformParser->parse([ + 'class' => sprintf('%s:%s:%s', ApiPlatformParser::OUT_PREFIX, Dummy::class, 'get'), + ]); + + $this->assertEquals([ + 'name_converted' => [ + 'dataType' => DataTypes::STRING, + 'required' => false, + 'description' => 'A converted name', + 'readonly' => false, + ], + ], $actual); + } } diff --git a/tests/Swagger/Serializer/DocumentationNormalizerTest.php b/tests/Swagger/Serializer/DocumentationNormalizerTest.php index b3bafc545c4..66871f49793 100644 --- a/tests/Swagger/Serializer/DocumentationNormalizerTest.php +++ b/tests/Swagger/Serializer/DocumentationNormalizerTest.php @@ -29,6 +29,7 @@ use ApiPlatform\Core\Tests\Fixtures\DummyFilter; use ApiPlatform\Core\Tests\Fixtures\TestBundle\Entity\Dummy; use Symfony\Component\PropertyInfo\Type; +use Symfony\Component\Serializer\NameConverter\NameConverterInterface; /** * @author Amrouche Hamza @@ -235,6 +236,103 @@ public function testNormalize() $this->assertEquals($expected, $normalizer->normalize($documentation)); } + public function testNormalizeWithNameConverter() + { + $documentation = new Documentation(new ResourceNameCollection([Dummy::class]), 'Dummy API', 'This is a dummy API', '1.2.3', ['jsonld' => ['application/ld+json']]); + + $propertyNameCollectionFactoryProphecy = $this->prophesize(PropertyNameCollectionFactoryInterface::class); + $propertyNameCollectionFactoryProphecy->create(Dummy::class, [])->shouldBeCalled()->willReturn(new PropertyNameCollection(['name', 'nameConverted'])); + + $dummyMetadata = new ResourceMetadata('Dummy', 'This is a dummy.', null, ['get' => ['method' => 'GET']], [], []); + + $resourceMetadataFactoryProphecy = $this->prophesize(ResourceMetadataFactoryInterface::class); + $resourceMetadataFactoryProphecy->create(Dummy::class)->shouldBeCalled()->willReturn($dummyMetadata); + + $propertyMetadataFactoryProphecy = $this->prophesize(PropertyMetadataFactoryInterface::class); + $propertyMetadataFactoryProphecy->create(Dummy::class, 'name')->shouldBeCalled()->willReturn(new PropertyMetadata(new Type(Type::BUILTIN_TYPE_STRING), 'This is a name.', true, true, null, null, false)); + $propertyMetadataFactoryProphecy->create(Dummy::class, 'nameConverted')->shouldBeCalled()->willReturn(new PropertyMetadata(new Type(Type::BUILTIN_TYPE_STRING), 'This is a converted name.', true, true, null, null, false)); + + $resourceClassResolverProphecy = $this->prophesize(ResourceClassResolverInterface::class); + $resourceClassResolverProphecy->isResourceClass(Dummy::class)->willReturn(true); + + $operationMethodResolverProphecy = $this->prophesize(OperationMethodResolverInterface::class); + $operationMethodResolverProphecy->getItemOperationMethod(Dummy::class, 'get')->shouldBeCalled()->willReturn('GET'); + + $urlGeneratorProphecy = $this->prophesize(UrlGeneratorInterface::class); + $urlGeneratorProphecy->generate('api_entrypoint')->willReturn('/app_dev.php/')->shouldBeCalled(); + + $nameConverterProphecy = $this->prophesize(NameConverterInterface::class); + $nameConverterProphecy->normalize('name')->willReturn('name')->shouldBeCalled(); + $nameConverterProphecy->normalize('nameConverted')->willReturn('name_converted')->shouldBeCalled(); + + $operationPathResolver = new CustomOperationPathResolver(new UnderscoreOperationPathResolver()); + + $normalizer = new DocumentationNormalizer( + $resourceMetadataFactoryProphecy->reveal(), + $propertyNameCollectionFactoryProphecy->reveal(), + $propertyMetadataFactoryProphecy->reveal(), + $resourceClassResolverProphecy->reveal(), + $operationMethodResolverProphecy->reveal(), + $operationPathResolver, + $urlGeneratorProphecy->reveal(), + null, + $nameConverterProphecy->reveal() + ); + + $expected = [ + 'swagger' => '2.0', + 'basePath' => '/app_dev.php/', + 'info' => [ + 'title' => 'Dummy API', + 'description' => 'This is a dummy API', + 'version' => '1.2.3', + ], + 'paths' => new \ArrayObject([ + '/dummies/{id}' => [ + 'get' => new \ArrayObject([ + 'tags' => ['Dummy'], + 'operationId' => 'getDummyItem', + 'produces' => ['application/ld+json'], + 'summary' => 'Retrieves a Dummy resource.', + 'parameters' => [ + [ + 'name' => 'id', + 'in' => 'path', + 'type' => 'integer', + 'required' => true, + ], + ], + 'responses' => [ + 200 => [ + 'description' => 'Dummy resource response', + 'schema' => ['$ref' => '#/definitions/Dummy'], + ], + 404 => ['description' => 'Resource not found'], + ], + ]), + ], + ]), + 'definitions' => new \ArrayObject([ + 'Dummy' => new \ArrayObject([ + 'type' => 'object', + 'description' => 'This is a dummy.', + 'properties' => [ + 'name' => new \ArrayObject([ + 'type' => 'string', + 'description' => 'This is a name.', + ]), + 'name_converted' => new \ArrayObject([ + 'type' => 'string', + 'description' => 'This is a converted name.', + ]), + ], + ]), + ]), + ]; + + $this->assertEquals($expected, $normalizer->normalize($documentation)); + } + public function testNormalizeWithOnlyNormalizationGroups() { $title = 'Test API';