Skip to content

Commit d3d0ca2

Browse files
authored
fix(serializer): invalid uri variable 400 response (#7135)
1 parent 4dd0cdf commit d3d0ca2

File tree

6 files changed

+220
-2
lines changed

6 files changed

+220
-2
lines changed

phpunit.xml.dist

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@
3838
<directory>.</directory>
3939
</include>
4040
<exclude>
41+
<directory>tests</directory>
4142
<directory>features</directory>
4243
<directory>vendor</directory>
4344
<file>.php-cs-fixer.dist.php</file>

src/Laravel/Exception/ErrorHandler.php

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515

1616
use ApiPlatform\Laravel\ApiResource\Error;
1717
use ApiPlatform\Laravel\Controller\ApiPlatformController;
18+
use ApiPlatform\Metadata\Exception\InvalidUriVariableException;
1819
use ApiPlatform\Metadata\Exception\ProblemExceptionInterface;
1920
use ApiPlatform\Metadata\Exception\StatusAwareExceptionInterface;
2021
use ApiPlatform\Metadata\HttpOperation;
@@ -192,7 +193,7 @@ private function getStatusCode(?HttpOperation $apiOperation, ?HttpOperation $err
192193
return $exception->getStatusCode();
193194
}
194195

195-
if ($exception instanceof RequestExceptionInterface) {
196+
if ($exception instanceof RequestExceptionInterface || $exception instanceof InvalidUriVariableException) {
196197
return 400;
197198
}
198199

src/Symfony/EventListener/ErrorListener.php

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515

1616
use ApiPlatform\Metadata\Error as ErrorOperation;
1717
use ApiPlatform\Metadata\Exception\HttpExceptionInterface;
18+
use ApiPlatform\Metadata\Exception\InvalidUriVariableException;
1819
use ApiPlatform\Metadata\Exception\ProblemExceptionInterface;
1920
use ApiPlatform\Metadata\HttpOperation;
2021
use ApiPlatform\Metadata\IdentifiersExtractorInterface;
@@ -178,7 +179,7 @@ private function getStatusCode(?HttpOperation $apiOperation, Request $request, ?
178179
return $exception->getStatusCode();
179180
}
180181

181-
if ($exception instanceof RequestExceptionInterface) {
182+
if ($exception instanceof RequestExceptionInterface || $exception instanceof InvalidUriVariableException) {
182183
return 400;
183184
}
184185

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the API Platform project.
5+
*
6+
* (c) Kévin Dunglas <[email protected]>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
declare(strict_types=1);
13+
14+
namespace ApiPlatform\Tests\Fixtures\TestBundle\Entity\Issue7135;
15+
16+
use ApiPlatform\Metadata\ApiResource;
17+
use ApiPlatform\Metadata\Get;
18+
use Doctrine\ORM\Mapping as ORM;
19+
use Symfony\Component\Uid\Uuid;
20+
21+
#[ORM\Entity()]
22+
#[ApiResource(
23+
shortName: 'BarPr7135',
24+
operations: [
25+
new Get(
26+
uriTemplate: '/pull-request-7135/bar/{id}',
27+
),
28+
]
29+
)]
30+
#[ORM\Table(name: 'bar6466')]
31+
class Bar
32+
{
33+
#[ORM\Id]
34+
#[ORM\Column(type: 'symfony_uuid', unique: true)]
35+
#[ORM\GeneratedValue(strategy: 'CUSTOM')]
36+
#[ORM\CustomIdGenerator(class: 'doctrine.uuid_generator')]
37+
public Uuid $id;
38+
39+
#[ORM\Column]
40+
public string $title = '';
41+
42+
public function __construct(?Uuid $id = null)
43+
{
44+
$this->id = $id ?: Uuid::v7();
45+
}
46+
}
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the API Platform project.
5+
*
6+
* (c) Kévin Dunglas <[email protected]>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
declare(strict_types=1);
13+
14+
namespace ApiPlatform\Tests\Fixtures\TestBundle\Entity\Issue7135;
15+
16+
use ApiPlatform\Metadata\ApiResource;
17+
use ApiPlatform\Metadata\Post;
18+
use Doctrine\ORM\Mapping as ORM;
19+
use Symfony\Component\Uid\Uuid;
20+
21+
#[ORM\Entity()]
22+
#[ApiResource(
23+
shortName: 'FooPr7135',
24+
operations: [
25+
new Post(
26+
uriTemplate: '/pull-request-7135/foo/',
27+
),
28+
],
29+
normalizationContext: ['iri_only' => true],
30+
)]
31+
#[ORM\Table(name: 'foo6466')]
32+
class Foo
33+
{
34+
#[ORM\Id]
35+
#[ORM\Column(type: 'symfony_uuid', unique: true)]
36+
#[ORM\GeneratedValue(strategy: 'CUSTOM')]
37+
#[ORM\CustomIdGenerator(class: 'doctrine.uuid_generator')]
38+
public Uuid $id;
39+
40+
#[ORM\ManyToOne(targetEntity: Bar::class)]
41+
#[ORM\JoinColumn(referencedColumnName: 'id', nullable: false)]
42+
public Bar $bar;
43+
44+
public function __construct()
45+
{
46+
$this->id = Uuid::v7();
47+
}
48+
}
Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the API Platform project.
5+
*
6+
* (c) Kévin Dunglas <[email protected]>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
declare(strict_types=1);
13+
14+
namespace ApiPlatform\Tests\Functional\Issues;
15+
16+
use ApiPlatform\Symfony\Bundle\Test\ApiTestCase;
17+
use ApiPlatform\Tests\Fixtures\TestBundle\Entity\Issue7135\Bar;
18+
use ApiPlatform\Tests\Fixtures\TestBundle\Entity\Issue7135\Foo;
19+
use ApiPlatform\Tests\RecreateSchemaTrait;
20+
use ApiPlatform\Tests\SetupClassResourcesTrait;
21+
use Doctrine\ORM\EntityManagerInterface;
22+
use Doctrine\ORM\Tools\SchemaTool;
23+
use Symfony\Component\Uid\Uuid;
24+
25+
class Issue7135Test extends ApiTestCase
26+
{
27+
use RecreateSchemaTrait;
28+
use SetupClassResourcesTrait;
29+
30+
protected static ?bool $alwaysBootKernel = false;
31+
32+
/**
33+
* @return class-string[]
34+
*/
35+
public static function getResources(): array
36+
{
37+
return [Bar::class, Foo::class];
38+
}
39+
40+
public function testValidPostRequestWithIriWhenIdentifierIsUuid(): void
41+
{
42+
$container = static::getContainer();
43+
if ('mongodb' === $container->getParameter('kernel.environment')) {
44+
$this->markTestSkipped();
45+
}
46+
47+
$this->recreateSchema(self::getResources());
48+
$bar = $this->loadBarFixture();
49+
50+
$response = self::createClient()->request('POST', '/pull-request-7135/foo/', [
51+
'json' => [
52+
'bar' => 'pull-request-7135/bar/'.$bar->id,
53+
],
54+
]);
55+
56+
self::assertEquals(201, $response->getStatusCode());
57+
}
58+
59+
public function testInvalidPostRequestWithIriWhenIdentifierIsUuid(): void
60+
{
61+
$container = static::getContainer();
62+
if ('mongodb' === $container->getParameter('kernel.environment')) {
63+
$this->markTestSkipped();
64+
}
65+
66+
$response = self::createClient()->request('POST', '/pull-request-7135/foo/', [
67+
'json' => [
68+
'bar' => 'pull-request-7135/bar/invalid-uuid',
69+
],
70+
]);
71+
72+
self::assertEquals(400, $response->getStatusCode());
73+
self::assertJsonContains(['detail' => 'Identifier "id" could not be transformed.']);
74+
}
75+
76+
public function testInvalidGetRequestWhenIdentifierIsUuid(): void
77+
{
78+
$container = static::getContainer();
79+
if ('mongodb' === $container->getParameter('kernel.environment')) {
80+
$this->markTestSkipped();
81+
}
82+
83+
$response = self::createClient()->request('GET', '/pull-request-7135/bar/invalid-uuid');
84+
85+
self::assertEquals(404, $response->getStatusCode());
86+
}
87+
88+
protected function loadBarFixture(): Bar
89+
{
90+
$container = static::getContainer();
91+
$registry = $container->get('doctrine');
92+
$manager = $registry->getManager();
93+
94+
$bar = new Bar(Uuid::fromString('0196b66f-66bd-780b-95fe-0ce987a32357'));
95+
$bar->title = 'Bar one';
96+
$manager->persist($bar);
97+
98+
$manager->flush();
99+
100+
return $bar;
101+
}
102+
103+
protected function tearDown(): void
104+
{
105+
$container = static::getContainer();
106+
$registry = $container->get('doctrine');
107+
$manager = $registry->getManager();
108+
if (!$manager instanceof EntityManagerInterface) {
109+
return;
110+
}
111+
112+
$classes = [];
113+
foreach (self::getResources() as $entityClass) {
114+
$classes[] = $manager->getClassMetadata($entityClass);
115+
}
116+
117+
$schemaTool = new SchemaTool($manager);
118+
@$schemaTool->dropSchema($classes);
119+
parent::tearDown();
120+
}
121+
}

0 commit comments

Comments
 (0)