mirror of
https://github.com/retailcrm/NelmioApiDocBundle.git
synced 2025-02-09 02:59:27 +03:00
Implement alternative naming system via configuration (#1312)
* implement alternative naming system via configuration * use strict comparison * test di configs * rever * test naming aliases are applied * set "default" as default area * test names are passed to generators * cs formatting * added extra check for built-int types * cs * added documentation about alternative names * allow to create the same alias in two different areas * document and test better aliasing strategy * specify that the last matching rule is used * Make last matching rule wins * Fix documentation
This commit is contained in:
parent
cf0857af64
commit
ab005c4129
@ -28,6 +28,8 @@ final class ApiDocGenerator
|
||||
|
||||
private $cacheItemPool;
|
||||
|
||||
private $alternativeNames = [];
|
||||
|
||||
/**
|
||||
* @param DescriberInterface[]|iterable $describers
|
||||
* @param ModelDescriberInterface[]|iterable $modelDescribers
|
||||
@ -40,6 +42,11 @@ final class ApiDocGenerator
|
||||
$this->cacheItemId = $cacheItemId;
|
||||
}
|
||||
|
||||
public function setAlternativeNames(array $alternativeNames)
|
||||
{
|
||||
$this->alternativeNames = $alternativeNames;
|
||||
}
|
||||
|
||||
public function generate(): Swagger
|
||||
{
|
||||
if (null !== $this->swagger) {
|
||||
@ -54,7 +61,7 @@ final class ApiDocGenerator
|
||||
}
|
||||
|
||||
$this->swagger = new Swagger();
|
||||
$modelRegistry = new ModelRegistry($this->modelDescribers, $this->swagger);
|
||||
$modelRegistry = new ModelRegistry($this->modelDescribers, $this->swagger, $this->alternativeNames);
|
||||
foreach ($this->describers as $describer) {
|
||||
if ($describer instanceof ModelRegistryAwareInterface) {
|
||||
$describer->setModelRegistry($modelRegistry);
|
||||
|
@ -85,6 +85,23 @@ final class Configuration implements ConfigurationInterface
|
||||
->children()
|
||||
->booleanNode('use_jms')->defaultFalse()->end()
|
||||
->end()
|
||||
->children()
|
||||
->arrayNode('names')
|
||||
->prototype('array')
|
||||
->children()
|
||||
->scalarNode('alias')->isRequired()->end()
|
||||
->scalarNode('type')->isRequired()->end()
|
||||
->arrayNode('groups')
|
||||
->defaultValue([])
|
||||
->prototype('scalar')->end()
|
||||
->end()
|
||||
->arrayNode('areas')
|
||||
->defaultValue([])
|
||||
->prototype('scalar')->end()
|
||||
->end()
|
||||
->end()
|
||||
->end()
|
||||
->end()
|
||||
->end()
|
||||
->end();
|
||||
|
||||
|
@ -61,8 +61,11 @@ final class NelmioApiDocExtension extends Extension implements PrependExtensionI
|
||||
|
||||
$container->setParameter('nelmio_api_doc.areas', array_keys($config['areas']));
|
||||
foreach ($config['areas'] as $area => $areaConfig) {
|
||||
$nameAliases = $this->findNameAliases($config['models']['names'], $area);
|
||||
|
||||
$container->register(sprintf('nelmio_api_doc.generator.%s', $area), ApiDocGenerator::class)
|
||||
->setPublic(false)
|
||||
->addMethodCall('setAlternativeNames', [$nameAliases])
|
||||
->setArguments([
|
||||
new TaggedIteratorArgument(sprintf('nelmio_api_doc.describer.%s', $area)),
|
||||
new TaggedIteratorArgument('nelmio_api_doc.model_describer'),
|
||||
@ -152,4 +155,21 @@ final class NelmioApiDocExtension extends Extension implements PrependExtensionI
|
||||
// Import the base configuration
|
||||
$container->getDefinition('nelmio_api_doc.describers.config')->replaceArgument(0, $config['documentation']);
|
||||
}
|
||||
|
||||
private function findNameAliases(array $names, string $area): array
|
||||
{
|
||||
$nameAliases = array_filter($names, function (array $aliasInfo) use ($area) {
|
||||
return empty($aliasInfo['areas']) || in_array($area, $aliasInfo['areas'], true);
|
||||
});
|
||||
|
||||
$aliases = [];
|
||||
foreach ($nameAliases as $nameAlias) {
|
||||
$aliases[$nameAlias['alias']] = [
|
||||
'type' => $nameAlias['type'],
|
||||
'groups' => $nameAlias['groups'],
|
||||
];
|
||||
}
|
||||
|
||||
return $aliases;
|
||||
}
|
||||
}
|
||||
|
@ -19,6 +19,8 @@ use Symfony\Component\PropertyInfo\Type;
|
||||
|
||||
final class ModelRegistry
|
||||
{
|
||||
private $alternativeNames = [];
|
||||
|
||||
private $unregistered = [];
|
||||
|
||||
private $models = [];
|
||||
@ -34,10 +36,11 @@ final class ModelRegistry
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
public function __construct($modelDescribers, Swagger $api)
|
||||
public function __construct($modelDescribers, Swagger $api, array $alternativeNames = [])
|
||||
{
|
||||
$this->modelDescribers = $modelDescribers;
|
||||
$this->api = $api;
|
||||
$this->alternativeNames = array_reverse($alternativeNames); // last rule wins
|
||||
}
|
||||
|
||||
public function register(Model $model): string
|
||||
@ -95,7 +98,9 @@ final class ModelRegistry
|
||||
private function generateModelName(Model $model): string
|
||||
{
|
||||
$definitions = $this->api->getDefinitions();
|
||||
$base = $name = $this->getTypeShortName($model->getType());
|
||||
|
||||
$name = $base = $this->getAlternativeName($model) ?? $this->getTypeShortName($model->getType());
|
||||
|
||||
$i = 1;
|
||||
while ($definitions->has($name)) {
|
||||
++$i;
|
||||
@ -105,6 +110,27 @@ final class ModelRegistry
|
||||
return $name;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Model $model
|
||||
*
|
||||
* @return string|null
|
||||
*/
|
||||
private function getAlternativeName(Model $model)
|
||||
{
|
||||
$type = $model->getType();
|
||||
foreach ($this->alternativeNames as $alternativeName => $criteria) {
|
||||
if (
|
||||
Type::BUILTIN_TYPE_OBJECT === $type->getBuiltinType() &&
|
||||
$type->getClassName() === $criteria['type'] &&
|
||||
$criteria['groups'] == $model->getGroups()
|
||||
) {
|
||||
return $alternativeName;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private function getTypeShortName(Type $type): string
|
||||
{
|
||||
if (null !== $type->getCollectionValueType()) {
|
||||
|
28
Resources/doc/alternative_names.rst
Normal file
28
Resources/doc/alternative_names.rst
Normal file
@ -0,0 +1,28 @@
|
||||
Alternative Names
|
||||
=================
|
||||
|
||||
NelmioApiDoc generates automatically the model names but the ``nelmio_api_doc.models.names`` option allows to
|
||||
customize the names for some models.
|
||||
|
||||
Configuration
|
||||
-------------
|
||||
|
||||
You can define alternative names for each group and area combination, the last matching rule is used:
|
||||
|
||||
.. code-block:: yaml
|
||||
|
||||
nelmio_api_doc:
|
||||
models:
|
||||
names:
|
||||
- { alias: MainUser, type: App\Entity\User}
|
||||
- { alias: MainUser_light, type: App\Entity\User, groups: [light] }
|
||||
- { alias: MainUser_secret, type: App\Entity\User, areas: [private] }
|
||||
- { alias: MainUser, type: App\Entity\User, groups: [standard], areas: [private] }
|
||||
|
||||
|
||||
In this case the class ``App\Entity\User`` will be aliased into:
|
||||
|
||||
- ``MainUser`` when no more detailed rules are specified
|
||||
- ``MainUser_light`` when the group is equal to ``light``
|
||||
- ``MainUser_secret`` for the ``private`` area
|
||||
- ``MainUser`` for the ``private`` area when the group is equal to ``standard``
|
@ -302,6 +302,7 @@ If you need more complex features, take a look at:
|
||||
:maxdepth: 1
|
||||
|
||||
areas
|
||||
alternative_names
|
||||
faq
|
||||
|
||||
.. _`Symfony PropertyInfo component`: https://symfony.com/doc/current/components/property_info.html
|
||||
|
@ -37,6 +37,74 @@ class ConfigurationTest extends TestCase
|
||||
$this->assertSame($areas, $config['areas']);
|
||||
}
|
||||
|
||||
public function testAlternativeNames()
|
||||
{
|
||||
$processor = new Processor();
|
||||
$config = $processor->processConfiguration(new Configuration(), [[
|
||||
'models' => [
|
||||
'names' => [
|
||||
[
|
||||
'alias' => 'Foo1',
|
||||
'type' => 'App\Foo',
|
||||
'groups' => ['group'],
|
||||
],
|
||||
[
|
||||
'alias' => 'Foo2',
|
||||
'type' => 'App\Foo',
|
||||
'groups' => [],
|
||||
],
|
||||
[
|
||||
'alias' => 'Foo3',
|
||||
'type' => 'App\Foo',
|
||||
],
|
||||
[
|
||||
'alias' => 'Foo4',
|
||||
'type' => 'App\Foo',
|
||||
'groups' => ['group'],
|
||||
'areas' => ['internal'],
|
||||
],
|
||||
[
|
||||
'alias' => 'Foo1',
|
||||
'type' => 'App\Foo',
|
||||
'areas' => ['internal'],
|
||||
],
|
||||
],
|
||||
],
|
||||
]]);
|
||||
$this->assertEquals([
|
||||
[
|
||||
'alias' => 'Foo1',
|
||||
'type' => 'App\Foo',
|
||||
'groups' => ['group'],
|
||||
'areas' => [],
|
||||
],
|
||||
[
|
||||
'alias' => 'Foo2',
|
||||
'type' => 'App\Foo',
|
||||
'groups' => [],
|
||||
'areas' => [],
|
||||
],
|
||||
[
|
||||
'alias' => 'Foo3',
|
||||
'type' => 'App\Foo',
|
||||
'groups' => [],
|
||||
'areas' => [],
|
||||
],
|
||||
[
|
||||
'alias' => 'Foo4',
|
||||
'type' => 'App\\Foo',
|
||||
'groups' => ['group'],
|
||||
'areas' => ['internal'],
|
||||
],
|
||||
[
|
||||
'alias' => 'Foo1',
|
||||
'type' => 'App\\Foo',
|
||||
'groups' => [],
|
||||
'areas' => ['internal'],
|
||||
],
|
||||
], $config['models']['names']);
|
||||
}
|
||||
|
||||
/**
|
||||
* @group legacy
|
||||
* @expectedException \InvalidArgumentException
|
||||
|
@ -17,6 +17,74 @@ use Symfony\Component\DependencyInjection\ContainerBuilder;
|
||||
|
||||
class NelmioApiDocExtensionTest extends TestCase
|
||||
{
|
||||
public function testNameAliasesArePassedToModelRegistry()
|
||||
{
|
||||
$container = new ContainerBuilder();
|
||||
$container->setParameter('kernel.bundles', []);
|
||||
$extension = new NelmioApiDocExtension();
|
||||
$extension->load([[
|
||||
'areas' => [
|
||||
'default' => ['path_patterns' => ['/foo'], 'host_patterns' => []],
|
||||
'commercial' => ['path_patterns' => ['/internal'], 'host_patterns' => []],
|
||||
],
|
||||
'models' => [
|
||||
'names' => [
|
||||
[ // Test1 alias for all the areas
|
||||
'alias' => 'Test1',
|
||||
'type' => 'App\Test',
|
||||
],
|
||||
[ // Foo1 alias for all the areas
|
||||
'alias' => 'Foo1',
|
||||
'type' => 'App\Foo',
|
||||
],
|
||||
[ // overwrite Foo1 alias for all the commercial area
|
||||
'alias' => 'Foo1',
|
||||
'type' => 'App\Bar',
|
||||
'areas' => ['commercial'],
|
||||
],
|
||||
],
|
||||
],
|
||||
]], $container);
|
||||
|
||||
$methodCalls = $container->getDefinition('nelmio_api_doc.generator.default')->getMethodCalls();
|
||||
$foundMethodCall = false;
|
||||
foreach ($methodCalls as $methodCall) {
|
||||
if ('setAlternativeNames' === $methodCall[0]) {
|
||||
$this->assertEquals([
|
||||
'Foo1' => [
|
||||
'type' => 'App\\Foo',
|
||||
'groups' => [],
|
||||
],
|
||||
'Test1' => [
|
||||
'type' => 'App\\Test',
|
||||
'groups' => [],
|
||||
],
|
||||
], $methodCall[1][0]);
|
||||
$foundMethodCall = true;
|
||||
}
|
||||
}
|
||||
$this->assertTrue($foundMethodCall);
|
||||
|
||||
$methodCalls = $container->getDefinition('nelmio_api_doc.generator.commercial')->getMethodCalls();
|
||||
$foundMethodCall = false;
|
||||
foreach ($methodCalls as $methodCall) {
|
||||
if ('setAlternativeNames' === $methodCall[0]) {
|
||||
$this->assertEquals([
|
||||
'Foo1' => [
|
||||
'type' => 'App\\Bar',
|
||||
'groups' => [],
|
||||
],
|
||||
'Test1' => [
|
||||
'type' => 'App\\Test',
|
||||
'groups' => [],
|
||||
],
|
||||
], $methodCall[1][0]);
|
||||
$foundMethodCall = true;
|
||||
}
|
||||
}
|
||||
$this->assertTrue($foundMethodCall);
|
||||
}
|
||||
|
||||
public function testMergesRootKeysFromMultipleConfigurations()
|
||||
{
|
||||
$container = new ContainerBuilder();
|
||||
|
@ -11,7 +11,6 @@
|
||||
|
||||
namespace Nelmio\ApiDocBundle\Tests\Model;
|
||||
|
||||
use EXSyst\Component\Swagger\Schema;
|
||||
use EXSyst\Component\Swagger\Swagger;
|
||||
use Nelmio\ApiDocBundle\Model\Model;
|
||||
use Nelmio\ApiDocBundle\Model\ModelRegistry;
|
||||
@ -20,6 +19,79 @@ use Symfony\Component\PropertyInfo\Type;
|
||||
|
||||
class ModelRegistryTest extends TestCase
|
||||
{
|
||||
public function testNameAliasingNotAppliedForCollections()
|
||||
{
|
||||
$alternativeNames = [
|
||||
'Foo1' => [
|
||||
'type' => self::class,
|
||||
'groups' => ['group1'],
|
||||
],
|
||||
];
|
||||
$registry = new ModelRegistry([], new Swagger(), $alternativeNames);
|
||||
$type = new Type(Type::BUILTIN_TYPE_ARRAY, false, null, true);
|
||||
|
||||
$this->assertEquals('#/definitions/array', $registry->register(new Model($type, ['group1'])));
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider getNameAlternatives
|
||||
*
|
||||
* @param $expected
|
||||
*/
|
||||
public function testNameAliasingForObjects(string $expected, $groups, array $alternativeNames)
|
||||
{
|
||||
$registry = new ModelRegistry([], new Swagger(), $alternativeNames);
|
||||
$type = new Type(Type::BUILTIN_TYPE_OBJECT, false, self::class);
|
||||
|
||||
$this->assertEquals($expected, $registry->register(new Model($type, $groups)));
|
||||
}
|
||||
|
||||
public function getNameAlternatives()
|
||||
{
|
||||
return [
|
||||
[
|
||||
'#/definitions/ModelRegistryTest',
|
||||
null,
|
||||
[
|
||||
'Foo1' => [
|
||||
'type' => self::class,
|
||||
'groups' => ['group1'],
|
||||
],
|
||||
],
|
||||
],
|
||||
[
|
||||
'#/definitions/Foo1',
|
||||
['group1'],
|
||||
[
|
||||
'Foo1' => [
|
||||
'type' => self::class,
|
||||
'groups' => ['group1'],
|
||||
],
|
||||
],
|
||||
],
|
||||
[
|
||||
'#/definitions/Foo1',
|
||||
['group1', 'group2'],
|
||||
[
|
||||
'Foo1' => [
|
||||
'type' => self::class,
|
||||
'groups' => ['group1', 'group2'],
|
||||
],
|
||||
],
|
||||
],
|
||||
[
|
||||
'#/definitions/Foo1',
|
||||
null,
|
||||
[
|
||||
'Foo1' => [
|
||||
'type' => self::class,
|
||||
'groups' => [],
|
||||
],
|
||||
],
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider unsupportedTypesProvider
|
||||
*/
|
||||
|
Loading…
x
Reference in New Issue
Block a user