From d5a5a94e50ef424b980e48b8772d14c6c7e6404c Mon Sep 17 00:00:00 2001 From: Amrouche Hamza Date: Thu, 7 Jul 2016 08:53:25 +0200 Subject: [PATCH 01/10] feat: add tests and add collection --- src/Swagger/ApiDocumentationBuilder.php | 22 ++++- tests/Swagger/ApiDocumentationBuilderTest.php | 98 +++++++++++++++++++ 2 files changed, 115 insertions(+), 5 deletions(-) create mode 100644 tests/Swagger/ApiDocumentationBuilderTest.php diff --git a/src/Swagger/ApiDocumentationBuilder.php b/src/Swagger/ApiDocumentationBuilder.php index 27d7f738d92..bd87c3ac958 100644 --- a/src/Swagger/ApiDocumentationBuilder.php +++ b/src/Swagger/ApiDocumentationBuilder.php @@ -74,8 +74,9 @@ public function __construct(ResourceNameCollectionFactoryInterface $resourceName public function getApiDocumentation() { $classes = []; - $itemOperations = []; - $itemOperations['operation'] = []; + $operation = []; + $operation['item'] = []; + $operation['collection'] = []; $itemOperationsDocs = []; $properties = []; @@ -147,18 +148,28 @@ public function getApiDocumentation() if ($operations = $resourceMetadata->getItemOperations()) { foreach ($operations as $operationName => $itemOperation) { $swaggerOperation = $this->getSwaggerOperation($resourceClass, $resourceMetadata, $operationName, $itemOperation, $prefixedShortName, false); - $itemOperations['operation'] = array_merge($itemOperations['operation'], $swaggerOperation); + $operation['item'] = array_merge($operation['item'], $swaggerOperation); } } + if ($operations = $resourceMetadata->getCollectionOperations()) { + foreach ($operations as $operationName => $collectionOperation) { + $swaggerOperation = $this->getSwaggerOperation($resourceClass, $resourceMetadata, $operationName, $collectionOperation, $prefixedShortName, true); + $operation['collection'] = array_merge($operation['collection'], $swaggerOperation); + } + } + + try { $resourceClassIri = $this->iriConverter->getIriFromResourceClass($resourceClass); } catch (InvalidArgumentException $e) { $resourceClassIri = '/nopaths'; } + $itemOperationsDocs[$resourceClassIri] = $operation['collection']; + $resourceClassIri .= '/{id}'; - $itemOperationsDocs[$resourceClassIri] = $itemOperations['operation']; + $itemOperationsDocs[$resourceClassIri] = $operation['item']; $classes[] = $class; } @@ -192,6 +203,7 @@ private function getSwaggerOperation(string $resourceClass, ResourceMetadata $re } else { $method = $this->operationMethodResolver->getItemOperationMethod($resourceClass, $operationName); } + $methodSwagger = strtolower($method); $swaggerOperation = $operation['swagger_context'] ?? []; $shortName = $resourceMetadata->getShortName(); @@ -226,7 +238,7 @@ private function getSwaggerOperation(string $resourceClass, ResourceMetadata $re $swaggerOperation[$methodSwagger]['summary'] = sprintf('Creates a %s resource.', $shortName); } if ($this->resourceClassResolver->isResourceClass($shortName)) { - $swaggerOperation[$methodSwagger]['parameters'] = [ + $swaggerOperation[$methodSwagger]['parameters'][] = [ 'in' => 'body', 'name' => 'body', 'description' => sprintf('%s resource to be added', $shortName), diff --git a/tests/Swagger/ApiDocumentationBuilderTest.php b/tests/Swagger/ApiDocumentationBuilderTest.php new file mode 100644 index 00000000000..0c0b91cf949 --- /dev/null +++ b/tests/Swagger/ApiDocumentationBuilderTest.php @@ -0,0 +1,98 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace ApiPlatform\Core\Tests\Swagger; + +use ApiPlatform\Core\Api\IriConverterInterface; +use ApiPlatform\Core\Api\OperationMethodResolverInterface; +use ApiPlatform\Core\Api\ResourceClassResolverInterface; +use ApiPlatform\Core\Api\UrlGeneratorInterface; +use ApiPlatform\Core\JsonLd\ContextBuilderInterface; +use ApiPlatform\Core\Metadata\Property\Factory\PropertyMetadataFactoryInterface; +use ApiPlatform\Core\Metadata\Property\Factory\PropertyNameCollectionFactoryInterface; +use ApiPlatform\Core\Metadata\Property\PropertyMetadata; +use ApiPlatform\Core\Metadata\Property\PropertyNameCollection; +use ApiPlatform\Core\Metadata\Resource\Factory\ResourceMetadataFactoryInterface; +use ApiPlatform\Core\Metadata\Resource\Factory\ResourceNameCollectionFactoryInterface; +use ApiPlatform\Core\Metadata\Resource\ResourceMetadata; +use ApiPlatform\Core\Metadata\Resource\ResourceNameCollection; +use ApiPlatform\Core\Swagger\ApiDocumentationBuilder; +use Symfony\Component\PropertyInfo\Type; + +/** + * @author Amrouche Hamza + */ +class ApiDocumentationBuilderTest extends \PHPUnit_Framework_TestCase /**/ +{ + public function testGetApiDocumention() + { + $resourceNameCollectionFactoryProphecy = $this->prophesize(ResourceNameCollectionFactoryInterface::class); + $resourceMetadataFactoryProphecy = $this->prophesize(ResourceMetadataFactoryInterface::class); + $propertyNameCollectionFactoryProphecy = $this->prophesize(PropertyNameCollectionFactoryInterface::class); + $propertyMetadataFactoryProphecy = $this->prophesize(PropertyMetadataFactoryInterface::class); + $contextBuilderProphecy = $this->prophesize(ContextBuilderInterface::class); + $resourceClassResolverProphecy = $this->prophesize(ResourceClassResolverInterface::class); + $operationMethodResolverProphecy = $this->prophesize(OperationMethodResolverInterface::class); + $urlGeneratorProphecy = $this->prophesize(UrlGeneratorInterface::class); + $titre = 'Test Api'; + $desc = 'test ApiGerard'; + $iriConverter = $this->prophesize(IriConverterInterface::class); + $version = '1.0.0'; + $host = 'http://exemple.com'; + $schema = 'http'; + $dummyMetadata = new ResourceMetadata('dummy', 'dummy', '#dummy', ['get' => ['method' => 'GET'], 'put' => ['method' => 'PUT']], ['get' => ['method' => 'GET'], 'post' => ['method' => 'POST']], []); + $resourceNameCollectionFactoryProphecy->create()->willReturn(new ResourceNameCollection(['dummy' => 'dummy']))->shouldBeCalled(); + $resourceMetadataFactoryProphecy->create('dummy')->shouldBeCalled()->willReturn($dummyMetadata); + $propertyNameCollectionFactoryProphecy->create('dummy', [])->shouldBeCalled()->willReturn(new PropertyNameCollection(['name'])); + $propertyMetadataFactoryProphecy->create('dummy', 'name')->shouldBeCalled()->willReturn(new PropertyMetadata(new Type(Type::BUILTIN_TYPE_STRING), 'name', true, true, true, true, false, false, null, [])); + $operationMethodResolverProphecy->getItemOperationMethod('dummy', 'get')->shouldBeCalled()->willReturn('get'); + $operationMethodResolverProphecy->getItemOperationMethod('dummy', 'put')->shouldBeCalled()->willReturn('put'); + $operationMethodResolverProphecy->getCollectionOperationMethod('dummy', 'get')->shouldBeCalled()->willReturn('get'); + $operationMethodResolverProphecy->getCollectionOperationMethod('dummy', 'post')->shouldBeCalled()->willReturn('post'); + $iriConverter->getIriFromResourceClass('dummy')->shouldBeCalled()->willReturn('/dummies'); + $apiDocumentationBuilder = new ApiDocumentationBuilder($resourceNameCollectionFactoryProphecy->reveal(), $resourceMetadataFactoryProphecy->reveal(), $propertyNameCollectionFactoryProphecy->reveal(), $propertyMetadataFactoryProphecy->reveal(), $contextBuilderProphecy->reveal(), $resourceClassResolverProphecy->reveal(), $operationMethodResolverProphecy->reveal(), $urlGeneratorProphecy->reveal(), $iriConverter->reveal(), $titre, $desc, $version, $host, $schema); + + $swaggerDocumentation = $apiDocumentationBuilder->getApiDocumentation(); + $this->assertEquals($swaggerDocumentation['swagger'], 2.0); + $this->assertEquals($swaggerDocumentation['info']['title'], $titre); + $this->assertEquals($swaggerDocumentation['info']['description'], $desc); + $this->assertEquals($swaggerDocumentation['host'], $host); + $this->assertEquals($swaggerDocumentation['definitions'], ['dummy' => ['type' => 'object', 'properties' => ['name' => ['type' => 'string']]]]); + $this->assertEquals($swaggerDocumentation['externalDocs' + ], ['description' => 'Find more about API Platform', 'url' => 'https://api-platform.com']); + + $this->assertEquals($swaggerDocumentation['paths']['/dummies']['get'], [ + 'tags' => [0 => 'dummy'], + 'produces' => ['application/ld+json'], + 'consumes' => ['application/ld+json'], + ] + ); + + $this->assertEquals($swaggerDocumentation['paths']['/dummies']['post'], [ + 'tags' => [0 => 'dummy'], + 'produces' => ['application/ld+json'], + 'consumes' => ['application/ld+json'], + ] + ); + $this->assertEquals($swaggerDocumentation['paths']['/dummies/{id}']['get'], [ + 'tags' => [0 => 'dummy'], + 'produces' => ['application/ld+json'], + 'consumes' => ['application/ld+json'], + ] + ); + $this->assertEquals($swaggerDocumentation['paths']['/dummies/{id}']['put'], [ + 'tags' => [0 => 'dummy'], + 'produces' => ['application/ld+json'], + 'consumes' => ['application/ld+json'], + ] + ); + } +} From 269db5458a3de9dfef01b78101bbe120023d1631 Mon Sep 17 00:00:00 2001 From: Amrouche Hamza Date: Thu, 7 Jul 2016 16:03:39 +0200 Subject: [PATCH 02/10] feat: add property description --- src/Swagger/ApiDocumentationBuilder.php | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Swagger/ApiDocumentationBuilder.php b/src/Swagger/ApiDocumentationBuilder.php index bd87c3ac958..a14a659aafd 100644 --- a/src/Swagger/ApiDocumentationBuilder.php +++ b/src/Swagger/ApiDocumentationBuilder.php @@ -125,6 +125,7 @@ public function getApiDocumentation() $property[$propertyName] = [ 'type' => $range, + 'description' => $propertyMetadata->getDescription() ]; if (is_array($range)) { From bb5e70b2b4beaf86fc635e2b4b5647ac897c292f Mon Sep 17 00:00:00 2001 From: Amrouche Hamza Date: Thu, 7 Jul 2016 16:19:40 +0200 Subject: [PATCH 03/10] fix produces --- .../Symfony/Bundle/Resources/config/swagger.xml | 3 +-- src/Swagger/ApiDocumentationBuilder.php | 17 +++++++---------- 2 files changed, 8 insertions(+), 12 deletions(-) diff --git a/src/Bridge/Symfony/Bundle/Resources/config/swagger.xml b/src/Bridge/Symfony/Bundle/Resources/config/swagger.xml index 38a63e5c9f3..e8e14047653 100644 --- a/src/Bridge/Symfony/Bundle/Resources/config/swagger.xml +++ b/src/Bridge/Symfony/Bundle/Resources/config/swagger.xml @@ -16,11 +16,10 @@ + %api_platform.title% %api_platform.description% 2.0 - %router.request_context.host% - %router.request_context.scheme% diff --git a/src/Swagger/ApiDocumentationBuilder.php b/src/Swagger/ApiDocumentationBuilder.php index a14a659aafd..abecc2df7f7 100644 --- a/src/Swagger/ApiDocumentationBuilder.php +++ b/src/Swagger/ApiDocumentationBuilder.php @@ -46,11 +46,10 @@ final class ApiDocumentationBuilder implements ApiDocumentationBuilderInterface private $description; private $iriConverter; private $version; - private $host; - private $schema; + private $formats; const SWAGGER_VERSION = '2.0'; - public function __construct(ResourceNameCollectionFactoryInterface $resourceNameCollectionFactory, ResourceMetadataFactoryInterface $resourceMetadataFactory, PropertyNameCollectionFactoryInterface $propertyNameCollectionFactory, PropertyMetadataFactoryInterface $propertyMetadataFactory, ContextBuilderInterface $contextBuilder, ResourceClassResolverInterface $resourceClassResolver, OperationMethodResolverInterface $operationMethodResolver, UrlGeneratorInterface $urlGenerator, IriConverterInterface $iriConverter, string $title, string $description, string $version = null, string $host, string $schema) + public function __construct(ResourceNameCollectionFactoryInterface $resourceNameCollectionFactory, ResourceMetadataFactoryInterface $resourceMetadataFactory, PropertyNameCollectionFactoryInterface $propertyNameCollectionFactory, PropertyMetadataFactoryInterface $propertyMetadataFactory, ContextBuilderInterface $contextBuilder, ResourceClassResolverInterface $resourceClassResolver, OperationMethodResolverInterface $operationMethodResolver, UrlGeneratorInterface $urlGenerator, IriConverterInterface $iriConverter, array $formats, string $title, string $description, string $version = null) { $this->resourceNameCollectionFactory = $resourceNameCollectionFactory; $this->resourceMetadataFactory = $resourceMetadataFactory; @@ -60,12 +59,11 @@ public function __construct(ResourceNameCollectionFactoryInterface $resourceName $this->resourceClassResolver = $resourceClassResolver; $this->operationMethodResolver = $operationMethodResolver; $this->urlGenerator = $urlGenerator; + $this->formats = array_values($formats); $this->title = $title; $this->description = $description; $this->iriConverter = $iriConverter; $this->version = $version; - $this->host = $host; - $this->schema[] = $schema; } /** @@ -125,7 +123,7 @@ public function getApiDocumentation() $property[$propertyName] = [ 'type' => $range, - 'description' => $propertyMetadata->getDescription() + 'description' => $propertyMetadata->getDescription() ?: '' ]; if (is_array($range)) { @@ -183,12 +181,10 @@ public function getApiDocumentation() $doc['info']['description'] = $this->description; } $doc['info']['version'] = $this->version ?? '0.0.0'; - $doc['host'] = $this->host; $doc['basePath'] = $this->urlGenerator->generate('api_jsonld_entrypoint'); $doc['definitions'] = $properties; $doc['externalDocs'] = ['description' => 'Find more about API Platform', 'url' => 'https://api-platform.com']; $doc['tags'] = $classes; - $doc['schemes'] = $this->schema; // more schema ? $doc['paths'] = $itemOperationsDocs; return $doc; @@ -197,7 +193,7 @@ public function getApiDocumentation() /** * Gets and populates if applicable a Swagger operation. */ - private function getSwaggerOperation(string $resourceClass, ResourceMetadata $resourceMetadata, string $operationName, array $operation, string $prefixedShortName, bool $collection) : array + private function getSwaggerOperation(string $resourceClass, ResourceMetadata $resourceMetadata, string $operationName, array $operation, string $prefixedShortName, bool $collection, array $properties) : array { if ($collection) { $method = $this->operationMethodResolver->getCollectionOperationMethod($resourceClass, $operationName); @@ -210,7 +206,7 @@ private function getSwaggerOperation(string $resourceClass, ResourceMetadata $re $shortName = $resourceMetadata->getShortName(); $swaggerOperation[$methodSwagger] = []; $swaggerOperation[$methodSwagger]['tags'] = [$shortName]; - $swaggerOperation[$methodSwagger]['produces'] = ['application/ld+json']; + $swaggerOperation[$methodSwagger]['produces'] = $this->formats; $swaggerOperation[$methodSwagger]['consumes'] = $swaggerOperation[$methodSwagger]['produces']; switch ($method) { case 'GET': @@ -228,6 +224,7 @@ private function getSwaggerOperation(string $resourceClass, ResourceMetadata $re 'required' => true, 'type' => 'integer', ]; + $swaggerOperation[$methodSwagger]['parameters'][] = } $swaggerOperation[$methodSwagger]['responses'] = [ '200' => ['description' => 'Valid ID'], From 6615cba2a5b036e8ad2a93591db245cf608407d7 Mon Sep 17 00:00:00 2001 From: Amrouche Hamza Date: Thu, 7 Jul 2016 17:48:06 +0200 Subject: [PATCH 04/10] feat: add property description --- src/Swagger/ApiDocumentationBuilder.php | 138 +++++++++++++----------- 1 file changed, 73 insertions(+), 65 deletions(-) diff --git a/src/Swagger/ApiDocumentationBuilder.php b/src/Swagger/ApiDocumentationBuilder.php index abecc2df7f7..2a6f8e07649 100644 --- a/src/Swagger/ApiDocumentationBuilder.php +++ b/src/Swagger/ApiDocumentationBuilder.php @@ -59,7 +59,7 @@ public function __construct(ResourceNameCollectionFactoryInterface $resourceName $this->resourceClassResolver = $resourceClassResolver; $this->operationMethodResolver = $operationMethodResolver; $this->urlGenerator = $urlGenerator; - $this->formats = array_values($formats); + $this->formats = array_keys($formats); $this->title = $title; $this->description = $description; $this->iriConverter = $iriConverter; @@ -77,7 +77,7 @@ public function getApiDocumentation() $operation['collection'] = []; $itemOperationsDocs = []; - $properties = []; + $definitions = []; foreach ($this->resourceNameCollectionFactory->create() as $resourceClass) { $resourceMetadata = $this->resourceMetadataFactory->create($resourceClass); @@ -113,47 +113,44 @@ public function getApiDocumentation() $context['serializer_groups'] = isset($context['serializer_groups']) ? array_merge($context['serializer_groups'], $attributes['denormalization_context']['groups']) : $context['serializer_groups']; } + $definitions[$shortName] = [ + 'type' => 'object', + ]; foreach ($this->propertyNameCollectionFactory->create($resourceClass, $context) as $propertyName) { $propertyMetadata = $this->propertyMetadataFactory->create($resourceClass, $propertyName); - if ($propertyMetadata->isIdentifier() && !$propertyMetadata->isWritable()) { continue; } - $range = $this->getRange($propertyMetadata); - - $property[$propertyName] = [ - 'type' => $range, - 'description' => $propertyMetadata->getDescription() ?: '' - ]; - - if (is_array($range)) { - $property[$propertyName] = $range; - } - - $required = []; if ($propertyMetadata->isRequired()) { - $required = array_merge($required, [$propertyName]); + $definitions[$shortName]['required'][] = $propertyName; } - if (!empty($required)) { - $properties[$shortName]['required'] = $required; - } + $range = $this->getRange($propertyMetadata); + $definitions[$shortName]['properties'][$propertyName]['description'] = $propertyMetadata->getDescription() ?: ''; - $properties[$shortName]['type'] = 'object'; - $properties[$shortName]['properties'] = $property; + + + if ($range['complex']) { + $definitions[$shortName]['properties'][$propertyName] = ['$ref' => $range['value']]; + } else { + $definitions[$shortName]['properties'][$propertyName] = [ + 'type' => $range['value'], + ]; + } + } if ($operations = $resourceMetadata->getItemOperations()) { foreach ($operations as $operationName => $itemOperation) { - $swaggerOperation = $this->getSwaggerOperation($resourceClass, $resourceMetadata, $operationName, $itemOperation, $prefixedShortName, false); + $swaggerOperation = $this->getSwaggerOperation($resourceClass, $resourceMetadata, $operationName, $itemOperation, $prefixedShortName, false, $definitions); $operation['item'] = array_merge($operation['item'], $swaggerOperation); } } if ($operations = $resourceMetadata->getCollectionOperations()) { foreach ($operations as $operationName => $collectionOperation) { - $swaggerOperation = $this->getSwaggerOperation($resourceClass, $resourceMetadata, $operationName, $collectionOperation, $prefixedShortName, true); + $swaggerOperation = $this->getSwaggerOperation($resourceClass, $resourceMetadata, $operationName, $collectionOperation, $prefixedShortName, true, $definitions); $operation['collection'] = array_merge($operation['collection'], $swaggerOperation); } } @@ -161,14 +158,15 @@ public function getApiDocumentation() try { $resourceClassIri = $this->iriConverter->getIriFromResourceClass($resourceClass); + $itemOperationsDocs[$resourceClassIri] = $operation['collection']; + + $resourceClassIri .= '/{id}'; + + $itemOperationsDocs[$resourceClassIri] = $operation['item']; } catch (InvalidArgumentException $e) { - $resourceClassIri = '/nopaths'; - } - $itemOperationsDocs[$resourceClassIri] = $operation['collection']; - $resourceClassIri .= '/{id}'; + } - $itemOperationsDocs[$resourceClassIri] = $operation['item']; $classes[] = $class; } @@ -182,7 +180,7 @@ public function getApiDocumentation() } $doc['info']['version'] = $this->version ?? '0.0.0'; $doc['basePath'] = $this->urlGenerator->generate('api_jsonld_entrypoint'); - $doc['definitions'] = $properties; + $doc['definitions'] = $definitions; $doc['externalDocs'] = ['description' => 'Find more about API Platform', 'url' => 'https://api-platform.com']; $doc['tags'] = $classes; $doc['paths'] = $itemOperationsDocs; @@ -208,23 +206,35 @@ private function getSwaggerOperation(string $resourceClass, ResourceMetadata $re $swaggerOperation[$methodSwagger]['tags'] = [$shortName]; $swaggerOperation[$methodSwagger]['produces'] = $this->formats; $swaggerOperation[$methodSwagger]['consumes'] = $swaggerOperation[$methodSwagger]['produces']; + switch ($method) { case 'GET': if ($collection) { if (!isset($swaggerOperation[$methodSwagger]['title'])) { $swaggerOperation[$methodSwagger]['summary'] = sprintf('Retrieves the collection of %s resources.', $shortName); } + if($this->resourceClassResolver->isResourceClass($resourceClass)) { + $swaggerOperation[$methodSwagger]['parameters'][] = [ + 'in' => 'body', + 'name' => 'body', + 'description' => sprintf('%s resource to be added', $shortName), + 'schema' => [ + '$ref' => sprintf('#/definitions/%s', $shortName), + ], + ]; + } } else { if (!isset($swaggerOperation[$methodSwagger]['title'])) { $swaggerOperation[$methodSwagger]['summary'] = sprintf('Retrieves %s resource.', $shortName); } + $swaggerOperation[$methodSwagger]['parameters'][] = [ 'name' => 'id', 'in' => 'path', 'required' => true, 'type' => 'integer', ]; - $swaggerOperation[$methodSwagger]['parameters'][] = + } $swaggerOperation[$methodSwagger]['responses'] = [ '200' => ['description' => 'Valid ID'], @@ -235,12 +245,13 @@ private function getSwaggerOperation(string $resourceClass, ResourceMetadata $re if (!isset($swaggerOperation[$methodSwagger]['title'])) { $swaggerOperation[$methodSwagger]['summary'] = sprintf('Creates a %s resource.', $shortName); } - if ($this->resourceClassResolver->isResourceClass($shortName)) { + + if($this->resourceClassResolver->isResourceClass($resourceClass)) { $swaggerOperation[$methodSwagger]['parameters'][] = [ - 'in' => 'body', - 'name' => 'body', + 'in' => 'body', + 'name' => 'body', 'description' => sprintf('%s resource to be added', $shortName), - 'schema' => [ + 'schema' => [ '$ref' => sprintf('#/definitions/%s', $shortName), ], ]; @@ -256,29 +267,27 @@ private function getSwaggerOperation(string $resourceClass, ResourceMetadata $re if (!isset($swaggerOperation[$methodSwagger]['title'])) { $swaggerOperation[$methodSwagger]['summary'] = sprintf('Replaces the %s resource.', $shortName); } - $swaggerOperation[$methodSwagger]['parameters'] = [[ - 'name' => 'id', - 'in' => 'path', - 'required' => true, - 'type' => 'integer', - ]]; - if ($this->resourceClassResolver->isResourceClass($shortName)) { - $swaggerOperation[$methodSwagger]['parameters'] = [[ - 'name' => 'id', - 'in' => 'path', - 'required' => true, - 'type' => 'integer', - ], + + if($this->resourceClassResolver->isResourceClass($resourceClass)) { + + $swaggerOperation[$methodSwagger]['parameters'] = [ [ - 'in' => 'body', - 'name' => 'body', - 'description' => sprintf('%s resource to be added', $shortName), - 'schema' => [ - '$ref' => sprintf('#/definitions/%s', $shortName), + 'name' => 'id', + 'in' => 'path', + 'required' => true, + 'type' => 'integer', ], - ], ]; - } + [ + 'in' => 'body', + 'name' => 'body', + 'description' => sprintf('%s resource to be added', $shortName), + 'schema' => [ + '$ref' => sprintf('#/definitions/%s', $shortName), + ], + ], + ]; + } $swaggerOperation[$methodSwagger]['responses'] = [ '200' => ['description' => 'Valid ID'], ]; @@ -306,10 +315,11 @@ private function getSwaggerOperation(string $resourceClass, ResourceMetadata $re * * @param PropertyMetadata $propertyMetadata * - * @return string|null + * @return array */ - private function getRange(PropertyMetadata $propertyMetadata) + private function getRange(PropertyMetadata $propertyMetadata) : array { + $type = $propertyMetadata->getType(); if (!$type) { return; @@ -321,16 +331,14 @@ private function getRange(PropertyMetadata $propertyMetadata) switch ($type->getBuiltinType()) { case Type::BUILTIN_TYPE_STRING: - return 'string'; + return ['complex' => false, 'value' => 'string']; case Type::BUILTIN_TYPE_INT: - return 'integer'; - + return ['complex' => false, 'value' => 'integer']; case Type::BUILTIN_TYPE_FLOAT: - return 'number'; - + return ['complex' => false, 'value' => 'number']; case Type::BUILTIN_TYPE_BOOL: - return 'boolean'; + return ['complex' => false, 'value' => 'boolean']; case Type::BUILTIN_TYPE_OBJECT: $className = $type->getClassName(); @@ -338,17 +346,17 @@ private function getRange(PropertyMetadata $propertyMetadata) if (null !== $className) { $reflection = new \ReflectionClass($className); if ($reflection->implementsInterface(\DateTimeInterface::class)) { - return 'string'; + return ['complex' => false, 'value' => 'string']; } $className = $type->getClassName(); if ($this->resourceClassResolver->isResourceClass($className)) { - return ['$ref' => sprintf('#/definitions/%s', $this->resourceMetadataFactory->create($className)->getShortName())]; + return ['complex' => true, 'value' => sprintf('#/definitions/%s', $this->resourceMetadataFactory->create($className)->getShortName())]; } } break; default: - return 'null'; + return ['complex' => false, 'value' => 'null']; break; } } From f0a0bb85ecedda037d40c1ea09a0181d4c7a7cef Mon Sep 17 00:00:00 2001 From: Amrouche Hamza Date: Thu, 7 Jul 2016 17:56:29 +0200 Subject: [PATCH 05/10] feat: add property description --- features/swagger/doc.feature | 1 - tests/Swagger/ApiDocumentationBuilderTest.php | 4 ++-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/features/swagger/doc.feature b/features/swagger/doc.feature index ef824dfea65..2637aa66f25 100644 --- a/features/swagger/doc.feature +++ b/features/swagger/doc.feature @@ -13,7 +13,6 @@ Feature: Documentation support # Root properties And the JSON node "info.title" should be equal to "My Dummy API" And the JSON node "info.description" should be equal to "This is a test API." - #And the JSON node "host" should be equal to "exemple.com" And the JSON node "basePath" should be equal to "/" # Supported classes And the Swagger class "CircularReference" exist diff --git a/tests/Swagger/ApiDocumentationBuilderTest.php b/tests/Swagger/ApiDocumentationBuilderTest.php index 0c0b91cf949..69c7ee72735 100644 --- a/tests/Swagger/ApiDocumentationBuilderTest.php +++ b/tests/Swagger/ApiDocumentationBuilderTest.php @@ -48,6 +48,7 @@ public function testGetApiDocumention() $version = '1.0.0'; $host = 'http://exemple.com'; $schema = 'http'; + $formats = ['application/ld+json' => 'ld+json']; $dummyMetadata = new ResourceMetadata('dummy', 'dummy', '#dummy', ['get' => ['method' => 'GET'], 'put' => ['method' => 'PUT']], ['get' => ['method' => 'GET'], 'post' => ['method' => 'POST']], []); $resourceNameCollectionFactoryProphecy->create()->willReturn(new ResourceNameCollection(['dummy' => 'dummy']))->shouldBeCalled(); $resourceMetadataFactoryProphecy->create('dummy')->shouldBeCalled()->willReturn($dummyMetadata); @@ -58,13 +59,12 @@ public function testGetApiDocumention() $operationMethodResolverProphecy->getCollectionOperationMethod('dummy', 'get')->shouldBeCalled()->willReturn('get'); $operationMethodResolverProphecy->getCollectionOperationMethod('dummy', 'post')->shouldBeCalled()->willReturn('post'); $iriConverter->getIriFromResourceClass('dummy')->shouldBeCalled()->willReturn('/dummies'); - $apiDocumentationBuilder = new ApiDocumentationBuilder($resourceNameCollectionFactoryProphecy->reveal(), $resourceMetadataFactoryProphecy->reveal(), $propertyNameCollectionFactoryProphecy->reveal(), $propertyMetadataFactoryProphecy->reveal(), $contextBuilderProphecy->reveal(), $resourceClassResolverProphecy->reveal(), $operationMethodResolverProphecy->reveal(), $urlGeneratorProphecy->reveal(), $iriConverter->reveal(), $titre, $desc, $version, $host, $schema); + $apiDocumentationBuilder = new ApiDocumentationBuilder($resourceNameCollectionFactoryProphecy->reveal(), $resourceMetadataFactoryProphecy->reveal(), $propertyNameCollectionFactoryProphecy->reveal(), $propertyMetadataFactoryProphecy->reveal(), $contextBuilderProphecy->reveal(), $resourceClassResolverProphecy->reveal(), $operationMethodResolverProphecy->reveal(), $urlGeneratorProphecy->reveal(), $iriConverter->reveal(), $formats, $titre, $desc); $swaggerDocumentation = $apiDocumentationBuilder->getApiDocumentation(); $this->assertEquals($swaggerDocumentation['swagger'], 2.0); $this->assertEquals($swaggerDocumentation['info']['title'], $titre); $this->assertEquals($swaggerDocumentation['info']['description'], $desc); - $this->assertEquals($swaggerDocumentation['host'], $host); $this->assertEquals($swaggerDocumentation['definitions'], ['dummy' => ['type' => 'object', 'properties' => ['name' => ['type' => 'string']]]]); $this->assertEquals($swaggerDocumentation['externalDocs' ], ['description' => 'Find more about API Platform', 'url' => 'https://api-platform.com']); From d1f34d8751f1ec43b311b6347f4304609a0869e4 Mon Sep 17 00:00:00 2001 From: Amrouche Hamza Date: Thu, 7 Jul 2016 18:12:50 +0200 Subject: [PATCH 06/10] feat: refactor --- src/Swagger/ApiDocumentationBuilder.php | 30 +++++++++++++++---------- 1 file changed, 18 insertions(+), 12 deletions(-) diff --git a/src/Swagger/ApiDocumentationBuilder.php b/src/Swagger/ApiDocumentationBuilder.php index 2a6f8e07649..e8a0fc102f0 100644 --- a/src/Swagger/ApiDocumentationBuilder.php +++ b/src/Swagger/ApiDocumentationBuilder.php @@ -315,7 +315,7 @@ private function getSwaggerOperation(string $resourceClass, ResourceMetadata $re * * @param PropertyMetadata $propertyMetadata * - * @return array + * @return array|null */ private function getRange(PropertyMetadata $propertyMetadata) : array { @@ -335,29 +335,35 @@ private function getRange(PropertyMetadata $propertyMetadata) : array case Type::BUILTIN_TYPE_INT: return ['complex' => false, 'value' => 'integer']; + case Type::BUILTIN_TYPE_FLOAT: return ['complex' => false, 'value' => 'number']; + case Type::BUILTIN_TYPE_BOOL: return ['complex' => false, 'value' => 'boolean']; case Type::BUILTIN_TYPE_OBJECT: $className = $type->getClassName(); + if (null === $className) { + return; + } - if (null !== $className) { - $reflection = new \ReflectionClass($className); - if ($reflection->implementsInterface(\DateTimeInterface::class)) { - return ['complex' => false, 'value' => 'string']; - } + if (is_subclass_of($className, \DateTimeInterface::class)) { + return ['complex' => false, 'value' => 'string']; + } - $className = $type->getClassName(); - if ($this->resourceClassResolver->isResourceClass($className)) { - return ['complex' => true, 'value' => sprintf('#/definitions/%s', $this->resourceMetadataFactory->create($className)->getShortName())]; - } + if (!$this->resourceClassResolver->isResourceClass($className)) { + return; } - break; + + if ($propertyMetadata->isReadableLink()) { + return ['complex' => true, 'value' => sprintf('#/definitions/%s', $this->resourceMetadataFactory->create($className)->getShortName())]; + } + + return ['complex' => false, 'value' => 'string']; + default: return ['complex' => false, 'value' => 'null']; - break; } } } From ecc8eebe736e4cb5f5bdd38e32d5ed6211d74fb4 Mon Sep 17 00:00:00 2001 From: Amrouche Hamza Date: Thu, 7 Jul 2016 18:22:39 +0200 Subject: [PATCH 07/10] feat: refactor --- src/Swagger/ApiDocumentationBuilder.php | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/Swagger/ApiDocumentationBuilder.php b/src/Swagger/ApiDocumentationBuilder.php index e8a0fc102f0..74198336997 100644 --- a/src/Swagger/ApiDocumentationBuilder.php +++ b/src/Swagger/ApiDocumentationBuilder.php @@ -126,11 +126,14 @@ public function getApiDocumentation() $definitions[$shortName]['required'][] = $propertyName; } + $range = $this->getRange($propertyMetadata); + if (null === $range) { + continue; + } + $definitions[$shortName]['properties'][$propertyName]['description'] = $propertyMetadata->getDescription() ?: ''; - - if ($range['complex']) { $definitions[$shortName]['properties'][$propertyName] = ['$ref' => $range['value']]; } else { @@ -138,7 +141,6 @@ public function getApiDocumentation() 'type' => $range['value'], ]; } - } if ($operations = $resourceMetadata->getItemOperations()) { From db42275cac42c0708c1ad041b31fb4d16025845a Mon Sep 17 00:00:00 2001 From: Amrouche Hamza Date: Thu, 7 Jul 2016 18:24:06 +0200 Subject: [PATCH 08/10] cs --- .../Bundle/Resources/config/swagger.xml | 2 +- src/Swagger/ApiDocumentationBuilder.php | 43 ++++++++----------- 2 files changed, 20 insertions(+), 25 deletions(-) diff --git a/src/Bridge/Symfony/Bundle/Resources/config/swagger.xml b/src/Bridge/Symfony/Bundle/Resources/config/swagger.xml index e8e14047653..2771ad7a961 100644 --- a/src/Bridge/Symfony/Bundle/Resources/config/swagger.xml +++ b/src/Bridge/Symfony/Bundle/Resources/config/swagger.xml @@ -16,7 +16,7 @@ - + %api_platform.formats% %api_platform.title% %api_platform.description% 2.0 diff --git a/src/Swagger/ApiDocumentationBuilder.php b/src/Swagger/ApiDocumentationBuilder.php index 74198336997..c7f41dfe903 100644 --- a/src/Swagger/ApiDocumentationBuilder.php +++ b/src/Swagger/ApiDocumentationBuilder.php @@ -166,7 +166,6 @@ public function getApiDocumentation() $itemOperationsDocs[$resourceClassIri] = $operation['item']; } catch (InvalidArgumentException $e) { - } $classes[] = $class; @@ -208,19 +207,19 @@ private function getSwaggerOperation(string $resourceClass, ResourceMetadata $re $swaggerOperation[$methodSwagger]['tags'] = [$shortName]; $swaggerOperation[$methodSwagger]['produces'] = $this->formats; $swaggerOperation[$methodSwagger]['consumes'] = $swaggerOperation[$methodSwagger]['produces']; - + switch ($method) { case 'GET': if ($collection) { if (!isset($swaggerOperation[$methodSwagger]['title'])) { $swaggerOperation[$methodSwagger]['summary'] = sprintf('Retrieves the collection of %s resources.', $shortName); } - if($this->resourceClassResolver->isResourceClass($resourceClass)) { + if ($this->resourceClassResolver->isResourceClass($resourceClass)) { $swaggerOperation[$methodSwagger]['parameters'][] = [ - 'in' => 'body', - 'name' => 'body', + 'in' => 'body', + 'name' => 'body', 'description' => sprintf('%s resource to be added', $shortName), - 'schema' => [ + 'schema' => [ '$ref' => sprintf('#/definitions/%s', $shortName), ], ]; @@ -236,7 +235,6 @@ private function getSwaggerOperation(string $resourceClass, ResourceMetadata $re 'required' => true, 'type' => 'integer', ]; - } $swaggerOperation[$methodSwagger]['responses'] = [ '200' => ['description' => 'Valid ID'], @@ -248,12 +246,12 @@ private function getSwaggerOperation(string $resourceClass, ResourceMetadata $re $swaggerOperation[$methodSwagger]['summary'] = sprintf('Creates a %s resource.', $shortName); } - if($this->resourceClassResolver->isResourceClass($resourceClass)) { + if ($this->resourceClassResolver->isResourceClass($resourceClass)) { $swaggerOperation[$methodSwagger]['parameters'][] = [ - 'in' => 'body', - 'name' => 'body', + 'in' => 'body', + 'name' => 'body', 'description' => sprintf('%s resource to be added', $shortName), - 'schema' => [ + 'schema' => [ '$ref' => sprintf('#/definitions/%s', $shortName), ], ]; @@ -269,26 +267,24 @@ private function getSwaggerOperation(string $resourceClass, ResourceMetadata $re if (!isset($swaggerOperation[$methodSwagger]['title'])) { $swaggerOperation[$methodSwagger]['summary'] = sprintf('Replaces the %s resource.', $shortName); } - - if($this->resourceClassResolver->isResourceClass($resourceClass)) { + if ($this->resourceClassResolver->isResourceClass($resourceClass)) { $swaggerOperation[$methodSwagger]['parameters'] = [ [ - 'name' => 'id', - 'in' => 'path', + 'name' => 'id', + 'in' => 'path', 'required' => true, - 'type' => 'integer', + 'type' => 'integer', ], [ - 'in' => 'body', - 'name' => 'body', + 'in' => 'body', + 'name' => 'body', 'description' => sprintf('%s resource to be added', $shortName), - 'schema' => [ + 'schema' => [ '$ref' => sprintf('#/definitions/%s', $shortName), ], ], ]; - } $swaggerOperation[$methodSwagger]['responses'] = [ '200' => ['description' => 'Valid ID'], @@ -321,7 +317,6 @@ private function getSwaggerOperation(string $resourceClass, ResourceMetadata $re */ private function getRange(PropertyMetadata $propertyMetadata) : array { - $type = $propertyMetadata->getType(); if (!$type) { return; @@ -337,10 +332,10 @@ private function getRange(PropertyMetadata $propertyMetadata) : array case Type::BUILTIN_TYPE_INT: return ['complex' => false, 'value' => 'integer']; - + case Type::BUILTIN_TYPE_FLOAT: return ['complex' => false, 'value' => 'number']; - + case Type::BUILTIN_TYPE_BOOL: return ['complex' => false, 'value' => 'boolean']; @@ -359,7 +354,7 @@ private function getRange(PropertyMetadata $propertyMetadata) : array } if ($propertyMetadata->isReadableLink()) { - return ['complex' => true, 'value' => sprintf('#/definitions/%s', $this->resourceMetadataFactory->create($className)->getShortName())]; + return ['complex' => true, 'value' => sprintf('#/definitions/%s', $this->resourceMetadataFactory->create($className)->getShortName())]; } return ['complex' => false, 'value' => 'string']; From 41699802650f967ac59c0caa818575ce12b0e6ae Mon Sep 17 00:00:00 2001 From: Amrouche Hamza Date: Fri, 8 Jul 2016 11:24:11 +0200 Subject: [PATCH 09/10] feat: take comments --- .../DependencyInjection/ApiPlatformExtension.php | 1 + .../Bundle/DependencyInjection/Configuration.php | 1 + .../Symfony/Bundle/Resources/config/swagger.xml | 2 +- src/Swagger/ApiDocumentationBuilder.php | 11 ++++++----- .../DependencyInjection/ApiPlatformExtensionTest.php | 2 ++ .../Bundle/DependencyInjection/ConfigurationTest.php | 3 ++- 6 files changed, 13 insertions(+), 7 deletions(-) diff --git a/src/Bridge/Symfony/Bundle/DependencyInjection/ApiPlatformExtension.php b/src/Bridge/Symfony/Bundle/DependencyInjection/ApiPlatformExtension.php index 63a87ab06d5..b26ea843a12 100644 --- a/src/Bridge/Symfony/Bundle/DependencyInjection/ApiPlatformExtension.php +++ b/src/Bridge/Symfony/Bundle/DependencyInjection/ApiPlatformExtension.php @@ -68,6 +68,7 @@ public function load(array $configs, ContainerBuilder $container) $container->setParameter('api_platform.title', $config['title']); $container->setParameter('api_platform.description', $config['description']); + $container->setParameter('api_platform.version', $config['version']); $container->setParameter('api_platform.formats', $formats); $container->setParameter('api_platform.collection.order', $config['collection']['order']); $container->setParameter('api_platform.collection.order_parameter_name', $config['collection']['order_parameter_name']); diff --git a/src/Bridge/Symfony/Bundle/DependencyInjection/Configuration.php b/src/Bridge/Symfony/Bundle/DependencyInjection/Configuration.php index 4461bf1438f..fc6462a0727 100644 --- a/src/Bridge/Symfony/Bundle/DependencyInjection/Configuration.php +++ b/src/Bridge/Symfony/Bundle/DependencyInjection/Configuration.php @@ -33,6 +33,7 @@ public function getConfigTreeBuilder() ->children() ->scalarNode('title')->defaultValue('')->info('The title of the API.')->end() ->scalarNode('description')->defaultValue('')->info('The description of the API.')->end() + ->scalarNode('version')->defaultValue('')->info('The version of the API.')->end() ->arrayNode('formats') ->defaultValue(['jsonld' => ['mime_types' => ['application/ld+json']]]) ->info('The list of enabled formats. The first one will be the default.') diff --git a/src/Bridge/Symfony/Bundle/Resources/config/swagger.xml b/src/Bridge/Symfony/Bundle/Resources/config/swagger.xml index 2771ad7a961..89108324757 100644 --- a/src/Bridge/Symfony/Bundle/Resources/config/swagger.xml +++ b/src/Bridge/Symfony/Bundle/Resources/config/swagger.xml @@ -19,7 +19,7 @@ %api_platform.formats% %api_platform.title% %api_platform.description% - 2.0 + %api_platform.version% diff --git a/src/Swagger/ApiDocumentationBuilder.php b/src/Swagger/ApiDocumentationBuilder.php index c7f41dfe903..8ecc5289673 100644 --- a/src/Swagger/ApiDocumentationBuilder.php +++ b/src/Swagger/ApiDocumentationBuilder.php @@ -34,6 +34,8 @@ */ final class ApiDocumentationBuilder implements ApiDocumentationBuilderInterface { + const SWAGGER_VERSION = '2.0'; + private $resourceNameCollectionFactory; private $resourceMetadataFactory; private $propertyNameCollectionFactory; @@ -47,7 +49,6 @@ final class ApiDocumentationBuilder implements ApiDocumentationBuilderInterface private $iriConverter; private $version; private $formats; - const SWAGGER_VERSION = '2.0'; public function __construct(ResourceNameCollectionFactoryInterface $resourceNameCollectionFactory, ResourceMetadataFactoryInterface $resourceMetadataFactory, PropertyNameCollectionFactoryInterface $propertyNameCollectionFactory, PropertyMetadataFactoryInterface $propertyMetadataFactory, ContextBuilderInterface $contextBuilder, ResourceClassResolverInterface $resourceClassResolver, OperationMethodResolverInterface $operationMethodResolver, UrlGeneratorInterface $urlGenerator, IriConverterInterface $iriConverter, array $formats, string $title, string $description, string $version = null) { @@ -113,9 +114,7 @@ public function getApiDocumentation() $context['serializer_groups'] = isset($context['serializer_groups']) ? array_merge($context['serializer_groups'], $attributes['denormalization_context']['groups']) : $context['serializer_groups']; } - $definitions[$shortName] = [ - 'type' => 'object', - ]; + $definitions[$shortName] = ['type' => 'object']; foreach ($this->propertyNameCollectionFactory->create($resourceClass, $context) as $propertyName) { $propertyMetadata = $this->propertyMetadataFactory->create($resourceClass, $propertyName); if ($propertyMetadata->isIdentifier() && !$propertyMetadata->isWritable()) { @@ -132,7 +131,9 @@ public function getApiDocumentation() continue; } - $definitions[$shortName]['properties'][$propertyName]['description'] = $propertyMetadata->getDescription() ?: ''; + if ($propertyMetadata->getDescription()) { + $definitions[$shortName]['properties'][$propertyName]['description'] = $propertyMetadata->getDescription(); + } if ($range['complex']) { $definitions[$shortName]['properties'][$propertyName] = ['$ref' => $range['value']]; diff --git a/tests/Bridge/Symfony/Bundle/DependencyInjection/ApiPlatformExtensionTest.php b/tests/Bridge/Symfony/Bundle/DependencyInjection/ApiPlatformExtensionTest.php index 4b90dc6fb99..ff23dca6f91 100644 --- a/tests/Bridge/Symfony/Bundle/DependencyInjection/ApiPlatformExtensionTest.php +++ b/tests/Bridge/Symfony/Bundle/DependencyInjection/ApiPlatformExtensionTest.php @@ -32,6 +32,7 @@ class ApiPlatformExtensionTest extends \PHPUnit_Framework_TestCase 'api_platform' => [ 'title' => 'title', 'description' => 'description', + 'version' => 'version', ], ]; @@ -172,6 +173,7 @@ private function getContainerBuilderProphecy() $parameters = [ 'api_platform.title' => 'title', 'api_platform.description' => 'description', + 'api_platform.version' => 'version', 'api_platform.formats' => ['application/ld+json' => 'jsonld'], 'api_platform.collection.order' => null, 'api_platform.collection.order_parameter_name' => 'order', diff --git a/tests/Bridge/Symfony/Bundle/DependencyInjection/ConfigurationTest.php b/tests/Bridge/Symfony/Bundle/DependencyInjection/ConfigurationTest.php index 2fe86407519..99ad63ce7c3 100644 --- a/tests/Bridge/Symfony/Bundle/DependencyInjection/ConfigurationTest.php +++ b/tests/Bridge/Symfony/Bundle/DependencyInjection/ConfigurationTest.php @@ -26,13 +26,14 @@ public function testDefaultConfig() $configuration = new Configuration(); $treeBuilder = $configuration->getConfigTreeBuilder(); $processor = new Processor(); - $config = $processor->processConfiguration($configuration, ['api_platform' => ['title' => 'title', 'description' => 'description']]); + $config = $processor->processConfiguration($configuration, ['api_platform' => ['title' => 'title', 'description' => 'description', 'version' => '1.0.0']]); $this->assertInstanceOf(ConfigurationInterface::class, $configuration); $this->assertInstanceOf(TreeBuilder::class, $treeBuilder); $this->assertEquals([ 'title' => 'title', 'description' => 'description', + 'version' => '1.0.0', 'formats' => ['jsonld' => ['mime_types' => ['application/ld+json']]], 'naming' => [ 'resource_path_naming_strategy' => 'api_platform.naming.resource_path_naming_strategy.underscore', From db4a01105635ec108ca1a75035c2d7a987dce52b Mon Sep 17 00:00:00 2001 From: Amrouche Hamza Date: Fri, 8 Jul 2016 20:55:50 +0200 Subject: [PATCH 10/10] feat: default value --- src/Bridge/Symfony/Bundle/DependencyInjection/Configuration.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Bridge/Symfony/Bundle/DependencyInjection/Configuration.php b/src/Bridge/Symfony/Bundle/DependencyInjection/Configuration.php index fc6462a0727..346566f65a8 100644 --- a/src/Bridge/Symfony/Bundle/DependencyInjection/Configuration.php +++ b/src/Bridge/Symfony/Bundle/DependencyInjection/Configuration.php @@ -33,7 +33,7 @@ public function getConfigTreeBuilder() ->children() ->scalarNode('title')->defaultValue('')->info('The title of the API.')->end() ->scalarNode('description')->defaultValue('')->info('The description of the API.')->end() - ->scalarNode('version')->defaultValue('')->info('The version of the API.')->end() + ->scalarNode('version')->defaultValue('0.0.0')->info('The version of the API.')->end() ->arrayNode('formats') ->defaultValue(['jsonld' => ['mime_types' => ['application/ld+json']]]) ->info('The list of enabled formats. The first one will be the default.')