diff --git a/.commitlintrc b/.commitlintrc index e552e57c20a..6e77094ed54 100644 --- a/.commitlintrc +++ b/.commitlintrc @@ -16,6 +16,7 @@ "jsonapi", "graphql", "openapi", + "parametervalidator", "serializer", "jsonschema", "validation", diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 42b3b0c0ecb..d67c9a4d039 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -22,7 +22,7 @@ jobs: with: fetch-depth: 0 - name: Run commitlint - run: | + run: | commit=$(gh api \ /repos/${{ github.repository }}/pulls/${{github.event.number}}/commits \ | jq -r '.[0].commit.message' \ @@ -197,11 +197,13 @@ jobs: - JsonSchema - OpenApi - Metadata + - ParameterValidator - Elasticsearch - HttpCache - RamseyUuid - GraphQl - Serializer + - Symfony fail-fast: false steps: - name: Checkout @@ -1139,4 +1141,3 @@ jobs: name: openapi-docs-php${{ matrix.php }} path: build/out/openapi continue-on-error: true - diff --git a/src/Api/QueryParameterValidator/QueryParameterValidator.php b/src/Api/QueryParameterValidator/QueryParameterValidator.php index 1a36f6e6284..4c3954865bd 100644 --- a/src/Api/QueryParameterValidator/QueryParameterValidator.php +++ b/src/Api/QueryParameterValidator/QueryParameterValidator.php @@ -13,63 +13,13 @@ namespace ApiPlatform\Api\QueryParameterValidator; -use ApiPlatform\Api\FilterLocatorTrait; -use ApiPlatform\Api\QueryParameterValidator\Validator\ArrayItems; -use ApiPlatform\Api\QueryParameterValidator\Validator\Bounds; -use ApiPlatform\Api\QueryParameterValidator\Validator\Enum; -use ApiPlatform\Api\QueryParameterValidator\Validator\Length; -use ApiPlatform\Api\QueryParameterValidator\Validator\MultipleOf; -use ApiPlatform\Api\QueryParameterValidator\Validator\Pattern; -use ApiPlatform\Api\QueryParameterValidator\Validator\Required; -use ApiPlatform\Exception\FilterValidationException; -use Psr\Container\ContainerInterface; +use ApiPlatform\ParameterValidator\ParameterValidator as NewQueryParameterValidator; /** * Validates query parameters depending on filter description. * - * @author Julien Deniau + * @deprecated use ApiPlatform\QueryParameterValidator\QueryParameterValidator instead */ -class QueryParameterValidator +class QueryParameterValidator extends NewQueryParameterValidator { - use FilterLocatorTrait; - - private array $validators; - - public function __construct(ContainerInterface $filterLocator) - { - $this->setFilterLocator($filterLocator); - - $this->validators = [ - new ArrayItems(), - new Bounds(), - new Enum(), - new Length(), - new MultipleOf(), - new Pattern(), - new Required(), - ]; - } - - public function validateFilters(string $resourceClass, array $resourceFilters, array $queryParameters): void - { - $errorList = []; - - foreach ($resourceFilters as $filterId) { - if (!$filter = $this->getFilter($filterId)) { - continue; - } - - foreach ($filter->getDescription($resourceClass) as $name => $data) { - foreach ($this->validators as $validator) { - if ($errors = $validator->validate($name, $data, $queryParameters)) { - $errorList[] = $errors; - } - } - } - } - - if ($errorList) { - throw new FilterValidationException(array_merge(...$errorList)); - } - } } diff --git a/src/Api/QueryParameterValidator/Validator/ArrayItems.php b/src/Api/QueryParameterValidator/Validator/ArrayItems.php index 8cc5b50c92c..c641bd3c82a 100644 --- a/src/Api/QueryParameterValidator/Validator/ArrayItems.php +++ b/src/Api/QueryParameterValidator/Validator/ArrayItems.php @@ -13,6 +13,12 @@ namespace ApiPlatform\Api\QueryParameterValidator\Validator; +use ApiPlatform\ParameterValidator\Validator\CheckFilterDeprecationsTrait; +use ApiPlatform\ParameterValidator\Validator\ValidatorInterface; + +/** + * @deprecated use \ApiPlatform\ParameterValidator\Validator\ArrayItems instead + */ final class ArrayItems implements ValidatorInterface { use CheckFilterDeprecationsTrait; diff --git a/src/Api/QueryParameterValidator/Validator/Bounds.php b/src/Api/QueryParameterValidator/Validator/Bounds.php index 9ebf3285380..a76c25d709e 100644 --- a/src/Api/QueryParameterValidator/Validator/Bounds.php +++ b/src/Api/QueryParameterValidator/Validator/Bounds.php @@ -13,6 +13,12 @@ namespace ApiPlatform\Api\QueryParameterValidator\Validator; +use ApiPlatform\ParameterValidator\Validator\CheckFilterDeprecationsTrait; +use ApiPlatform\ParameterValidator\Validator\ValidatorInterface; + +/** + * @deprecated use \ApiPlatform\ParameterValidator\Validator\Bounds instead + */ final class Bounds implements ValidatorInterface { use CheckFilterDeprecationsTrait; diff --git a/src/Api/QueryParameterValidator/Validator/Enum.php b/src/Api/QueryParameterValidator/Validator/Enum.php index d8f44634039..cd7c7a5e64a 100644 --- a/src/Api/QueryParameterValidator/Validator/Enum.php +++ b/src/Api/QueryParameterValidator/Validator/Enum.php @@ -13,6 +13,12 @@ namespace ApiPlatform\Api\QueryParameterValidator\Validator; +use ApiPlatform\ParameterValidator\Validator\CheckFilterDeprecationsTrait; +use ApiPlatform\ParameterValidator\Validator\ValidatorInterface; + +/** + * @deprecated use \ApiPlatform\ParameterValidator\Validator\Enum instead + */ final class Enum implements ValidatorInterface { use CheckFilterDeprecationsTrait; diff --git a/src/Api/QueryParameterValidator/Validator/Length.php b/src/Api/QueryParameterValidator/Validator/Length.php index c96f86511c2..dc6c3618f19 100644 --- a/src/Api/QueryParameterValidator/Validator/Length.php +++ b/src/Api/QueryParameterValidator/Validator/Length.php @@ -13,6 +13,12 @@ namespace ApiPlatform\Api\QueryParameterValidator\Validator; +use ApiPlatform\ParameterValidator\Validator\CheckFilterDeprecationsTrait; +use ApiPlatform\ParameterValidator\Validator\ValidatorInterface; + +/** + * @deprecated use \ApiPlatform\ParameterValidator\Validator\Length instead + */ final class Length implements ValidatorInterface { use CheckFilterDeprecationsTrait; diff --git a/src/Api/QueryParameterValidator/Validator/MultipleOf.php b/src/Api/QueryParameterValidator/Validator/MultipleOf.php index 5d792a3ba50..abf12a8f666 100644 --- a/src/Api/QueryParameterValidator/Validator/MultipleOf.php +++ b/src/Api/QueryParameterValidator/Validator/MultipleOf.php @@ -13,6 +13,12 @@ namespace ApiPlatform\Api\QueryParameterValidator\Validator; +use ApiPlatform\ParameterValidator\Validator\CheckFilterDeprecationsTrait; +use ApiPlatform\ParameterValidator\Validator\ValidatorInterface; + +/** + * @deprecated use \ApiPlatform\ParameterValidator\Validator\MultipleOf instead + */ final class MultipleOf implements ValidatorInterface { use CheckFilterDeprecationsTrait; diff --git a/src/Api/QueryParameterValidator/Validator/Pattern.php b/src/Api/QueryParameterValidator/Validator/Pattern.php index 8ad2fadbc30..cb61274285a 100644 --- a/src/Api/QueryParameterValidator/Validator/Pattern.php +++ b/src/Api/QueryParameterValidator/Validator/Pattern.php @@ -13,6 +13,12 @@ namespace ApiPlatform\Api\QueryParameterValidator\Validator; +use ApiPlatform\ParameterValidator\Validator\CheckFilterDeprecationsTrait; +use ApiPlatform\ParameterValidator\Validator\ValidatorInterface; + +/** + * @deprecated use \ApiPlatform\ParameterValidator\Validator\Pattern instead + */ final class Pattern implements ValidatorInterface { use CheckFilterDeprecationsTrait; diff --git a/src/Api/QueryParameterValidator/Validator/Required.php b/src/Api/QueryParameterValidator/Validator/Required.php index c335beb0666..afd7c98c986 100644 --- a/src/Api/QueryParameterValidator/Validator/Required.php +++ b/src/Api/QueryParameterValidator/Validator/Required.php @@ -13,8 +13,13 @@ namespace ApiPlatform\Api\QueryParameterValidator\Validator; +use ApiPlatform\ParameterValidator\Validator\CheckFilterDeprecationsTrait; +use ApiPlatform\ParameterValidator\Validator\ValidatorInterface; use ApiPlatform\State\Util\RequestParser; +/** + * @deprecated use \ApiPlatform\ParameterValidator\Validator\Required instead + */ final class Required implements ValidatorInterface { use CheckFilterDeprecationsTrait; diff --git a/src/Api/QueryParameterValidator/Validator/ValidatorInterface.php b/src/Api/QueryParameterValidator/Validator/ValidatorInterface.php index 2e11fe569c6..2bb50512048 100644 --- a/src/Api/QueryParameterValidator/Validator/ValidatorInterface.php +++ b/src/Api/QueryParameterValidator/Validator/ValidatorInterface.php @@ -13,12 +13,9 @@ namespace ApiPlatform\Api\QueryParameterValidator\Validator; -interface ValidatorInterface +use ApiPlatform\ParameterValidator\Validator as ParameterValidatorComponent; + +/** @deprecated use \ApiPlatform\ParameterValidator\Validator\ValidatorInterface instead */ +interface ValidatorInterface extends ParameterValidatorComponent\ValidatorInterface { - /** - * @param string $name the parameter name to validate - * @param array $filterDescription the filter descriptions as returned by `\ApiPlatform\Api\FilterInterface::getDescription()` - * @param array $queryParameters the list of query parameter - */ - public function validate(string $name, array $filterDescription, array $queryParameters): array; } diff --git a/src/Exception/FilterValidationException.php b/src/Exception/FilterValidationException.php index 784e005a3a1..a8af9149e19 100644 --- a/src/Exception/FilterValidationException.php +++ b/src/Exception/FilterValidationException.php @@ -13,12 +13,16 @@ namespace ApiPlatform\Exception; +use ApiPlatform\ParameterValidator\Exception\ValidationExceptionInterface; + /** * Filter validation exception. * * @author Julien DENIAU + * + * @deprecated use \ApiPlatform\Metadata\Exception\ValidationException instead */ -final class FilterValidationException extends \Exception implements ExceptionInterface, \Stringable +final class FilterValidationException extends \Exception implements ValidationExceptionInterface, ExceptionInterface, \Stringable { public function __construct(private readonly array $constraintViolationList, string $message = '', int $code = 0, \Exception $previous = null) { diff --git a/src/ParameterValidator/.gitignore b/src/ParameterValidator/.gitignore new file mode 100644 index 00000000000..eb0a8e7b262 --- /dev/null +++ b/src/ParameterValidator/.gitignore @@ -0,0 +1,3 @@ +/composer.lock +/vendor +/.phpunit.result.cache diff --git a/src/ParameterValidator/Exception/ValidationException.php b/src/ParameterValidator/Exception/ValidationException.php new file mode 100644 index 00000000000..73833bf698e --- /dev/null +++ b/src/ParameterValidator/Exception/ValidationException.php @@ -0,0 +1,32 @@ + + * + * 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\ParameterValidator\Exception; + +/** + * Filter validation exception. + * + * @author Julien DENIAU + */ +final class ValidationException extends \Exception implements ValidationExceptionInterface +{ + public function __construct(private readonly array $constraintViolationList, string $message = '', int $code = 0, \Exception $previous = null) + { + parent::__construct($message ?: $this->__toString(), $code, $previous); + } + + public function __toString(): string + { + return implode("\n", $this->constraintViolationList); + } +} diff --git a/src/ParameterValidator/Exception/ValidationExceptionInterface.php b/src/ParameterValidator/Exception/ValidationExceptionInterface.php new file mode 100644 index 00000000000..863c5ff6854 --- /dev/null +++ b/src/ParameterValidator/Exception/ValidationExceptionInterface.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\ParameterValidator\Exception; + +use ApiPlatform\Metadata\Exception\ExceptionInterface; + +/** + * This Exception is thrown when any parameter validation fails. + */ +interface ValidationExceptionInterface extends ExceptionInterface, \Stringable +{ +} diff --git a/src/ParameterValidator/FilterLocatorTrait.php b/src/ParameterValidator/FilterLocatorTrait.php new file mode 100644 index 00000000000..946c824f34f --- /dev/null +++ b/src/ParameterValidator/FilterLocatorTrait.php @@ -0,0 +1,56 @@ + + * + * 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\ParameterValidator; + +use ApiPlatform\Exception\InvalidArgumentException; +use ApiPlatform\Metadata\FilterInterface; +use Psr\Container\ContainerInterface; + +/** + * Manipulates filters with a backward compatibility between the new filter locator and the deprecated filter collection. + * + * @author Baptiste Meyer + * + * @deprecated + * + * @internal + */ +trait FilterLocatorTrait +{ + private ?ContainerInterface $filterLocator = null; + + /** + * Sets a filter locator with a backward compatibility. + */ + private function setFilterLocator(?ContainerInterface $filterLocator, bool $allowNull = false): void + { + if ($filterLocator instanceof ContainerInterface || (null === $filterLocator && $allowNull)) { + $this->filterLocator = $filterLocator; + } else { + throw new InvalidArgumentException(sprintf('The "$filterLocator" argument is expected to be an implementation of the "%s" interface%s.', ContainerInterface::class, $allowNull ? ' or null' : '')); + } + } + + /** + * Gets a filter with a backward compatibility. + */ + private function getFilter(string $filterId): null|FilterInterface + { + if ($this->filterLocator && $this->filterLocator->has($filterId)) { + return $this->filterLocator->get($filterId); + } + + return null; + } +} diff --git a/src/ParameterValidator/LICENSE b/src/ParameterValidator/LICENSE new file mode 100644 index 00000000000..1ca98eeb824 --- /dev/null +++ b/src/ParameterValidator/LICENSE @@ -0,0 +1,21 @@ +The MIT license + +Copyright (c) 2015-present Kévin Dunglas + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is furnished +to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/src/ParameterValidator/ParameterValidator.php b/src/ParameterValidator/ParameterValidator.php new file mode 100644 index 00000000000..a915bd0381c --- /dev/null +++ b/src/ParameterValidator/ParameterValidator.php @@ -0,0 +1,74 @@ + + * + * 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\ParameterValidator; + +use ApiPlatform\ParameterValidator\Exception\ValidationException; +use ApiPlatform\ParameterValidator\Validator\ArrayItems; +use ApiPlatform\ParameterValidator\Validator\Bounds; +use ApiPlatform\ParameterValidator\Validator\Enum; +use ApiPlatform\ParameterValidator\Validator\Length; +use ApiPlatform\ParameterValidator\Validator\MultipleOf; +use ApiPlatform\ParameterValidator\Validator\Pattern; +use ApiPlatform\ParameterValidator\Validator\Required; +use Psr\Container\ContainerInterface; + +/** + * Validates parameters depending on filter description. + * + * @author Julien Deniau + */ +class ParameterValidator +{ + use FilterLocatorTrait; + + private array $validators; + + public function __construct(ContainerInterface $filterLocator) + { + $this->setFilterLocator($filterLocator); + + $this->validators = [ + new ArrayItems(), + new Bounds(), + new Enum(), + new Length(), + new MultipleOf(), + new Pattern(), + new Required(), + ]; + } + + public function validateFilters(string $resourceClass, array $resourceFilters, array $queryParameters): void + { + $errorList = []; + + foreach ($resourceFilters as $filterId) { + if (!$filter = $this->getFilter($filterId)) { + continue; + } + + foreach ($filter->getDescription($resourceClass) as $name => $data) { + foreach ($this->validators as $validator) { + if ($errors = $validator->validate($name, $data, $queryParameters)) { + $errorList[] = $errors; + } + } + } + } + + if ($errorList) { + throw new ValidationException(array_merge(...$errorList)); + } + } +} diff --git a/src/ParameterValidator/README.md b/src/ParameterValidator/README.md new file mode 100644 index 00000000000..60cee8fe0cb --- /dev/null +++ b/src/ParameterValidator/README.md @@ -0,0 +1 @@ +# API Platform - Parameter Validator diff --git a/tests/Api/QueryParameterValidator/QueryParameterValidatorTest.php b/src/ParameterValidator/Tests/ParameterValidatorTest.php similarity index 88% rename from tests/Api/QueryParameterValidator/QueryParameterValidatorTest.php rename to src/ParameterValidator/Tests/ParameterValidatorTest.php index de930e4f7ea..7749b711345 100644 --- a/tests/Api/QueryParameterValidator/QueryParameterValidatorTest.php +++ b/src/ParameterValidator/Tests/ParameterValidatorTest.php @@ -11,11 +11,12 @@ declare(strict_types=1); -namespace ApiPlatform\Tests\Api\QueryParameterValidator; +namespace ApiPlatform\ParameterValidator\Tests; -use ApiPlatform\Api\FilterInterface; -use ApiPlatform\Api\QueryParameterValidator\QueryParameterValidator; use ApiPlatform\Exception\FilterValidationException; +use ApiPlatform\Metadata\FilterInterface; +use ApiPlatform\ParameterValidator\Exception\ValidationException; +use ApiPlatform\ParameterValidator\ParameterValidator; use ApiPlatform\Tests\Fixtures\TestBundle\Entity\Dummy; use PHPUnit\Framework\TestCase; use Prophecy\PhpUnit\ProphecyTrait; @@ -23,15 +24,13 @@ use Psr\Container\ContainerInterface; /** - * Class QueryParameterValidatorTest. - * * @author Julien Deniau */ -class QueryParameterValidatorTest extends TestCase +class ParameterValidatorTest extends TestCase { use ProphecyTrait; - private QueryParameterValidator $testedInstance; + private ParameterValidator $testedInstance; private ObjectProphecy $filterLocatorProphecy; /** @@ -41,7 +40,7 @@ protected function setUp(): void { $this->filterLocatorProphecy = $this->prophesize(ContainerInterface::class); - $this->testedInstance = new QueryParameterValidator( + $this->testedInstance = new ParameterValidator( $this->filterLocatorProphecy->reveal() ); } @@ -96,7 +95,7 @@ public function testOnKernelRequestWithRequiredFilterNotSet(): void ->shouldBeCalled() ->willReturn($filterProphecy->reveal()); - $this->expectException(FilterValidationException::class); + $this->expectException(ValidationException::class); $this->expectExceptionMessage('Query parameter "required" is required'); $this->testedInstance->validateFilters(Dummy::class, ['some_filter'], $request); } diff --git a/tests/Api/QueryParameterValidator/Validator/ArrayItemsTest.php b/src/ParameterValidator/Tests/Validator/ArrayItemsTest.php similarity index 98% rename from tests/Api/QueryParameterValidator/Validator/ArrayItemsTest.php rename to src/ParameterValidator/Tests/Validator/ArrayItemsTest.php index e5a97b3daba..6e922ba3396 100644 --- a/tests/Api/QueryParameterValidator/Validator/ArrayItemsTest.php +++ b/src/ParameterValidator/Tests/Validator/ArrayItemsTest.php @@ -11,9 +11,9 @@ declare(strict_types=1); -namespace ApiPlatform\Tests\Api\QueryParameterValidator\Validator; +namespace ApiPlatform\ParameterValidator\Tests\Validator; -use ApiPlatform\Api\QueryParameterValidator\Validator\ArrayItems; +use ApiPlatform\ParameterValidator\Validator\ArrayItems; use PHPUnit\Framework\TestCase; /** diff --git a/tests/Api/QueryParameterValidator/Validator/BoundsTest.php b/src/ParameterValidator/Tests/Validator/BoundsTest.php similarity index 98% rename from tests/Api/QueryParameterValidator/Validator/BoundsTest.php rename to src/ParameterValidator/Tests/Validator/BoundsTest.php index 3bc91ca9926..2706f8e9e23 100644 --- a/tests/Api/QueryParameterValidator/Validator/BoundsTest.php +++ b/src/ParameterValidator/Tests/Validator/BoundsTest.php @@ -11,9 +11,9 @@ declare(strict_types=1); -namespace ApiPlatform\Tests\Api\QueryParameterValidator\Validator; +namespace ApiPlatform\ParameterValidator\Tests\Validator; -use ApiPlatform\Api\QueryParameterValidator\Validator\Bounds; +use ApiPlatform\ParameterValidator\Validator\Bounds; use PHPUnit\Framework\TestCase; /** diff --git a/tests/Api/QueryParameterValidator/Validator/EnumTest.php b/src/ParameterValidator/Tests/Validator/EnumTest.php similarity index 95% rename from tests/Api/QueryParameterValidator/Validator/EnumTest.php rename to src/ParameterValidator/Tests/Validator/EnumTest.php index e2389cfb763..7f21ca17c0c 100644 --- a/tests/Api/QueryParameterValidator/Validator/EnumTest.php +++ b/src/ParameterValidator/Tests/Validator/EnumTest.php @@ -11,9 +11,9 @@ declare(strict_types=1); -namespace ApiPlatform\Tests\Api\QueryParameterValidator\Validator; +namespace ApiPlatform\ParameterValidator\Tests\Validator; -use ApiPlatform\Api\QueryParameterValidator\Validator\Enum; +use ApiPlatform\ParameterValidator\Validator\Enum; use PHPUnit\Framework\TestCase; /** diff --git a/tests/Api/QueryParameterValidator/Validator/LengthTest.php b/src/ParameterValidator/Tests/Validator/LengthTest.php similarity index 98% rename from tests/Api/QueryParameterValidator/Validator/LengthTest.php rename to src/ParameterValidator/Tests/Validator/LengthTest.php index 09ab8557214..cb9a244a56c 100644 --- a/tests/Api/QueryParameterValidator/Validator/LengthTest.php +++ b/src/ParameterValidator/Tests/Validator/LengthTest.php @@ -11,9 +11,9 @@ declare(strict_types=1); -namespace ApiPlatform\Tests\Api\QueryParameterValidator\Validator; +namespace ApiPlatform\ParameterValidator\Tests\Validator; -use ApiPlatform\Api\QueryParameterValidator\Validator\Length; +use ApiPlatform\ParameterValidator\Validator\Length; use PHPUnit\Framework\TestCase; /** diff --git a/tests/Api/QueryParameterValidator/Validator/MultipleOfTest.php b/src/ParameterValidator/Tests/Validator/MultipleOfTest.php similarity index 95% rename from tests/Api/QueryParameterValidator/Validator/MultipleOfTest.php rename to src/ParameterValidator/Tests/Validator/MultipleOfTest.php index 783e73f338d..58227b76725 100644 --- a/tests/Api/QueryParameterValidator/Validator/MultipleOfTest.php +++ b/src/ParameterValidator/Tests/Validator/MultipleOfTest.php @@ -11,9 +11,9 @@ declare(strict_types=1); -namespace ApiPlatform\Tests\Api\QueryParameterValidator\Validator; +namespace ApiPlatform\ParameterValidator\Tests\Validator; -use ApiPlatform\Api\QueryParameterValidator\Validator\MultipleOf; +use ApiPlatform\ParameterValidator\Validator\MultipleOf; use PHPUnit\Framework\TestCase; /** diff --git a/tests/Api/QueryParameterValidator/Validator/PatternTest.php b/src/ParameterValidator/Tests/Validator/PatternTest.php similarity index 97% rename from tests/Api/QueryParameterValidator/Validator/PatternTest.php rename to src/ParameterValidator/Tests/Validator/PatternTest.php index 9c9dcd9ac3a..bfa987a5240 100644 --- a/tests/Api/QueryParameterValidator/Validator/PatternTest.php +++ b/src/ParameterValidator/Tests/Validator/PatternTest.php @@ -11,9 +11,9 @@ declare(strict_types=1); -namespace ApiPlatform\Tests\Api\QueryParameterValidator\Validator; +namespace ApiPlatform\ParameterValidator\Tests\Validator; -use ApiPlatform\Api\QueryParameterValidator\Validator\Pattern; +use ApiPlatform\ParameterValidator\Validator\Pattern; use PHPUnit\Framework\TestCase; /** diff --git a/tests/Api/QueryParameterValidator/Validator/RequiredTest.php b/src/ParameterValidator/Tests/Validator/RequiredTest.php similarity index 97% rename from tests/Api/QueryParameterValidator/Validator/RequiredTest.php rename to src/ParameterValidator/Tests/Validator/RequiredTest.php index bca7b404ab1..ce6419bca0f 100644 --- a/tests/Api/QueryParameterValidator/Validator/RequiredTest.php +++ b/src/ParameterValidator/Tests/Validator/RequiredTest.php @@ -11,9 +11,9 @@ declare(strict_types=1); -namespace ApiPlatform\Tests\Api\QueryParameterValidator\Validator; +namespace ApiPlatform\ParameterValidator\Tests\Validator; -use ApiPlatform\Api\QueryParameterValidator\Validator\Required; +use ApiPlatform\ParameterValidator\Validator\Required; use PHPUnit\Framework\TestCase; /** diff --git a/src/ParameterValidator/Validator/ArrayItems.php b/src/ParameterValidator/Validator/ArrayItems.php new file mode 100644 index 00000000000..9b01ee02b46 --- /dev/null +++ b/src/ParameterValidator/Validator/ArrayItems.php @@ -0,0 +1,85 @@ + + * + * 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\ParameterValidator\Validator; + +final class ArrayItems implements ValidatorInterface +{ + use CheckFilterDeprecationsTrait; + + /** + * {@inheritdoc} + */ + public function validate(string $name, array $filterDescription, array $queryParameters): array + { + if (!\array_key_exists($name, $queryParameters)) { + return []; + } + + $this->checkFilterDeprecations($filterDescription); + + $maxItems = $filterDescription['openapi']['maxItems'] ?? $filterDescription['swagger']['maxItems'] ?? null; + $minItems = $filterDescription['openapi']['minItems'] ?? $filterDescription['swagger']['minItems'] ?? null; + $uniqueItems = $filterDescription['openapi']['uniqueItems'] ?? $filterDescription['swagger']['uniqueItems'] ?? false; + + $errorList = []; + + $value = $this->getValue($name, $filterDescription, $queryParameters); + $nbItems = \count($value); + + if (null !== $maxItems && $nbItems > $maxItems) { + $errorList[] = sprintf('Query parameter "%s" must contain less than %d values', $name, $maxItems); + } + + if (null !== $minItems && $nbItems < $minItems) { + $errorList[] = sprintf('Query parameter "%s" must contain more than %d values', $name, $minItems); + } + + if (true === $uniqueItems && $nbItems > \count(array_unique($value))) { + $errorList[] = sprintf('Query parameter "%s" must contain unique values', $name); + } + + return $errorList; + } + + private function getValue(string $name, array $filterDescription, array $queryParameters): array + { + $value = $queryParameters[$name] ?? null; + + if (empty($value) && '0' !== $value) { + return []; + } + + if (\is_array($value)) { + return $value; + } + + $collectionFormat = $filterDescription['openapi']['collectionFormat'] ?? $filterDescription['swagger']['collectionFormat'] ?? 'csv'; + + return explode(self::getSeparator($collectionFormat), (string) $value) ?: []; // @phpstan-ignore-line + } + + /** + * @return non-empty-string + */ + private static function getSeparator(string $collectionFormat): string + { + return match ($collectionFormat) { + 'csv' => ',', + 'ssv' => ' ', + 'tsv' => '\t', + 'pipes' => '|', + default => throw new \InvalidArgumentException(sprintf('Unknown collection format %s', $collectionFormat)), + }; + } +} diff --git a/src/ParameterValidator/Validator/Bounds.php b/src/ParameterValidator/Validator/Bounds.php new file mode 100644 index 00000000000..a6eb374b45e --- /dev/null +++ b/src/ParameterValidator/Validator/Bounds.php @@ -0,0 +1,55 @@ + + * + * 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\ParameterValidator\Validator; + +final class Bounds implements ValidatorInterface +{ + use CheckFilterDeprecationsTrait; + + /** + * {@inheritdoc} + */ + public function validate(string $name, array $filterDescription, array $queryParameters): array + { + $value = $queryParameters[$name] ?? null; + if (empty($value) && '0' !== $value) { + return []; + } + + $this->checkFilterDeprecations($filterDescription); + + $maximum = $filterDescription['openapi']['maximum'] ?? $filterDescription['swagger']['maximum'] ?? null; + $minimum = $filterDescription['openapi']['minimum'] ?? $filterDescription['swagger']['minimum'] ?? null; + + $errorList = []; + + if (null !== $maximum) { + if (($filterDescription['openapi']['exclusiveMaximum'] ?? $filterDescription['swagger']['exclusiveMaximum'] ?? false) && $value >= $maximum) { + $errorList[] = sprintf('Query parameter "%s" must be less than %s', $name, $maximum); + } elseif ($value > $maximum) { + $errorList[] = sprintf('Query parameter "%s" must be less than or equal to %s', $name, $maximum); + } + } + + if (null !== $minimum) { + if (($filterDescription['openapi']['exclusiveMinimum'] ?? $filterDescription['swagger']['exclusiveMinimum'] ?? false) && $value <= $minimum) { + $errorList[] = sprintf('Query parameter "%s" must be greater than %s', $name, $minimum); + } elseif ($value < $minimum) { + $errorList[] = sprintf('Query parameter "%s" must be greater than or equal to %s', $name, $minimum); + } + } + + return $errorList; + } +} diff --git a/src/Api/QueryParameterValidator/Validator/CheckFilterDeprecationsTrait.php b/src/ParameterValidator/Validator/CheckFilterDeprecationsTrait.php similarity index 92% rename from src/Api/QueryParameterValidator/Validator/CheckFilterDeprecationsTrait.php rename to src/ParameterValidator/Validator/CheckFilterDeprecationsTrait.php index a72f79ed102..3658a699266 100644 --- a/src/Api/QueryParameterValidator/Validator/CheckFilterDeprecationsTrait.php +++ b/src/ParameterValidator/Validator/CheckFilterDeprecationsTrait.php @@ -11,7 +11,7 @@ declare(strict_types=1); -namespace ApiPlatform\Api\QueryParameterValidator\Validator; +namespace ApiPlatform\ParameterValidator\Validator; /** * @internal diff --git a/src/ParameterValidator/Validator/Enum.php b/src/ParameterValidator/Validator/Enum.php new file mode 100644 index 00000000000..516f5a377c6 --- /dev/null +++ b/src/ParameterValidator/Validator/Enum.php @@ -0,0 +1,42 @@ + + * + * 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\ParameterValidator\Validator; + +final class Enum implements ValidatorInterface +{ + use CheckFilterDeprecationsTrait; + + /** + * {@inheritdoc} + */ + public function validate(string $name, array $filterDescription, array $queryParameters): array + { + $value = $queryParameters[$name] ?? null; + if (empty($value) && '0' !== $value || !\is_string($value)) { + return []; + } + + $this->checkFilterDeprecations($filterDescription); + + $enum = $filterDescription['openapi']['enum'] ?? $filterDescription['swagger']['enum'] ?? null; + + if (null !== $enum && !\in_array($value, $enum, true)) { + return [ + sprintf('Query parameter "%s" must be one of "%s"', $name, implode(', ', $enum)), + ]; + } + + return []; + } +} diff --git a/src/ParameterValidator/Validator/Length.php b/src/ParameterValidator/Validator/Length.php new file mode 100644 index 00000000000..c9c7a61f8dd --- /dev/null +++ b/src/ParameterValidator/Validator/Length.php @@ -0,0 +1,47 @@ + + * + * 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\ParameterValidator\Validator; + +final class Length implements ValidatorInterface +{ + use CheckFilterDeprecationsTrait; + + /** + * {@inheritdoc} + */ + public function validate(string $name, array $filterDescription, array $queryParameters): array + { + $value = $queryParameters[$name] ?? null; + if (empty($value) && '0' !== $value || !\is_string($value)) { + return []; + } + + $this->checkFilterDeprecations($filterDescription); + + $maxLength = $filterDescription['openapi']['maxLength'] ?? $filterDescription['swagger']['maxLength'] ?? null; + $minLength = $filterDescription['openapi']['minLength'] ?? $filterDescription['swagger']['minLength'] ?? null; + + $errorList = []; + + if (null !== $maxLength && mb_strlen($value) > $maxLength) { + $errorList[] = sprintf('Query parameter "%s" length must be lower than or equal to %s', $name, $maxLength); + } + + if (null !== $minLength && mb_strlen($value) < $minLength) { + $errorList[] = sprintf('Query parameter "%s" length must be greater than or equal to %s', $name, $minLength); + } + + return $errorList; + } +} diff --git a/src/ParameterValidator/Validator/MultipleOf.php b/src/ParameterValidator/Validator/MultipleOf.php new file mode 100644 index 00000000000..d1b3b3c4bc8 --- /dev/null +++ b/src/ParameterValidator/Validator/MultipleOf.php @@ -0,0 +1,42 @@ + + * + * 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\ParameterValidator\Validator; + +final class MultipleOf implements ValidatorInterface +{ + use CheckFilterDeprecationsTrait; + + /** + * {@inheritdoc} + */ + public function validate(string $name, array $filterDescription, array $queryParameters): array + { + $value = $queryParameters[$name] ?? null; + if (empty($value) && '0' !== $value || !\is_string($value)) { + return []; + } + + $this->checkFilterDeprecations($filterDescription); + + $multipleOf = $filterDescription['openapi']['multipleOf'] ?? $filterDescription['swagger']['multipleOf'] ?? null; + + if (null !== $multipleOf && 0 !== ($value % $multipleOf)) { + return [ + sprintf('Query parameter "%s" must multiple of %s', $name, $multipleOf), + ]; + } + + return []; + } +} diff --git a/src/ParameterValidator/Validator/Pattern.php b/src/ParameterValidator/Validator/Pattern.php new file mode 100644 index 00000000000..7110e852ecf --- /dev/null +++ b/src/ParameterValidator/Validator/Pattern.php @@ -0,0 +1,42 @@ + + * + * 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\ParameterValidator\Validator; + +final class Pattern implements ValidatorInterface +{ + use CheckFilterDeprecationsTrait; + + /** + * {@inheritdoc} + */ + public function validate(string $name, array $filterDescription, array $queryParameters): array + { + $value = $queryParameters[$name] ?? null; + if (empty($value) && '0' !== $value || !\is_string($value)) { + return []; + } + + $this->checkFilterDeprecations($filterDescription); + + $pattern = $filterDescription['openapi']['pattern'] ?? $filterDescription['swagger']['pattern'] ?? null; + + if (null !== $pattern && !preg_match($pattern, $value)) { + return [ + sprintf('Query parameter "%s" must match pattern %s', $name, $pattern), + ]; + } + + return []; + } +} diff --git a/src/ParameterValidator/Validator/Required.php b/src/ParameterValidator/Validator/Required.php new file mode 100644 index 00000000000..c23cabab329 --- /dev/null +++ b/src/ParameterValidator/Validator/Required.php @@ -0,0 +1,106 @@ + + * + * 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\ParameterValidator\Validator; + +use ApiPlatform\State\Util\RequestParser; + +final class Required implements ValidatorInterface +{ + use CheckFilterDeprecationsTrait; + + /** + * {@inheritdoc} + */ + public function validate(string $name, array $filterDescription, array $queryParameters): array + { + // filter is not required, the `checkRequired` method can not break + if (!($filterDescription['required'] ?? false)) { + return []; + } + + // if query param is not given, then break + if (!$this->requestHasQueryParameter($queryParameters, $name)) { + return [ + sprintf('Query parameter "%s" is required', $name), + ]; + } + + $this->checkFilterDeprecations($filterDescription); + + // if query param is empty and the configuration does not allow it + if (!($filterDescription['openapi']['allowEmptyValue'] ?? $filterDescription['swagger']['allowEmptyValue'] ?? false) && empty($this->requestGetQueryParameter($queryParameters, $name))) { + return [ + sprintf('Query parameter "%s" does not allow empty value', $name), + ]; + } + + return []; + } + + /** + * Test if request has required parameter. + */ + private function requestHasQueryParameter(array $queryParameters, string $name): bool + { + $matches = RequestParser::parseRequestParams($name); + if (!$matches) { + return false; + } + + $rootName = array_keys($matches)[0] ?? ''; + if (!$rootName) { + return false; + } + + if (\is_array($matches[$rootName])) { + $keyName = array_keys($matches[$rootName])[0]; + + $queryParameter = $queryParameters[(string) $rootName] ?? null; + + return \is_array($queryParameter) && isset($queryParameter[$keyName]); + } + + return \array_key_exists((string) $rootName, $queryParameters); + } + + /** + * Test if required filter is valid. It validates array notation too like "required[bar]". + */ + private function requestGetQueryParameter(array $queryParameters, string $name) + { + $matches = RequestParser::parseRequestParams($name); + if (empty($matches)) { + return null; + } + + $rootName = array_keys($matches)[0] ?? ''; + if (!$rootName) { + return null; + } + + if (\is_array($matches[$rootName])) { + $keyName = array_keys($matches[$rootName])[0]; + + $queryParameter = $queryParameters[(string) $rootName] ?? null; + + if (\is_array($queryParameter) && isset($queryParameter[$keyName])) { + return $queryParameter[$keyName]; + } + + return null; + } + + return $queryParameters[(string) $rootName]; + } +} diff --git a/src/ParameterValidator/Validator/ValidatorInterface.php b/src/ParameterValidator/Validator/ValidatorInterface.php new file mode 100644 index 00000000000..8df402952c6 --- /dev/null +++ b/src/ParameterValidator/Validator/ValidatorInterface.php @@ -0,0 +1,24 @@ + + * + * 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\ParameterValidator\Validator; + +interface ValidatorInterface +{ + /** + * @param string $name the parameter name to validate + * @param array $filterDescription the filter descriptions as returned by `\ApiPlatform\Api\FilterInterface::getDescription()` + * @param array $queryParameters the list of query parameter + */ + public function validate(string $name, array $filterDescription, array $queryParameters): array; +} diff --git a/src/ParameterValidator/composer.json b/src/ParameterValidator/composer.json new file mode 100644 index 00000000000..9cd7b3d42ca --- /dev/null +++ b/src/ParameterValidator/composer.json @@ -0,0 +1,74 @@ +{ + "name": "api-platform/parameter-validator", + "description": "Validates parameters depending on API-Platform filter description", + "type": "library", + "keywords": [ + "REST", + "GraphQL", + "API", + "JSON-LD", + "Hydra", + "JSONAPI", + "OpenAPI", + "HAL", + "Swagger" + ], + "homepage": "https://api-platform.com", + "license": "MIT", + "authors": [ + { + "name": "Kévin Dunglas", + "email": "kevin@dunglas.fr", + "homepage": "https://dunglas.fr" + }, + { + "name": "API Platform Community", + "homepage": "https://api-platform.com/community/contributors" + } + ], + "require": { + "php": ">=8.1", + "api-platform/state": "*@dev || ^3.1", + "psr/container": "1.1|2.0", + "symfony/deprecation-contracts": "^3.1" + }, + "require-dev": { + "phpspec/prophecy-phpunit": "^2.1", + "sebastian/comparator": "<5.0", + "symfony/phpunit-bridge": "^6.1" + }, + "suggest": { + }, + "autoload": { + "psr-4": { + "ApiPlatform\\ParameterValidator\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "config": { + "preferred-install": { + "*": "dist" + }, + "sort-packages": true, + "allow-plugins": { + "composer/package-versions-deprecated": true, + "phpstan/extension-installer": true + } + }, + "extra": { + "branch-alias": { + "dev-main": "3.2.x-dev" + }, + "symfony": { + "require": "^6.1" + } + }, + "repositories": [ + { + "type": "path", + "url": "../State" + } + ] +} diff --git a/src/ParameterValidator/phpunit.xml.dist b/src/ParameterValidator/phpunit.xml.dist new file mode 100644 index 00000000000..3a36ca8b8cb --- /dev/null +++ b/src/ParameterValidator/phpunit.xml.dist @@ -0,0 +1,30 @@ + + + + + + + + + + ./Tests/ + + + + + + ./ + + + ./Tests + ./vendor + + + diff --git a/src/Symfony/Bundle/DependencyInjection/Configuration.php b/src/Symfony/Bundle/DependencyInjection/Configuration.php index 948cf49f156..cdb3af6bbcd 100644 --- a/src/Symfony/Bundle/DependencyInjection/Configuration.php +++ b/src/Symfony/Bundle/DependencyInjection/Configuration.php @@ -16,11 +16,11 @@ use ApiPlatform\Doctrine\Common\Filter\OrderFilterInterface; use ApiPlatform\Elasticsearch\Metadata\Document\DocumentMetadata; use ApiPlatform\Elasticsearch\State\Options; -use ApiPlatform\Exception\FilterValidationException; use ApiPlatform\Exception\InvalidArgumentException; use ApiPlatform\Metadata\ApiResource; use ApiPlatform\Metadata\Post; use ApiPlatform\Metadata\Put; +use ApiPlatform\ParameterValidator\Exception\ValidationExceptionInterface; use Doctrine\Bundle\DoctrineBundle\DoctrineBundle; use Doctrine\Bundle\MongoDBBundle\DoctrineMongoDBBundle; use Doctrine\ORM\EntityManagerInterface; @@ -285,7 +285,7 @@ private function addSwaggerSection(ArrayNodeDefinition $rootNode): void }) ->end() ->validate() - ->ifTrue(static fn($v): bool => $v !== array_intersect($v, $supportedVersions)) + ->ifTrue(static fn ($v): bool => $v !== array_intersect($v, $supportedVersions)) ->thenInvalid(sprintf('Only the versions %s are supported. Got %s.', implode(' and ', $supportedVersions), '%s')) ->end() ->prototype('scalar')->end() @@ -293,7 +293,7 @@ private function addSwaggerSection(ArrayNodeDefinition $rootNode): void ->arrayNode('api_keys') ->useAttributeAsKey('key') ->validate() - ->ifTrue(static fn($v): bool => (bool) array_filter(array_keys($v), fn($item) => !preg_match('/^[a-zA-Z0-9._-]+$/', $item))) + ->ifTrue(static fn ($v): bool => (bool) array_filter(array_keys($v), fn ($item) => !preg_match('/^[a-zA-Z0-9._-]+$/', $item))) ->thenInvalid('The api keys "key" is not valid according to the pattern enforced by OpenAPI 3.1 ^[a-zA-Z0-9._-]+$.') ->end() ->prototype('array') @@ -311,7 +311,7 @@ private function addSwaggerSection(ArrayNodeDefinition $rootNode): void ->variableNode('swagger_ui_extra_configuration') ->defaultValue([]) ->validate() - ->ifTrue(static fn($v): bool => false === \is_array($v)) + ->ifTrue(static fn ($v): bool => false === \is_array($v)) ->thenInvalid('The swagger_ui_extra_configuration parameter must be an array.') ->end() ->info('To pass extra configuration to Swagger UI, like docExpansion or filter.') @@ -356,7 +356,7 @@ private function addHttpCacheSection(ArrayNodeDefinition $rootNode): void ->variableNode('request_options') ->defaultValue([]) ->validate() - ->ifTrue(static fn($v): bool => false === \is_array($v)) + ->ifTrue(static fn ($v): bool => false === \is_array($v)) ->thenInvalid('The request_options parameter must be an array.') ->end() ->info('To pass options to the client charged with the request.') @@ -480,7 +480,7 @@ private function addOpenApiSection(ArrayNodeDefinition $rootNode): void ->variableNode('swagger_ui_extra_configuration') ->defaultValue([]) ->validate() - ->ifTrue(static fn($v): bool => false === \is_array($v)) + ->ifTrue(static fn ($v): bool => false === \is_array($v)) ->thenInvalid('The swagger_ui_extra_configuration parameter must be an array.') ->end() ->info('To pass extra configuration to Swagger UI, like docExpansion or filter.') @@ -501,7 +501,7 @@ private function addExceptionToStatusSection(ArrayNodeDefinition $rootNode): voi ->defaultValue([ SerializerExceptionInterface::class => Response::HTTP_BAD_REQUEST, InvalidArgumentException::class => Response::HTTP_BAD_REQUEST, - FilterValidationException::class => Response::HTTP_BAD_REQUEST, + ValidationExceptionInterface::class => Response::HTTP_BAD_REQUEST, OptimisticLockException::class => Response::HTTP_CONFLICT, ]) ->info('The list of exceptions mapped to their HTTP status code.') @@ -589,7 +589,7 @@ private function addMakerSection(ArrayNodeDefinition $rootNode): void ->end(); } - private function defineDefault(ArrayNodeDefinition $defaultsNode, \ReflectionClass $reflectionClass, CamelCaseToSnakeCaseNameConverter $nameConverter) + private function defineDefault(ArrayNodeDefinition $defaultsNode, \ReflectionClass $reflectionClass, CamelCaseToSnakeCaseNameConverter $nameConverter): void { foreach ($reflectionClass->getConstructor()->getParameters() as $parameter) { $defaultsNode->children()->variableNode($nameConverter->normalize($parameter->getName())); diff --git a/src/Symfony/Bundle/Resources/config/symfony/validator.xml b/src/Symfony/Bundle/Resources/config/symfony/validator.xml index ac5f21faa9b..ac2d10fea1b 100644 --- a/src/Symfony/Bundle/Resources/config/symfony/validator.xml +++ b/src/Symfony/Bundle/Resources/config/symfony/validator.xml @@ -11,7 +11,7 @@ - + diff --git a/src/Symfony/EventListener/QueryParameterValidateListener.php b/src/Symfony/EventListener/QueryParameterValidateListener.php index ea83890bf95..9573e37cecc 100644 --- a/src/Symfony/EventListener/QueryParameterValidateListener.php +++ b/src/Symfony/EventListener/QueryParameterValidateListener.php @@ -13,11 +13,11 @@ namespace ApiPlatform\Symfony\EventListener; -use ApiPlatform\Api\QueryParameterValidator\QueryParameterValidator; use ApiPlatform\Doctrine\Odm\State\Options as ODMOptions; use ApiPlatform\Doctrine\Orm\State\Options; use ApiPlatform\Metadata\CollectionOperationInterface; use ApiPlatform\Metadata\Resource\Factory\ResourceMetadataCollectionFactoryInterface; +use ApiPlatform\ParameterValidator\ParameterValidator; use ApiPlatform\State\Util\OperationRequestInitiatorTrait; use ApiPlatform\State\Util\RequestParser; use ApiPlatform\Symfony\Util\RequestAttributesExtractor; @@ -34,7 +34,7 @@ final class QueryParameterValidateListener public const OPERATION_ATTRIBUTE_KEY = 'query_parameter_validate'; - public function __construct(private readonly QueryParameterValidator $queryParameterValidator, ResourceMetadataCollectionFactoryInterface $resourceMetadataCollectionFactory = null) + public function __construct(private readonly ParameterValidator $queryParameterValidator, ResourceMetadataCollectionFactoryInterface $resourceMetadataCollectionFactory = null) { $this->resourceMetadataCollectionFactory = $resourceMetadataCollectionFactory; } diff --git a/tests/Symfony/EventListener/QueryParameterValidateListenerTest.php b/src/Symfony/Tests/EventListener/QueryParameterValidateListenerTest.php similarity index 90% rename from tests/Symfony/EventListener/QueryParameterValidateListenerTest.php rename to src/Symfony/Tests/EventListener/QueryParameterValidateListenerTest.php index 6a57065d482..cf9c7bb098a 100644 --- a/tests/Symfony/EventListener/QueryParameterValidateListenerTest.php +++ b/src/Symfony/Tests/EventListener/QueryParameterValidateListenerTest.php @@ -11,15 +11,15 @@ declare(strict_types=1); -namespace ApiPlatform\Tests\Symfony\EventListener; +namespace ApiPlatform\Symfony\Tests\EventListener; -use ApiPlatform\Api\QueryParameterValidator\QueryParameterValidator; -use ApiPlatform\Exception\FilterValidationException; use ApiPlatform\Metadata\ApiResource; use ApiPlatform\Metadata\Get; use ApiPlatform\Metadata\GetCollection; use ApiPlatform\Metadata\Resource\Factory\ResourceMetadataCollectionFactoryInterface; use ApiPlatform\Metadata\Resource\ResourceMetadataCollection; +use ApiPlatform\ParameterValidator\Exception\ValidationException; +use ApiPlatform\ParameterValidator\ParameterValidator; use ApiPlatform\Symfony\EventListener\QueryParameterValidateListener; use ApiPlatform\Tests\Fixtures\TestBundle\Entity\Dummy; use PHPUnit\Framework\TestCase; @@ -68,7 +68,7 @@ public function testDoNotValidateWhenDisabledGlobally(): void $eventProphecy = $this->prophesize(RequestEvent::class); $eventProphecy->getRequest()->willReturn($request); - $queryParameterValidator = $this->prophesize(QueryParameterValidator::class); + $queryParameterValidator = $this->prophesize(ParameterValidator::class); $queryParameterValidator->validateFilters(Argument::cetera())->shouldNotBeCalled(); $listener = new QueryParameterValidateListener( @@ -93,7 +93,7 @@ public function testDoNotValidateWhenDisabledInOperationAttribute(): void $eventProphecy = $this->prophesize(RequestEvent::class); $eventProphecy->getRequest()->willReturn($request); - $queryParameterValidator = $this->prophesize(QueryParameterValidator::class); + $queryParameterValidator = $this->prophesize(ParameterValidator::class); $queryParameterValidator->validateFilters(Argument::cetera())->shouldNotBeCalled(); $listener = new QueryParameterValidateListener( @@ -123,7 +123,7 @@ public function testOnKernelRequestWithWrongFilter(): void } /** - * if the required parameter is not set, throw an FilterValidationException. + * if the required parameter is not set, throw an ValidationException. */ public function testOnKernelRequestWithRequiredFilterNotSet(): void { @@ -138,8 +138,8 @@ public function testOnKernelRequestWithRequiredFilterNotSet(): void $this->queryParameterValidator ->validateFilters(Dummy::class, ['some_filter'], []) ->shouldBeCalled() - ->willThrow(new FilterValidationException(['Query parameter "required" is required'])); - $this->expectException(FilterValidationException::class); + ->willThrow(new ValidationException(['Query parameter "required" is required'])); + $this->expectException(ValidationException::class); $this->expectExceptionMessage('Query parameter "required" is required'); $this->testedInstance->onKernelRequest($eventProphecy->reveal()); } @@ -180,7 +180,7 @@ private function setUpWithFilters(array $filters = []): void ]), ])); - $this->queryParameterValidator = $this->prophesize(QueryParameterValidator::class); + $this->queryParameterValidator = $this->prophesize(ParameterValidator::class); $this->testedInstance = new QueryParameterValidateListener( $this->queryParameterValidator->reveal(), diff --git a/tests/Symfony/Validator/State/QueryParameterValidateProviderTest.php b/src/Symfony/Tests/Validator/State/QueryParameterValidateProviderTest.php similarity index 88% rename from tests/Symfony/Validator/State/QueryParameterValidateProviderTest.php rename to src/Symfony/Tests/Validator/State/QueryParameterValidateProviderTest.php index b86fee95e83..30a14d5c3f5 100644 --- a/tests/Symfony/Validator/State/QueryParameterValidateProviderTest.php +++ b/src/Symfony/Tests/Validator/State/QueryParameterValidateProviderTest.php @@ -11,10 +11,10 @@ declare(strict_types=1); -namespace ApiPlatform\Tests\Symfony\Validator\State; +namespace ApiPlatform\Symfony\Tests\Validator\State; -use ApiPlatform\Api\QueryParameterValidator\QueryParameterValidator; use ApiPlatform\Metadata\GetCollection; +use ApiPlatform\ParameterValidator\ParameterValidator; use ApiPlatform\State\ProviderInterface; use ApiPlatform\Symfony\Validator\State\QueryParameterValidateProvider; use PHPUnit\Framework\TestCase; @@ -36,7 +36,7 @@ public function testValidate(): void $obj = new \stdClass(); $decorated = $this->createMock(ProviderInterface::class); $decorated->method('provide')->willReturn($obj); - $validator = $this->createMock(QueryParameterValidator::class); + $validator = $this->createMock(ParameterValidator::class); $validator->expects($this->once())->method('validateFilters')->with('foo', $filters, ['foo' => 'bar']); $provider = new QueryParameterValidateProvider($decorated, $validator); $provider->provide($operation, [], $context); diff --git a/src/Symfony/Validator/State/QueryParameterValidateProvider.php b/src/Symfony/Validator/State/QueryParameterValidateProvider.php index 8fa33128984..4a5b66ad055 100644 --- a/src/Symfony/Validator/State/QueryParameterValidateProvider.php +++ b/src/Symfony/Validator/State/QueryParameterValidateProvider.php @@ -13,17 +13,17 @@ namespace ApiPlatform\Symfony\Validator\State; -use ApiPlatform\Api\QueryParameterValidator\QueryParameterValidator; use ApiPlatform\Doctrine\Orm\State\Options; use ApiPlatform\Metadata\CollectionOperationInterface; use ApiPlatform\Metadata\HttpOperation; use ApiPlatform\Metadata\Operation; +use ApiPlatform\ParameterValidator\ParameterValidator; use ApiPlatform\State\ProviderInterface; use ApiPlatform\State\Util\RequestParser; final class QueryParameterValidateProvider implements ProviderInterface { - public function __construct(private readonly ProviderInterface $decorated, private readonly QueryParameterValidator $queryParameterValidator) + public function __construct(private readonly ProviderInterface $decorated, private readonly ParameterValidator $queryParameterValidator) { } diff --git a/src/Symfony/composer.json b/src/Symfony/composer.json index f31de449c8a..e1672e58332 100644 --- a/src/Symfony/composer.json +++ b/src/Symfony/composer.json @@ -20,8 +20,17 @@ ], "require": { "php": ">=8.1", + "api-platform/documentation": "*@dev || ^3.1", + "api-platform/elasticsearch": "*@dev || ^3.1", + "api-platform/graphql": "*@dev || ^3.1", + "api-platform/http-cache": "*@dev || ^3.1", + "api-platform/json-schema": "*@dev || ^3.1", + "api-platform/jsonld": "*@dev || ^3.1", "api-platform/metadata": "*@dev || ^3.1", + "api-platform/parameter-validator": "*@dev || ^3.1", + "api-platform/serializer": "*@dev || ^3.1", "api-platform/state": "*@dev || ^3.1", + "api-platform/validator": "*@dev || ^3.1", "symfony/property-info": "^6.1", "symfony/serializer": "^6.1", "symfony/security-core": "^6.1" @@ -66,6 +75,10 @@ "type": "path", "url": "../Metadata" }, + { + "type": "path", + "url": "../ParameterValidator" + }, { "type": "path", "url": "../State" diff --git a/src/deprecation.php b/src/deprecation.php index 2b6daa5a99c..dd69bf985a9 100644 --- a/src/deprecation.php +++ b/src/deprecation.php @@ -14,6 +14,14 @@ $deprecatedClassesWithAliases = [ \ApiPlatform\HttpCache\EventListener\AddHeadersListener::class => \ApiPlatform\Symfony\EventListener\AddHeadersListener::class, \ApiPlatform\HttpCache\EventListener\AddTagsListener::class => \ApiPlatform\Symfony\EventListener\AddTagsListener::class, + \ApiPlatform\Exception\FilterValidationException::class => \ApiPlatform\ParameterValidator\Exception\ValidationException::class, + \ApiPlatform\Api\QueryParameterValidator\Validator\ArrayItems::class => \ApiPlatform\ParameterValidator\Validator\ArrayItems::class, + \ApiPlatform\Api\QueryParameterValidator\Validator\Bounds::class => \ApiPlatform\ParameterValidator\Validator\Bounds::class, + \ApiPlatform\Api\QueryParameterValidator\Validator\Enum::class => \ApiPlatform\ParameterValidator\Validator\Enum::class, + \ApiPlatform\Api\QueryParameterValidator\Validator\Length::class => \ApiPlatform\ParameterValidator\Validator\Length::class, + \ApiPlatform\Api\QueryParameterValidator\Validator\MultipleOf::class => \ApiPlatform\ParameterValidator\Validator\MultipleOf::class, + \ApiPlatform\Api\QueryParameterValidator\Validator\Pattern::class => \ApiPlatform\ParameterValidator\Validator\Pattern::class, + \ApiPlatform\Api\QueryParameterValidator\Validator\Required::class => \ApiPlatform\ParameterValidator\Validator\Required::class, ]; spl_autoload_register(function ($className) use ($deprecatedClassesWithAliases): void { diff --git a/tests/Symfony/Bundle/DependencyInjection/ConfigurationTest.php b/tests/Symfony/Bundle/DependencyInjection/ConfigurationTest.php index 33eda6cf678..aa728a7e5fe 100644 --- a/tests/Symfony/Bundle/DependencyInjection/ConfigurationTest.php +++ b/tests/Symfony/Bundle/DependencyInjection/ConfigurationTest.php @@ -13,8 +13,8 @@ namespace ApiPlatform\Tests\Symfony\Bundle\DependencyInjection; -use ApiPlatform\Exception\FilterValidationException; use ApiPlatform\Exception\InvalidArgumentException; +use ApiPlatform\ParameterValidator\Exception\ValidationExceptionInterface; use ApiPlatform\Symfony\Bundle\DependencyInjection\Configuration; use Doctrine\ORM\OptimisticLockException; use PHPUnit\Framework\TestCase; @@ -101,7 +101,7 @@ private function runDefaultConfigTests(array $doctrineIntegrationsToLoad = ['orm 'exception_to_status' => [ ExceptionInterface::class => Response::HTTP_BAD_REQUEST, InvalidArgumentException::class => Response::HTTP_BAD_REQUEST, - FilterValidationException::class => Response::HTTP_BAD_REQUEST, + ValidationExceptionInterface::class => Response::HTTP_BAD_REQUEST, OptimisticLockException::class => Response::HTTP_CONFLICT, ], 'path_segment_name_generator' => 'api_platform.metadata.path_segment_name_generator.underscore',