Skip to content

Commit

Permalink
OpenApi 3 Support (#1623)
Browse files Browse the repository at this point in the history
* Initial pass for OA3 upgrade

* Fix Util Tests

* Fix first batch of Unit Tests. Up to Model

* Another batch of fixed tests

* Update annotations

* Convert Model & Property Describers

* Update tests, Fix RouteDescribers, FIx additional bugs

* Another batch of updates

* Another batch of fixed Functional Tests

* Fix FunctionalTest tests

* Fix Bazinga Tests

* FIx FOS Rest

* Fix JMS TEsts & describers

* Fix all Tests

* Fix few stuff from own CR

* CS Fixes

* CS Fixes 2

* CS Fixes 3

* CS Fixes 4

* Remove collection bug

* Updates after first CRs

* CS

* Drop support for SF3

* Update the docs

* Add an upgrade guide

* misc doc fixes

* Configurable media types

* Code Style Fixes

* Don't use ::$ref for @response and @RequestBody

* Fix upgrading guide

* Fix OA case

Co-authored-by: Filip Benčo <[email protected]>
Co-authored-by: Guilhem Niot <[email protected]>
Co-authored-by: Mantis Development <[email protected]>
  • Loading branch information
4 people authored May 28, 2020
1 parent 2a78b42 commit 78664ef
Show file tree
Hide file tree
Showing 81 changed files with 2,848 additions and 1,332 deletions.
2 changes: 0 additions & 2 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,6 @@ matrix:
include:
- php: 7.1
env: COMPOSER_FLAGS="--prefer-lowest"
- php: 7.2
env: SYMFONY_VERSION=^3.4
- php: 7.3
env: SYMFONY_VERSION=^4.0
- php: 7.3
Expand Down
6 changes: 3 additions & 3 deletions Annotation/Model.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@

namespace Nelmio\ApiDocBundle\Annotation;

use Swagger\Annotations\AbstractAnnotation;
use OpenApi\Annotations\AbstractAnnotation;
use OpenApi\Annotations\Parameter;

/**
* @Annotation
Expand All @@ -28,8 +29,7 @@ final class Model extends AbstractAnnotation
public static $_required = ['type'];

public static $_parents = [
'Swagger\Annotations\Parameter',
'Swagger\Annotations\Response',
Parameter::class,
];

/**
Expand Down
2 changes: 1 addition & 1 deletion Annotation/Operation.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@

namespace Nelmio\ApiDocBundle\Annotation;

use Swagger\Annotations\Operation as BaseOperation;
use OpenApi\Annotations\Operation as BaseOperation;

/**
* @Annotation
Expand Down
2 changes: 1 addition & 1 deletion Annotation/Security.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@

namespace Nelmio\ApiDocBundle\Annotation;

use Swagger\Annotations\AbstractAnnotation;
use OpenApi\Annotations\AbstractAnnotation;

/**
* @Annotation
Expand Down
34 changes: 21 additions & 13 deletions ApiDocGenerator.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,23 +11,31 @@

namespace Nelmio\ApiDocBundle;

use EXSyst\Component\Swagger\Swagger;
use Nelmio\ApiDocBundle\Describer\DescriberInterface;
use Nelmio\ApiDocBundle\Describer\ModelRegistryAwareInterface;
use Nelmio\ApiDocBundle\Model\ModelRegistry;
use Nelmio\ApiDocBundle\ModelDescriber\ModelDescriberInterface;
use OpenApi\Annotations\OpenApi;
use Psr\Cache\CacheItemPoolInterface;

final class ApiDocGenerator
{
private $swagger;
/** @var OpenApi */
private $openApi;

/** @var iterable|DescriberInterface[] */
private $describers;

/** @var iterable|ModelDescriberInterface[] */
private $modelDescribers;

/** @var CacheItemPoolInterface|null */
private $cacheItemPool;

/** @var string|null */
private $cacheItemId;

/** @var string[] */
private $alternativeNames = [];

/**
Expand All @@ -47,34 +55,34 @@ public function setAlternativeNames(array $alternativeNames)
$this->alternativeNames = $alternativeNames;
}

public function generate(): Swagger
public function generate(): OpenApi
{
if (null !== $this->swagger) {
return $this->swagger;
if (null !== $this->openApi) {
return $this->openApi;
}

if ($this->cacheItemPool) {
$item = $this->cacheItemPool->getItem($this->cacheItemId ?? 'swagger_doc');
$item = $this->cacheItemPool->getItem($this->cacheItemId ?? 'openapi_doc');
if ($item->isHit()) {
return $this->swagger = $item->get();
return $this->openApi = $item->get();
}
}

$this->swagger = new Swagger();
$modelRegistry = new ModelRegistry($this->modelDescribers, $this->swagger, $this->alternativeNames);
$this->openApi = new OpenApi([]);
$modelRegistry = new ModelRegistry($this->modelDescribers, $this->openApi, $this->alternativeNames);
foreach ($this->describers as $describer) {
if ($describer instanceof ModelRegistryAwareInterface) {
$describer->setModelRegistry($modelRegistry);
}

$describer->describe($this->swagger);
$describer->describe($this->openApi);
}
$modelRegistry->registerDefinitions();
$modelRegistry->registerSchemas();

if (isset($item)) {
$this->cacheItemPool->save($item->set($this->swagger));
$this->cacheItemPool->save($item->set($this->openApi));
}

return $this->swagger;
return $this->openApi;
}
}
10 changes: 7 additions & 3 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,10 +1,14 @@
CHANGELOG
=========

3.3.0 (unreleased)
------------------
4.0.0
-----
* Added support of OpenAPI 3.0. The internals were completely reworked and this version introduces BC breaks.

3.3.0
-----

* Usage of Google Fonts was removed. System fonts `serif` / `sans` will be used instead.
* Usage of Google Fonts was removed. System fonts `serif` / `sans` will be used instead.
This can lead to a different look on different operating systems.
You can [re-add Google Fonts again manually by overriding the template](https://symfony.com/doc/current/bundles/NelmioApiDocBundle/faq.html#re-add-google-fonts).

Expand Down
11 changes: 5 additions & 6 deletions Controller/DocumentationController.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@
namespace Nelmio\ApiDocBundle\Controller;

use Nelmio\ApiDocBundle\ApiDocGenerator;
use OpenApi\Annotations\OpenApi;
use OpenApi\Annotations\Server;
use Psr\Container\ContainerInterface;
use Symfony\Component\DependencyInjection\ServiceLocator;
use Symfony\Component\HttpFoundation\JsonResponse;
Expand Down Expand Up @@ -47,14 +49,11 @@ public function __invoke(Request $request, $area = 'default')
throw new BadRequestHttpException(sprintf('Area "%s" is not supported as it isn\'t defined in config.', $area));
}

$spec = $this->generatorLocator->get($area)->generate()->toArray();
/** @var OpenApi $spec */
$spec = $this->generatorLocator->get($area)->generate();

if ('' !== $request->getBaseUrl()) {
$spec['basePath'] = $request->getBaseUrl();
}

if (empty($spec['host'])) {
$spec['host'] = $request->getHost();
$spec->servers = [new Server(['url' => $request->getSchemeAndHttpHost().$request->getBaseUrl()])];
}

return new JsonResponse($spec);
Expand Down
13 changes: 10 additions & 3 deletions Controller/SwaggerUiController.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@
namespace Nelmio\ApiDocBundle\Controller;

use Nelmio\ApiDocBundle\ApiDocGenerator;
use OpenApi\Annotations\OpenApi;
use OpenApi\Annotations\Server;
use Psr\Container\ContainerInterface;
use Symfony\Component\DependencyInjection\ServiceLocator;
use Symfony\Component\HttpFoundation\Request;
Expand Down Expand Up @@ -60,13 +62,18 @@ public function __invoke(Request $request, $area = 'default')
throw new BadRequestHttpException(sprintf('Area "%s" is not supported as it isn\'t defined in config.%s', $area, $advice));
}

