Make the document generated valid

This commit is contained in:
Ener-Getick 2016-08-01 19:58:57 +02:00
parent d1adffe41f
commit 02601125bd
No known key found for this signature in database
GPG Key ID: 9E5D2DB67BF054DD
18 changed files with 183 additions and 27 deletions

View File

@ -12,7 +12,7 @@
namespace EXSyst\Bundle\ApiDocBundle; namespace EXSyst\Bundle\ApiDocBundle;
use EXSyst\Bundle\ApiDocBundle\Describer\DescriberInterface; use EXSyst\Bundle\ApiDocBundle\Describer\DescriberInterface;
use EXSyst\Swagger\Swagger; use EXSyst\Component\Swagger\Swagger;
class ApiDocGenerator class ApiDocGenerator
{ {

View File

@ -0,0 +1,64 @@
<?php
/*
* This file is part of the ApiDocBundle package.
*
* (c) EXSyst
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace EXSyst\Bundle\ApiDocBundle\Describer;
use EXSyst\Component\Swagger\Swagger;
/**
* Makes the swagger documentation valid even if there are missing fields.
*
* @author Ener-Getick <egetick@gmail.com>
*/
class DefaultDescriber implements DescriberInterface
{
public function describe(Swagger $api)
{
// Info
$info = $api->getInfo();
if (null === $info->getTitle()) {
$info->setTitle('');
}
if (null === $info->getVersion()) {
$info->setVersion('0.0.0');
}
// Paths
$paths = $api->getPaths();
foreach ($paths as $uri => $path) {
// Path Parameters
preg_match_all('/\{(.+)\}/SU', $uri, $matches);
$pathParameters = $matches[1];
foreach ($path->getMethods() as $method) {
$operation = $path->getOperation($method);
$parameters = $operation->getParameters();
// Default Path Parameters
foreach ($pathParameters as $pathParameter) {
if ($parameters->has($pathParameter, 'path')) {
continue;
}
$parameters->get($pathParameter, 'path')
->setRequired(true)
->setType('string');
}
// Default Response
if (0 === iterator_count($operation->getResponses())) {
$defaultResponse = $operation->getResponses()->get('default');
$defaultResponse->setDescription('');
}
}
}
}
}

View File

@ -11,7 +11,7 @@
namespace EXSyst\Bundle\ApiDocBundle\Describer; namespace EXSyst\Bundle\ApiDocBundle\Describer;
use EXSyst\Swagger\Swagger; use EXSyst\Component\Swagger\Swagger;
interface DescriberInterface interface DescriberInterface
{ {

View File

@ -11,7 +11,7 @@
namespace EXSyst\Bundle\ApiDocBundle\Describer; namespace EXSyst\Bundle\ApiDocBundle\Describer;
use EXSyst\Swagger\Swagger; use EXSyst\Component\Swagger\Swagger;
class ExternalDocDescriber implements DescriberInterface class ExternalDocDescriber implements DescriberInterface
{ {

View File

@ -13,7 +13,7 @@ namespace EXSyst\Bundle\ApiDocBundle\Describer;
use Doctrine\Common\Util\ClassUtils; use Doctrine\Common\Util\ClassUtils;
use EXSyst\Bundle\ApiDocBundle\RouteDescriber\RouteDescriberInterface; use EXSyst\Bundle\ApiDocBundle\RouteDescriber\RouteDescriberInterface;
use EXSyst\Swagger\Swagger; use EXSyst\Component\Swagger\Swagger;
use Symfony\Bundle\FrameworkBundle\Controller\ControllerNameParser; use Symfony\Bundle\FrameworkBundle\Controller\ControllerNameParser;
use Symfony\Component\DependencyInjection\ContainerInterface; use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\Routing\Route; use Symfony\Component\Routing\Route;

View File

@ -16,9 +16,22 @@ class SwaggerPhpDescriber extends ExternalDocDescriber
public function __construct(string $projectPath, bool $overwrite = false) public function __construct(string $projectPath, bool $overwrite = false)
{ {
parent::__construct(function () use ($projectPath) { parent::__construct(function () use ($projectPath) {
$annotation = \Swagger\scan($projectPath); // Catch notices as the documentation can be completed by other describers
$prevHandler = set_error_handler(function ($type, $message, $file, $line, $context) use (&$prevHandler) {
if (E_USER_NOTICE === $type || E_USER_WARNING === $type) {
return;
}
return json_decode(json_encode($annotation)); return null !== $prevHandler && call_user_func($prevHandler, $type, $message, $file, $line, $context);
});
try {
$annotation = \Swagger\scan($projectPath);
return json_decode(json_encode($annotation));
} finally {
restore_error_handler();
}
}, $overwrite); }, $overwrite);
} }
} }

View File

@ -24,6 +24,10 @@
<tag name="exsyst_api_doc.describer" priority="-100" /> <tag name="exsyst_api_doc.describer" priority="-100" />
</service> </service>
<service id="exsyst_api_doc.describers.default" class="EXSyst\Bundle\ApiDocBundle\Describer\DefaultDescriber" public="false">
<tag name="exsyst_api_doc.describer" priority="-1000" />
</service>
<!-- Routing Extractors --> <!-- Routing Extractors -->
<service id="exsyst_api_doc.route_describers.route_metadata" class="EXSyst\Bundle\ApiDocBundle\RouteDescriber\RouteMetadataDescriber" public="false"> <service id="exsyst_api_doc.route_describers.route_metadata" class="EXSyst\Bundle\ApiDocBundle\RouteDescriber\RouteMetadataDescriber" public="false">
<tag name="exsyst_api_doc.route_describer" priority="-50" /> <tag name="exsyst_api_doc.route_describer" priority="-50" />

View File

@ -12,8 +12,8 @@
namespace EXSyst\Bundle\ApiDocBundle\RouteDescriber; namespace EXSyst\Bundle\ApiDocBundle\RouteDescriber;
use Doctrine\Common\Annotations\Reader; use Doctrine\Common\Annotations\Reader;
use EXSyst\Swagger\Parameter; use EXSyst\Component\Swagger\Parameter;
use EXSyst\Swagger\Swagger; use EXSyst\Component\Swagger\Swagger;
use Nelmio\ApiDocBundle\Annotation\ApiDoc; use Nelmio\ApiDocBundle\Annotation\ApiDoc;
use Symfony\Component\Routing\Route; use Symfony\Component\Routing\Route;

View File

@ -11,7 +11,7 @@
namespace EXSyst\Bundle\ApiDocBundle\RouteDescriber; namespace EXSyst\Bundle\ApiDocBundle\RouteDescriber;
use EXSyst\Swagger\Swagger; use EXSyst\Component\Swagger\Swagger;
use phpDocumentor\Reflection\DocBlockFactory; use phpDocumentor\Reflection\DocBlockFactory;
use phpDocumentor\Reflection\DocBlockFactoryInterface; use phpDocumentor\Reflection\DocBlockFactoryInterface;
use Symfony\Component\Routing\Route; use Symfony\Component\Routing\Route;
@ -48,10 +48,14 @@ class PhpDocDescriber implements RouteDescriberInterface
if (null !== $docBlock) { if (null !== $docBlock) {
$operation->setSummary($docBlock->getSummary()); $operation->setSummary($docBlock->getSummary());
$operation->setDescription((string) $docBlock->getDescription()); $operation->setDescription((string) $docBlock->getDescription());
$operation->setDeprecated($operation->getDeprecated() || $docBlock->hasTag('deprecated')); if ($docBlock->hasTag('deprecated')) {
$operation->setDeprecated(true);
}
} }
if (null !== $classDocBlock) { if (null !== $classDocBlock) {
$operation->setDeprecated($operation->getDeprecated() || $classDocBlock->hasTag('deprecated')); if ($classDocBlock->hasTag('deprecated')) {
$operation->setDeprecated(true);
}
} }
} }
} }

