From 393a6c061e8211478b85e0ba6b09b6660a765da0 Mon Sep 17 00:00:00 2001 From: Guilhem N Date: Fri, 5 Jan 2018 13:08:02 +0100 Subject: [PATCH] Add areas support (#1169) * Add areas support * Document the Areas feature * Allow to expose swagger area as JSON * Fixes * last fixes --- .styleci.yml | 2 + ApiDocGenerator.php | 5 +- CHANGELOG.md | 15 +++++ Controller/DocumentationController.php | 31 ++++++++-- Controller/SwaggerUiController.php | 31 ++++++++-- .../Compiler/AddModelDescribersPass.php | 31 ---------- .../Compiler/AddRouteDescribersPass.php | 31 ---------- ...scribersPass.php => TagDescribersPass.php} | 16 ++--- DependencyInjection/Configuration.php | 47 ++++++++++++-- DependencyInjection/NelmioApiDocExtension.php | 61 ++++++++++++++---- NelmioApiDocBundle.php | 8 +-- Resources/config/services.xml | 17 +---- Resources/config/swagger_php.xml | 16 ----- Resources/doc/areas.rst | 48 ++++++++++++++ Resources/doc/index.rst | 34 ++++++---- Tests/ApiDocGeneratorTest.php | 36 +++++++++++ Tests/Controller/ControllersTest.php | 41 ++++++++++++ .../DependencyInjection/ConfigurationTest.php | 62 +++++++++++++++++++ .../Functional/Controller/TestController.php | 32 ++++++++++ Tests/Functional/SwaggerUiTest.php | 27 +++++++- Tests/Functional/TestKernel.php | 9 +-- 21 files changed, 447 insertions(+), 153 deletions(-) delete mode 100644 DependencyInjection/Compiler/AddModelDescribersPass.php delete mode 100644 DependencyInjection/Compiler/AddRouteDescribersPass.php rename DependencyInjection/Compiler/{AddDescribersPass.php => TagDescribersPass.php} (51%) delete mode 100644 Resources/config/swagger_php.xml create mode 100644 Resources/doc/areas.rst create mode 100644 Tests/ApiDocGeneratorTest.php create mode 100644 Tests/Controller/ControllersTest.php create mode 100644 Tests/DependencyInjection/ConfigurationTest.php create mode 100644 Tests/Functional/Controller/TestController.php diff --git a/.styleci.yml b/.styleci.yml index 6232d56..c41d891 100644 --- a/.styleci.yml +++ b/.styleci.yml @@ -7,3 +7,5 @@ enabled: disabled: - unalign_equals + - braces + - property_separation diff --git a/ApiDocGenerator.php b/ApiDocGenerator.php index 822e8c1..4b7b796 100644 --- a/ApiDocGenerator.php +++ b/ApiDocGenerator.php @@ -32,11 +32,12 @@ final class ApiDocGenerator * @param DescriberInterface[]|iterable $describers * @param ModelDescriberInterface[]|iterable $modelDescribers */ - public function __construct($describers, $modelDescribers, CacheItemPoolInterface $cacheItemPool = null) + public function __construct($describers, $modelDescribers, CacheItemPoolInterface $cacheItemPool = null, string $cacheItemId = null) { $this->describers = $describers; $this->modelDescribers = $modelDescribers; $this->cacheItemPool = $cacheItemPool; + $this->cacheItemId = $cacheItemId; } public function generate(): Swagger @@ -46,7 +47,7 @@ final class ApiDocGenerator } if ($this->cacheItemPool) { - $item = $this->cacheItemPool->getItem('swagger_doc'); + $item = $this->cacheItemPool->getItem($this->cacheItemId ?? 'swagger_doc'); if ($item->isHit()) { return $this->swagger = $item->get(); } diff --git a/CHANGELOG.md b/CHANGELOG.md index 4ffca06..d0497d6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,21 @@ JMS Serializer SwaggerPHP * Handle `enum` and `default` properties from SwaggerPHP annotation +Config +* `nelmio_api_doc.routes` has been replaced by `nelmio_api_doc.areas`. Please update your config accordingly. + + Before: + ```yml + nelmio_api_doc: + routes: [ path_patterns: [ /api ] ] + ``` + + After: + ```yml + nelmio_api_doc: + areas: [ path_patterns: [ /api ] ] + ``` + 3.0.0 (2017-12-10) ------------------ diff --git a/Controller/DocumentationController.php b/Controller/DocumentationController.php index 5129d04..2bc0915 100644 --- a/Controller/DocumentationController.php +++ b/Controller/DocumentationController.php @@ -12,21 +12,42 @@ namespace Nelmio\ApiDocBundle\Controller; use Nelmio\ApiDocBundle\ApiDocGenerator; +use Psr\Container\ContainerInterface; +use Symfony\Component\DependencyInjection\ServiceLocator; use Symfony\Component\HttpFoundation\JsonResponse; use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpKernel\Exception\BadRequestHttpException; final class DocumentationController { - private $apiDocGenerator; + private $generatorLocator; - public function __construct(ApiDocGenerator $apiDocGenerator) + /** + * @param ContainerInterface $generatorLocator + */ + public function __construct($generatorLocator) { - $this->apiDocGenerator = $apiDocGenerator; + if (!$generatorLocator instanceof ContainerInterface) { + if (!$generatorLocator instanceof ApiDocGenerator) { + throw new \InvalidArgumentException(sprintf('Providing an instance of "%s" to "%s" is not supported.', get_class($generatorLocator), __METHOD__)); + } + + @trigger_error(sprintf('Providing an instance of "%s" to "%s()" is deprecated since version 3.1. Provide it an instance of "%s" instead.', ApiDocGenerator::class, __METHOD__, ContainerInterface::class), E_USER_DEPRECATED); + $generatorLocator = new ServiceLocator(['default' => function () use ($generatorLocator): ApiDocGenerator { + return $generatorLocator; + }]); + } + + $this->generatorLocator = $generatorLocator; } - public function __invoke(Request $request) + public function __invoke(Request $request, $area = 'default') { - $spec = $this->apiDocGenerator->generate()->toArray(); + if (!$this->generatorLocator->has($area)) { + throw new BadRequestHttpException(sprintf('Area "%s" is not supported.', $area)); + } + + $spec = $this->generatorLocator->get($area)->generate()->toArray(); if ('' !== $request->getBaseUrl()) { $spec['basePath'] = $request->getBaseUrl(); } diff --git a/Controller/SwaggerUiController.php b/Controller/SwaggerUiController.php index b435bb1..a8ccca3 100644 --- a/Controller/SwaggerUiController.php +++ b/Controller/SwaggerUiController.php @@ -12,24 +12,45 @@ namespace Nelmio\ApiDocBundle\Controller; use Nelmio\ApiDocBundle\ApiDocGenerator; +use Psr\Container\ContainerInterface; +use Symfony\Component\DependencyInjection\ServiceLocator; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\HttpKernel\Exception\BadRequestHttpException; final class SwaggerUiController { - private $apiDocGenerator; + private $generatorLocator; private $twig; - public function __construct(ApiDocGenerator $apiDocGenerator, \Twig_Environment $twig) + /** + * @param ContainerInterface $generatorLocator + */ + public function __construct($generatorLocator, \Twig_Environment $twig) { - $this->apiDocGenerator = $apiDocGenerator; + if (!$generatorLocator instanceof ContainerInterface) { + if (!$generatorLocator instanceof ApiDocGenerator) { + throw new \InvalidArgumentException(sprintf('Providing an instance of "%s" to "%s" is not supported.', get_class($generatorLocator), __METHOD__)); + } + + @trigger_error(sprintf('Providing an instance of "%s" to "%s()" is deprecated since version 3.1. Provide it an instance of "%s" instead.', ApiDocGenerator::class, __METHOD__, ContainerInterface::class), E_USER_DEPRECATED); + $generatorLocator = new ServiceLocator(['default' => function () use ($generatorLocator): ApiDocGenerator { + return $generatorLocator; + }]); + } + + $this->generatorLocator = $generatorLocator; $this->twig = $twig; } - public function __invoke(Request $request) + public function __invoke(Request $request, $area = 'default') { - $spec = $this->apiDocGenerator->generate()->toArray(); + if (!$this->generatorLocator->has($area)) { + throw new BadRequestHttpException(sprintf('Area "%s" is not supported.', $area)); + } + + $spec = $this->generatorLocator->get($area)->generate()->toArray(); if ('' !== $request->getBaseUrl()) { $spec['basePath'] = $request->getBaseUrl(); } diff --git a/DependencyInjection/Compiler/AddModelDescribersPass.php b/DependencyInjection/Compiler/AddModelDescribersPass.php deleted file mode 100644 index 18094d4..0000000 --- a/DependencyInjection/Compiler/AddModelDescribersPass.php +++ /dev/null @@ -1,31 +0,0 @@ -findAndSortTaggedServices('nelmio_api_doc.model_describer', $container); - - $container->getDefinition('nelmio_api_doc.generator')->replaceArgument(1, $modelDescribers); - } -} diff --git a/DependencyInjection/Compiler/AddRouteDescribersPass.php b/DependencyInjection/Compiler/AddRouteDescribersPass.php deleted file mode 100644 index f29c59a..0000000 --- a/DependencyInjection/Compiler/AddRouteDescribersPass.php +++ /dev/null @@ -1,31 +0,0 @@ -findAndSortTaggedServices('nelmio_api_doc.route_describer', $container); - - $container->getDefinition('nelmio_api_doc.describers.route')->replaceArgument(2, $routeDescribers); - } -} diff --git a/DependencyInjection/Compiler/AddDescribersPass.php b/DependencyInjection/Compiler/TagDescribersPass.php similarity index 51% rename from DependencyInjection/Compiler/AddDescribersPass.php rename to DependencyInjection/Compiler/TagDescribersPass.php index ad2203b..cad60e8 100644 --- a/DependencyInjection/Compiler/AddDescribersPass.php +++ b/DependencyInjection/Compiler/TagDescribersPass.php @@ -12,20 +12,22 @@ namespace Nelmio\ApiDocBundle\DependencyInjection\Compiler; use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; -use Symfony\Component\DependencyInjection\Compiler\PriorityTaggedServiceTrait; use Symfony\Component\DependencyInjection\ContainerBuilder; /** * @internal */ -final class AddDescribersPass implements CompilerPassInterface +final class TagDescribersPass implements CompilerPassInterface { - use PriorityTaggedServiceTrait; - public function process(ContainerBuilder $container) { - $describers = $this->findAndSortTaggedServices('nelmio_api_doc.describer', $container); - - $container->getDefinition('nelmio_api_doc.generator')->replaceArgument(0, $describers); + foreach ($container->findTaggedServiceIds('nelmio_api_doc.describer') as $id => $tags) { + $describer = $container->getDefinition($id); + foreach ($container->getParameter('nelmio_api_doc.areas') as $area) { + foreach ($tags as $tag) { + $describer->addTag(sprintf('nelmio_api_doc.describer.%s', $area), $tag); + } + } + } } } diff --git a/DependencyInjection/Configuration.php b/DependencyInjection/Configuration.php index 744b48f..8e1ee63 100644 --- a/DependencyInjection/Configuration.php +++ b/DependencyInjection/Configuration.php @@ -21,6 +21,24 @@ final class Configuration implements ConfigurationInterface $treeBuilder = new TreeBuilder(); $treeBuilder ->root('nelmio_api_doc') + ->beforeNormalization() + ->ifTrue(function ($v) { + return !isset($v['areas']) && isset($v['routes']); + }) + ->then(function ($v) { + $v['areas'] = $v['routes']; + unset($v['routes']); + @trigger_error('The `nelmio_api_doc.routes` config option is deprecated. Please use `nelmio_api_doc.areas` instead (just replace `routes` by `areas` in your config).', E_USER_DEPRECATED); + + return $v; + }) + ->end() + ->beforeNormalization() + ->ifTrue(function ($v) { + return isset($v['routes']); + }) + ->thenInvalid('You must not use both `nelmio_api_doc.areas` and `nelmio_api_doc.routes` config options. Please update your config to only use `nelmio_api_doc.areas`.') + ->end() ->children() ->arrayNode('documentation') ->useAttributeAsKey('key') @@ -28,13 +46,30 @@ final class Configuration implements ConfigurationInterface ->example(['info' => ['title' => 'My App']]) ->prototype('variable')->end() ->end() - ->arrayNode('routes') + ->arrayNode('areas') ->info('Filter the routes that are documented') - ->addDefaultsIfNotSet() - ->children() - ->arrayNode('path_patterns') - ->example(['^/api', '^/api(?!/admin)']) - ->prototype('scalar')->end() + ->beforeNormalization() + ->ifTrue(function ($v) { + return empty($v) or isset($v['path_patterns']); + }) + ->then(function ($v) { + return ['default' => $v]; + }) + ->end() + ->validate() + ->ifTrue(function ($v) { + return !isset($v['default']); + }) + ->thenInvalid('You must specify a `default` area under `nelmio_api_doc.areas`.') + ->end() + ->useAttributeAsKey('name') + ->prototype('array') + ->addDefaultsIfNotSet() + ->children() + ->arrayNode('path_patterns') + ->example(['^/api', '^/api(?!/admin)']) + ->prototype('scalar')->end() + ->end() ->end() ->end() ->end() diff --git a/DependencyInjection/NelmioApiDocExtension.php b/DependencyInjection/NelmioApiDocExtension.php index f5add0f..b966fb1 100644 --- a/DependencyInjection/NelmioApiDocExtension.php +++ b/DependencyInjection/NelmioApiDocExtension.php @@ -12,10 +12,14 @@ namespace Nelmio\ApiDocBundle\DependencyInjection; use FOS\RestBundle\Controller\Annotations\ParamInterface; +use Nelmio\ApiDocBundle\ApiDocGenerator; +use Nelmio\ApiDocBundle\Describer\RouteDescriber; +use Nelmio\ApiDocBundle\Describer\SwaggerPhpDescriber; use Nelmio\ApiDocBundle\ModelDescriber\JMSModelDescriber; use Nelmio\ApiDocBundle\Routing\FilteredRouteCollectionBuilder; use phpDocumentor\Reflection\DocBlockFactory; use Symfony\Component\Config\FileLocator; +use Symfony\Component\DependencyInjection\Argument\TaggedIteratorArgument; use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\Definition; use Symfony\Component\DependencyInjection\Extension\PrependExtensionInterface; @@ -54,22 +58,57 @@ final class NelmioApiDocExtension extends Extension implements PrependExtensionI $routesDefinition = (new Definition(RouteCollection::class)) ->setFactory([new Reference('router'), 'getRouteCollection']); - if (0 === count($config['routes']['path_patterns'])) { - $container->setDefinition('nelmio_api_doc.routes', $routesDefinition) - ->setPublic(false); - } else { - $container->register('nelmio_api_doc.routes', RouteCollection::class) + $container->setParameter('nelmio_api_doc.areas', array_keys($config['areas'])); + foreach ($config['areas'] as $area => $areaConfig) { + $container->register(sprintf('nelmio_api_doc.generator.%s', $area), ApiDocGenerator::class) ->setPublic(false) - ->setFactory([ - (new Definition(FilteredRouteCollectionBuilder::class)) - ->addArgument($config['routes']['path_patterns']), - 'filter', + ->setArguments([ + new TaggedIteratorArgument(sprintf('nelmio_api_doc.describer.%s', $area)), + new TaggedIteratorArgument('nelmio_api_doc.model_describer'), + ]); + + if (0 === count($areaConfig['path_patterns'])) { + $container->setDefinition(sprintf('nelmio_api_doc.routes.%s', $area), $routesDefinition) + ->setPublic(false); + } else { + $container->register(sprintf('nelmio_api_doc.routes.%s', $area), RouteCollection::class) + ->setPublic(false) + ->setFactory([ + (new Definition(FilteredRouteCollectionBuilder::class)) + ->addArgument($areaConfig['path_patterns']), + 'filter', + ]) + ->addArgument($routesDefinition); + } + + $container->register(sprintf('nelmio_api_doc.describers.route.%s', $area), RouteDescriber::class) + ->setPublic(false) + ->setArguments([ + new Reference(sprintf('nelmio_api_doc.routes.%s', $area)), + new Reference('nelmio_api_doc.controller_reflector'), + new TaggedIteratorArgument('nelmio_api_doc.route_describer'), ]) - ->addArgument($routesDefinition); + ->addTag(sprintf('nelmio_api_doc.describer.%s', $area), ['priority' => -400]); + + $container->register(sprintf('nelmio_api_doc.describers.swagger_php.%s', $area), SwaggerPhpDescriber::class) + ->setPublic(false) + ->setArguments([ + new Reference(sprintf('nelmio_api_doc.routes.%s', $area)), + new Reference('nelmio_api_doc.controller_reflector'), + new Reference('annotation_reader'), + ]) + ->addTag(sprintf('nelmio_api_doc.describer.%s', $area), ['priority' => -200]); } + $container->register('nelmio_api_doc.generator_locator') + ->setPublic(false) + ->addTag('container.service_locator') + ->addArgument(array_combine( + array_keys($config['areas']), + array_map(function ($area) { return new Reference(sprintf('nelmio_api_doc.generator.%s', $area)); }, array_keys($config['areas'])) + )); + // Import services needed for each library - $loader->load('swagger_php.xml'); if (class_exists(DocBlockFactory::class)) { $loader->load('php_doc.xml'); } diff --git a/NelmioApiDocBundle.php b/NelmioApiDocBundle.php index df48166..1d1791f 100644 --- a/NelmioApiDocBundle.php +++ b/NelmioApiDocBundle.php @@ -11,10 +11,8 @@ namespace Nelmio\ApiDocBundle; -use Nelmio\ApiDocBundle\DependencyInjection\Compiler\AddDescribersPass; -use Nelmio\ApiDocBundle\DependencyInjection\Compiler\AddModelDescribersPass; -use Nelmio\ApiDocBundle\DependencyInjection\Compiler\AddRouteDescribersPass; use Nelmio\ApiDocBundle\DependencyInjection\Compiler\ConfigurationPass; +use Nelmio\ApiDocBundle\DependencyInjection\Compiler\TagDescribersPass; use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\HttpKernel\Bundle\Bundle; @@ -26,8 +24,6 @@ final class NelmioApiDocBundle extends Bundle public function build(ContainerBuilder $container) { $container->addCompilerPass(new ConfigurationPass()); - $container->addCompilerPass(new AddDescribersPass()); - $container->addCompilerPass(new AddModelDescribersPass()); - $container->addCompilerPass(new AddRouteDescribersPass()); + $container->addCompilerPass(new TagDescribersPass()); } } diff --git a/Resources/config/services.xml b/Resources/config/services.xml index 401b78d..107ada3 100644 --- a/Resources/config/services.xml +++ b/Resources/config/services.xml @@ -6,19 +6,16 @@ - + - + - - - - + @@ -32,14 +29,6 @@ - - - - - - - - diff --git a/Resources/config/swagger_php.xml b/Resources/config/swagger_php.xml deleted file mode 100644 index b2e249c..0000000 --- a/Resources/config/swagger_php.xml +++ /dev/null @@ -1,16 +0,0 @@ - - - - - - - - - - - - - - diff --git a/Resources/doc/areas.rst b/Resources/doc/areas.rst new file mode 100644 index 0000000..f996ca7 --- /dev/null +++ b/Resources/doc/areas.rst @@ -0,0 +1,48 @@ +Areas +===== + +We've already seen that you can configure which routes are documented using ``nelmio_api_doc.areas``: + +.. code-block:: yaml + + nelmio_api_doc: + areas: + path_patterns: [ ^/api ] + +But in fact, this config option is way more powerful and allows you to split your documentation in several parts. + +Configuration +------------- + +You can define areas which will each generates a different documentation: + +.. code-block:: yaml + + nelmio_api_doc: + areas: + default: + path_patterns: [ ^/api ] + internal: + path_patterns: [ ^/internal ] + commercial: + path_patterns: [ ^/commercial ] + +Your main documentation is under the ``default`` area. It's the one shown when accessing ``/api/doc``. + +Then update your routing to be able to access your different documentations: + +.. code-block:: yaml + + # app/config/routing.yml + app.swagger_ui: + path: /api/doc/{area} + methods: GET + defaults: { _controller: nelmio_api_doc.controller.swagger_ui, area: default } + + # To expose them as JSON + #app.swagger.areas: + # path: /api/doc/{area}.json + # methods: GET + # defaults: { _controller: nelmio_api_doc.controller.swagger } + +That's all! You can now access ``/api/doc/internal`` and ``/api/doc/commercial``. diff --git a/Resources/doc/index.rst b/Resources/doc/index.rst index a6c5290..106110d 100644 --- a/Resources/doc/index.rst +++ b/Resources/doc/index.rst @@ -1,6 +1,6 @@ NelmioApiDocBundle ================== - + The **NelmioApiDocBundle** bundle allows you to generate documentation in the OpenAPI (Swagger) format and provides a sandbox to interactively browse the API documentation. @@ -12,7 +12,7 @@ This bundle supports _Symfony_ route requirements, PHP annotations, [_FOSRestBundle_](https://github.com/FriendsOfSymfony/FOSRestBundle) annotations and apps using [_Api-Platform_](https://github.com/api-platform/api-platform). -For models, it supports the Symfony serializer and the JMS serializer. +For models, it supports the Symfony serializer and the JMS serializer. Migrate from 2.x to 3.0 ----------------------- @@ -21,8 +21,8 @@ Migrate from 2.x to 3.0 Installation ------------ - -Open a command console, enter your project directory and execute the following command to download the latest version of this bundle: + +Open a command console, enter your project directory and execute the following command to download the latest version of this bundle: .. code-block:: bash @@ -65,7 +65,7 @@ Open a command console, enter your project directory and execute the following c path: /api/doc.json methods: GET defaults: { _controller: nelmio_api_doc.controller.swagger } - + As you just installed the bundle, you'll likely see routes you don't want in your documentation such as `/_profiler/`. To fix this, you can filter the routes that are documented by configuring the bundle: @@ -79,7 +79,7 @@ Open a command console, enter your project directory and execute the following c - ^/api(?!/doc$) How does this bundle work? --------------------------- +-------------------------- It generates you a swagger documentation from your symfony app thanks to _Describers_. Each of these _Describers_ extract infos from various sources. @@ -88,11 +88,11 @@ routes, etc. If you configured the ``app.swagger_ui`` route above, you can browse your documentation at `http://example.org/api/doc`. - + Using the bundle ---------------- -You can configure global information in the bundle configuration ``documentation.info`` section (take a look at +You can configure global information in the bundle configuration ``documentation.info`` section (take a look at [the Swagger specification](http://swagger.io/specification/) to know the fields available): @@ -151,7 +151,7 @@ Use models As shown in the example above, the bundle provides the ``@Model`` annotation. When you use it, the bundle will deduce your model properties. - + It has two options: * ``type`` to specify your model's type:: @@ -165,12 +165,12 @@ It has two options: /** * @Model(type=User::class, groups={"non_sensitive_data"}) */ - + .. warning:: The ``@Model`` annotation acts like a ``@Schema`` annotation. If you nest it with a ``@Schema`` annotation, the bundle will consider that you're documenting an array of models. - + For instance, the following example:: /** @@ -215,7 +215,7 @@ It has two options: public function myAction() { } - + If you're not using the JMS Serializer ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -240,3 +240,13 @@ support in your config: nelmio_api_doc: models: { use_jms: false } + +Learn more +---------- + +If you need more complex features, take a look at: + +.. toctree:: + :maxdepth: 1 + + areas diff --git a/Tests/ApiDocGeneratorTest.php b/Tests/ApiDocGeneratorTest.php new file mode 100644 index 0000000..8eac565 --- /dev/null +++ b/Tests/ApiDocGeneratorTest.php @@ -0,0 +1,36 @@ +assertEquals($generator->generate(), $adapter->getItem('swagger_doc')->get()); + } + + public function testCacheWithCustomId() + { + $adapter = new ArrayAdapter(); + $generator = new ApiDocGenerator([new DefaultDescriber()], [], $adapter, 'custom_id'); + + $this->assertEquals($generator->generate(), $adapter->getItem('custom_id')->get()); + } +} diff --git a/Tests/Controller/ControllersTest.php b/Tests/Controller/ControllersTest.php new file mode 100644 index 0000000..d94f57d --- /dev/null +++ b/Tests/Controller/ControllersTest.php @@ -0,0 +1,41 @@ +createMock('Twig_Environment')); + $controller(new Request()); + } + + /** + * @group legacy + * @expectedDeprecation Providing an instance of "Nelmio\ApiDocBundle\ApiDocGenerator" to "Nelmio\ApiDocBundle\Controller\DocumentationController::__construct()" is deprecated since version 3.1. Provide it an instance of "Psr\Container\ContainerInterface" instead. + */ + public function testDocumentationControllerInstanciation() + { + $controller = new DocumentationController(new ApiDocGenerator([], [])); + $controller(new Request()); + } +} diff --git a/Tests/DependencyInjection/ConfigurationTest.php b/Tests/DependencyInjection/ConfigurationTest.php new file mode 100644 index 0000000..c506d90 --- /dev/null +++ b/Tests/DependencyInjection/ConfigurationTest.php @@ -0,0 +1,62 @@ +processConfiguration(new Configuration(), [['areas' => ['path_patterns' => ['/foo']]]]); + + $this->assertEquals(['default' => ['path_patterns' => ['/foo']]], $config['areas']); + } + + public function testAreas() + { + $processor = new Processor(); + $config = $processor->processConfiguration(new Configuration(), [['areas' => $areas = [ + 'default' => ['path_patterns' => ['/foo']], + 'internal' => ['path_patterns' => ['/internal']], + 'commercial' => ['path_patterns' => ['/internal']], + ]]]); + + $this->assertEquals($areas, $config['areas']); + } + + /** + * @group legacy + * @expectedException \InvalidArgumentException + * @expectedExceptionMessage You must not use both `nelmio_api_doc.areas` and `nelmio_api_doc.routes` config options. Please update your config to only use `nelmio_api_doc.areas`. + */ + public function testBothAreasAndRoutes() + { + $processor = new Processor(); + $config = $processor->processConfiguration(new Configuration(), [['areas' => [], 'routes' => []]]); + } + + /** + * @group legacy + * @expectedDeprecation The `nelmio_api_doc.routes` config option is deprecated. Please use `nelmio_api_doc.areas` instead (just replace `routes` by `areas` in your config). + */ + public function testDefaultConfig() + { + $processor = new Processor(); + $config = $processor->processConfiguration(new Configuration(), [['routes' => ['path_patterns' => ['/foo']]]]); + + $this->assertEquals(['default' => ['path_patterns' => ['/foo']]], $config['areas']); + } +} diff --git a/Tests/Functional/Controller/TestController.php b/Tests/Functional/Controller/TestController.php new file mode 100644 index 0000000..ef3c80a --- /dev/null +++ b/Tests/Functional/Controller/TestController.php @@ -0,0 +1,32 @@ + '/app_dev.php/docs', 'SCRIPT_FILENAME' => '/var/www/app/web/app_dev.php']); } - public function testSwaggerUi() + /** + * @dataProvider areaProvider + */ + public function testSwaggerUi($url, $area, $expected) { $client = self::createClient(); - $crawler = $client->request('GET', '/app_dev.php/docs/'); + $crawler = $client->request('GET', '/app_dev.php'.$url); $response = $client->getResponse(); $this->assertEquals(200, $response->getStatusCode()); $this->assertEquals('text/html; charset=UTF-8', $response->headers->get('Content-Type')); + $this->assertEquals($expected, json_decode($crawler->filterXPath('//script[@id="swagger-data"]')->text(), true)['spec']); + } + + public function areaProvider() + { $expected = $this->getSwaggerDefinition()->toArray(); $expected['basePath'] = '/app_dev.php'; - $this->assertEquals($expected, json_decode($crawler->filterXPath('//script[@id="swagger-data"]')->text(), true)['spec']); + yield ['/docs', 'default', $expected]; + + // Api-platform documentation + $expected['paths'] = [ + '/api/dummies' => $expected['paths']['/api/dummies'], + '/api/foo' => $expected['paths']['/api/foo'], + '/api/dummies/{id}' => $expected['paths']['/api/dummies/{id}'], + '/test/test/' => ['get' => [ + 'responses' => ['200' => ['description' => 'Test']], + ]], + ]; + $expected['definitions'] = ['Dummy' => $expected['definitions']['Dummy']]; + + yield ['/docs/test', 'test', $expected]; } public function testJsonDocs() diff --git a/Tests/Functional/TestKernel.php b/Tests/Functional/TestKernel.php index cfce559..22bd4b8 100644 --- a/Tests/Functional/TestKernel.php +++ b/Tests/Functional/TestKernel.php @@ -64,11 +64,11 @@ class TestKernel extends Kernel */ protected function configureRoutes(RouteCollectionBuilder $routes) { + $routes->import(__DIR__.'/Controller/TestController.php', '/', 'annotation'); $routes->import(__DIR__.'/Controller/ApiController.php', '/', 'annotation'); $routes->import(__DIR__.'/Controller/UndocumentedController.php', '/', 'annotation'); $routes->import('', '/api', 'api_platform'); - $routes->import('@NelmioApiDocBundle/Resources/config/routing/swaggerui.xml', '/docs'); - + $routes->add('/docs/{area}', 'nelmio_api_doc.controller.swagger_ui')->setDefault('area', 'default'); $routes->add('/docs.json', 'nelmio_api_doc.controller.swagger'); if ($this->useJMS) { @@ -110,8 +110,9 @@ class TestKernel extends Kernel 'title' => 'My Test App', ], ], - 'routes' => [ - 'path_patterns' => ['^/api(?!/admin)'], + 'areas' => [ + 'default' => ['path_patterns' => ['^/api(?!/admin)']], + 'test' => ['path_patterns' => ['^/test']], ], ]); }