Skip to content

Commit

Permalink
[OpenAPI] Extract the JSON Schema builder
Browse files Browse the repository at this point in the history
  • Loading branch information
dunglas committed Aug 19, 2019
1 parent 92f655c commit c421a61
Show file tree
Hide file tree
Showing 17 changed files with 958 additions and 436 deletions.
2 changes: 1 addition & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@
"symfony/http-client": "^4.3",
"symfony/mercure-bundle": "*",
"symfony/messenger": "^4.3",
"symfony/phpunit-bridge": "^4.3.1",
"symfony/phpunit-bridge": "^4.3@dev",
"symfony/routing": "^3.4 || ^4.0",
"symfony/security-bundle": "^3.4 || ^4.0",
"symfony/security-core": "^4.3",
Expand Down
2 changes: 1 addition & 1 deletion features/openapi/docs.feature
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ Feature: Documentation support
And the JSON node "paths./related_dummies/{id}/related_to_dummy_friends.get.parameters" should have 6 elements

# Subcollection - check schema
And the JSON node "paths./related_dummies/{id}/related_to_dummy_friends.get.responses.200.content.application/ld+json.schema.items.$ref" should be equal to "#/components/schemas/RelatedToDummyFriend-fakemanytomany"
And the JSON node "paths./related_dummies/{id}/related_to_dummy_friends.get.responses.200.content.application/ld+json.schema.properties.hydra:member.items.$ref" should be equal to "#/components/schemas/RelatedToDummyFriend:jsonld-fakemanytomany"

# Deprecations
And the JSON node "paths./dummies.get.deprecated" should not exist
Expand Down
3 changes: 0 additions & 3 deletions phpstan.neon.dist
Original file line number Diff line number Diff line change
Expand Up @@ -51,9 +51,6 @@ parameters:
- '#Parameter \#1 \$defaultContext of class Symfony\\Component\\Serializer\\Encoder\\Json(De|En)code constructor expects array, (int|true) given\.#'
- '#Parameter \#(2|3) \$(resourceMetadataFactory|pagination) of class ApiPlatform\\Core\\Bridge\\Doctrine\\Orm\\Extension\\PaginationExtension constructor expects (ApiPlatform\\Core\\Metadata\\Resource\\Factory\\ResourceMetadataFactoryInterface\|Symfony\\Component\\HttpFoundation\\RequestStack|ApiPlatform\\Core\\DataProvider\\Pagination\|ApiPlatform\\Core\\Metadata\\Resource\\Factory\\ResourceMetadataFactoryInterface), stdClass given\.#'
- '#Parameter \#[0-9] \$filterLocator of class ApiPlatform\\Core\\Bridge\\Doctrine\\Orm\\Extension\\FilterExtension constructor expects ApiPlatform\\Core\\Api\\FilterCollection|Psr\\Container\\ContainerInterface(\|null)?, ArrayObject given\.#'
-
message: '#Parameter \#9 \$nameConverter of class ApiPlatform\\Core\\Swagger\\Serializer\\DocumentationNormalizer constructor expects Symfony\\Component\\Serializer\\NameConverter\\NameConverterInterface\|null, object given\.#'
path: %currentWorkingDirectory%/tests/Swagger/Serializer/DocumentationNormalizer*Test.php
-
message: '#Parameter \#1 \$resource of method ApiPlatform\\Core\\Metadata\\Extractor\\XmlExtractor::getAttributes\(\) expects SimpleXMLElement, object given\.#'
path: %currentWorkingDirectory%/src/Metadata/Extractor/XmlExtractor.php
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -311,6 +311,7 @@ private function registerSwaggerConfiguration(ContainerBuilder $container, array
return;
}

$loader->load('json_schema.xml');
$loader->load('swagger.xml');

