From c7367d66447135bec962e213da2d0c8213f2d545 Mon Sep 17 00:00:00 2001 From: Guilhem Niot Date: Mon, 26 Jun 2017 10:34:42 +0200 Subject: [PATCH] Add a controller exposing the documentation in json --- Annotation/Model.php | 3 +- Controller/DocumentationController.php | 30 +++++++++++++++++ DependencyInjection/NelmioApiDocExtension.php | 10 +++--- Describer/SwaggerPhpDescriber.php | 5 +-- README.md | 27 ++++++++++------ Resources/config/services.xml | 6 +++- SwaggerPhp/ModelRegister.php | 1 - Tests/Functional/Controller/ApiController.php | 3 +- Tests/Functional/Form/DummyType.php | 14 ++++++-- Tests/Functional/FunctionalTest.php | 31 +----------------- Tests/Functional/SwaggerUiTest.php | 15 +++++++-- Tests/Functional/TestKernel.php | 5 +-- Tests/Functional/WebTestCase.php | 32 +++++++++++++++++-- Tests/Model/ModelRegistryTest.php | 2 +- .../FilteredRouteCollectionBuilderTest.php | 4 +-- 15 files changed, 124 insertions(+), 64 deletions(-) create mode 100644 Controller/DocumentationController.php diff --git a/Annotation/Model.php b/Annotation/Model.php index 2d3c04a..811b00b 100644 --- a/Annotation/Model.php +++ b/Annotation/Model.php @@ -18,8 +18,7 @@ use Swagger\Annotations\AbstractAnnotation; */ final class Model extends AbstractAnnotation { - - /** @inheritdoc */ + /** {@inheritdoc} */ public static $_types = [ 'type' => 'string', 'groups' => '[string]', diff --git a/Controller/DocumentationController.php b/Controller/DocumentationController.php new file mode 100644 index 0000000..1c218e4 --- /dev/null +++ b/Controller/DocumentationController.php @@ -0,0 +1,30 @@ +apiDocGenerator = $apiDocGenerator; + } + + public function __invoke() + { + return new JsonResponse($this->apiDocGenerator->generate()->toArray()); + } +} diff --git a/DependencyInjection/NelmioApiDocExtension.php b/DependencyInjection/NelmioApiDocExtension.php index f4fe537..bb98a58 100644 --- a/DependencyInjection/NelmioApiDocExtension.php +++ b/DependencyInjection/NelmioApiDocExtension.php @@ -12,18 +12,18 @@ namespace Nelmio\ApiDocBundle\DependencyInjection; use FOS\RestBundle\Controller\Annotations\ParamInterface; +use Nelmio\ApiDocBundle\ModelDescriber\FormModelDescriber; +use Nelmio\ApiDocBundle\Routing\FilteredRouteCollectionBuilder; use phpDocumentor\Reflection\DocBlockFactory; use Symfony\Component\Config\FileLocator; use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\Definition; -use Symfony\Component\DependencyInjection\Reference; use Symfony\Component\DependencyInjection\Extension\PrependExtensionInterface; use Symfony\Component\DependencyInjection\Loader\XmlFileLoader; +use Symfony\Component\DependencyInjection\Reference; use Symfony\Component\Form\FormInterface; use Symfony\Component\HttpKernel\DependencyInjection\Extension; use Symfony\Component\Routing\RouteCollection; -use Nelmio\ApiDocBundle\Routing\FilteredRouteCollectionBuilder; -use Nelmio\ApiDocBundle\ModelDescriber\FormModelDescriber; final class NelmioApiDocExtension extends Extension implements PrependExtensionInterface { @@ -65,8 +65,8 @@ final class NelmioApiDocExtension extends Extension implements PrependExtensionI ->setFactory([ (new Definition(FilteredRouteCollectionBuilder::class)) ->addArgument($config['routes']['path_patterns']), - 'filter'] - ) + 'filter', + ]) ->addArgument($routesDefinition); } diff --git a/Describer/SwaggerPhpDescriber.php b/Describer/SwaggerPhpDescriber.php index 1b21659..62552b4 100644 --- a/Describer/SwaggerPhpDescriber.php +++ b/Describer/SwaggerPhpDescriber.php @@ -18,9 +18,9 @@ use Nelmio\ApiDocBundle\SwaggerPhp\AddDefaults; use Nelmio\ApiDocBundle\SwaggerPhp\ModelRegister; use Nelmio\ApiDocBundle\Util\ControllerReflector; use Swagger\Analysis; +use Swagger\Annotations\AbstractAnnotation; use Swagger\Annotations as SWG; use Swagger\Context; -use Swagger\Annotations\AbstractAnnotation; use Symfony\Component\Routing\RouteCollection; final class SwaggerPhpDescriber extends ExternalDocDescriber implements ModelRegistryAwareInterface @@ -175,7 +175,8 @@ final class SwaggerPhpDescriber extends ExternalDocDescriber implements ModelReg return $path; } - private function updateNestedAnnotations($value, Context $context) { + private function updateNestedAnnotations($value, Context $context) + { if ($value instanceof AbstractAnnotation) { $value->_context = $context; } elseif (!is_array($value)) { diff --git a/README.md b/README.md index d15fd61..c6d48b0 100644 --- a/README.md +++ b/README.md @@ -39,31 +39,40 @@ class AppKernel extends Kernel } ``` -To access your documentation in your browser, register the following route: +To browse your documentation with Swagger UI, register the following route: ```yml # app/config/routing.yml -NelmioApiDocBundle: +app.swagger_ui: resource: "@NelmioApiDocBundle/Resources/config/routing/swaggerui.xml" prefix: /api/doc ``` +If you also want to expose it in JSON, register this route: + +```yml +# app/config/routing.yml +app.swagger: + path: /api/doc.json + methods: GET + defaults: { _controller: nelmio_api_doc.controller.swagger } +``` + ## What does this bundle? It generates you a swagger documentation from your symfony app thanks to -_Describers_. Each of this _Describers_ extract infos from various sources. +_Describers_. Each of these _Describers_ extract infos from various sources. For instance, one extract data from SwaggerPHP annotations, one from your routes, etc. -If you configured the route above, you can browse your documentation at -`http://example.org/api/doc`. +If you configured the ``app.swagger_ui`` route above, you can browse your +documentation at `http://example.org/api/doc`. ## Configure the bundle -If 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: +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: ```yml # app/config/config.yml diff --git a/Resources/config/services.xml b/Resources/config/services.xml index 7dbd707..2fa25bd 100644 --- a/Resources/config/services.xml +++ b/Resources/config/services.xml @@ -4,12 +4,16 @@ xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd"> - + + + + + diff --git a/SwaggerPhp/ModelRegister.php b/SwaggerPhp/ModelRegister.php index e234dd5..de911ef 100644 --- a/SwaggerPhp/ModelRegister.php +++ b/SwaggerPhp/ModelRegister.php @@ -70,7 +70,6 @@ final class ModelRegister continue; } - $annotation->merge([new $annotationClass([ 'ref' => $this->modelRegistry->register(new Model($this->createType($model->type), $model->groups)), ])]); diff --git a/Tests/Functional/Controller/ApiController.php b/Tests/Functional/Controller/ApiController.php index 2ab02bf..a3a54e0 100644 --- a/Tests/Functional/Controller/ApiController.php +++ b/Tests/Functional/Controller/ApiController.php @@ -15,8 +15,8 @@ use FOS\RestBundle\Controller\Annotations\QueryParam; use FOS\RestBundle\Controller\Annotations\RequestParam; use Nelmio\ApiDocBundle\Annotation\Model; use Nelmio\ApiDocBundle\Annotation\Operation; -use Nelmio\ApiDocBundle\Tests\Functional\Entity\User; use Nelmio\ApiDocBundle\Tests\Functional\Entity\Article; +use Nelmio\ApiDocBundle\Tests\Functional\Entity\User; use Nelmio\ApiDocBundle\Tests\Functional\Form\DummyType; use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route; use Swagger\Annotations as SWG; @@ -134,6 +134,5 @@ class ApiController */ public function formAction() { - } } diff --git a/Tests/Functional/Form/DummyType.php b/Tests/Functional/Form/DummyType.php index 65fa997..a590dba 100644 --- a/Tests/Functional/Form/DummyType.php +++ b/Tests/Functional/Form/DummyType.php @@ -1,11 +1,19 @@ [ 'type' => 'string', 'enum' => ['male', 'female'], - ] + ], ], 'required' => ['foo'], ], $this->getModel('DummyType')->toArray()); } - - private function getSwaggerDefinition() - { - static::createClient(); - - return static::$kernel->getContainer()->get('nelmio_api_doc.generator')->generate(); - } - - private function getModel($name): Schema - { - $definitions = $this->getSwaggerDefinition()->getDefinitions(); - $this->assertTrue($definitions->has($name)); - - return $definitions->get($name); - } - - private function getOperation($path, $method): Operation - { - $api = $this->getSwaggerDefinition(); - $paths = $api->getPaths(); - - $this->assertTrue($paths->has($path), sprintf('Path "%s" does not exist.', $path)); - $action = $paths->get($path); - - $this->assertTrue($action->hasOperation($method), sprintf('Operation "%s" for path "%s" does not exist', $path, $method)); - - return $action->getOperation($method); - } } diff --git a/Tests/Functional/SwaggerUiTest.php b/Tests/Functional/SwaggerUiTest.php index 3183300..b294c33 100644 --- a/Tests/Functional/SwaggerUiTest.php +++ b/Tests/Functional/SwaggerUiTest.php @@ -23,7 +23,18 @@ class SwaggerUiTest extends WebTestCase $this->assertEquals('text/html; charset=UTF-8', $response->headers->get('Content-Type')); $swaggerUiSpec = json_decode($crawler->filterXPath('//script[@id="swagger-data"]')->text(), true); - $appSpec = $client->getContainer()->get('nelmio_api_doc.generator')->generate()->toArray(); - $this->assertEquals($appSpec, $swaggerUiSpec['spec']); + $this->assertEquals($this->getSwaggerDefinition()->toArray(), $swaggerUiSpec['spec']); + } + + public function testJsonDocs() + { + $client = self::createClient(); + $crawler = $client->request('GET', '/docs.json'); + + $response = $client->getResponse(); + $this->assertEquals(200, $response->getStatusCode()); + $this->assertEquals('application/json', $response->headers->get('Content-Type')); + + $this->assertEquals($this->getSwaggerDefinition()->toArray(), json_decode($response->getContent(), true)); } } diff --git a/Tests/Functional/TestKernel.php b/Tests/Functional/TestKernel.php index 96364dd..46d0fe2 100644 --- a/Tests/Functional/TestKernel.php +++ b/Tests/Functional/TestKernel.php @@ -14,7 +14,6 @@ namespace Nelmio\ApiDocBundle\Tests\Functional; use ApiPlatform\Core\Bridge\Symfony\Bundle\ApiPlatformBundle; use FOS\RestBundle\FOSRestBundle; use Nelmio\ApiDocBundle\NelmioApiDocBundle; -use Nelmio\ApiDocBundle\Tests\Functional\TestBundle; use Sensio\Bundle\FrameworkExtraBundle\SensioFrameworkExtraBundle; use Symfony\Bundle\FrameworkBundle\FrameworkBundle; use Symfony\Bundle\FrameworkBundle\Kernel\MicroKernelTrait; @@ -52,6 +51,8 @@ class TestKernel extends Kernel $routes->import(__DIR__.'/Controller/', '/', 'annotation'); $routes->import('', '/api', 'api_platform'); $routes->import('@NelmioApiDocBundle/Resources/config/routing/swaggerui.xml', '/docs'); + + $routes->add('/docs.json', 'nelmio_api_doc.controller.swagger'); } /** @@ -76,7 +77,7 @@ class TestKernel extends Kernel [ 'path' => '^/', 'fallback_format' => 'json', - ] + ], ], ], ]); diff --git a/Tests/Functional/WebTestCase.php b/Tests/Functional/WebTestCase.php index af25700..d14b391 100644 --- a/Tests/Functional/WebTestCase.php +++ b/Tests/Functional/WebTestCase.php @@ -11,8 +11,8 @@ namespace Nelmio\ApiDocBundle\Tests\Functional; - -use Nelmio\ApiDocBundle\Tests\Functional\TestKernel; +use EXSyst\Component\Swagger\Operation; +use EXSyst\Component\Swagger\Schema; use Symfony\Bundle\FrameworkBundle\Test\WebTestCase as BaseWebTestCase; class WebTestCase extends BaseWebTestCase @@ -24,4 +24,32 @@ class WebTestCase extends BaseWebTestCase { return TestKernel::class; } + + protected function getSwaggerDefinition() + { + static::createClient(); + + return static::$kernel->getContainer()->get('nelmio_api_doc.generator')->generate(); + } + + protected function getModel($name): Schema + { + $definitions = $this->getSwaggerDefinition()->getDefinitions(); + $this->assertTrue($definitions->has($name)); + + return $definitions->get($name); + } + + protected function getOperation($path, $method): Operation + { + $api = $this->getSwaggerDefinition(); + $paths = $api->getPaths(); + + $this->assertTrue($paths->has($path), sprintf('Path "%s" does not exist.', $path)); + $action = $paths->get($path); + + $this->assertTrue($action->hasOperation($method), sprintf('Operation "%s" for path "%s" does not exist', $path, $method)); + + return $action->getOperation($method); + } } diff --git a/Tests/Model/ModelRegistryTest.php b/Tests/Model/ModelRegistryTest.php index e041db9..56e900e 100644 --- a/Tests/Model/ModelRegistryTest.php +++ b/Tests/Model/ModelRegistryTest.php @@ -13,9 +13,9 @@ namespace Nelmio\ApiDocBundle\Tests\Model; use EXSyst\Component\Swagger\Schema; use EXSyst\Component\Swagger\Swagger; -use PHPUnit\Framework\TestCase; use Nelmio\ApiDocBundle\Model\Model; use Nelmio\ApiDocBundle\Model\ModelRegistry; +use PHPUnit\Framework\TestCase; use Symfony\Component\PropertyInfo\Type; class ModelRegistryTest extends TestCase diff --git a/Tests/Routing/FilteredRouteCollectionBuilderTest.php b/Tests/Routing/FilteredRouteCollectionBuilderTest.php index 4b57b53..a15ff25 100644 --- a/Tests/Routing/FilteredRouteCollectionBuilderTest.php +++ b/Tests/Routing/FilteredRouteCollectionBuilderTest.php @@ -12,12 +12,12 @@ namespace Nelmio\ApiDocBundle\Tests\Routing; use Nelmio\ApiDocBundle\Routing\FilteredRouteCollectionBuilder; +use PHPUnit\Framework\TestCase; use Symfony\Component\Routing\Route; use Symfony\Component\Routing\RouteCollection; -use PHPUnit\Framework\TestCase; /** - * Tests for FilteredRouteCollectionBuilder class + * Tests for FilteredRouteCollectionBuilder class. */ class FilteredRouteCollectionBuilderTest extends TestCase {