Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
98 changes: 50 additions & 48 deletions src/Hydra/JsonSchema/SchemaFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -117,69 +117,35 @@ public function buildSchema(string $className, string $format = 'jsonld', string
$format = 'json';
}

if ('jsonld' !== $format) {
if ('jsonld' !== $format || !$this->isResourceClass($className)) {
return $this->schemaFactory->buildSchema($className, $format, $type, $operation, $schema, $serializerContext, $forceCollection);
}
if (!$this->isResourceClass($className)) {
$operation = null;
$inputOrOutputClass = null;
$serializerContext ??= [];
} else {
$operation = $this->findOperation($className, $type, $operation, $serializerContext, $format);
$inputOrOutputClass = $this->findOutputClass($className, $type, $operation, $serializerContext);
$serializerContext ??= $this->getSerializerContext($operation, $type);
}

$operation = $this->findOperation($className, $type, $operation, $serializerContext, $format);
$inputOrOutputClass = $this->findOutputClass($className, $type, $operation, $serializerContext);
$serializerContext ??= $this->getSerializerContext($operation, $type);

if (null === $inputOrOutputClass) {
// input or output disabled
return $this->schemaFactory->buildSchema($className, $format, $type, $operation, $schema, $serializerContext, $forceCollection);
}

$definitionName = $this->definitionNameFactory->create($className, $format, $inputOrOutputClass, $operation, $serializerContext);

// JSON-LD is slightly different then JSON:API or HAL
// All the references that are resources must also be in JSON-LD therefore combining
// the HydraItemBaseSchema and the JSON schema is harder (unless we loop again through all relationship)
// The less intensive path is to compute the jsonld schemas, then to combine in an allOf
$schema = $this->schemaFactory->buildSchema($className, 'jsonld', $type, $operation, $schema, $serializerContext, $forceCollection);
$definitions = $schema->getDefinitions();

$prefix = $this->getSchemaUriPrefix($schema->getVersion());
$collectionKey = $schema->getItemsDefinitionKey();
$key = $schema->getRootDefinitionKey() ?? $collectionKey;

if (!$collectionKey) {
if ($this->transformed[$definitionName] ?? false) {
return $schema;
}

$hasNoId = Schema::TYPE_OUTPUT === $type && false === ($serializerContext['gen_id'] ?? true);
$baseName = self::ITEM_BASE_SCHEMA_NAME;

if ($hasNoId) {
$baseName = self::ITEM_WITHOUT_ID_BASE_SCHEMA_NAME;
$definitionName = $schema->getRootDefinitionKey() ?? $this->definitionNameFactory->create($className, $format, $inputOrOutputClass, $operation, $serializerContext);
$this->decorateItemDefinition($definitionName, $definitions, $prefix, $type, $serializerContext);

if (isset($definitions[$definitionName])) {
$currentDefinitions = $schema->getDefinitions();
$schema->exchangeArray([]); // Clear the schema
$schema['$ref'] = $prefix.$definitionName;
$schema->setDefinitions($currentDefinitions);
}

if (!isset($definitions[$baseName])) {
$definitions[$baseName] = $hasNoId ? self::ITEM_BASE_SCHEMA_WITHOUT_ID : self::ITEM_BASE_SCHEMA_WITH_ID;
}

$allOf = new \ArrayObject(['allOf' => [
['$ref' => $prefix.$baseName],
$definitions[$key],
]]);

if (isset($definitions[$key]['description'])) {
$allOf['description'] = $definitions[$key]['description'];
}

$definitions[$definitionName] = $allOf;
unset($definitions[$definitionName]['allOf'][1]['description']);

$schema['$ref'] = $prefix.$definitionName;

$this->transformed[$definitionName] = true;

return $schema;
}

Expand All @@ -206,11 +172,11 @@ public function buildSchema(string $className, string $format = 'jsonld', string
'type' => 'object',
'required' => [
$hydraPrefix.'member',
'items' => ['type' => 'object'],
],
'properties' => [
$hydraPrefix.'member' => [
'type' => 'array',
'items' => ['type' => 'object'],
],
$hydraPrefix.'totalItems' => [
'type' => 'integer',
Expand Down Expand Up @@ -276,6 +242,7 @@ public function buildSchema(string $className, string $format = 'jsonld', string
];
}

$definitionName = $this->definitionNameFactory->create($className, $format, $inputOrOutputClass, $operation, $serializerContext);
$schema['type'] = 'object';
$schema['description'] = "$definitionName collection.";
$schema['allOf'] = [
Expand All @@ -293,6 +260,10 @@ public function buildSchema(string $className, string $format = 'jsonld', string

unset($schema['items']);

if (isset($definitions[$collectionKey])) {
$this->decorateItemDefinition($collectionKey, $definitions, $prefix, $type, $serializerContext);
}

return $schema;
}

Expand All @@ -302,4 +273,35 @@ public function setSchemaFactory(SchemaFactoryInterface $schemaFactory): void
$this->schemaFactory->setSchemaFactory($schemaFactory);
}
}

private function decorateItemDefinition(string $definitionName, \ArrayObject $definitions, string $prefix, string $type, ?array $serializerContext): void
{
if (!isset($definitions[$definitionName]) || ($this->transformed[$definitionName] ?? false)) {
return;
}

$hasNoId = Schema::TYPE_OUTPUT === $type && false === ($serializerContext['gen_id'] ?? true);
$baseName = self::ITEM_BASE_SCHEMA_NAME;
if ($hasNoId) {
$baseName = self::ITEM_WITHOUT_ID_BASE_SCHEMA_NAME;
}

if (!isset($definitions[$baseName])) {
$definitions[$baseName] = $hasNoId ? self::ITEM_BASE_SCHEMA_WITHOUT_ID : self::ITEM_BASE_SCHEMA_WITH_ID;
}

$allOf = new \ArrayObject(['allOf' => [
['$ref' => $prefix.$baseName],
$definitions[$definitionName],
]]);

if (isset($definitions[$definitionName]['description'])) {
$allOf['description'] = $definitions[$definitionName]['description'];
}

$definitions[$definitionName] = $allOf;
unset($definitions[$definitionName]['allOf'][1]['description']);

$this->transformed[$definitionName] = true;
}
}
31 changes: 31 additions & 0 deletions tests/Fixtures/TestBundle/ApiResource/Issue7426/Boat.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
<?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\Tests\Fixtures\TestBundle\ApiResource\Issue7426;

use ApiPlatform\Metadata\ApiProperty;
use ApiPlatform\Metadata\GetCollection;
use Symfony\Component\Serializer\Attribute\Groups;

#[GetCollection(
normalizationContext: ['groups' => ['boat:read']],
)]
class Boat
{
#[ApiProperty(identifier: true)]
#[Groups(['boat:read'])]
public int $id;

#[Groups(['boat:read'])]
public string $name;
}
13 changes: 12 additions & 1 deletion tests/Functional/JsonSchema/JsonLdJsonSchemaTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,9 @@
namespace ApiPlatform\Tests\Functional\JsonSchema;

