Add a controller exposing the documentation in json

This commit is contained in:
Guilhem Niot 2017-06-26 10:34:42 +02:00
parent 8c244f73e5
commit c7367d6644
15 changed files with 124 additions and 64 deletions

View File

@ -18,8 +18,7 @@ use Swagger\Annotations\AbstractAnnotation;
*/ */
final class Model extends AbstractAnnotation final class Model extends AbstractAnnotation
{ {
/** {@inheritdoc} */
/** @inheritdoc */
public static $_types = [ public static $_types = [
'type' => 'string', 'type' => 'string',
'groups' => '[string]', 'groups' => '[string]',

View File

@ -0,0 +1,30 @@
<?php
/*
* This file is part of the NelmioApiDocBundle package.
*
* (c) Nelmio
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Nelmio\ApiDocBundle\Controller;
use Nelmio\ApiDocBundle\ApiDocGenerator;
use Symfony\Component\HttpFoundation\JsonResponse;
final class DocumentationController
{
private $apiDocGenerator;
public function __construct(ApiDocGenerator $apiDocGenerator)
{
$this->apiDocGenerator = $apiDocGenerator;
}
public function __invoke()
{
return new JsonResponse($this->apiDocGenerator->generate()->toArray());
}
}

View File

@ -12,18 +12,18 @@
namespace Nelmio\ApiDocBundle\DependencyInjection; namespace Nelmio\ApiDocBundle\DependencyInjection;
use FOS\RestBundle\Controller\Annotations\ParamInterface; use FOS\RestBundle\Controller\Annotations\ParamInterface;
use Nelmio\ApiDocBundle\ModelDescriber\FormModelDescriber;
use Nelmio\ApiDocBundle\Routing\FilteredRouteCollectionBuilder;
use phpDocumentor\Reflection\DocBlockFactory; use phpDocumentor\Reflection\DocBlockFactory;
use Symfony\Component\Config\FileLocator; use Symfony\Component\Config\FileLocator;
use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Definition; use Symfony\Component\DependencyInjection\Definition;
use Symfony\Component\DependencyInjection\Reference;
use Symfony\Component\DependencyInjection\Extension\PrependExtensionInterface; use Symfony\Component\DependencyInjection\Extension\PrependExtensionInterface;
use Symfony\Component\DependencyInjection\Loader\XmlFileLoader; use Symfony\Component\DependencyInjection\Loader\XmlFileLoader;
use Symfony\Component\DependencyInjection\Reference;
use Symfony\Component\Form\FormInterface; use Symfony\Component\Form\FormInterface;
use Symfony\Component\HttpKernel\DependencyInjection\Extension; use Symfony\Component\HttpKernel\DependencyInjection\Extension;
use Symfony\Component\Routing\RouteCollection; use Symfony\Component\Routing\RouteCollection;
use Nelmio\ApiDocBundle\Routing\FilteredRouteCollectionBuilder;
use Nelmio\ApiDocBundle\ModelDescriber\FormModelDescriber;
final class NelmioApiDocExtension extends Extension implements PrependExtensionInterface final class NelmioApiDocExtension extends Extension implements PrependExtensionInterface
{ {
@ -65,8 +65,8 @@ final class NelmioApiDocExtension extends Extension implements PrependExtensionI
->setFactory([ ->setFactory([
(new Definition(FilteredRouteCollectionBuilder::class)) (new Definition(FilteredRouteCollectionBuilder::class))
->addArgument($config['routes']['path_patterns']), ->addArgument($config['routes']['path_patterns']),
'filter'] 'filter',
) ])
->addArgument($routesDefinition); ->addArgument($routesDefinition);
} }

View File

@ -18,9 +18,9 @@ use Nelmio\ApiDocBundle\SwaggerPhp\AddDefaults;
use Nelmio\ApiDocBundle\SwaggerPhp\ModelRegister; use Nelmio\ApiDocBundle\SwaggerPhp\ModelRegister;
use Nelmio\ApiDocBundle\Util\ControllerReflector; use Nelmio\ApiDocBundle\Util\ControllerReflector;
use Swagger\Analysis; use Swagger\Analysis;
use Swagger\Annotations\AbstractAnnotation;
use Swagger\Annotations as SWG; use Swagger\Annotations as SWG;
use Swagger\Context; use Swagger\Context;
use Swagger\Annotations\AbstractAnnotation;
use Symfony\Component\Routing\RouteCollection; use Symfony\Component\Routing\RouteCollection;
final class SwaggerPhpDescriber extends ExternalDocDescriber implements ModelRegistryAwareInterface final class SwaggerPhpDescriber extends ExternalDocDescriber implements ModelRegistryAwareInterface
@ -175,7 +175,8 @@ final class SwaggerPhpDescriber extends ExternalDocDescriber implements ModelReg
return $path; return $path;
} }
private function updateNestedAnnotations($value, Context $context) { private function updateNestedAnnotations($value, Context $context)
{
if ($value instanceof AbstractAnnotation) { if ($value instanceof AbstractAnnotation) {
$value->_context = $context; $value->_context = $context;
} elseif (!is_array($value)) { } elseif (!is_array($value)) {

View File

@ -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 ```yml
# app/config/routing.yml # app/config/routing.yml
NelmioApiDocBundle: app.swagger_ui:
resource: "@NelmioApiDocBundle/Resources/config/routing/swaggerui.xml" resource: "@NelmioApiDocBundle/Resources/config/routing/swaggerui.xml"
prefix: /api/doc 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? ## What does this bundle?
It generates you a swagger documentation from your symfony app thanks to 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 For instance, one extract data from SwaggerPHP annotations, one from your
routes, etc. routes, etc.
If you configured the route above, you can browse your documentation at If you configured the ``app.swagger_ui`` route above, you can browse your
`http://example.org/api/doc`. documentation at `http://example.org/api/doc`.
## Configure the bundle ## Configure the bundle
If you just installed the bundle, you'll likely see routes you don't want in As you just installed the bundle, you'll likely see routes you don't want in
your documentation such as `/_profiler/`. your documentation such as `/_profiler/`. To fix this, you can filter the
To fix this, you can filter the routes that are documented by configuring the routes that are documented by configuring the bundle:
bundle:
```yml ```yml
# app/config/config.yml # app/config/config.yml

View File

@ -4,12 +4,16 @@
xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd"> xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd">
<services> <services>
<!-- Swagger Ui Controller --> <!-- Controllers -->
<service id="nelmio_api_doc.controller.swagger_ui" class="Nelmio\ApiDocBundle\Controller\SwaggerUiController"> <service id="nelmio_api_doc.controller.swagger_ui" class="Nelmio\ApiDocBundle\Controller\SwaggerUiController">
<argument type="service" id="nelmio_api_doc.generator" /> <argument type="service" id="nelmio_api_doc.generator" />
<argument type="service" id="twig" /> <argument type="service" id="twig" />
</service> </service>
<service id="nelmio_api_doc.controller.swagger" class="Nelmio\ApiDocBundle\Controller\DocumentationController">
<argument type="service" id="nelmio_api_doc.generator" />
</service>
<!-- Swagger Spec Generator --> <!-- Swagger Spec Generator -->
<service id="nelmio_api_doc.generator" class="Nelmio\ApiDocBundle\ApiDocGenerator"> <service id="nelmio_api_doc.generator" class="Nelmio\ApiDocBundle\ApiDocGenerator">
<argument type="collection" /> <!-- Describers --> <argument type="collection" /> <!-- Describers -->

View File

@ -70,7 +70,6 @@ final class ModelRegister
continue; continue;
} }
$annotation->merge([new $annotationClass([ $annotation->merge([new $annotationClass([
'ref' => $this->modelRegistry->register(new Model($this->createType($model->type), $model->groups)), 'ref' => $this->modelRegistry->register(new Model($this->createType($model->type), $model->groups)),
])]); ])]);

View File

@ -15,8 +15,8 @@ use FOS\RestBundle\Controller\Annotations\QueryParam;
use FOS\RestBundle\Controller\Annotations\RequestParam; use FOS\RestBundle\Controller\Annotations\RequestParam;
use Nelmio\ApiDocBundle\Annotation\Model; use Nelmio\ApiDocBundle\Annotation\Model;
use Nelmio\ApiDocBundle\Annotation\Operation; 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\Article;
use Nelmio\ApiDocBundle\Tests\Functional\Entity\User;
use Nelmio\ApiDocBundle\Tests\Functional\Form\DummyType; use Nelmio\ApiDocBundle\Tests\Functional\Form\DummyType;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route; use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
use Swagger\Annotations as SWG; use Swagger\Annotations as SWG;
@ -134,6 +134,5 @@ class ApiController
*/ */
public function formAction() public function formAction()
{ {
} }
} }

View File

@ -1,11 +1,19 @@
<?php <?php
/*
* This file is part of the NelmioApiDocBundle package.
*
* (c) Nelmio
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Nelmio\ApiDocBundle\Tests\Functional\Form; namespace Nelmio\ApiDocBundle\Tests\Functional\Form;
use Nelmio\ApiDocBundle\Annotation\Model;
use Swagger\Annotations\Definition;
use Symfony\Component\Form\AbstractType; use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Form\Extension\Core\Type\ChoiceType; use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Form\FormBuilderInterface; use Symfony\Component\Form\FormBuilderInterface;
class DummyType extends AbstractType class DummyType extends AbstractType

View File

@ -12,7 +12,6 @@
namespace Nelmio\ApiDocBundle\Tests\Functional; namespace Nelmio\ApiDocBundle\Tests\Functional;
use EXSyst\Component\Swagger\Operation; use EXSyst\Component\Swagger\Operation;
use EXSyst\Component\Swagger\Schema;
use EXSyst\Component\Swagger\Tag; use EXSyst\Component\Swagger\Tag;
class FunctionalTest extends WebTestCase class FunctionalTest extends WebTestCase
@ -178,37 +177,9 @@ class FunctionalTest extends WebTestCase
'foo' => [ 'foo' => [
'type' => 'string', 'type' => 'string',
'enum' => ['male', 'female'], 'enum' => ['male', 'female'],
] ],
], ],
'required' => ['foo'], 'required' => ['foo'],
], $this->getModel('DummyType')->toArray()); ], $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);
}
} }

View File

@ -23,7 +23,18 @@ class SwaggerUiTest extends WebTestCase
$this->assertEquals('text/html; charset=UTF-8', $response->headers->get('Content-Type')); $this->assertEquals('text/html; charset=UTF-8', $response->headers->get('Content-Type'));
$swaggerUiSpec = json_decode($crawler->filterXPath('//script[@id="swagger-data"]')->text(), true); $swaggerUiSpec = json_decode($crawler->filterXPath('//script[@id="swagger-data"]')->text(), true);
$appSpec = $client->getContainer()->get('nelmio_api_doc.generator')->generate()->toArray(); $this->assertEquals($this->getSwaggerDefinition()->toArray(), $swaggerUiSpec['spec']);
$this->assertEquals($appSpec, $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));
} }
} }

View File

@ -14,7 +14,6 @@ namespace Nelmio\ApiDocBundle\Tests\Functional;
use ApiPlatform\Core\Bridge\Symfony\Bundle\ApiPlatformBundle; use ApiPlatform\Core\Bridge\Symfony\Bundle\ApiPlatformBundle;
use FOS\RestBundle\FOSRestBundle; use FOS\RestBundle\FOSRestBundle;
use Nelmio\ApiDocBundle\NelmioApiDocBundle; use Nelmio\ApiDocBundle\NelmioApiDocBundle;
use Nelmio\ApiDocBundle\Tests\Functional\TestBundle;
use Sensio\Bundle\FrameworkExtraBundle\SensioFrameworkExtraBundle; use Sensio\Bundle\FrameworkExtraBundle\SensioFrameworkExtraBundle;
use Symfony\Bundle\FrameworkBundle\FrameworkBundle; use Symfony\Bundle\FrameworkBundle\FrameworkBundle;
use Symfony\Bundle\FrameworkBundle\Kernel\MicroKernelTrait; use Symfony\Bundle\FrameworkBundle\Kernel\MicroKernelTrait;
@ -52,6 +51,8 @@ class TestKernel extends Kernel
$routes->import(__DIR__.'/Controller/', '/', 'annotation'); $routes->import(__DIR__.'/Controller/', '/', 'annotation');
$routes->import('', '/api', 'api_platform'); $routes->import('', '/api', 'api_platform');
$routes->import('@NelmioApiDocBundle/Resources/config/routing/swaggerui.xml', '/docs'); $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' => '^/', 'path' => '^/',
'fallback_format' => 'json', 'fallback_format' => 'json',
] ],
], ],
], ],
]); ]);

View File

@ -11,8 +11,8 @@
namespace Nelmio\ApiDocBundle\Tests\Functional; namespace Nelmio\ApiDocBundle\Tests\Functional;
use EXSyst\Component\Swagger\Operation;
use Nelmio\ApiDocBundle\Tests\Functional\TestKernel; use EXSyst\Component\Swagger\Schema;
use Symfony\Bundle\FrameworkBundle\Test\WebTestCase as BaseWebTestCase; use Symfony\Bundle\FrameworkBundle\Test\WebTestCase as BaseWebTestCase;
class WebTestCase extends BaseWebTestCase class WebTestCase extends BaseWebTestCase
@ -24,4 +24,32 @@ class WebTestCase extends BaseWebTestCase
{ {
return TestKernel::class; 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);
}
} }

View File

@ -13,9 +13,9 @@ namespace Nelmio\ApiDocBundle\Tests\Model;
use EXSyst\Component\Swagger\Schema; use EXSyst\Component\Swagger\Schema;
use EXSyst\Component\Swagger\Swagger; use EXSyst\Component\Swagger\Swagger;
use PHPUnit\Framework\TestCase;
use Nelmio\ApiDocBundle\Model\Model; use Nelmio\ApiDocBundle\Model\Model;
use Nelmio\ApiDocBundle\Model\ModelRegistry; use Nelmio\ApiDocBundle\Model\ModelRegistry;
use PHPUnit\Framework\TestCase;
use Symfony\Component\PropertyInfo\Type; use Symfony\Component\PropertyInfo\Type;
class ModelRegistryTest extends TestCase class ModelRegistryTest extends TestCase

View File

@ -12,12 +12,12 @@
namespace Nelmio\ApiDocBundle\Tests\Routing; namespace Nelmio\ApiDocBundle\Tests\Routing;
use Nelmio\ApiDocBundle\Routing\FilteredRouteCollectionBuilder; use Nelmio\ApiDocBundle\Routing\FilteredRouteCollectionBuilder;
use PHPUnit\Framework\TestCase;
use Symfony\Component\Routing\Route; use Symfony\Component\Routing\Route;
use Symfony\Component\Routing\RouteCollection; use Symfony\Component\Routing\RouteCollection;
use PHPUnit\Framework\TestCase;
/** /**
* Tests for FilteredRouteCollectionBuilder class * Tests for FilteredRouteCollectionBuilder class.
*/ */
class FilteredRouteCollectionBuilderTest extends TestCase class FilteredRouteCollectionBuilderTest extends TestCase
{ {