View File

@ -11,7 +11,7 @@
namespace EXSyst\Bundle\ApiDocBundle\RouteDescriber; namespace EXSyst\Bundle\ApiDocBundle\RouteDescriber;
use EXSyst\Swagger\Swagger; use EXSyst\Component\Swagger\Swagger;
use Symfony\Component\Routing\Route; use Symfony\Component\Routing\Route;
interface RouteDescriberInterface interface RouteDescriberInterface

View File

@ -11,8 +11,8 @@
namespace EXSyst\Bundle\ApiDocBundle\RouteDescriber; namespace EXSyst\Bundle\ApiDocBundle\RouteDescriber;
use EXSyst\Swagger\Operation; use EXSyst\Component\Swagger\Operation;
use EXSyst\Swagger\Swagger; use EXSyst\Component\Swagger\Swagger;
use Symfony\Component\Routing\Route; use Symfony\Component\Routing\Route;
/** /**
@ -27,7 +27,7 @@ trait RouteDescriberTrait
*/ */
private function getOperations(Swagger $api, Route $route) private function getOperations(Swagger $api, Route $route)
{ {
$path = $api->getPaths()->get($route->getPath()); $path = $api->getPaths()->get($this->normalizePath($route->getPath()));
$methods = $route->getMethods() ?: Swagger::$METHODS; $methods = $route->getMethods() ?: Swagger::$METHODS;
foreach ($methods as $method) { foreach ($methods as $method) {
$method = strtolower($method); $method = strtolower($method);
@ -40,4 +40,13 @@ trait RouteDescriberTrait
return $operations; return $operations;
} }
private function normalizePath(string $path)
{
if (substr($path, -10) === '.{_format}') {
$path = substr($path, 0, -10);
}
return $path;
}
} }

View File

@ -11,7 +11,7 @@
namespace EXSyst\Bundle\ApiDocBundle\RouteDescriber; namespace EXSyst\Bundle\ApiDocBundle\RouteDescriber;
use EXSyst\Swagger\Swagger; use EXSyst\Component\Swagger\Swagger;
use Symfony\Component\Routing\Route; use Symfony\Component\Routing\Route;
class RouteMetadataDescriber implements RouteDescriberInterface class RouteMetadataDescriber implements RouteDescriberInterface

