diff --git a/phpunit.xml.dist b/phpunit.xml.dist index 4b347487936..d8527fddfa3 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -38,6 +38,7 @@ . + tests features vendor .php-cs-fixer.dist.php diff --git a/src/Laravel/Exception/ErrorHandler.php b/src/Laravel/Exception/ErrorHandler.php index 7dcc70277a9..f496c5d1eb5 100644 --- a/src/Laravel/Exception/ErrorHandler.php +++ b/src/Laravel/Exception/ErrorHandler.php @@ -15,6 +15,7 @@ use ApiPlatform\Laravel\ApiResource\Error; use ApiPlatform\Laravel\Controller\ApiPlatformController; +use ApiPlatform\Metadata\Exception\InvalidUriVariableException; use ApiPlatform\Metadata\Exception\ProblemExceptionInterface; use ApiPlatform\Metadata\Exception\StatusAwareExceptionInterface; use ApiPlatform\Metadata\HttpOperation; @@ -192,7 +193,7 @@ private function getStatusCode(?HttpOperation $apiOperation, ?HttpOperation $err return $exception->getStatusCode(); } - if ($exception instanceof RequestExceptionInterface) { + if ($exception instanceof RequestExceptionInterface || $exception instanceof InvalidUriVariableException) { return 400; } diff --git a/src/Symfony/EventListener/ErrorListener.php b/src/Symfony/EventListener/ErrorListener.php index 6f5bf6ebaf0..19e5593a3ca 100644 --- a/src/Symfony/EventListener/ErrorListener.php +++ b/src/Symfony/EventListener/ErrorListener.php @@ -15,6 +15,7 @@ use ApiPlatform\Metadata\Error as ErrorOperation; use ApiPlatform\Metadata\Exception\HttpExceptionInterface; +use ApiPlatform\Metadata\Exception\InvalidUriVariableException; use ApiPlatform\Metadata\Exception\ProblemExceptionInterface; use ApiPlatform\Metadata\HttpOperation; use ApiPlatform\Metadata\IdentifiersExtractorInterface; @@ -178,7 +179,7 @@ private function getStatusCode(?HttpOperation $apiOperation, Request $request, ? return $exception->getStatusCode(); } - if ($exception instanceof RequestExceptionInterface) { + if ($exception instanceof RequestExceptionInterface || $exception instanceof InvalidUriVariableException) { return 400; } diff --git a/tests/Fixtures/TestBundle/Entity/Issue7135/Bar.php b/tests/Fixtures/TestBundle/Entity/Issue7135/Bar.php new file mode 100644 index 00000000000..c762b92c718 --- /dev/null +++ b/tests/Fixtures/TestBundle/Entity/Issue7135/Bar.php @@ -0,0 +1,46 @@ + + * + * 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\Issue7135; + +use ApiPlatform\Metadata\ApiResource; +use ApiPlatform\Metadata\Get; +use Doctrine\ORM\Mapping as ORM; +use Symfony\Component\Uid\Uuid; + +#[ORM\Entity()] +#[ApiResource( + shortName: 'BarPr7135', + operations: [ + new Get( + uriTemplate: '/pull-request-7135/bar/{id}', + ), + ] +)] +#[ORM\Table(name: 'bar6466')] +class Bar +{ + #[ORM\Id] + #[ORM\Column(type: 'symfony_uuid', unique: true)] + #[ORM\GeneratedValue(strategy: 'CUSTOM')] + #[ORM\CustomIdGenerator(class: 'doctrine.uuid_generator')] + public Uuid $id; + + #[ORM\Column] + public string $title = ''; + + public function __construct(?Uuid $id = null) + { + $this->id = $id ?: Uuid::v7(); + } +} diff --git a/tests/Fixtures/TestBundle/Entity/Issue7135/Foo.php b/tests/Fixtures/TestBundle/Entity/Issue7135/Foo.php new file mode 100644 index 00000000000..b45378e50c8 --- /dev/null +++ b/tests/Fixtures/TestBundle/Entity/Issue7135/Foo.php @@ -0,0 +1,48 @@ + + * + * 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\Issue7135; + +use ApiPlatform\Metadata\ApiResource; +use ApiPlatform\Metadata\Post; +use Doctrine\ORM\Mapping as ORM; +use Symfony\Component\Uid\Uuid; + +#[ORM\Entity()] +#[ApiResource( + shortName: 'FooPr7135', + operations: [ + new Post( + uriTemplate: '/pull-request-7135/foo/', + ), + ], + normalizationContext: ['iri_only' => true], +)] +#[ORM\Table(name: 'foo6466')] +class Foo +{ + #[ORM\Id] + #[ORM\Column(type: 'symfony_uuid', unique: true)] + #[ORM\GeneratedValue(strategy: 'CUSTOM')] + #[ORM\CustomIdGenerator(class: 'doctrine.uuid_generator')] + public Uuid $id; + + #[ORM\ManyToOne(targetEntity: Bar::class)] + #[ORM\JoinColumn(referencedColumnName: 'id', nullable: false)] + public Bar $bar; + + public function __construct() + { + $this->id = Uuid::v7(); + } +} diff --git a/tests/Functional/Issues/Issue7135Test.php b/tests/Functional/Issues/Issue7135Test.php new file mode 100644 index 00000000000..22aa5c310fe --- /dev/null +++ b/tests/Functional/Issues/Issue7135Test.php @@ -0,0 +1,121 @@ + + * + * 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\Functional\Issues; + +use ApiPlatform\Symfony\Bundle\Test\ApiTestCase; +use ApiPlatform\Tests\Fixtures\TestBundle\Entity\Issue7135\Bar; +use ApiPlatform\Tests\Fixtures\TestBundle\Entity\Issue7135\Foo; +use ApiPlatform\Tests\RecreateSchemaTrait; +use ApiPlatform\Tests\SetupClassResourcesTrait; +use Doctrine\ORM\EntityManagerInterface; +use Doctrine\ORM\Tools\SchemaTool; +use Symfony\Component\Uid\Uuid; + +class Issue7135Test extends ApiTestCase +{ + use RecreateSchemaTrait; + use SetupClassResourcesTrait; + + protected static ?bool $alwaysBootKernel = false; + + /** + * @return class-string[] + */ + public static function getResources(): array + { + return [Bar::class, Foo::class]; + } + + public function testValidPostRequestWithIriWhenIdentifierIsUuid(): void + { + $container = static::getContainer(); + if ('mongodb' === $container->getParameter('kernel.environment')) { + $this->markTestSkipped(); + } + + $this->recreateSchema(self::getResources()); + $bar = $this->loadBarFixture(); + + $response = self::createClient()->request('POST', '/pull-request-7135/foo/', [ + 'json' => [ + 'bar' => 'pull-request-7135/bar/'.$bar->id, + ], + ]); + + self::assertEquals(201, $response->getStatusCode()); + } + + public function testInvalidPostRequestWithIriWhenIdentifierIsUuid(): void + { + $container = static::getContainer(); + if ('mongodb' === $container->getParameter('kernel.environment')) { + $this->markTestSkipped(); + } + + $response = self::createClient()->request('POST', '/pull-request-7135/foo/', [ + 'json' => [ + 'bar' => 'pull-request-7135/bar/invalid-uuid', + ], + ]); + + self::assertEquals(400, $response->getStatusCode()); + self::assertJsonContains(['detail' => 'Identifier "id" could not be transformed.']); + } + + public function testInvalidGetRequestWhenIdentifierIsUuid(): void + { + $container = static::getContainer(); + if ('mongodb' === $container->getParameter('kernel.environment')) { + $this->markTestSkipped(); + } + + $response = self::createClient()->request('GET', '/pull-request-7135/bar/invalid-uuid'); + + self::assertEquals(404, $response->getStatusCode()); + } + + protected function loadBarFixture(): Bar + { + $container = static::getContainer(); + $registry = $container->get('doctrine'); + $manager = $registry->getManager(); + + $bar = new Bar(Uuid::fromString('0196b66f-66bd-780b-95fe-0ce987a32357')); + $bar->title = 'Bar one'; + $manager->persist($bar); + + $manager->flush(); + + return $bar; + } + + protected function tearDown(): void + { + $container = static::getContainer(); + $registry = $container->get('doctrine'); + $manager = $registry->getManager(); + if (!$manager instanceof EntityManagerInterface) { + return; + } + + $classes = []; + foreach (self::getResources() as $entityClass) { + $classes[] = $manager->getClassMetadata($entityClass); + } + + $schemaTool = new SchemaTool($manager); + @$schemaTool->dropSchema($classes); + parent::tearDown(); + } +}