use ApiPlatform\JsonSchema\SchemaFactoryInterface;
use ApiPlatform\Metadata\Operation\Factory\OperationMetadataFactoryInterface;
use ApiPlatform\Symfony\Bundle\Test\ApiTestCase;
use ApiPlatform\Tests\Fixtures\TestBundle\ApiResource\Issue7426\Boat;
use ApiPlatform\Tests\Fixtures\TestBundle\Entity\Issue5793\BagOfTests;
use ApiPlatform\Tests\Fixtures\TestBundle\Entity\Issue6212\Nest;
use ApiPlatform\Tests\Fixtures\TestBundle\Entity\RelatedDummy;
Expand All @@ -27,6 +29,7 @@ final class JsonLdJsonSchemaTest extends ApiTestCase
use SetupClassResourcesTrait;

protected SchemaFactoryInterface $schemaFactory;
protected OperationMetadataFactoryInterface $operationMetadataFactory;

protected static ?bool $alwaysBootKernel = false;

Expand All @@ -35,13 +38,14 @@ final class JsonLdJsonSchemaTest extends ApiTestCase
*/
public static function getResources(): array
{
return [RelatedDummy::class, ThirdLevel::class, RelatedToDummyFriend::class];
return [RelatedDummy::class, ThirdLevel::class, RelatedToDummyFriend::class, Boat::class];
}

protected function setUp(): void
{
parent::setUp();
$this->schemaFactory = self::getContainer()->get('api_platform.json_schema.schema_factory');
$this->operationMetadataFactory = self::getContainer()->get('api_platform.metadata.operation.metadata_factory');
}

public function testSubSchemaJsonLd(): void
Expand Down Expand Up @@ -169,4 +173,11 @@ public function testArraySchemaWithMultipleUnionTypes(): void

$this->assertArrayHasKey('Nest.jsonld', $schema['definitions']);
}

public function testSchemaWithoutGetOperation(): void
{
$schema = $this->schemaFactory->buildSchema(Boat::class, 'jsonld', 'output', $this->operationMetadataFactory->create('_api_/boats{._format}_get_collection'));

$this->assertEquals(['$ref' => '#/definitions/HydraItemBaseSchema'], $schema->getDefinitions()['Boat.jsonld-boat.read']['allOf'][0]);
}
}
Loading