View File

@ -0,0 +1,27 @@
<?php
/*
* This file is part of the ApiDocBundle package.
*
* (c) EXSyst
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace EXSyst\Bundle\ApiDocBundle\Tests\Describer;
use EXSyst\Component\Swagger\Swagger;
abstract class AbstractDescriberTest extends \PHPUnit_Framework_TestCase
{
protected $describer;
protected function getSwaggerDoc(): Swagger
{
$api = new Swagger();
$this->describer->describe($api);
return $api;
}
}

View File

@ -0,0 +1,32 @@
<?php
/*
* This file is part of the ApiDocBundle package.
*
* (c) EXSyst
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace EXSyst\Bundle\ApiDocBundle\Tests\Describer;
use EXSyst\Bundle\ApiDocBundle\Describer\SwaggerPhpDescriber;
use EXSyst\Component\Swagger\Swagger;
class SwaggerPhpDescriberTest extends AbstractDescriberTest
{
public function testDescribe()
{
$api = $this->getSwaggerDoc();
$info = $api->getInfo();
$this->assertEquals('My Awesome App', $info->getTitle());
$this->assertEquals('1.3', $info->getVersion());
}
protected function setUp()
{
$this->describer = new SwaggerPhpDescriber(__DIR__.'/../Fixtures');
}
}

View File

@ -24,7 +24,7 @@ class ApiController
} }
/** /**
* @Route("/nelmio", methods={"POST"}) * @Route("/nelmio/{foo}", methods={"POST"})
* @ApiDoc( * @ApiDoc(
* description="This action is described." * description="This action is described."
* ) * )

View File

@ -22,7 +22,7 @@ class FunctionalTest extends WebTestCase
$this->assertEquals(['https'], $operation->getSchemes()); $this->assertEquals(['https'], $operation->getSchemes());
$this->assertEmpty($operation->getSummary()); $this->assertEmpty($operation->getSummary());
$this->assertEmpty($operation->getDescription()); $this->assertEmpty($operation->getDescription());
$this->assertFalse($operation->getDeprecated()); $this->assertNull($operation->getDeprecated());
$parameters = $operation->getParameters(); $parameters = $operation->getParameters();
$this->assertTrue($parameters->has('user', 'path')); $this->assertTrue($parameters->has('user', 'path'));
@ -35,10 +35,14 @@ class FunctionalTest extends WebTestCase
public function testNelmioAction() public function testNelmioAction()
{ {
$operation = $this->getOperation('/nelmio', 'post'); $operation = $this->getOperation('/nelmio/{foo}', 'post');
$this->assertEquals('This action is described.', $operation->getDescription()); $this->assertEquals('This action is described.', $operation->getDescription());
$this->assertFalse($operation->getDeprecated()); $this->assertFalse($operation->getDeprecated());
$foo = $operation->getParameters()->get('foo', 'path');
$this->assertTrue($foo->getRequired());
$this->assertEquals('string', $foo->getType());
} }
public function testDeprecatedAction() public function testDeprecatedAction()
@ -50,13 +54,12 @@ class FunctionalTest extends WebTestCase
$this->assertTrue($operation->getDeprecated()); $this->assertTrue($operation->getDeprecated());
} }
public function testSwaggerPhpInfo() public function testApiPlatform()
{ {
$api = $this->getSwaggerDefinition(); $operation = $this->getOperation('/api/dummies', 'get');
$info = $api->getInfo(); $operation = $this->getOperation('/api/foo', 'get');
$operation = $this->getOperation('/api/foo', 'post');
$this->assertEquals('My Awesome App', $info->getTitle()); $operation = $this->getOperation('/api/dummies/{id}', 'get');
$this->assertEquals('1.3', $info->getVersion());
} }
private function getSwaggerDefinition() private function getSwaggerDefinition()

View File

@ -11,7 +11,7 @@
"require": { "require": {
"php": "^7.0", "php": "^7.0",
"symfony/framework-bundle": "^3.2", "symfony/framework-bundle": "^3.2",
"exsyst/swagger": "~0.1" "exsyst/swagger": "dev-master"
}, },
"require-dev": { "require-dev": {
"symfony/validator": "^3.2", "symfony/validator": "^3.2",
@ -24,7 +24,7 @@
"phpdocumentor/reflection-docblock": "^3.1", "phpdocumentor/reflection-docblock": "^3.1",
"phpunit/phpunit": "^5.4", "phpunit/phpunit": "^5.4",
"zircote/swagger-php": "^2.0", "zircote/swagger-php": "^2.0",
"api-platform/core": "^2.0" "api-platform/core": "dev-master"
}, },
"suggest": { "suggest": {
"nelmio/api-doc-bundle": "For using the ApiDoc annotation.", "nelmio/api-doc-bundle": "For using the ApiDoc annotation.",