From 663e376f86f52abaa950748f3ff6b174ac41488f Mon Sep 17 00:00:00 2001 From: Maxcastel Date: Fri, 16 Oct 2026 13:44:18 +0200 Subject: [PATCH 1/2] fix(metadata): reject invalid HTTP status codes on operations --- .../Factory/OperationDefaultsTrait.php | 6 +++++ .../InvalidExceptionToStatusOperation.php | 23 +++++++++++++++++++ .../ApiResource/InvalidStatusOperation.php | 23 +++++++++++++++++++ ...sResourceMetadataCollectionFactoryTest.php | 22 ++++++++++++++++++ 4 files changed, 74 insertions(+) create mode 100644 src/Metadata/Tests/Fixtures/ApiResource/InvalidExceptionToStatusOperation.php create mode 100644 src/Metadata/Tests/Fixtures/ApiResource/InvalidStatusOperation.php diff --git a/src/Metadata/Resource/Factory/OperationDefaultsTrait.php b/src/Metadata/Resource/Factory/OperationDefaultsTrait.php index 93f90ca03a..0a1ac6a36d 100644 --- a/src/Metadata/Resource/Factory/OperationDefaultsTrait.php +++ b/src/Metadata/Resource/Factory/OperationDefaultsTrait.php @@ -225,6 +225,12 @@ private function getOperationWithDefaults(ApiResource $resource, Operation $oper $operationName = $operation->getName() ?? $this->getDefaultOperationName($operation, $resource->getClass()); + foreach ([$operation->getStatus(), ...array_values($operation->getExceptionToStatus() ?? [])] as $status) { + if (null !== $status && (!\is_int($status) || $status < 100 || $status >= 600)) { + throw new RuntimeException(\sprintf('The HTTP status code "%s" of operation "%s" of resource "%s" is not valid.', $status, $operationName, $resource->getClass())); + } + } + return [ $operationName, $operation, diff --git a/src/Metadata/Tests/Fixtures/ApiResource/InvalidExceptionToStatusOperation.php b/src/Metadata/Tests/Fixtures/ApiResource/InvalidExceptionToStatusOperation.php new file mode 100644 index 0000000000..c0e3217330 --- /dev/null +++ b/src/Metadata/Tests/Fixtures/ApiResource/InvalidExceptionToStatusOperation.php @@ -0,0 +1,23 @@ + + * + * 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\Metadata\Tests\Fixtures\ApiResource; + +use ApiPlatform\Metadata\ApiResource; +use ApiPlatform\Metadata\Get; + +#[ApiResource(operations: [new Get(exceptionToStatus: [\LogicException::class => 999])])] +class InvalidExceptionToStatusOperation +{ + public ?int $id = null; +} diff --git a/src/Metadata/Tests/Fixtures/ApiResource/InvalidStatusOperation.php b/src/Metadata/Tests/Fixtures/ApiResource/InvalidStatusOperation.php new file mode 100644 index 0000000000..b10db554ab --- /dev/null +++ b/src/Metadata/Tests/Fixtures/ApiResource/InvalidStatusOperation.php @@ -0,0 +1,23 @@ + + * + * 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\Metadata\Tests\Fixtures\ApiResource; + +use ApiPlatform\Metadata\ApiResource; +use ApiPlatform\Metadata\Post; + +#[ApiResource(operations: [new Post(status: 999)])] +class InvalidStatusOperation +{ + public ?int $id = null; +} diff --git a/src/Metadata/Tests/Resource/Factory/AttributesResourceMetadataCollectionFactoryTest.php b/src/Metadata/Tests/Resource/Factory/AttributesResourceMetadataCollectionFactoryTest.php index 1a995b3736..bb3440bef2 100644 --- a/src/Metadata/Tests/Resource/Factory/AttributesResourceMetadataCollectionFactoryTest.php +++ b/src/Metadata/Tests/Resource/Factory/AttributesResourceMetadataCollectionFactoryTest.php @@ -37,6 +37,8 @@ use ApiPlatform\Metadata\Tests\Fixtures\ApiResource\AttributeResource; use ApiPlatform\Metadata\Tests\Fixtures\ApiResource\AttributeResources; use ApiPlatform\Metadata\Tests\Fixtures\ApiResource\ExtraPropertiesResource; +use ApiPlatform\Metadata\Tests\Fixtures\ApiResource\InvalidExceptionToStatusOperation; +use ApiPlatform\Metadata\Tests\Fixtures\ApiResource\InvalidStatusOperation; use ApiPlatform\Metadata\Tests\Fixtures\ApiResource\MutationDescription; use ApiPlatform\Metadata\Tests\Fixtures\ApiResource\PasswordResource; use ApiPlatform\Metadata\Tests\Fixtures\ApiResource\ResourceClassPropagation; @@ -379,6 +381,26 @@ public function testDeduplicateShortNames(): void } } + public function testInvalidStatusThrows(): void + { + $factory = new AttributesResourceMetadataCollectionFactory(); + + $this->expectException(\ApiPlatform\Metadata\Exception\RuntimeException::class); + $this->expectExceptionMessage(\sprintf('The HTTP status code "999" of operation "_api_InvalidStatusOperation_post" of resource "%s" is not valid.', InvalidStatusOperation::class)); + + $factory->create(InvalidStatusOperation::class); + } + + public function testInvalidExceptionToStatusThrows(): void + { + $factory = new AttributesResourceMetadataCollectionFactory(); + + $this->expectException(\ApiPlatform\Metadata\Exception\RuntimeException::class); + $this->expectExceptionMessage(\sprintf('The HTTP status code "999" of operation "_api_InvalidExceptionToStatusOperation_get" of resource "%s" is not valid.', InvalidExceptionToStatusOperation::class)); + + $factory->create(InvalidExceptionToStatusOperation::class); + } + public function testWithParameters(): void { $attributeResourceMetadataCollectionFactory = new AttributesResourceMetadataCollectionFactory(); From c5a3a0d0664c86b8a24e5c322c7abef2a4c1582b Mon Sep 17 00:00:00 2001 From: soyuka Date: Tue, 3 Nov 2026 16:42:55 +0100 Subject: [PATCH 2/2] refactor(metadata): split status checks --- .../Factory/OperationDefaultsTrait.php | 11 ++++++--- .../ApiResource/InvalidStatusOperation.php | 23 ------------------- ...sResourceMetadataCollectionFactoryTest.php | 16 +++---------- 3 files changed, 11 insertions(+), 39 deletions(-) delete mode 100644 src/Metadata/Tests/Fixtures/ApiResource/InvalidStatusOperation.php diff --git a/src/Metadata/Resource/Factory/OperationDefaultsTrait.php b/src/Metadata/Resource/Factory/OperationDefaultsTrait.php index 0a1ac6a36d..82d0535e23 100644 --- a/src/Metadata/Resource/Factory/OperationDefaultsTrait.php +++ b/src/Metadata/Resource/Factory/OperationDefaultsTrait.php @@ -225,9 +225,14 @@ private function getOperationWithDefaults(ApiResource $resource, Operation $oper $operationName = $operation->getName() ?? $this->getDefaultOperationName($operation, $resource->getClass()); - foreach ([$operation->getStatus(), ...array_values($operation->getExceptionToStatus() ?? [])] as $status) { - if (null !== $status && (!\is_int($status) || $status < 100 || $status >= 600)) { - throw new RuntimeException(\sprintf('The HTTP status code "%s" of operation "%s" of resource "%s" is not valid.', $status, $operationName, $resource->getClass())); + $status = $operation->getStatus(); + if (null !== $status && ($status < 100 || $status >= 600)) { + throw new RuntimeException(\sprintf('The HTTP status code "%s" of operation "%s" of resource "%s" is not valid.', $status, $operationName, $resource->getClass())); + } + + foreach ($operation->getExceptionToStatus() ?? [] as $exceptionStatus) { + if (!\is_int($exceptionStatus) || $exceptionStatus < 100 || $exceptionStatus >= 600) { + throw new RuntimeException(\sprintf('The HTTP status code "%s" of operation "%s" of resource "%s" is not valid.', $exceptionStatus, $operationName, $resource->getClass())); } } diff --git a/src/Metadata/Tests/Fixtures/ApiResource/InvalidStatusOperation.php b/src/Metadata/Tests/Fixtures/ApiResource/InvalidStatusOperation.php deleted file mode 100644 index b10db554ab..0000000000 --- a/src/Metadata/Tests/Fixtures/ApiResource/InvalidStatusOperation.php +++ /dev/null @@ -1,23 +0,0 @@ - - * - * 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\Metadata\Tests\Fixtures\ApiResource; - -use ApiPlatform\Metadata\ApiResource; -use ApiPlatform\Metadata\Post; - -#[ApiResource(operations: [new Post(status: 999)])] -class InvalidStatusOperation -{ - public ?int $id = null; -} diff --git a/src/Metadata/Tests/Resource/Factory/AttributesResourceMetadataCollectionFactoryTest.php b/src/Metadata/Tests/Resource/Factory/AttributesResourceMetadataCollectionFactoryTest.php index bb3440bef2..be5a6a7746 100644 --- a/src/Metadata/Tests/Resource/Factory/AttributesResourceMetadataCollectionFactoryTest.php +++ b/src/Metadata/Tests/Resource/Factory/AttributesResourceMetadataCollectionFactoryTest.php @@ -15,6 +15,7 @@ use ApiPlatform\Metadata\ApiResource; use ApiPlatform\Metadata\Delete; +use ApiPlatform\Metadata\Exception\RuntimeException; use ApiPlatform\Metadata\Get; use ApiPlatform\Metadata\GetCollection; use ApiPlatform\Metadata\GraphQl\DeleteMutation; @@ -38,7 +39,6 @@ use ApiPlatform\Metadata\Tests\Fixtures\ApiResource\AttributeResources; use ApiPlatform\Metadata\Tests\Fixtures\ApiResource\ExtraPropertiesResource; use ApiPlatform\Metadata\Tests\Fixtures\ApiResource\InvalidExceptionToStatusOperation; -use ApiPlatform\Metadata\Tests\Fixtures\ApiResource\InvalidStatusOperation; use ApiPlatform\Metadata\Tests\Fixtures\ApiResource\MutationDescription; use ApiPlatform\Metadata\Tests\Fixtures\ApiResource\PasswordResource; use ApiPlatform\Metadata\Tests\Fixtures\ApiResource\ResourceClassPropagation; @@ -381,21 +381,11 @@ public function testDeduplicateShortNames(): void } } - public function testInvalidStatusThrows(): void - { - $factory = new AttributesResourceMetadataCollectionFactory(); - - $this->expectException(\ApiPlatform\Metadata\Exception\RuntimeException::class); - $this->expectExceptionMessage(\sprintf('The HTTP status code "999" of operation "_api_InvalidStatusOperation_post" of resource "%s" is not valid.', InvalidStatusOperation::class)); - - $factory->create(InvalidStatusOperation::class); - } - public function testInvalidExceptionToStatusThrows(): void { $factory = new AttributesResourceMetadataCollectionFactory(); - $this->expectException(\ApiPlatform\Metadata\Exception\RuntimeException::class); + $this->expectException(RuntimeException::class); $this->expectExceptionMessage(\sprintf('The HTTP status code "999" of operation "_api_InvalidExceptionToStatusOperation_get" of resource "%s" is not valid.', InvalidExceptionToStatusOperation::class)); $factory->create(InvalidExceptionToStatusOperation::class); @@ -457,7 +447,7 @@ public function testDuplicateOperationNameThrows(): void { $factory = new AttributesResourceMetadataCollectionFactory(); - $this->expectException(\ApiPlatform\Metadata\Exception\RuntimeException::class); + $this->expectException(RuntimeException::class); $this->expectExceptionMessageMatches('/_api_\/forms\/\{id\}\/submit\{\._format\}/'); $factory->create(SameNameDifferentMethodOperations::class);