diff --git a/features/filter/filter_validation.feature b/features/filter/filter_validation.feature
index ab85b45dd5d..23b63bcff09 100644
--- a/features/filter/filter_validation.feature
+++ b/features/filter/filter_validation.feature
@@ -2,16 +2,18 @@ Feature: Validate filters based upon filter description
@createSchema
Scenario: Required filter should not throw an error if set
- When I am on "/filter_validators?required=foo"
+ When I am on "/filter_validators?required=foo&required-allow-empty=&arrayRequired[foo]="
Then the response status code should be 200
- When I am on "/filter_validators?required="
- Then the response status code should be 200
+ Scenario: Required filter that does not allow empty value should throw an error if empty
+ When I am on "/filter_validators?required=&required-allow-empty=&arrayRequired[foo]="
+ Then the response status code should be 400
+ And the JSON node "detail" should be equal to 'Query parameter "required" does not allow empty value'
Scenario: Required filter should throw an error if not set
When I am on "/filter_validators"
Then the response status code should be 400
- And the JSON node "detail" should be equal to 'Query parameter "required" is required'
+ Then the JSON node "detail" should match '/^Query parameter "required" is required\nQuery parameter "required-allow-empty" is required$/'
Scenario: Required filter should not throw an error if set
When I am on "/array_filter_validators?arrayRequired[]=foo&indexedArrayRequired[foo]=foo"
@@ -37,3 +39,129 @@ Feature: Validate filters based upon filter description
When I am on "/array_filter_validators?arrayRequired[]=foo&indexedArrayRequired[bar]=bar"
Then the response status code should be 400
And the JSON node "detail" should be equal to 'Query parameter "indexedArrayRequired[foo]" is required'
+
+ Scenario: Test filter bounds: maximum
+ When I am on "/filter_validators?required=foo&required-allow-empty&maximum=10"
+ Then the response status code should be 200
+
+ When I am on "/filter_validators?required=foo&required-allow-empty&maximum=11"
+ Then the response status code should be 400
+ And the JSON node "detail" should be equal to 'Query parameter "maximum" must be less than or equal to 10'
+
+ Scenario: Test filter bounds: exclusiveMaximum
+ When I am on "/filter_validators?required=foo&required-allow-empty&exclusiveMaximum=9"
+ Then the response status code should be 200
+
+ When I am on "/filter_validators?required=foo&required-allow-empty&exclusiveMaximum=10"
+ Then the response status code should be 400
+ And the JSON node "detail" should be equal to 'Query parameter "exclusiveMaximum" must be less than 10'
+
+ Scenario: Test filter bounds: minimum
+ When I am on "/filter_validators?required=foo&required-allow-empty&minimum=5"
+ Then the response status code should be 200
+
+ When I am on "/filter_validators?required=foo&required-allow-empty&minimum=0"
+ Then the response status code should be 400
+ And the JSON node "detail" should be equal to 'Query parameter "minimum" must be greater than or equal to 5'
+
+ Scenario: Test filter bounds: exclusiveMinimum
+ When I am on "/filter_validators?required=foo&required-allow-empty&exclusiveMinimum=6"
+ Then the response status code should be 200
+
+ When I am on "/filter_validators?required=foo&required-allow-empty&exclusiveMinimum=5"
+ Then the response status code should be 400
+ And the JSON node "detail" should be equal to 'Query parameter "exclusiveMinimum" must be greater than 5'
+
+ Scenario: Test filter bounds: max length
+ When I am on "/filter_validators?required=foo&required-allow-empty&max-length-3=123"
+ Then the response status code should be 200
+
+ When I am on "/filter_validators?required=foo&required-allow-empty&max-length-3=1234"
+ Then the response status code should be 400
+ And the JSON node "detail" should be equal to 'Query parameter "max-length-3" length must be lower than or equal to 3'
+
+ Scenario: Do not throw an error if value is not an array
+ When I am on "/filter_validators?required=foo&required-allow-empty&max-length-3[]=12345"
+ Then the response status code should be 200
+
+ Scenario: Test filter bounds: min length
+ When I am on "/filter_validators?required=foo&required-allow-empty&min-length-3=123"
+ Then the response status code should be 200
+
+ When I am on "/filter_validators?required=foo&required-allow-empty&min-length-3=12"
+ Then the response status code should be 400
+ And the JSON node "detail" should be equal to 'Query parameter "min-length-3" length must be greater than or equal to 3'
+
+ Scenario: Test filter pattern
+ When I am on "/filter_validators?required=foo&required-allow-empty&pattern=pattern"
+ When I am on "/filter_validators?required=foo&required-allow-empty&pattern=nrettap"
+ Then the response status code should be 200
+
+ When I am on "/filter_validators?required=foo&required-allow-empty&pattern=not-pattern"
+ Then the response status code should be 400
+ And the JSON node "detail" should be equal to 'Query parameter "pattern" must match pattern /^(pattern|nrettap)$/'
+
+ Scenario: Test filter enum
+ When I am on "/filter_validators?required=foo&required-allow-empty&enum=in-enum"
+ Then the response status code should be 200
+
+ When I am on "/filter_validators?required=foo&required-allow-empty&enum=not-in-enum"
+ Then the response status code should be 400
+ And the JSON node "detail" should be equal to 'Query parameter "enum" must be one of "in-enum, mune-ni"'
+
+ Scenario: Test filter multipleOf
+ When I am on "/filter_validators?required=foo&required-allow-empty&multiple-of=4"
+ Then the response status code should be 200
+
+ When I am on "/filter_validators?required=foo&required-allow-empty&multiple-of=3"
+ Then the response status code should be 400
+ And the JSON node "detail" should be equal to 'Query parameter "multiple-of" must multiple of 2'
+
+ Scenario: Test filter array items csv format minItems
+ When I am on "/filter_validators?required=foo&required-allow-empty&csv-min-2=a,b"
+ Then the response status code should be 200
+
+ When I am on "/filter_validators?required=foo&required-allow-empty&csv-min-2=a"
+ Then the response status code should be 400
+ And the JSON node "detail" should be equal to 'Query parameter "csv-min-2" must contain more than 2 values'
+
+ Scenario: Test filter array items csv format maxItems
+ When I am on "/filter_validators?required=foo&required-allow-empty&csv-max-3=a,b,c"
+ Then the response status code should be 200
+
+ When I am on "/filter_validators?required=foo&required-allow-empty&csv-max-3=a,b,c,d"
+ Then the response status code should be 400
+ And the JSON node "detail" should be equal to 'Query parameter "csv-max-3" must contain less than 3 values'
+
+ Scenario: Test filter array items tsv format minItems
+ When I am on "/filter_validators?required=foo&required-allow-empty&tsv-min-2=a\tb"
+ Then the response status code should be 200
+
+ When I am on "/filter_validators?required=foo&required-allow-empty&tsv-min-2=a,b"
+ Then the response status code should be 400
+ And the JSON node "detail" should be equal to 'Query parameter "tsv-min-2" must contain more than 2 values'
+
+ Scenario: Test filter array items pipes format minItems
+ When I am on "/filter_validators?required=foo&required-allow-empty&pipes-min-2=a|b"
+ Then the response status code should be 200
+
+ When I am on "/filter_validators?required=foo&required-allow-empty&pipes-min-2=a,b"
+ Then the response status code should be 400
+ And the JSON node "detail" should be equal to 'Query parameter "pipes-min-2" must contain more than 2 values'
+
+ Scenario: Test filter array items ssv format minItems
+ When I am on "/filter_validators?required=foo&required-allow-empty&ssv-min-2=a b"
+ Then the response status code should be 200
+
+ When I am on "/filter_validators?required=foo&required-allow-empty&ssv-min-2=a,b"
+ Then the response status code should be 400
+ And the JSON node "detail" should be equal to 'Query parameter "ssv-min-2" must contain more than 2 values'
+
+ @dropSchema
+ Scenario: Test filter array items unique items
+ When I am on "/filter_validators?required=foo&required-allow-empty&csv-uniques=a,b"
+ Then the response status code should be 200
+
+ When I am on "/filter_validators?required=foo&required-allow-empty&csv-uniques=a,a"
+ Then the response status code should be 400
+ And the JSON node "detail" should be equal to 'Query parameter "csv-uniques" must contain unique values'
diff --git a/src/Bridge/Symfony/Bundle/Resources/config/validator.xml b/src/Bridge/Symfony/Bundle/Resources/config/validator.xml
index d34dcd8cfe6..2526c93cfc6 100644
--- a/src/Bridge/Symfony/Bundle/Resources/config/validator.xml
+++ b/src/Bridge/Symfony/Bundle/Resources/config/validator.xml
@@ -23,9 +23,13 @@
-
-
+
+
+
+
+
+
diff --git a/src/Bridge/Symfony/Validator/Validator.php b/src/Bridge/Symfony/Validator/Validator.php
index 84a7e3a36dc..b804999e732 100644
--- a/src/Bridge/Symfony/Validator/Validator.php
+++ b/src/Bridge/Symfony/Validator/Validator.php
@@ -23,6 +23,8 @@
* Validates an item using the Symfony validator component.
*
* @author Kévin Dunglas
+ *
+ * @final
*/
class Validator implements ValidatorInterface
{
diff --git a/src/EventListener/QueryParameterValidateListener.php b/src/EventListener/QueryParameterValidateListener.php
new file mode 100644
index 00000000000..e9334c1583c
--- /dev/null
+++ b/src/EventListener/QueryParameterValidateListener.php
@@ -0,0 +1,59 @@
+
+ *
+ * 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\Core\EventListener;
+
+use ApiPlatform\Core\Filter\QueryParameterValidator;
+use ApiPlatform\Core\Metadata\Resource\Factory\ResourceMetadataFactoryInterface;
+use ApiPlatform\Core\Util\RequestAttributesExtractor;
+use ApiPlatform\Core\Util\RequestParser;
+use Symfony\Component\HttpKernel\Event\RequestEvent;
+
+/**
+ * Validates query parameters depending on filter description.
+ *
+ * @author Julien Deniau
+ */
+final class QueryParameterValidateListener
+{
+ private $resourceMetadataFactory;
+
+ private $queryParameterValidator;
+
+ public function __construct(ResourceMetadataFactoryInterface $resourceMetadataFactory, QueryParameterValidator $queryParameterValidator)
+ {
+ $this->resourceMetadataFactory = $resourceMetadataFactory;
+ $this->queryParameterValidator = $queryParameterValidator;
+ }
+
+ public function onKernelRequest(RequestEvent $event)
+ {
+ $request = $event->getRequest();
+ if (
+ !$request->isMethodSafe()
+ || !($attributes = RequestAttributesExtractor::extractAttributes($request))
+ || !isset($attributes['collection_operation_name'])
+ || !($operationName = $attributes['collection_operation_name'])
+ || 'GET' !== $request->getMethod()
+ ) {
+ return;
+ }
+ $queryString = RequestParser::getQueryString($request);
+ $queryParameters = $queryString ? RequestParser::parseRequestParams($queryString) : [];
+
+ $resourceMetadata = $this->resourceMetadataFactory->create($attributes['resource_class']);
+ $resourceFilters = $resourceMetadata->getCollectionOperationAttribute($operationName, 'filters', [], true);
+
+ $this->queryParameterValidator->validateFilters($attributes['resource_class'], $resourceFilters, $queryParameters);
+ }
+}
diff --git a/src/Filter/QueryParameterValidateListener.php b/src/Filter/QueryParameterValidateListener.php
deleted file mode 100644
index c6583a02c44..00000000000
--- a/src/Filter/QueryParameterValidateListener.php
+++ /dev/null
@@ -1,104 +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\Core\Filter;
-
-use ApiPlatform\Core\Api\FilterLocatorTrait;
-use ApiPlatform\Core\Exception\FilterValidationException;
-use ApiPlatform\Core\Metadata\Resource\Factory\ResourceMetadataFactoryInterface;
-use ApiPlatform\Core\Util\RequestAttributesExtractor;
-use Psr\Container\ContainerInterface;
-use Symfony\Component\HttpFoundation\Request;
-use Symfony\Component\HttpKernel\Event\RequestEvent;
-
-/**
- * Validates query parameters depending on filter description.
- *
- * @author Julien Deniau
- */
-final class QueryParameterValidateListener
-{
- use FilterLocatorTrait;
-
- private $resourceMetadataFactory;
-
- public function __construct(ResourceMetadataFactoryInterface $resourceMetadataFactory, ContainerInterface $filterLocator)
- {
- $this->resourceMetadataFactory = $resourceMetadataFactory;
- $this->setFilterLocator($filterLocator);
- }
-
- public function onKernelRequest(RequestEvent $event): void
- {
- $request = $event->getRequest();
- if (
- !$request->isMethodSafe()
- || !($attributes = RequestAttributesExtractor::extractAttributes($request))
- || !isset($attributes['collection_operation_name'])
- || 'get' !== ($operationName = $attributes['collection_operation_name'])
- ) {
- return;
- }
-
- $resourceMetadata = $this->resourceMetadataFactory->create($attributes['resource_class']);
- $resourceFilters = $resourceMetadata->getCollectionOperationAttribute($operationName, 'filters', [], true);
-
- $errorList = [];
- foreach ($resourceFilters as $filterId) {
- if (!$filter = $this->getFilter($filterId)) {
- continue;
- }
-
- foreach ($filter->getDescription($attributes['resource_class']) as $name => $data) {
- if (!($data['required'] ?? false)) { // property is not required
- continue;
- }
-
- if (!$this->isRequiredFilterValid($name, $request)) {
- $errorList[] = sprintf('Query parameter "%s" is required', $name);
- }
- }
- }
-
- if ($errorList) {
- throw new FilterValidationException($errorList);
- }
- }
-
- /**
- * Test if required filter is valid. It validates array notation too like "required[bar]".
- */
- private function isRequiredFilterValid(string $name, Request $request): bool
- {
- $matches = [];
- parse_str($name, $matches);
- if (!$matches) {
- return false;
- }
-
- $rootName = (string) (array_keys($matches)[0] ?? null);
- if (!$rootName) {
- return false;
- }
-
- if (\is_array($matches[$rootName])) {
- $keyName = array_keys($matches[$rootName])[0];
-
- $queryParameter = $request->query->get($rootName);
-
- return \is_array($queryParameter) && isset($queryParameter[$keyName]);
- }
-
- return null !== $request->query->get($rootName);
- }
-}
diff --git a/src/Filter/QueryParameterValidator.php b/src/Filter/QueryParameterValidator.php
new file mode 100644
index 00000000000..5a85a22333a
--- /dev/null
+++ b/src/Filter/QueryParameterValidator.php
@@ -0,0 +1,66 @@
+
+ *
+ * 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\Core\Filter;
+
+use ApiPlatform\Core\Api\FilterLocatorTrait;
+use ApiPlatform\Core\Exception\FilterValidationException;
+use Psr\Container\ContainerInterface;
+
+/**
+ * Validates query parameters depending on filter description.
+ *
+ * @author Julien Deniau
+ */
+class QueryParameterValidator
+{
+ use FilterLocatorTrait;
+
+ private $validators;
+
+ public function __construct(ContainerInterface $filterLocator)
+ {
+ $this->setFilterLocator($filterLocator);
+
+ $this->validators = [
+ new Validator\ArrayItems(),
+ new Validator\Bounds(),
+ new Validator\Enum(),
+ new Validator\Length(),
+ new Validator\MultipleOf(),
+ new Validator\Pattern(),
+ new Validator\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) {
+ $errorList = array_merge($errorList, $validator->validate($name, $data, $queryParameters));
+ }
+ }
+ }
+
+ if ($errorList) {
+ throw new FilterValidationException($errorList);
+ }
+ }
+}
diff --git a/src/Filter/Validator/ArrayItems.php b/src/Filter/Validator/ArrayItems.php
new file mode 100644
index 00000000000..e29e7200f84
--- /dev/null
+++ b/src/Filter/Validator/ArrayItems.php
@@ -0,0 +1,83 @@
+
+ *
+ * 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\Core\Filter\Validator;
+
+final class ArrayItems implements ValidatorInterface
+{
+ /**
+ * {@inheritdoc}
+ */
+ public function validate(string $name, array $filterDescription, array $queryParameters): array
+ {
+ if (!\array_key_exists($name, $queryParameters)) {
+ return [];
+ }
+
+ $maxItems = $filterDescription['swagger']['maxItems'] ?? null;
+ $minItems = $filterDescription['swagger']['minItems'] ?? null;
+ $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['swagger']['collectionFormat'] ?? 'csv';
+
+ return explode(self::getSeparator($collectionFormat), $value) ?: [];
+ }
+
+ private static function getSeparator(string $collectionFormat): string
+ {
+ switch ($collectionFormat) {
+ case 'csv':
+ return ',';
+ case 'ssv':
+ return ' ';
+ case 'tsv':
+ return '\t';
+ case 'pipes':
+ return '|';
+ default:
+ throw new \InvalidArgumentException(sprintf('Unknown collection format %s', $collectionFormat));
+ }
+ }
+}
diff --git a/src/Filter/Validator/Bounds.php b/src/Filter/Validator/Bounds.php
new file mode 100644
index 00000000000..bb7c974b2c1
--- /dev/null
+++ b/src/Filter/Validator/Bounds.php
@@ -0,0 +1,51 @@
+
+ *
+ * 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\Core\Filter\Validator;
+
+final class Bounds implements ValidatorInterface
+{
+ /**
+ * {@inheritdoc}
+ */
+ public function validate(string $name, array $filterDescription, array $queryParameters): array
+ {
+ $value = $queryParameters[$name] ?? null;
+ if (empty($value) && '0' !== $value) {
+ return [];
+ }
+
+ $maximum = $filterDescription['swagger']['maximum'] ?? null;
+ $minimum = $filterDescription['swagger']['minimum'] ?? null;
+
+ $errorList = [];
+
+ if (null !== $maximum) {
+ if (($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['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/Filter/Validator/Enum.php b/src/Filter/Validator/Enum.php
new file mode 100644
index 00000000000..5393de43ad0
--- /dev/null
+++ b/src/Filter/Validator/Enum.php
@@ -0,0 +1,38 @@
+
+ *
+ * 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\Core\Filter\Validator;
+
+final class Enum implements ValidatorInterface
+{
+ /**
+ * {@inheritdoc}
+ */
+ public function validate(string $name, array $filterDescription, array $queryParameters): array
+ {
+ $value = $queryParameters[$name] ?? null;
+ if (empty($value) && '0' !== $value || !\is_string($value)) {
+ return [];
+ }
+
+ $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/Filter/Validator/Length.php b/src/Filter/Validator/Length.php
new file mode 100644
index 00000000000..6897ef57e02
--- /dev/null
+++ b/src/Filter/Validator/Length.php
@@ -0,0 +1,43 @@
+
+ *
+ * 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\Core\Filter\Validator;
+
+final class Length implements ValidatorInterface
+{
+ /**
+ * {@inheritdoc}
+ */
+ public function validate(string $name, array $filterDescription, array $queryParameters): array
+ {
+ $value = $queryParameters[$name] ?? null;
+ if (empty($value) && '0' !== $value || !\is_string($value)) {
+ return [];
+ }
+
+ $maxLength = $filterDescription['swagger']['maxLength'] ?? null;
+ $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/Filter/Validator/MultipleOf.php b/src/Filter/Validator/MultipleOf.php
new file mode 100644
index 00000000000..75235007bdd
--- /dev/null
+++ b/src/Filter/Validator/MultipleOf.php
@@ -0,0 +1,38 @@
+
+ *
+ * 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\Core\Filter\Validator;
+
+final class MultipleOf implements ValidatorInterface
+{
+ /**
+ * {@inheritdoc}
+ */
+ public function validate(string $name, array $filterDescription, array $queryParameters): array
+ {
+ $value = $queryParameters[$name] ?? null;
+ if (empty($value) && '0' !== $value || !\is_string($value)) {
+ return [];
+ }
+
+ $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/Filter/Validator/Pattern.php b/src/Filter/Validator/Pattern.php
new file mode 100644
index 00000000000..5346feac04b
--- /dev/null
+++ b/src/Filter/Validator/Pattern.php
@@ -0,0 +1,38 @@
+
+ *
+ * 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\Core\Filter\Validator;
+
+final class Pattern implements ValidatorInterface
+{
+ /**
+ * {@inheritdoc}
+ */
+ public function validate(string $name, array $filterDescription, array $queryParameters): array
+ {
+ $value = $queryParameters[$name] ?? null;
+ if (empty($value) && '0' !== $value || !\is_string($value)) {
+ return [];
+ }
+
+ $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/Filter/Validator/Required.php b/src/Filter/Validator/Required.php
new file mode 100644
index 00000000000..4c5fb2d92a8
--- /dev/null
+++ b/src/Filter/Validator/Required.php
@@ -0,0 +1,104 @@
+
+ *
+ * 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\Core\Filter\Validator;
+
+final class Required implements ValidatorInterface
+{
+ /**
+ * {@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),
+ ];
+ }
+
+ // if query param is empty and the configuration does not allow it
+ if (!($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 = [];
+ parse_str($name, $matches);
+ 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]".
+ *
+ * @return ?mixed
+ */
+ private function requestGetQueryParameter(array $queryParameters, string $name)
+ {
+ $matches = [];
+ parse_str($name, $matches);
+ 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/Filter/Validator/ValidatorInterface.php b/src/Filter/Validator/ValidatorInterface.php
new file mode 100644
index 00000000000..2a2113362b0
--- /dev/null
+++ b/src/Filter/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\Core\Filter\Validator;
+
+interface ValidatorInterface
+{
+ /**
+ * @var string the parameter name to validate
+ * @var array $filterDescription the filter descriptions as returned by `ApiPlatform\Core\Api\FilterInterface::getDescription()`
+ * @var array $queryParameters the list of query parameter
+ */
+ public function validate(string $name, array $filterDescription, array $queryParameters): array;
+}
diff --git a/tests/Bridge/Symfony/Bundle/DependencyInjection/ApiPlatformExtensionTest.php b/tests/Bridge/Symfony/Bundle/DependencyInjection/ApiPlatformExtensionTest.php
index e9034147400..93c83f0429d 100644
--- a/tests/Bridge/Symfony/Bundle/DependencyInjection/ApiPlatformExtensionTest.php
+++ b/tests/Bridge/Symfony/Bundle/DependencyInjection/ApiPlatformExtensionTest.php
@@ -1236,6 +1236,7 @@ private function getBaseContainerBuilderProphecy(array $doctrineIntegrationsToLo
'api_platform.swagger.action.ui',
'api_platform.swagger.listener.ui',
'api_platform.validator',
+ 'api_platform.validator.query_parameter_validator',
'test.api_platform.client',
];
diff --git a/tests/Filter/QueryParameterValidateListenerTest.php b/tests/EventListener/QueryParameterValidateListenerTest.php
similarity index 69%
rename from tests/Filter/QueryParameterValidateListenerTest.php
rename to tests/EventListener/QueryParameterValidateListenerTest.php
index 47e6efacfb2..8a0b53f8361 100644
--- a/tests/Filter/QueryParameterValidateListenerTest.php
+++ b/tests/EventListener/QueryParameterValidateListenerTest.php
@@ -11,23 +11,22 @@
declare(strict_types=1);
-namespace ApiPlatform\Core\Tests\Filter;
+namespace ApiPlatform\Core\Tests\EventListener;
-use ApiPlatform\Core\Api\FilterInterface;
+use ApiPlatform\Core\EventListener\QueryParameterValidateListener;
use ApiPlatform\Core\Exception\FilterValidationException;
-use ApiPlatform\Core\Filter\QueryParameterValidateListener;
+use ApiPlatform\Core\Filter\QueryParameterValidator;
use ApiPlatform\Core\Metadata\Resource\Factory\ResourceMetadataFactoryInterface;
use ApiPlatform\Core\Metadata\Resource\ResourceMetadata;
use ApiPlatform\Core\Tests\Fixtures\TestBundle\Entity\Dummy;
use PHPUnit\Framework\TestCase;
-use Psr\Container\ContainerInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpKernel\Event\RequestEvent;
class QueryParameterValidateListenerTest extends TestCase
{
private $testedInstance;
- private $filterLocatorProphecy;
+ private $queryParameterValidor;
/**
* unsafe method should not use filter validations.
@@ -60,8 +59,7 @@ public function testOnKernelRequestWithWrongFilter()
$eventProphecy = $this->prophesize(RequestEvent::class);
$eventProphecy->getRequest()->willReturn($request)->shouldBeCalled();
- $this->filterLocatorProphecy->has('some_inexistent_filter')->shouldBeCalled();
- $this->filterLocatorProphecy->get('some_inexistent_filter')->shouldNotBeCalled();
+ $this->queryParameterValidor->validateFilters(Dummy::class, ['some_inexistent_filter'], [])->shouldBeCalled();
$this->assertNull(
$this->testedInstance->onKernelRequest($eventProphecy->reveal())
@@ -81,24 +79,10 @@ public function testOnKernelRequestWithRequiredFilterNotSet()
$eventProphecy = $this->prophesize(RequestEvent::class);
$eventProphecy->getRequest()->willReturn($request)->shouldBeCalled();
- $this->filterLocatorProphecy
- ->has('some_filter')
+ $this->queryParameterValidor
+ ->validateFilters(Dummy::class, ['some_filter'], [])
->shouldBeCalled()
- ->willReturn(true);
- $filterProphecy = $this->prophesize(FilterInterface::class);
- $filterProphecy
- ->getDescription(Dummy::class)
- ->shouldBeCalled()
- ->willReturn([
- 'required' => [
- 'required' => true,
- ],
- ]);
- $this->filterLocatorProphecy
- ->get('some_filter')
- ->shouldBeCalled()
- ->willReturn($filterProphecy->reveal());
-
+ ->willThrow(new FilterValidationException(['Query parameter "required" is required']));
$this->expectException(FilterValidationException::class);
$this->expectExceptionMessage('Query parameter "required" is required');
$this->testedInstance->onKernelRequest($eventProphecy->reveal());
@@ -112,32 +96,21 @@ public function testOnKernelRequestWithRequiredFilter()
$this->setUpWithFilters(['some_filter']);
$request = new Request(
- ['required' => 'foo'],
[],
- ['_api_resource_class' => Dummy::class, '_api_collection_operation_name' => 'get']
+ [],
+ ['_api_resource_class' => Dummy::class, '_api_collection_operation_name' => 'get'],
+ [],
+ [],
+ ['QUERY_STRING' => 'required=foo']
);
$request->setMethod('GET');
$eventProphecy = $this->prophesize(RequestEvent::class);
$eventProphecy->getRequest()->willReturn($request)->shouldBeCalled();
- $this->filterLocatorProphecy
- ->has('some_filter')
- ->shouldBeCalled()
- ->willReturn(true);
- $filterProphecy = $this->prophesize(FilterInterface::class);
- $filterProphecy
- ->getDescription(Dummy::class)
- ->shouldBeCalled()
- ->willReturn([
- 'required' => [
- 'required' => true,
- ],
- ]);
- $this->filterLocatorProphecy
- ->get('some_filter')
- ->shouldBeCalled()
- ->willReturn($filterProphecy->reveal());
+ $this->queryParameterValidor
+ ->validateFilters(Dummy::class, ['some_filter'], ['required' => 'foo'])
+ ->shouldBeCalled();
$this->assertNull(
$this->testedInstance->onKernelRequest($eventProphecy->reveal())
@@ -156,11 +129,11 @@ private function setUpWithFilters(array $filters = [])
])
);
- $this->filterLocatorProphecy = $this->prophesize(ContainerInterface::class);
+ $this->queryParameterValidor = $this->prophesize(QueryParameterValidator::class);
$this->testedInstance = new QueryParameterValidateListener(
$resourceMetadataFactoryProphecy->reveal(),
- $this->filterLocatorProphecy->reveal()
+ $this->queryParameterValidor->reveal()
);
}
}
diff --git a/tests/Filter/QueryParameterValidatorTest.php b/tests/Filter/QueryParameterValidatorTest.php
new file mode 100644
index 00000000000..1c9e60f06f6
--- /dev/null
+++ b/tests/Filter/QueryParameterValidatorTest.php
@@ -0,0 +1,128 @@
+
+ *
+ * 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\Core\Tests\Filter;
+
+use ApiPlatform\Core\Api\FilterInterface;
+use ApiPlatform\Core\Exception\FilterValidationException;
+use ApiPlatform\Core\Filter\QueryParameterValidator;
+use ApiPlatform\Core\Tests\Fixtures\TestBundle\Entity\Dummy;
+use PHPUnit\Framework\TestCase;
+use Psr\Container\ContainerInterface;
+
+/**
+ * Class QueryParameterValidatorTest.
+ *
+ * @author Julien Deniau
+ */
+class QueryParameterValidatorTest extends TestCase
+{
+ private $testedInstance;
+ private $filterLocatorProphecy;
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function setUp(): void
+ {
+ $this->filterLocatorProphecy = $this->prophesize(ContainerInterface::class);
+
+ $this->testedInstance = new QueryParameterValidator(
+ $this->filterLocatorProphecy->reveal()
+ );
+ }
+
+ /**
+ * unsafe method should not use filter validations.
+ */
+ public function testOnKernelRequestWithUnsafeMethod()
+ {
+ $request = [];
+
+ $this->assertNull(
+ $this->testedInstance->validateFilters(Dummy::class, [], $request)
+ );
+ }
+
+ /**
+ * If the tested filter is non-existant, then nothing should append.
+ */
+ public function testOnKernelRequestWithWrongFilter()
+ {
+ $request = [];
+
+ $this->assertNull(
+ $this->testedInstance->validateFilters(Dummy::class, ['some_inexistent_filter'], $request)
+ );
+ }
+
+ /**
+ * if the required parameter is not set, throw an FilterValidationException.
+ */
+ public function testOnKernelRequestWithRequiredFilterNotSet()
+ {
+ $request = [];
+
+ $filterProphecy = $this->prophesize(FilterInterface::class);
+ $filterProphecy
+ ->getDescription(Dummy::class)
+ ->shouldBeCalled()
+ ->willReturn([
+ 'required' => [
+ 'required' => true,
+ ],
+ ]);
+ $this->filterLocatorProphecy
+ ->has('some_filter')
+ ->shouldBeCalled()
+ ->willReturn(true);
+ $this->filterLocatorProphecy
+ ->get('some_filter')
+ ->shouldBeCalled()
+ ->willReturn($filterProphecy->reveal());
+
+ $this->expectException(FilterValidationException::class);
+ $this->expectExceptionMessage('Query parameter "required" is required');
+ $this->testedInstance->validateFilters(Dummy::class, ['some_filter'], $request);
+ }
+
+ /**
+ * if the required parameter is set, no exception should be throwned.
+ */
+ public function testOnKernelRequestWithRequiredFilter()
+ {
+ $request = ['required' => 'foo'];
+
+ $this->filterLocatorProphecy
+ ->has('some_filter')
+ ->shouldBeCalled()
+ ->willReturn(true);
+ $filterProphecy = $this->prophesize(FilterInterface::class);
+ $filterProphecy
+ ->getDescription(Dummy::class)
+ ->shouldBeCalled()
+ ->willReturn([
+ 'required' => [
+ 'required' => true,
+ ],
+ ]);
+ $this->filterLocatorProphecy
+ ->get('some_filter')
+ ->shouldBeCalled()
+ ->willReturn($filterProphecy->reveal());
+
+ $this->assertNull(
+ $this->testedInstance->validateFilters(Dummy::class, ['some_filter'], $request)
+ );
+ }
+}
diff --git a/tests/Filter/Validator/ArrayItemsTest.php b/tests/Filter/Validator/ArrayItemsTest.php
new file mode 100644
index 00000000000..66aee6ee7e5
--- /dev/null
+++ b/tests/Filter/Validator/ArrayItemsTest.php
@@ -0,0 +1,198 @@
+
+ *
+ * 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\Core\Tests\Filter\Validator;
+
+use ApiPlatform\Core\Filter\Validator\ArrayItems;
+use PHPUnit\Framework\TestCase;
+
+/**
+ * @author Julien Deniau
+ */
+class ArrayItemsTest extends TestCase
+{
+ public function testNonDefinedFilter()
+ {
+ $request = [];
+ $filter = new ArrayItems();
+
+ $this->assertEmpty(
+ $filter->validate('some_filter', [], $request)
+ );
+ }
+
+ public function testEmptyQueryParameter()
+ {
+ $request = ['some_filter' => ''];
+ $filter = new ArrayItems();
+
+ $this->assertEmpty(
+ $filter->validate('some_filter', [], $request)
+ );
+ }
+
+ public function testNonMatchingParameter()
+ {
+ $filter = new ArrayItems();
+
+ $filterDefinition = [
+ 'swagger' => [
+ 'maxItems' => 3,
+ 'minItems' => 2,
+ ],
+ ];
+
+ $request = ['some_filter' => ['foo', 'bar', 'bar', 'foo']];
+ $this->assertEquals(
+ ['Query parameter "some_filter" must contain less than 3 values'],
+ $filter->validate('some_filter', $filterDefinition, $request)
+ );
+
+ $request = ['some_filter' => ['foo']];
+ $this->assertEquals(
+ ['Query parameter "some_filter" must contain more than 2 values'],
+ $filter->validate('some_filter', $filterDefinition, $request)
+ );
+ }
+
+ public function testMatchingParameter()
+ {
+ $filter = new ArrayItems();
+
+ $filterDefinition = [
+ 'swagger' => [
+ 'maxItems' => 3,
+ 'minItems' => 2,
+ ],
+ ];
+
+ $request = ['some_filter' => ['foo', 'bar']];
+ $this->assertEmpty(
+ $filter->validate('some_filter', $filterDefinition, $request)
+ );
+
+ $request = ['some_filter' => ['foo', 'bar', 'baz']];
+ $this->assertEmpty(
+ $filter->validate('some_filter', $filterDefinition, $request)
+ );
+ }
+
+ public function testNonMatchingUniqueItems()
+ {
+ $filter = new ArrayItems();
+
+ $filterDefinition = [
+ 'swagger' => [
+ 'uniqueItems' => true,
+ ],
+ ];
+
+ $request = ['some_filter' => ['foo', 'bar', 'bar', 'foo']];
+ $this->assertEquals(
+ ['Query parameter "some_filter" must contain unique values'],
+ $filter->validate('some_filter', $filterDefinition, $request)
+ );
+ }
+
+ public function testMatchingUniqueItems()
+ {
+ $filter = new ArrayItems();
+
+ $filterDefinition = [
+ 'swagger' => [
+ 'uniqueItems' => true,
+ ],
+ ];
+
+ $request = ['some_filter' => ['foo', 'bar', 'baz']];
+ $this->assertEmpty(
+ $filter->validate('some_filter', $filterDefinition, $request)
+ );
+ }
+
+ public function testSeparators()
+ {
+ $filter = new ArrayItems();
+
+ $filterDefinition = [
+ 'swagger' => [
+ 'maxItems' => 2,
+ 'uniqueItems' => true,
+ 'collectionFormat' => 'csv',
+ ],
+ ];
+
+ $request = ['some_filter' => 'foo,bar,bar'];
+ $this->assertEquals(
+ [
+ 'Query parameter "some_filter" must contain less than 2 values',
+ 'Query parameter "some_filter" must contain unique values',
+ ],
+ $filter->validate('some_filter', $filterDefinition, $request)
+ );
+
+ $filterDefinition['swagger']['collectionFormat'] = 'ssv';
+ $this->assertEmpty(
+ $filter->validate('some_filter', $filterDefinition, $request)
+ );
+
+ $filterDefinition['swagger']['collectionFormat'] = 'ssv';
+ $request = ['some_filter' => 'foo bar bar'];
+ $this->assertEquals(
+ [
+ 'Query parameter "some_filter" must contain less than 2 values',
+ 'Query parameter "some_filter" must contain unique values',
+ ],
+ $filter->validate('some_filter', $filterDefinition, $request)
+ );
+
+ $filterDefinition['swagger']['collectionFormat'] = 'tsv';
+ $request = ['some_filter' => 'foo\tbar\tbar'];
+ $this->assertEquals(
+ [
+ 'Query parameter "some_filter" must contain less than 2 values',
+ 'Query parameter "some_filter" must contain unique values',
+ ],
+ $filter->validate('some_filter', $filterDefinition, $request)
+ );
+
+ $filterDefinition['swagger']['collectionFormat'] = 'pipes';
+ $request = ['some_filter' => 'foo|bar|bar'];
+ $this->assertEquals(
+ [
+ 'Query parameter "some_filter" must contain less than 2 values',
+ 'Query parameter "some_filter" must contain unique values',
+ ],
+ $filter->validate('some_filter', $filterDefinition, $request)
+ );
+ }
+
+ public function testSeparatorsUnknownSeparator()
+ {
+ $filter = new ArrayItems();
+
+ $filterDefinition = [
+ 'swagger' => [
+ 'maxItems' => 2,
+ 'uniqueItems' => true,
+ 'collectionFormat' => 'unknownFormat',
+ ],
+ ];
+ $request = ['some_filter' => 'foo,bar,bar'];
+
+ $this->expectException(\InvalidArgumentException::class);
+ $this->expectExceptionMessage('Unknown collection format unknownFormat');
+
+ $filter->validate('some_filter', $filterDefinition, $request);
+ }
+}
diff --git a/tests/Filter/Validator/BoundsTest.php b/tests/Filter/Validator/BoundsTest.php
new file mode 100644
index 00000000000..50f2958ec43
--- /dev/null
+++ b/tests/Filter/Validator/BoundsTest.php
@@ -0,0 +1,177 @@
+
+ *
+ * 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\Core\Tests\Filter\Validator;
+
+use ApiPlatform\Core\Filter\Validator\Bounds;
+use PHPUnit\Framework\TestCase;
+
+/**
+ * @author Julien Deniau
+ */
+class BoundsTest extends TestCase
+{
+ public function testNonDefinedFilter()
+ {
+ $filter = new Bounds();
+
+ $this->assertEmpty(
+ $filter->validate('some_filter', [], [])
+ );
+ }
+
+ public function testEmptyQueryParameter()
+ {
+ $filter = new Bounds();
+
+ $this->assertEmpty(
+ $filter->validate('some_filter', [], ['some_filter' => ''])
+ );
+ }
+
+ public function testNonMatchingMinimum()
+ {
+ $request = ['some_filter' => '9'];
+ $filter = new Bounds();
+
+ $filterDefinition = [
+ 'swagger' => [
+ 'minimum' => 10,
+ ],
+ ];
+
+ $this->assertEquals(
+ ['Query parameter "some_filter" must be greater than or equal to 10'],
+ $filter->validate('some_filter', $filterDefinition, $request)
+ );
+
+ $filterDefinition = [
+ 'swagger' => [
+ 'minimum' => 10,
+ 'exclusiveMinimum' => false,
+ ],
+ ];
+
+ $this->assertEquals(
+ ['Query parameter "some_filter" must be greater than or equal to 10'],
+ $filter->validate('some_filter', $filterDefinition, $request)
+ );
+
+ $filterDefinition = [
+ 'swagger' => [
+ 'minimum' => 9,
+ 'exclusiveMinimum' => true,
+ ],
+ ];
+
+ $this->assertEquals(
+ ['Query parameter "some_filter" must be greater than 9'],
+ $filter->validate('some_filter', $filterDefinition, $request)
+ );
+ }
+
+ public function testMatchingMinimum()
+ {
+ $request = ['some_filter' => '10'];
+ $filter = new Bounds();
+
+ $filterDefinition = [
+ 'swagger' => [
+ 'minimum' => 10,
+ ],
+ ];
+
+ $this->assertEmpty(
+ $filter->validate('some_filter', $filterDefinition, $request)
+ );
+
+ $filterDefinition = [
+ 'swagger' => [
+ 'minimum' => 9,
+ 'exclusiveMinimum' => false,
+ ],
+ ];
+
+ $this->assertEmpty(
+ $filter->validate('some_filter', $filterDefinition, $request)
+ );
+ }
+
+ public function testNonMatchingMaximum()
+ {
+ $request = ['some_filter' => '11'];
+ $filter = new Bounds();
+
+ $filterDefinition = [
+ 'swagger' => [
+ 'maximum' => 10,
+ ],
+ ];
+
+ $this->assertEquals(
+ ['Query parameter "some_filter" must be less than or equal to 10'],
+ $filter->validate('some_filter', $filterDefinition, $request)
+ );
+
+ $filterDefinition = [
+ 'swagger' => [
+ 'maximum' => 10,
+ 'exclusiveMaximum' => false,
+ ],
+ ];
+
+ $this->assertEquals(
+ ['Query parameter "some_filter" must be less than or equal to 10'],
+ $filter->validate('some_filter', $filterDefinition, $request)
+ );
+
+ $filterDefinition = [
+ 'swagger' => [
+ 'maximum' => 9,
+ 'exclusiveMaximum' => true,
+ ],
+ ];
+
+ $this->assertEquals(
+ ['Query parameter "some_filter" must be less than 9'],
+ $filter->validate('some_filter', $filterDefinition, $request)
+ );
+ }
+
+ public function testMatchingMaximum()
+ {
+ $request = ['some_filter' => '10'];
+ $filter = new Bounds();
+
+ $filterDefinition = [
+ 'swagger' => [
+ 'maximum' => 10,
+ ],
+ ];
+
+ $this->assertEmpty(
+ $filter->validate('some_filter', $filterDefinition, $request)
+ );
+
+ $filterDefinition = [
+ 'swagger' => [
+ 'maximum' => 10,
+ 'exclusiveMaximum' => false,
+ ],
+ ];
+
+ $this->assertEmpty(
+ $filter->validate('some_filter', $filterDefinition, $request)
+ );
+ }
+}
diff --git a/tests/Filter/Validator/EnumTest.php b/tests/Filter/Validator/EnumTest.php
new file mode 100644
index 00000000000..bd55f076a65
--- /dev/null
+++ b/tests/Filter/Validator/EnumTest.php
@@ -0,0 +1,72 @@
+
+ *
+ * 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\Core\Tests\Filter\Validator;
+
+use ApiPlatform\Core\Filter\Validator\Enum;
+use PHPUnit\Framework\TestCase;
+
+/**
+ * @author Julien Deniau
+ */
+class EnumTest extends TestCase
+{
+ public function testNonDefinedFilter()
+ {
+ $filter = new Enum();
+
+ $this->assertEmpty(
+ $filter->validate('some_filter', [], [])
+ );
+ }
+
+ public function testEmptyQueryParameter()
+ {
+ $filter = new Enum();
+
+ $this->assertEmpty(
+ $filter->validate('some_filter', [], ['some_filter' => ''])
+ );
+ }
+
+ public function testNonMatchingParameter()
+ {
+ $filter = new Enum();
+
+ $filterDefinition = [
+ 'swagger' => [
+ 'enum' => ['foo', 'bar'],
+ ],
+ ];
+
+ $this->assertEquals(
+ ['Query parameter "some_filter" must be one of "foo, bar"'],
+ $filter->validate('some_filter', $filterDefinition, ['some_filter' => 'foobar'])
+ );
+ }
+
+ public function testMatchingParameter()
+ {
+ $filter = new Enum();
+
+ $filterDefinition = [
+ 'swagger' => [
+ 'enum' => ['foo', 'bar'],
+ ],
+ ];
+
+ $this->assertEmpty(
+ $filter->validate('some_filter', $filterDefinition, ['some_filter' => 'foo'])
+ );
+ }
+}
diff --git a/tests/Filter/Validator/LengthTest.php b/tests/Filter/Validator/LengthTest.php
new file mode 100644
index 00000000000..d9f36b3500c
--- /dev/null
+++ b/tests/Filter/Validator/LengthTest.php
@@ -0,0 +1,139 @@
+
+ *
+ * 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\Core\Tests\Filter\Validator;
+
+use ApiPlatform\Core\Filter\Validator\Length;
+use PHPUnit\Framework\TestCase;
+
+/**
+ * @author Julien Deniau
+ */
+class LengthTest extends TestCase
+{
+ public function testNonDefinedFilter()
+ {
+ $filter = new Length();
+
+ $this->assertEmpty(
+ $filter->validate('some_filter', [], [])
+ );
+ }
+
+ public function testEmptyQueryParameter()
+ {
+ $filter = new Length();
+
+ $this->assertEmpty(
+ $filter->validate('some_filter', [], ['some_filter' => ''])
+ );
+ }
+
+ public function testNonMatchingParameter()
+ {
+ $filter = new Length();
+
+ $filterDefinition = [
+ 'swagger' => [
+ 'minLength' => 3,
+ 'maxLength' => 5,
+ ],
+ ];
+
+ $this->assertEquals(
+ ['Query parameter "some_filter" length must be greater than or equal to 3'],
+ $filter->validate('some_filter', $filterDefinition, ['some_filter' => 'ab'])
+ );
+
+ $this->assertEquals(
+ ['Query parameter "some_filter" length must be lower than or equal to 5'],
+ $filter->validate('some_filter', $filterDefinition, ['some_filter' => 'abcdef'])
+ );
+ }
+
+ public function testNonMatchingParameterWithOnlyOneDefinition()
+ {
+ $filter = new Length();
+
+ $filterDefinition = [
+ 'swagger' => [
+ 'minLength' => 3,
+ ],
+ ];
+
+ $this->assertEquals(
+ ['Query parameter "some_filter" length must be greater than or equal to 3'],
+ $filter->validate('some_filter', $filterDefinition, ['some_filter' => 'ab'])
+ );
+
+ $filterDefinition = [
+ 'swagger' => [
+ 'maxLength' => 5,
+ ],
+ ];
+
+ $this->assertEquals(
+ ['Query parameter "some_filter" length must be lower than or equal to 5'],
+ $filter->validate('some_filter', $filterDefinition, ['some_filter' => 'abcdef'])
+ );
+ }
+
+ public function testMatchingParameter()
+ {
+ $filter = new Length();
+
+ $filterDefinition = [
+ 'swagger' => [
+ 'minLength' => 3,
+ 'maxLength' => 5,
+ ],
+ ];
+
+ $this->assertEmpty(
+ $filter->validate('some_filter', $filterDefinition, ['some_filter' => 'abc'])
+ );
+
+ $this->assertEmpty(
+ $filter->validate('some_filter', $filterDefinition, ['some_filter' => 'abcd'])
+ );
+
+ $this->assertEmpty(
+ $filter->validate('some_filter', $filterDefinition, ['some_filter' => 'abcde'])
+ );
+ }
+
+ public function testMatchingParameterWithOneDefinition()
+ {
+ $filter = new Length();
+
+ $filterDefinition = [
+ 'swagger' => [
+ 'minLength' => 3,
+ ],
+ ];
+
+ $this->assertEmpty(
+ $filter->validate('some_filter', $filterDefinition, ['some_filter' => 'abc'])
+ );
+
+ $filterDefinition = [
+ 'swagger' => [
+ 'maxLength' => 5,
+ ],
+ ];
+
+ $this->assertEmpty(
+ $filter->validate('some_filter', $filterDefinition, ['some_filter' => 'abcde'])
+ );
+ }
+}
diff --git a/tests/Filter/Validator/MultipleOfTest.php b/tests/Filter/Validator/MultipleOfTest.php
new file mode 100644
index 00000000000..f313cd3cba2
--- /dev/null
+++ b/tests/Filter/Validator/MultipleOfTest.php
@@ -0,0 +1,75 @@
+
+ *
+ * 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\Core\Tests\Filter\Validator;
+
+use ApiPlatform\Core\Filter\Validator\MultipleOf;
+use PHPUnit\Framework\TestCase;
+
+/**
+ * @author Julien Deniau
+ */
+class MultipleOfTest extends TestCase
+{
+ public function testNonDefinedFilter()
+ {
+ $filter = new MultipleOf();
+
+ $this->assertEmpty(
+ $filter->validate('some_filter', [], [])
+ );
+ }
+
+ public function testEmptyQueryParameter()
+ {
+ $request = ['some_filter' => ''];
+ $filter = new MultipleOf();
+
+ $this->assertEmpty(
+ $filter->validate('some_filter', [], $request)
+ );
+ }
+
+ public function testNonMatchingParameter()
+ {
+ $request = ['some_filter' => '8'];
+ $filter = new MultipleOf();
+
+ $filterDefinition = [
+ 'swagger' => [
+ 'multipleOf' => 3,
+ ],
+ ];
+
+ $this->assertEquals(
+ ['Query parameter "some_filter" must multiple of 3'],
+ $filter->validate('some_filter', $filterDefinition, $request)
+ );
+ }
+
+ public function testMatchingParameter()
+ {
+ $request = ['some_filter' => '8'];
+ $filter = new MultipleOf();
+
+ $filterDefinition = [
+ 'swagger' => [
+ 'multipleOf' => 4,
+ ],
+ ];
+
+ $this->assertEmpty(
+ $filter->validate('some_filter', $filterDefinition, $request)
+ );
+ }
+}
diff --git a/tests/Filter/Validator/PatternTest.php b/tests/Filter/Validator/PatternTest.php
new file mode 100644
index 00000000000..18f58aff86c
--- /dev/null
+++ b/tests/Filter/Validator/PatternTest.php
@@ -0,0 +1,100 @@
+
+ *
+ * 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\Core\Tests\Filter\Validator;
+
+use ApiPlatform\Core\Filter\Validator\Pattern;
+use PHPUnit\Framework\TestCase;
+
+/**
+ * @author Julien Deniau
+ */
+class PatternTest extends TestCase
+{
+ public function testNonDefinedFilter()
+ {
+ $filter = new Pattern();
+
+ $this->assertEmpty(
+ $filter->validate('some_filter', [], [])
+ );
+ }
+
+ public function testFilterWithEmptyValue()
+ {
+ $filter = new Pattern();
+
+ $explicitFilterDefinition = [
+ 'swagger' => [
+ 'pattern' => '/foo/',
+ ],
+ ];
+
+ $this->assertEmpty(
+ $filter->validate('some_filter', $explicitFilterDefinition, ['some_filter' => ''])
+ );
+
+ $weirdParameter = new \stdClass();
+ $weirdParameter->foo = 'non string value should not exists';
+ $this->assertEmpty(
+ $filter->validate('some_filter', $explicitFilterDefinition, ['some_filter' => $weirdParameter])
+ );
+ }
+
+ public function testFilterWithZeroAsParameter()
+ {
+ $filter = new Pattern();
+
+ $explicitFilterDefinition = [
+ 'swagger' => [
+ 'pattern' => '/foo/',
+ ],
+ ];
+
+ $this->assertEquals(
+ ['Query parameter "some_filter" must match pattern /foo/'],
+ $filter->validate('some_filter', $explicitFilterDefinition, ['some_filter' => '0'])
+ );
+ }
+
+ public function testFilterWithNonMatchingValue()
+ {
+ $filter = new Pattern();
+
+ $explicitFilterDefinition = [
+ 'swagger' => [
+ 'pattern' => '/foo/',
+ ],
+ ];
+
+ $this->assertEquals(
+ ['Query parameter "some_filter" must match pattern /foo/'],
+ $filter->validate('some_filter', $explicitFilterDefinition, ['some_filter' => 'bar'])
+ );
+ }
+
+ public function testFilterWithNonchingValue()
+ {
+ $filter = new Pattern();
+
+ $explicitFilterDefinition = [
+ 'swagger' => [
+ 'pattern' => '/foo \d+/',
+ ],
+ ];
+
+ $this->assertEmpty(
+ $filter->validate('some_filter', $explicitFilterDefinition, ['some_filter' => 'this is a foo '.random_int(0, 10).' and it should match'])
+ );
+ }
+}
diff --git a/tests/Filter/Validator/RequiredTest.php b/tests/Filter/Validator/RequiredTest.php
new file mode 100644
index 00000000000..fa7cd89ed97
--- /dev/null
+++ b/tests/Filter/Validator/RequiredTest.php
@@ -0,0 +1,104 @@
+
+ *
+ * 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\Core\Tests\Filter\Validator;
+
+use ApiPlatform\Core\Filter\Validator\Required;
+use PHPUnit\Framework\TestCase;
+
+/**
+ * Class RequiredTest.
+ *
+ * @author Julien Deniau
+ */
+class RequiredTest extends TestCase
+{
+ public function testNonRequiredFilter()
+ {
+ $request = [];
+ $filter = new Required();
+
+ $this->assertEmpty(
+ $filter->validate('some_filter', [], [])
+ );
+
+ $this->assertEmpty(
+ $filter->validate('some_filter', ['required' => false], $request)
+ );
+ }
+
+ public function testRequiredFilterNotInQuery()
+ {
+ $request = [];
+ $filter = new Required();
+
+ $this->assertEquals(
+ ['Query parameter "some_filter" is required'],
+ $filter->validate('some_filter', ['required' => true], $request)
+ );
+ }
+
+ public function testRequiredFilterIsPresent()
+ {
+ $request = ['some_filter' => 'some_value'];
+ $filter = new Required();
+
+ $this->assertEmpty(
+ $filter->validate('some_filter', ['required' => true], $request)
+ );
+ }
+
+ public function testEmptyValueNotAllowed()
+ {
+ $request = ['some_filter' => ''];
+ $filter = new Required();
+
+ $explicitFilterDefinition = [
+ 'required' => true,
+ 'swagger' => [
+ 'allowEmptyValue' => false,
+ ],
+ ];
+
+ $this->assertEquals(
+ ['Query parameter "some_filter" does not allow empty value'],
+ $filter->validate('some_filter', $explicitFilterDefinition, $request)
+ );
+
+ $implicitFilterDefinition = [
+ 'required' => true,
+ ];
+
+ $this->assertEquals(
+ ['Query parameter "some_filter" does not allow empty value'],
+ $filter->validate('some_filter', $implicitFilterDefinition, $request)
+ );
+ }
+
+ public function testEmptyValueAllowed()
+ {
+ $request = ['some_filter' => ''];
+ $filter = new Required();
+
+ $explicitFilterDefinition = [
+ 'required' => true,
+ 'swagger' => [
+ 'allowEmptyValue' => true,
+ ],
+ ];
+
+ $this->assertEmpty(
+ $filter->validate('some_filter', $explicitFilterDefinition, $request)
+ );
+ }
+}
diff --git a/tests/Fixtures/TestBundle/Document/FilterValidator.php b/tests/Fixtures/TestBundle/Document/FilterValidator.php
index 722ac9f849f..1756965196a 100644
--- a/tests/Fixtures/TestBundle/Document/FilterValidator.php
+++ b/tests/Fixtures/TestBundle/Document/FilterValidator.php
@@ -15,6 +15,13 @@
use ApiPlatform\Core\Annotation\ApiProperty;
use ApiPlatform\Core\Annotation\ApiResource;
+use ApiPlatform\Core\Tests\Fixtures\TestBundle\Filter\ArrayItemsFilter;
+use ApiPlatform\Core\Tests\Fixtures\TestBundle\Filter\BoundsFilter;
+use ApiPlatform\Core\Tests\Fixtures\TestBundle\Filter\EnumFilter;
+use ApiPlatform\Core\Tests\Fixtures\TestBundle\Filter\LengthFilter;
+use ApiPlatform\Core\Tests\Fixtures\TestBundle\Filter\MultipleOfFilter;
+use ApiPlatform\Core\Tests\Fixtures\TestBundle\Filter\PatternFilter;
+use ApiPlatform\Core\Tests\Fixtures\TestBundle\Filter\RequiredAllowEmptyFilter;
use ApiPlatform\Core\Tests\Fixtures\TestBundle\Filter\RequiredFilter;
use Doctrine\ODM\MongoDB\Mapping\Annotations as ODM;
@@ -26,7 +33,14 @@
*
* @ApiResource(attributes={
* "filters"={
- * RequiredFilter::class
+ * ArrayItemsFilter::class,
+ * BoundsFilter::class,
+ * EnumFilter::class,
+ * LengthFilter::class,
+ * MultipleOfFilter::class,
+ * PatternFilter::class,
+ * RequiredFilter::class,
+ * RequiredAllowEmptyFilter::class
* }
* })
* @ODM\Document
diff --git a/tests/Fixtures/TestBundle/Entity/FilterValidator.php b/tests/Fixtures/TestBundle/Entity/FilterValidator.php
index 118050a9b8f..eaa62b26345 100644
--- a/tests/Fixtures/TestBundle/Entity/FilterValidator.php
+++ b/tests/Fixtures/TestBundle/Entity/FilterValidator.php
@@ -15,6 +15,13 @@
use ApiPlatform\Core\Annotation\ApiProperty;
use ApiPlatform\Core\Annotation\ApiResource;
+use ApiPlatform\Core\Tests\Fixtures\TestBundle\Filter\ArrayItemsFilter;
+use ApiPlatform\Core\Tests\Fixtures\TestBundle\Filter\BoundsFilter;
+use ApiPlatform\Core\Tests\Fixtures\TestBundle\Filter\EnumFilter;
+use ApiPlatform\Core\Tests\Fixtures\TestBundle\Filter\LengthFilter;
+use ApiPlatform\Core\Tests\Fixtures\TestBundle\Filter\MultipleOfFilter;
+use ApiPlatform\Core\Tests\Fixtures\TestBundle\Filter\PatternFilter;
+use ApiPlatform\Core\Tests\Fixtures\TestBundle\Filter\RequiredAllowEmptyFilter;
use ApiPlatform\Core\Tests\Fixtures\TestBundle\Filter\RequiredFilter;
use Doctrine\ORM\Mapping as ORM;
@@ -25,7 +32,14 @@
*
* @ApiResource(attributes={
* "filters"={
- * RequiredFilter::class
+ * ArrayItemsFilter::class,
+ * BoundsFilter::class,
+ * EnumFilter::class,
+ * LengthFilter::class,
+ * MultipleOfFilter::class,
+ * PatternFilter::class,
+ * RequiredFilter::class,
+ * RequiredAllowEmptyFilter::class
* }
* })
* @ORM\Entity
diff --git a/tests/Fixtures/TestBundle/Filter/ArrayItemsFilter.php b/tests/Fixtures/TestBundle/Filter/ArrayItemsFilter.php
new file mode 100644
index 00000000000..848eaa34104
--- /dev/null
+++ b/tests/Fixtures/TestBundle/Filter/ArrayItemsFilter.php
@@ -0,0 +1,86 @@
+
+ *
+ * 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\Core\Tests\Fixtures\TestBundle\Filter;
+
+use ApiPlatform\Core\Bridge\Doctrine\Common\PropertyHelperTrait;
+use ApiPlatform\Core\Bridge\Doctrine\Orm\Filter\AbstractFilter;
+use ApiPlatform\Core\Bridge\Doctrine\Orm\Util\QueryNameGeneratorInterface;
+use Doctrine\ORM\QueryBuilder;
+
+class ArrayItemsFilter extends AbstractFilter
+{
+ use PropertyHelperTrait;
+
+ protected function filterProperty(string $property, $value, QueryBuilder $queryBuilder, QueryNameGeneratorInterface $queryNameGenerator, string $resourceClass, string $operationName = null)
+ {
+ }
+
+ // This function is only used to hook in documentation generators (supported by Swagger and Hydra)
+ public function getDescription(string $resourceClass): array
+ {
+ return [
+ 'csv-min-2' => [
+ 'property' => 'csv-min-2',
+ 'type' => 'array',
+ 'required' => false,
+ 'swagger' => [
+ 'minItems' => 2,
+ ],
+ ],
+ 'csv-max-3' => [
+ 'property' => 'csv-max-3',
+ 'type' => 'array',
+ 'required' => false,
+ 'swagger' => [
+ 'maxItems' => 3,
+ ],
+ ],
+ 'ssv-min-2' => [
+ 'property' => 'ssv-min-2',
+ 'type' => 'array',
+ 'required' => false,
+ 'swagger' => [
+ 'collectionFormat' => 'ssv',
+ 'minItems' => 2,
+ ],
+ ],
+ 'tsv-min-2' => [
+ 'property' => 'tsv-min-2',
+ 'type' => 'array',
+ 'required' => false,
+ 'swagger' => [
+ 'collectionFormat' => 'tsv',
+ 'minItems' => 2,
+ ],
+ ],
+ 'pipes-min-2' => [
+ 'property' => 'pipes-min-2',
+ 'type' => 'array',
+ 'required' => false,
+ 'swagger' => [
+ 'collectionFormat' => 'pipes',
+ 'minItems' => 2,
+ ],
+ ],
+ 'csv-uniques' => [
+ 'property' => 'csv-uniques',
+ 'type' => 'array',
+ 'required' => false,
+ 'swagger' => [
+ 'uniqueItems' => true,
+ ],
+ ],
+ ];
+ }
+}
diff --git a/tests/Fixtures/TestBundle/Filter/BoundsFilter.php b/tests/Fixtures/TestBundle/Filter/BoundsFilter.php
new file mode 100644
index 00000000000..9c3cca1984a
--- /dev/null
+++ b/tests/Fixtures/TestBundle/Filter/BoundsFilter.php
@@ -0,0 +1,69 @@
+
+ *
+ * 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\Core\Tests\Fixtures\TestBundle\Filter;
+
+use ApiPlatform\Core\Bridge\Doctrine\Common\PropertyHelperTrait;
+use ApiPlatform\Core\Bridge\Doctrine\Orm\Filter\AbstractFilter;
+use ApiPlatform\Core\Bridge\Doctrine\Orm\Util\QueryNameGeneratorInterface;
+use Doctrine\ORM\QueryBuilder;
+
+class BoundsFilter extends AbstractFilter
+{
+ use PropertyHelperTrait;
+
+ protected function filterProperty(string $property, $value, QueryBuilder $queryBuilder, QueryNameGeneratorInterface $queryNameGenerator, string $resourceClass, string $operationName = null)
+ {
+ }
+
+ // This function is only used to hook in documentation generators (supported by Swagger and Hydra)
+ public function getDescription(string $resourceClass): array
+ {
+ return [
+ 'maximum' => [
+ 'property' => 'maximum',
+ 'type' => 'float',
+ 'required' => false,
+ 'swagger' => [
+ 'maximum' => 10,
+ ],
+ ],
+ 'exclusiveMaximum' => [
+ 'property' => 'maximum',
+ 'type' => 'float',
+ 'required' => false,
+ 'swagger' => [
+ 'maximum' => 10,
+ 'exclusiveMaximum' => true,
+ ],
+ ],
+ 'minimum' => [
+ 'property' => 'minimum',
+ 'type' => 'float',
+ 'required' => false,
+ 'swagger' => [
+ 'minimum' => 5,
+ ],
+ ],
+ 'exclusiveMinimum' => [
+ 'property' => 'exclusiveMinimum',
+ 'type' => 'float',
+ 'required' => false,
+ 'swagger' => [
+ 'minimum' => 5,
+ 'exclusiveMinimum' => true,
+ ],
+ ],
+ ];
+ }
+}
diff --git a/tests/Fixtures/TestBundle/Filter/EnumFilter.php b/tests/Fixtures/TestBundle/Filter/EnumFilter.php
new file mode 100644
index 00000000000..1c4bd33fa27
--- /dev/null
+++ b/tests/Fixtures/TestBundle/Filter/EnumFilter.php
@@ -0,0 +1,43 @@
+
+ *
+ * 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\Core\Tests\Fixtures\TestBundle\Filter;
+
+use ApiPlatform\Core\Bridge\Doctrine\Common\PropertyHelperTrait;
+use ApiPlatform\Core\Bridge\Doctrine\Orm\Filter\AbstractFilter;
+use ApiPlatform\Core\Bridge\Doctrine\Orm\Util\QueryNameGeneratorInterface;
+use Doctrine\ORM\QueryBuilder;
+
+class EnumFilter extends AbstractFilter
+{
+ use PropertyHelperTrait;
+
+ protected function filterProperty(string $property, $value, QueryBuilder $queryBuilder, QueryNameGeneratorInterface $queryNameGenerator, string $resourceClass, string $operationName = null)
+ {
+ }
+
+ // This function is only used to hook in documentation generators (supported by Swagger and Hydra)
+ public function getDescription(string $resourceClass): array
+ {
+ return [
+ 'enum' => [
+ 'property' => 'enum',
+ 'type' => 'string',
+ 'required' => false,
+ 'swagger' => [
+ 'enum' => ['in-enum', 'mune-ni'],
+ ],
+ ],
+ ];
+ }
+}
diff --git a/tests/Fixtures/TestBundle/Filter/LengthFilter.php b/tests/Fixtures/TestBundle/Filter/LengthFilter.php
new file mode 100644
index 00000000000..6d44799156f
--- /dev/null
+++ b/tests/Fixtures/TestBundle/Filter/LengthFilter.php
@@ -0,0 +1,51 @@
+
+ *
+ * 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\Core\Tests\Fixtures\TestBundle\Filter;
+
+use ApiPlatform\Core\Bridge\Doctrine\Common\PropertyHelperTrait;
+use ApiPlatform\Core\Bridge\Doctrine\Orm\Filter\AbstractFilter;
+use ApiPlatform\Core\Bridge\Doctrine\Orm\Util\QueryNameGeneratorInterface;
+use Doctrine\ORM\QueryBuilder;
+
+class LengthFilter extends AbstractFilter
+{
+ use PropertyHelperTrait;
+
+ protected function filterProperty(string $property, $value, QueryBuilder $queryBuilder, QueryNameGeneratorInterface $queryNameGenerator, string $resourceClass, string $operationName = null)
+ {
+ }
+
+ // This function is only used to hook in documentation generators (supported by Swagger and Hydra)
+ public function getDescription(string $resourceClass): array
+ {
+ return [
+ 'max-length-3' => [
+ 'property' => 'max-length-3',
+ 'type' => 'string',
+ 'required' => false,
+ 'swagger' => [
+ 'maxLength' => 3,
+ ],
+ ],
+ 'min-length-3' => [
+ 'property' => 'min-length-3',
+ 'type' => 'string',
+ 'required' => false,
+ 'swagger' => [
+ 'minLength' => 3,
+ ],
+ ],
+ ];
+ }
+}
diff --git a/tests/Fixtures/TestBundle/Filter/MultipleOfFilter.php b/tests/Fixtures/TestBundle/Filter/MultipleOfFilter.php
new file mode 100644
index 00000000000..4918188526e
--- /dev/null
+++ b/tests/Fixtures/TestBundle/Filter/MultipleOfFilter.php
@@ -0,0 +1,43 @@
+
+ *
+ * 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\Core\Tests\Fixtures\TestBundle\Filter;
+
+use ApiPlatform\Core\Bridge\Doctrine\Common\PropertyHelperTrait;
+use ApiPlatform\Core\Bridge\Doctrine\Orm\Filter\AbstractFilter;
+use ApiPlatform\Core\Bridge\Doctrine\Orm\Util\QueryNameGeneratorInterface;
+use Doctrine\ORM\QueryBuilder;
+
+class MultipleOfFilter extends AbstractFilter
+{
+ use PropertyHelperTrait;
+
+ protected function filterProperty(string $property, $value, QueryBuilder $queryBuilder, QueryNameGeneratorInterface $queryNameGenerator, string $resourceClass, string $operationName = null)
+ {
+ }
+
+ // This function is only used to hook in documentation generators (supported by Swagger and Hydra)
+ public function getDescription(string $resourceClass): array
+ {
+ return [
+ 'multiple-of' => [
+ 'property' => 'multiple-of',
+ 'type' => 'float',
+ 'required' => false,
+ 'swagger' => [
+ 'multipleOf' => 2,
+ ],
+ ],
+ ];
+ }
+}
diff --git a/tests/Fixtures/TestBundle/Filter/PatternFilter.php b/tests/Fixtures/TestBundle/Filter/PatternFilter.php
new file mode 100644
index 00000000000..1cb136520d6
--- /dev/null
+++ b/tests/Fixtures/TestBundle/Filter/PatternFilter.php
@@ -0,0 +1,43 @@
+
+ *
+ * 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\Core\Tests\Fixtures\TestBundle\Filter;
+
+use ApiPlatform\Core\Bridge\Doctrine\Common\PropertyHelperTrait;
+use ApiPlatform\Core\Bridge\Doctrine\Orm\Filter\AbstractFilter;
+use ApiPlatform\Core\Bridge\Doctrine\Orm\Util\QueryNameGeneratorInterface;
+use Doctrine\ORM\QueryBuilder;
+
+class PatternFilter extends AbstractFilter
+{
+ use PropertyHelperTrait;
+
+ protected function filterProperty(string $property, $value, QueryBuilder $queryBuilder, QueryNameGeneratorInterface $queryNameGenerator, string $resourceClass, string $operationName = null)
+ {
+ }
+
+ // This function is only used to hook in documentation generators (supported by Swagger and Hydra)
+ public function getDescription(string $resourceClass): array
+ {
+ return [
+ 'pattern' => [
+ 'property' => 'pattern',
+ 'type' => 'string',
+ 'required' => false,
+ 'swagger' => [
+ 'pattern' => '/^(pattern|nrettap)$/',
+ ],
+ ],
+ ];
+ }
+}
diff --git a/tests/Fixtures/TestBundle/Filter/RequiredAllowEmptyFilter.php b/tests/Fixtures/TestBundle/Filter/RequiredAllowEmptyFilter.php
new file mode 100644
index 00000000000..8727b754d89
--- /dev/null
+++ b/tests/Fixtures/TestBundle/Filter/RequiredAllowEmptyFilter.php
@@ -0,0 +1,43 @@
+
+ *
+ * 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\Core\Tests\Fixtures\TestBundle\Filter;
+
+use ApiPlatform\Core\Bridge\Doctrine\Common\PropertyHelperTrait;
+use ApiPlatform\Core\Bridge\Doctrine\Orm\Filter\AbstractFilter;
+use ApiPlatform\Core\Bridge\Doctrine\Orm\Util\QueryNameGeneratorInterface;
+use Doctrine\ORM\QueryBuilder;
+
+class RequiredAllowEmptyFilter extends AbstractFilter
+{
+ use PropertyHelperTrait;
+
+ protected function filterProperty(string $property, $value, QueryBuilder $queryBuilder, QueryNameGeneratorInterface $queryNameGenerator, string $resourceClass, string $operationName = null)
+ {
+ }
+
+ // This function is only used to hook in documentation generators (supported by Swagger and Hydra)
+ public function getDescription(string $resourceClass): array
+ {
+ return [
+ 'required-allow-empty' => [
+ 'property' => 'required-allow-empty',
+ 'type' => 'string',
+ 'required' => true,
+ 'swagger' => [
+ 'allowEmptyValue' => true,
+ ],
+ ],
+ ];
+ }
+}
diff --git a/tests/Fixtures/app/config/config_common.yml b/tests/Fixtures/app/config/config_common.yml
index e4c861cbb4a..0fe02d4b37d 100644
--- a/tests/Fixtures/app/config/config_common.yml
+++ b/tests/Fixtures/app/config/config_common.yml
@@ -141,10 +141,38 @@ services:
arguments: ['@doctrine']
tags: ['api_platform.filter']
+ ApiPlatform\Core\Tests\Fixtures\TestBundle\Filter\RequiredAllowEmptyFilter:
+ arguments: [ '@doctrine' ]
+ tags: [ 'api_platform.filter' ]
+
+ ApiPlatform\Core\Tests\Fixtures\TestBundle\Filter\BoundsFilter:
+ arguments: [ '@doctrine' ]
+ tags: [ 'api_platform.filter' ]
+
+ ApiPlatform\Core\Tests\Fixtures\TestBundle\Filter\LengthFilter:
+ arguments: [ '@doctrine' ]
+ tags: [ 'api_platform.filter' ]
+
+ ApiPlatform\Core\Tests\Fixtures\TestBundle\Filter\PatternFilter:
+ arguments: [ '@doctrine' ]
+ tags: [ 'api_platform.filter' ]
+
ApiPlatform\Core\Tests\Fixtures\TestBundle\Controller\:
resource: '../../TestBundle/Controller'
tags: ['controller.service_arguments']
+ ApiPlatform\Core\Tests\Fixtures\TestBundle\Filter\EnumFilter:
+ arguments: [ '@doctrine' ]
+ tags: [ 'api_platform.filter' ]
+
+ ApiPlatform\Core\Tests\Fixtures\TestBundle\Filter\MultipleOfFilter:
+ arguments: [ '@doctrine' ]
+ tags: [ 'api_platform.filter' ]
+
+ ApiPlatform\Core\Tests\Fixtures\TestBundle\Filter\ArrayItemsFilter:
+ arguments: [ '@doctrine' ]
+ tags: [ 'api_platform.filter' ]
+
app.config_dummy_resource.action:
class: 'ApiPlatform\Core\Tests\Fixtures\TestBundle\Action\ConfigCustom'
arguments: ['@api_platform.item_data_provider']