$spec = $this->generatorLocator->get($area)->generate()->toArray();
/** @var OpenApi $spec */
$spec = $this->generatorLocator->get($area)->generate();

if ('' !== $request->getBaseUrl()) {
$spec['basePath'] = $request->getBaseUrl();
$spec->servers = [new Server(['url' => $request->getSchemeAndHttpHost().$request->getBaseUrl()])];
}

return new Response(
$this->twig->render('@NelmioApiDoc/SwaggerUi/index.html.twig', ['swagger_data' => ['spec' => $spec]]),
$this->twig->render(
'@NelmioApiDoc/SwaggerUi/index.html.twig',
['swagger_data' => ['spec' => json_decode($spec->toJson(), true)]]
),
Response::HTTP_OK,
['Content-Type' => 'text/html']
);
Expand Down
5 changes: 5 additions & 0 deletions DependencyInjection/Configuration.php
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,11 @@ public function getConfigTreeBuilder()
->example(['info' => ['title' => 'My App']])
->prototype('variable')->end()
->end()
->arrayNode('media_types')
->info('List of enabled Media Types')
->defaultValue(['json'])
->prototype('scalar')->end()
->end()
->arrayNode('areas')
->info('Filter the routes that are documented')
->defaultValue(
Expand Down
13 changes: 10 additions & 3 deletions DependencyInjection/NelmioApiDocExtension.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,8 @@
use JMS\Serializer\Visitor\SerializationVisitorInterface;
use Nelmio\ApiDocBundle\ApiDocGenerator;
use Nelmio\ApiDocBundle\Describer\ExternalDocDescriber;
use Nelmio\ApiDocBundle\Describer\OpenApiPhpDescriber;
use Nelmio\ApiDocBundle\Describer\RouteDescriber;
use Nelmio\ApiDocBundle\Describer\SwaggerPhpDescriber;
use Nelmio\ApiDocBundle\ModelDescriber\BazingaHateoasModelDescriber;
use Nelmio\ApiDocBundle\ModelDescriber\JMSModelDescriber;
use Nelmio\ApiDocBundle\Routing\FilteredRouteCollectionBuilder;
Expand Down Expand Up @@ -82,13 +82,14 @@ public function load(array $configs, ContainerBuilder $container)
])
->addTag(sprintf('nelmio_api_doc.describer.%s', $area), ['priority' => -400]);

