Skip to content

Commit

Permalink
fix(jsonschema): don't skip remaining multiple union types
Browse files Browse the repository at this point in the history
  • Loading branch information
GwendolenLynch committed Mar 12, 2024
1 parent 1cc114b commit 800c5bd
Show file tree
Hide file tree
Showing 6 changed files with 172 additions and 8 deletions.
22 changes: 14 additions & 8 deletions src/JsonSchema/SchemaFactory.php
Expand Up @@ -196,10 +196,13 @@ private function buildPropertySchema(Schema $schema, string $definitionName, str
// property schema is created in SchemaPropertyMetadataFactory, but it cannot build resource reference ($ref)
// complete property schema with resource reference ($ref) only if it's related to an object
$version = $schema->getVersion();
$subSchema = new Schema($version);
$subSchema->setDefinitions($schema->getDefinitions()); // Populate definitions of the main schema
$refs = [];
$isNullable = null;

foreach ($types as $type) {
$subSchema = new Schema($version);
$subSchema->setDefinitions($schema->getDefinitions()); // Populate definitions of the main schema

// TODO: in 3.3 add trigger_deprecation() as type factories are not used anymore, we moved this logic to SchemaPropertyMetadataFactory so that it gets cached
if ($typeFromFactory = $this->typeFactory?->getType($type, 'jsonschema', $propertyMetadata->isReadableLink(), $serializerContext)) {
$propertySchema = $typeFromFactory;
Expand Down Expand Up @@ -230,14 +233,17 @@ private function buildPropertySchema(Schema $schema, string $definitionName, str
break;
}

if ($type->isNullable()) {
$propertySchema['anyOf'] = [['$ref' => $subSchema['$ref']], ['type' => 'null']];
} else {
$propertySchema['$ref'] = $subSchema['$ref'];
}
$refs[] = ['$ref' => $subSchema['$ref']];
$isNullable = $isNullable ?? $type->isNullable();

Check warning on line 237 in src/JsonSchema/SchemaFactory.php

View check run for this annotation

Codecov / codecov/patch

src/JsonSchema/SchemaFactory.php#L236-L237

Added lines #L236 - L237 were not covered by tests
}

if ($isNullable && !\in_array(['type' => 'null'], $refs, true)) {

Check failure on line 240 in src/JsonSchema/SchemaFactory.php

View workflow job for this annotation

GitHub Actions / PHPStan (PHP 8.3)

Call to function in_array() with arguments array{type: 'null'}, array<int<0, max>, array{$ref: mixed}> and true will always evaluate to false.
$refs[] = ['type' => 'null'];

Check warning on line 241 in src/JsonSchema/SchemaFactory.php

View check run for this annotation

Codecov / codecov/patch

src/JsonSchema/SchemaFactory.php#L241

Added line #L241 was not covered by tests
}

if (\count($refs) > 0) {
$propertySchema['anyOf'] = $refs;

Check warning on line 245 in src/JsonSchema/SchemaFactory.php

View check run for this annotation

Codecov / codecov/patch

src/JsonSchema/SchemaFactory.php#L245

Added line #L245 was not covered by tests
unset($propertySchema['type']);
break;
}

$schema->getDefinitions()[$definitionName]['properties'][$normalizedPropertyName] = new \ArrayObject($propertySchema);
Expand Down
21 changes: 21 additions & 0 deletions tests/Fixtures/TestBundle/ApiResource/Issue6212/Bird.php
@@ -0,0 +1,21 @@
<?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\Issue6212;

interface Bird extends \JsonSerializable
{
public function getName(): ?string;

public function getAge(): ?int;
}
35 changes: 35 additions & 0 deletions tests/Fixtures/TestBundle/ApiResource/Issue6212/Robin.php
@@ -0,0 +1,35 @@
<?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\Issue6212;

final class Robin implements Bird
{
public ?string $name = null;
public ?int $age = null;

public function getName(): ?string
{
return $this->name;
}

public function getAge(): ?int
{
return $this->age;
}

public function jsonSerialize(): array
{
return get_object_vars($this);
}
}
36 changes: 36 additions & 0 deletions tests/Fixtures/TestBundle/ApiResource/Issue6212/Wren.php
@@ -0,0 +1,36 @@
<?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\Issue6212;

final class Wren implements Bird
{
public ?string $name = null;
public ?int $age = null;
public ?int $weight = null;

public function getName(): ?string
{
return $this->name;
}

public function getAge(): ?int
{
return $this->age;
}

public function jsonSerialize(): array
{
return get_object_vars($this);
}
}
50 changes: 50 additions & 0 deletions tests/Fixtures/TestBundle/Entity/Issue6212/Nest.php
@@ -0,0 +1,50 @@
<?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\Entity\Issue6212;

use ApiPlatform\Metadata\ApiResource;
use ApiPlatform\Tests\Fixtures\TestBundle\ApiResource\Issue6212\Bird;
use ApiPlatform\Tests\Fixtures\TestBundle\ApiResource\Issue6212\Robin;
use ApiPlatform\Tests\Fixtures\TestBundle\ApiResource\Issue6212\Wren;
use Doctrine\ORM\Mapping as ORM;

#[ApiResource]
#[ORM\Entity]
class Nest
{
#[ORM\Id]
#[ORM\GeneratedValue]
#[ORM\Column]
private ?int $id = null;

#[ORM\Column(type: 'json')]
private ?Bird $owner;

public function getId(): ?int
{
return $this->id;
}

public function getOwner(): ?Bird
{
return $this->owner;
}

public function setOwner(Wren|Robin|null $owner): static
{
$this->owner = $owner;

return $this;
}
}
16 changes: 16 additions & 0 deletions tests/JsonSchema/Command/JsonSchemaGenerateCommandTest.php
Expand Up @@ -124,6 +124,22 @@ public function testArraySchemaWithReference(): void
]);
}

public function testArraySchemaWithMultipleUnionTypes(): void
{
$this->tester->run(['command' => 'api:json-schema:generate', 'resource' => 'ApiPlatform\Tests\Fixtures\TestBundle\Entity\Issue6212\Nest', '--type' => 'output']);
$result = $this->tester->getDisplay();
$json = json_decode($result, associative: true);

$this->assertEquals($json['definitions']['Nest.jsonld']['properties']['owner']['anyOf'], [
['$ref' => '#/definitions/Wren.jsonld'],
['$ref' => '#/definitions/Robin.jsonld'],
['type' => 'null'],
]);

$this->assertArrayHasKey('Wren.jsonld', $json['definitions']);
$this->assertArrayHasKey('Robin.jsonld', $json['definitions']);
}

/**
* TODO: add deprecation (TypeFactory will be deprecated in api platform 3.3).
*
Expand Down

0 comments on commit 800c5bd

Please sign in to comment.