Skip to content

Commit

Permalink
fix(jsonschema): indirect resource input schema (api-platform#6001)
Browse files Browse the repository at this point in the history
Fixes api-platform#5998

A resource embedded in another class can be writable without having a
write operation (POST, PUT, PATCH).
  • Loading branch information
soyuka authored Nov 27, 2023
1 parent 49e4adc commit e7bc2ab
Show file tree
Hide file tree
Showing 5 changed files with 225 additions and 1 deletion.
7 changes: 6 additions & 1 deletion src/JsonSchema/SchemaFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,8 @@ public function buildSchema(string $className, string $format = 'json', string $
$method = Schema::TYPE_INPUT === $type ? 'POST' : 'GET';
}

if (Schema::TYPE_OUTPUT !== $type && !\in_array($method, ['POST', 'PATCH', 'PUT'], true)) {
// In case of FORCE_SUBSCHEMA an object can be writable through another class eventhough it has no POST operation
if (!($serializerContext[self::FORCE_SUBSCHEMA] ?? false) && Schema::TYPE_OUTPUT !== $type && !\in_array($method, ['POST', 'PATCH', 'PUT'], true)) {
return $schema;
}

Expand Down Expand Up @@ -217,6 +218,10 @@ private function buildPropertySchema(Schema $schema, string $definitionName, str
}

$subSchema = $this->buildSchema($className, $format, $parentType, null, $subSchema, $serializerContext + [self::FORCE_SUBSCHEMA => true], false);
if (!isset($subSchema['$ref'])) {
continue;
}

if ($isCollection) {
$propertySchema['items']['$ref'] = $subSchema['$ref'];
unset($propertySchema['items']['type']);
Expand Down
76 changes: 76 additions & 0 deletions tests/Fixtures/TestBundle/Entity/Issue5998/Issue5998Product.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
<?php

/*
* This file is part of the API Platform project.
*
* (c) Kévin Dunglas <[email protected]>
*
* 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\Issue5998;

use ApiPlatform\Metadata\ApiResource;
use ApiPlatform\Metadata\Post;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\Mapping as ORM;

#[ORM\Entity]
#[ApiResource]
#[Post(
denormalizationContext: ['groups' => ['product:write']],
input: SaveProduct::class,
)]
class Issue5998Product
{
#[ORM\Id]
#[ORM\GeneratedValue]
#[ORM\Column]
private ?int $id = null;

/**
* @var Collection<int, ProductCode>
*/
#[ORM\OneToMany(mappedBy: 'product', targetEntity: ProductCode::class, cascade: ['persist'], orphanRemoval: true)]
private Collection $codes;

public function __construct()
{
$this->codes = new ArrayCollection();
}

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

/**
* @return Collection<int, ProductCode>
*/
public function getCodes(): Collection
{
return $this->codes;
}

public function addCode(ProductCode $code): void
{
if (!$this->codes->contains($code)) {
$this->codes->add($code);
$code->setProduct($this);
}
}

public function removeCode(ProductCode $code): void
{
if ($this->codes->removeElement($code)) {
// set the owning side to null (unless already changed)
if ($code->getProduct() === $this) {
$code->setProduct(null);
}
}
}
}
77 changes: 77 additions & 0 deletions tests/Fixtures/TestBundle/Entity/Issue5998/ProductCode.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
<?php

/*
* This file is part of the API Platform project.
*
* (c) Kévin Dunglas <[email protected]>
*
* 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\Issue5998;

use ApiPlatform\Metadata\ApiResource;
use ApiPlatform\Metadata\Get;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Serializer\Annotation\Groups;

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

#[ORM\Column(length: 180)]
#[Groups(['product:write'])]
private ?string $type = null;

#[ORM\Column(length: 180)]
#[Groups(['product:write'])]
private ?string $value = null;

#[ORM\ManyToOne(inversedBy: 'codes')]
#[ORM\JoinColumn(nullable: false)]
private ?Issue5998Product $product = null;

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

public function getType(): ?string
{
return $this->type;
}

public function setType(string $type): void
{
$this->type = $type;
}

public function getValue(): ?string
{
return $this->value;
}

public function setValue(?string $value): void
{
$this->value = $value;
}

public function getProduct(): ?Issue5998Product
{
return $this->product;
}

public function setProduct(?Issue5998Product $product): void
{
$this->product = $product;
}
}
54 changes: 54 additions & 0 deletions tests/Fixtures/TestBundle/Entity/Issue5998/SaveProduct.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
<?php

/*
* This file is part of the API Platform project.
*
* (c) Kévin Dunglas <[email protected]>
*
* 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\Issue5998;

use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Symfony\Component\Serializer\Annotation\Groups;

class SaveProduct
{
/**
* @var Collection<int, ProductCode>
*/
#[Groups(['product:write'])]
private Collection $codes;

public function __construct()
{
$this->codes = new ArrayCollection();
}

/**
* @return Collection<int, ProductCode>
*/
public function getCodes(): Collection
{
return $this->codes;
}

public function addCode(ProductCode $code): void
{
if (!$this->codes->contains($code)) {
$this->codes->add($code);
}
}

public function removeCode(ProductCode $code): void
{
if ($this->codes->contains($code)) {
$this->codes->removeElement($code);
}
}
}
12 changes: 12 additions & 0 deletions tests/JsonSchema/Command/JsonSchemaGenerateCommandTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -138,4 +138,16 @@ public function testArraySchemaWithTypeFactory(): void

$this->assertEquals($json['definitions']['Foo.jsonld']['properties']['expiration'], ['type' => 'string', 'format' => 'date']);
}

/**
* Test issue #5998.
*/
public function testWritableNonResourceRef(): void
{
$this->tester->run(['command' => 'api:json-schema:generate', 'resource' => 'ApiPlatform\Tests\Fixtures\TestBundle\Entity\Issue5998\SaveProduct', '--type' => 'input']);
$result = $this->tester->getDisplay();
$json = json_decode($result, associative: true);

$this->assertEquals($json['definitions']['SaveProduct.jsonld']['properties']['codes']['items']['$ref'], '#/definitions/ProductCode.jsonld');
}
}

0 comments on commit e7bc2ab

Please sign in to comment.