$container->register(sprintf('nelmio_api_doc.describers.swagger_php.%s', $area), SwaggerPhpDescriber::class)
$container->register(sprintf('nelmio_api_doc.describers.openapi_php.%s', $area), OpenApiPhpDescriber::class)
->setPublic(false)
->setArguments([
new Reference(sprintf('nelmio_api_doc.routes.%s', $area)),
new Reference('nelmio_api_doc.controller_reflector'),
new Reference('annotation_reader'),
new Reference('logger'),
$config['media_types'],
])
->addTag(sprintf('nelmio_api_doc.describer.%s', $area), ['priority' => -200]);

Expand Down Expand Up @@ -135,11 +136,16 @@ public function load(array $configs, ContainerBuilder $container)
array_map(function ($area) { return new Reference(sprintf('nelmio_api_doc.generator.%s', $area)); }, array_keys($config['areas']))
));

$container->getDefinition('nelmio_api_doc.model_describers.object')
->setArgument(3, $config['media_types']);

// Import services needed for each library
$loader->load('php_doc.xml');

if (interface_exists(ParamInterface::class)) {
$loader->load('fos_rest.xml');
$container->getDefinition('nelmio_api_doc.route_describers.fos_rest')
->setArgument(1, $config['media_types']);
}

// ApiPlatform support
Expand All @@ -159,8 +165,9 @@ public function load(array $configs, ContainerBuilder $container)
->setPublic(false)
->setArguments([
new Reference('jms_serializer.metadata_factory'),
$jmsNamingStrategy,
new Reference('annotation_reader'),
$config['media_types'],
$jmsNamingStrategy,
])
->addTag('nelmio_api_doc.model_describer', ['priority' => 50]);