if ($config['enable_swagger_ui'] || $config['enable_re_doc']) {
Expand Down
6 changes: 6 additions & 0 deletions src/Bridge/Symfony/Bundle/Resources/config/hydra.xml
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,12 @@
<argument type="service" id="api_platform.filter_locator" />
</service>

<!-- JSON Schema -->

<service id="api_platform.hydra.json_schema.schema_factory" class="ApiPlatform\Core\Hydra\JsonSchema\SchemaFactory" decorates="api_platform.json_schema.schema_factory">
<argument type="service" id="api_platform.hydra.json_schema.schema_factory.inner" />
</service>

</services>

</container>
27 changes: 27 additions & 0 deletions src/Bridge/Symfony/Bundle/Resources/config/json_schema.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
<?xml version="1.0" ?>

<container xmlns="http://symfony.com/schema/dic/services"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd">

<services>
<defaults public="false" />

<service id="api_platform.json_schema.type_factory" class="ApiPlatform\Core\JsonSchema\TypeFactory">
<call method="setSchemaFactory">
<argument type="service" id="api_platform.json_schema.schema_factory"/>
</call>
</service>
<service id="ApiPlatform\Core\JsonSchema\TypeFactoryInterface" alias="api_platform.json_schema.type_factory" />

<service id="api_platform.json_schema.schema_factory" class="ApiPlatform\Core\JsonSchema\SchemaFactory">
<argument type="service" id="api_platform.json_schema.type_factory"></argument>
<argument type="service" id="api_platform.metadata.resource.metadata_factory" />
<argument type="service" id="api_platform.metadata.property.name_collection_factory" />
<argument type="service" id="api_platform.metadata.property.metadata_factory" />
<argument type="service" id="api_platform.name_converter" on-invalid="ignore" />
</service>
<service id="ApiPlatform\Core\JsonSchema\SchemaFactoryInterface" alias="api_platform.json_schema.schema_factory" />
</services>

</container>
6 changes: 3 additions & 3 deletions src/Bridge/Symfony/Bundle/Resources/config/swagger.xml
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,12 @@
<argument type="service" id="api_platform.metadata.resource.metadata_factory" />
<argument type="service" id="api_platform.metadata.property.name_collection_factory" />
<argument type="service" id="api_platform.metadata.property.metadata_factory" />
<argument type="service" id="api_platform.resource_class_resolver" />
<argument>null</argument>
<argument type="service" id="api_platform.json_schema.schema_factory" />
<argument type="service" id="api_platform.json_schema.type_factory" />
<argument type="service" id="api_platform.operation_path_resolver" />
<argument>null</argument>
<argument type="service" id="api_platform.filter_locator" />
<argument type="service" id="api_platform.name_converter" on-invalid="ignore" />
<argument>null</argument>
<argument>%api_platform.oauth.enabled%</argument>
<argument>%api_platform.oauth.type%</argument>
<argument>%api_platform.oauth.flow%</argument>
Expand Down
125 changes: 125 additions & 0 deletions src/Hydra/JsonSchema/SchemaFactory.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
<?php

/*
* This file is part of the API Platform project.
*
* (c) Kévin Dunglas <dunglas@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

declare(strict_types=1);

namespace ApiPlatform\Core\Hydra\JsonSchema;

use ApiPlatform\Core\JsonSchema\Schema;
use ApiPlatform\Core\JsonSchema\SchemaFactory as BaseSchemaFactory;
use ApiPlatform\Core\JsonSchema\SchemaFactoryInterface;

/**
* Generates the JSON Schema corresponding to a Hydra document.
*
* @experimental
*
* @author Kévin Dunglas <dunglas@gmail.com>
*/
final class SchemaFactory implements SchemaFactoryInterface
{
private const BASE_PROP = [
'readOnly' => true,
'type' => 'string',
];
private const BASE_PROPS = [
'@context' => self::BASE_PROP,
'@id' => self::BASE_PROP,
'@type' => self::BASE_PROP,
];

private $schemaFactory;

public function __construct(BaseSchemaFactory $schemaFactory)
{
$this->schemaFactory = $schemaFactory;
$schemaFactory->addDistinctFormat('jsonld');
}

/**
* {@inheritdoc}
*/
public function buildSchema(string $resourceClass, string $format = 'jsonld', bool $output = true, ?string $operationType = null, ?string $operationName = null, ?Schema $schema = null, ?array $serializerContext = null, bool $forceCollection = false): Schema
{
$schema = $this->schemaFactory->buildSchema($resourceClass, $format, $output, $operationType, $operationName, $schema, $serializerContext, $forceCollection);
if ('jsonld' !== $format) {
return $schema;
}

$definitions = $schema->getDefinitions();
if ($key = $schema->getRootDefinitionKey()) {
$definitions[$key]['properties'] = self::BASE_PROPS + ($definitions[$key]['properties'] ?? []);

return $schema;
}

if (($schema['type'] ?? '') === 'array') {
// hydra:collection
$items = $schema['items'];
unset($schema['items']);

$schema['type'] = 'object';
$schema['properties'] = [
'hydra:member' => [
'type' => 'array',
'items' => $items,
],
'hydra:totalItems' => [
'type' => 'integer',
'minimum' => 1,
],
'hydra:view' => [
'type' => 'object',
'properties' => [
'@id' => ['type' => 'string'],
'@type' => ['type' => 'string'],
'hydra:first' => [
'type' => 'integer',
'minimum' => 1,
],
'hydra:last' => [
'type' => 'integer',
'minimum' => 1,
],
'hydra:next' => [
'type' => 'integer',
'minimum' => 1,
],
],
],
'hydra:search' => [
'type' => 'object',
'properties' => [
'@type' => ['type' => 'string'],
'hydra:template' => ['type' => 'string'],
'hydra:variableRepresentation' => ['type' => 'string'],
'hydra:mapping' => [
'type' => 'array',
'items' => [
'type' => 'object',
'properties' => [
'@type' => ['type' => 'string'],
'variable' => ['type' => 'string'],
'property' => ['type' => 'string'],
'required' => ['type' => 'boolean'],
],
],
],
],
],
];

return $schema;
}

return $schema;
}
}
113 changes: 113 additions & 0 deletions src/JsonSchema/Schema.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
<?php

/*
* This file is part of the API Platform project.
*
* (c) Kévin Dunglas <dunglas@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

declare(strict_types=1);

namespace ApiPlatform\Core\JsonSchema;

/**
* Represents a JSON Schema document.
*
* Both the standard version and the OpenAPI flavors (v2 and v3) are supported.
*
* @see https://json-schema.org/latest/json-schema-core.html
* @see https://github.com/OAI/OpenAPI-Specification
*
* @experimental
*
* @author Kévin Dunglas <dunglas@gmail.com>
*/
final class Schema extends \ArrayObject
{
public const VERSION_JSON_SCHEMA = 'json-schema';
public const VERSION_SWAGGER = 'swagger';
public const VERSION_OPENAPI = 'openapi';

private $version;

public function __construct(string $version = self::VERSION_JSON_SCHEMA)
{
$this->version = $version;

parent::__construct(self::VERSION_JSON_SCHEMA === $this->version ? ['$schema' => 'http://json-schema.org/draft-07/schema#'] : []);
}

/**
* The flavor used for this document: JSON Schema, OpenAPI v2 or OpenAPI v3.
*/
public function getVersion(): string
{
return $this->version;
}

/**
* @param bool $includeDefinitions if set to false, definitions will not be included in the resulting array
*/
public function getArrayCopy(bool $includeDefinitions = true): array
{
$schema = parent::getArrayCopy();

if (!$includeDefinitions) {
unset($schema['definitions'], $schema['components']);
}

return $schema;
}

/**
* Retrieves the definitions used by this schema.
*/
public function getDefinitions(): \ArrayObject
{
$definitions = $this['definitions'] ?? $this['components']['schemas'] ?? new \ArrayObject();
$this->setDefinitions($definitions);

return $definitions;
}

/**
* Associates existing definitions to this schema.
*/
public function setDefinitions(\ArrayObject $definitions): void
{
if (self::VERSION_OPENAPI === $this->version) {
$this['components']['schemas'] = $definitions;

return;
}

$this['definitions'] = $definitions;
}

/**
* Returns the name of the root definition, if defined.
*/
public function getRootDefinitionKey(): ?string
{
if (!isset($this['$ref'])) {
return null;
}

// strlen('#/definitions/') = 14
// strlen('#/components/schemas/') = 21
$prefix = self::VERSION_OPENAPI === $this->version ? 21 : 14;

return substr($this['$ref'], $prefix);
}

/**
* Checks if this schema is initialized.
*/
public function isDefined(): bool
{
return isset($this['$ref']) || isset($this['type']);
}
}
Loading

0 comments on commit c421a61

Please sign in to comment.