Expand Down
8 changes: 7 additions & 1 deletion Describer/ApiPlatformDescriber.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
namespace Nelmio\ApiDocBundle\Describer;

use ApiPlatform\Core\Documentation\Documentation;
use ApiPlatform\Core\Swagger\Serializer\DocumentationNormalizer;
use Symfony\Component\Serializer\Normalizer\NormalizerInterface;

final class ApiPlatformDescriber extends ExternalDocDescriber
Expand All @@ -23,7 +24,12 @@ public function __construct(Documentation $documentation, NormalizerInterface $n
}

parent::__construct(function () use ($documentation, $normalizer) {
$documentation = (array) $normalizer->normalize($documentation);
$documentation = (array) $normalizer->normalize(
$documentation,
null,
[DocumentationNormalizer::SPEC_VERSION => 3]
);

unset($documentation['basePath']);

return $documentation;
Expand Down
34 changes: 18 additions & 16 deletions Describer/DefaultDescriber.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@

namespace Nelmio\ApiDocBundle\Describer;

use EXSyst\Component\Swagger\Swagger;
use Nelmio\ApiDocBundle\OpenApiPhp\Util;
use OpenApi\Annotations as OA;

/**
* Makes the swagger documentation valid even if there are missing fields.
Expand All @@ -20,27 +21,28 @@
*/
final class DefaultDescriber implements DescriberInterface
{
public function describe(Swagger $api)
public function describe(OA\OpenApi $api)
{
// Info
$info = $api->getInfo();
if (null === $info->getTitle()) {
$info->setTitle('');
/** @var OA\Info $info */
$info = Util::getChild($api, OA\Info::class);
if (OA\UNDEFINED === $info->title) {
$info->title = '';
}
if (null === $info->getVersion()) {
$info->setVersion('0.0.0');
if (OA\UNDEFINED === $info->version) {
$info->version = '0.0.0';
}

// Paths
$paths = $api->getPaths();
foreach ($paths as $uri => $path) {
foreach ($path->getMethods() as $method) {
$operation = $path->getOperation($method);

// Default Response
if (0 === iterator_count($operation->getResponses())) {
$defaultResponse = $operation->getResponses()->get('default');
$defaultResponse->setDescription('');
$paths = OA\UNDEFINED === $api->paths ? [] : $api->paths;
foreach ($paths as $path) {
foreach (Util::OPERATIONS as $method) {
/** @var OA\Operation $operation */
$operation = $path->{$method};
if (OA\UNDEFINED !== $operation && null !== $operation && empty($operation->responses ?? [])) {
/** @var OA\Response $response */
$response = Util::getIndexedCollectionItem($operation, OA\Response::class, 'default');
$response->description = '';
}
}
}
Expand Down
4 changes: 2 additions & 2 deletions Describer/DescriberInterface.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,9 @@

namespace Nelmio\ApiDocBundle\Describer;

use EXSyst\Component\Swagger\Swagger;
use OpenApi\Annotations\OpenApi;

interface DescriberInterface
{
public function describe(Swagger $api);
public function describe(OpenApi $api);
}
10 changes: 7 additions & 3 deletions Describer/ExternalDocDescriber.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@

namespace Nelmio\ApiDocBundle\Describer;

use EXSyst\Component\Swagger\Swagger;
use Nelmio\ApiDocBundle\OpenApiPhp\Util;
use OpenApi\Annotations as OA;

class ExternalDocDescriber implements DescriberInterface
{
Expand All @@ -29,10 +30,13 @@ public function __construct($externalDoc, bool $overwrite = false)
$this->overwrite = $overwrite;
}

public function describe(Swagger $api)
public function describe(OA\OpenApi $api)
{
$externalDoc = $this->getExternalDoc();
$api->merge($externalDoc, $this->overwrite);

if (!empty($externalDoc)) {
Util::merge($api, $externalDoc, $this->overwrite);
}
}

private function getExternalDoc()
Expand Down
Loading

0 comments on commit 78664ef

Please sign in to comment.