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/EventListener/QueryParameterValidateListener.php b/src/EventListener/QueryParameterValidateListener.php
new file mode 100644
index 00000000000..5c76d7e1fdc
--- /dev/null
+++ b/src/EventListener/QueryParameterValidateListener.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\Core\EventListener;
+
+use ApiPlatform\Core\Filter\QueryParameterValidator;
+use ApiPlatform\Core\Metadata\Resource\Factory\ResourceMetadataFactoryInterface;
+use ApiPlatform\Core\Util\RequestAttributesExtractor;
+use Symfony\Component\HttpKernel\Event\GetResponseEvent;
+
+/**
+ * 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(GetResponseEvent $event)
+ {
+ $request = $event->getRequest();
+ if (
+ !$request->isMethodSafe(false)
+ || !($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);
+
+ $this->queryParameterValidator->validateFilters($attributes['resource_class'], $resourceFilters, $request);
+ }
+}
diff --git a/src/Filter/QueryParameterValidateListener.php b/src/Filter/QueryParameterValidateListener.php
deleted file mode 100644
index ffb919a69fd..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\GetResponseEvent;
-
-/**
- * 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(GetResponseEvent $event)
- {
- $request = $event->getRequest();
- if (
- !$request->isMethodSafe(false)
- || !($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..db64bdd97dc
--- /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;
+use Symfony\Component\HttpFoundation\Request;
+
+/**
+ * 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, Request $request)
+ {
+ $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, $request));
+ }
+ }
+ }
+
+ 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..460ea825280
--- /dev/null
+++ b/src/Filter/Validator/ArrayItems.php
@@ -0,0 +1,82 @@
+
+ *
+ * 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;
+
+use Symfony\Component\HttpFoundation\Request;
+
+class ArrayItems implements ValidatorInterface
+{
+ public function validate(string $name, array $filterDescription, Request $request): array
+ {
+ if (!$request->query->has($name)) {
+ return [];
+ }
+
+ $maxItems = $filterDescription['swagger']['maxItems'] ?? null;
+ $minItems = $filterDescription['swagger']['minItems'] ?? null;
+ $uniqueItems = $filterDescription['swagger']['uniqueItems'] ?? false;
+
+ $errorList = [];
+
+ $value = $this->getValue($name, $filterDescription, $request);
+ $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, Request $request): array
+ {
+ $value = $request->query->get($name);
+
+ 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..4c7a1d57610
--- /dev/null
+++ b/src/Filter/Validator/Bounds.php
@@ -0,0 +1,50 @@
+
+ *
+ * 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;
+
+use Symfony\Component\HttpFoundation\Request;
+
+class Bounds implements ValidatorInterface
+{
+ public function validate(string $name, array $filterDescription, Request $request): array
+ {
+ $value = $request->query->get($name);
+ 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..7774eb951a2
--- /dev/null
+++ b/src/Filter/Validator/Enum.php
@@ -0,0 +1,37 @@
+
+ *
+ * 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;
+
+use Symfony\Component\HttpFoundation\Request;
+
+class Enum implements ValidatorInterface
+{
+ public function validate(string $name, array $filterDescription, Request $request): array
+ {
+ $value = $request->query->get($name);
+ 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..525d8012760
--- /dev/null
+++ b/src/Filter/Validator/Length.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\Core\Filter\Validator;
+
+use Symfony\Component\HttpFoundation\Request;
+
+class Length implements ValidatorInterface
+{
+ public function validate(string $name, array $filterDescription, Request $request): array
+ {
+ $maxLength = $filterDescription['swagger']['maxLength'] ?? null;
+ $minLength = $filterDescription['swagger']['minLength'] ?? null;
+
+ $value = $request->query->get($name);
+ if (empty($value) && '0' !== $value || !\is_string($value)) {
+ return [];
+ }
+
+ $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..e42726f9a56
--- /dev/null
+++ b/src/Filter/Validator/MultipleOf.php
@@ -0,0 +1,37 @@
+
+ *
+ * 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;
+
+use Symfony\Component\HttpFoundation\Request;
+
+class MultipleOf implements ValidatorInterface
+{
+ public function validate(string $name, array $filterDescription, Request $request): array
+ {
+ $value = $request->query->get($name);
+ 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..ee83f3ac755
--- /dev/null
+++ b/src/Filter/Validator/Pattern.php
@@ -0,0 +1,37 @@
+
+ *
+ * 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;
+
+use Symfony\Component\HttpFoundation\Request;
+
+class Pattern implements ValidatorInterface
+{
+ public function validate(string $name, array $filterDescription, Request $request): array
+ {
+ $value = $request->query->get($name);
+ 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..e93c6b85e4c
--- /dev/null
+++ b/src/Filter/Validator/Required.php
@@ -0,0 +1,101 @@
+
+ *
+ * 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;
+
+use Symfony\Component\HttpFoundation\Request;
+
+class Required implements ValidatorInterface
+{
+ public function validate(string $name, array $filterDescription, Request $request): 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($request, $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($request, $name))) {
+ return [
+ \sprintf('Query parameter "%s" does not allow empty value', $name),
+ ];
+ }
+
+ return [];
+ }
+
+ /**
+ * Test if request has required parameter.
+ */
+ private function requestHasQueryParameter(Request $request, 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 = $request->query->get((string) $rootName);
+
+ return \is_array($queryParameter) && isset($queryParameter[$keyName]);
+ }
+
+ return $request->query->has((string) $rootName);
+ }
+
+ /**
+ * Test if required filter is valid. It validates array notation too like "required[bar]".
+ */
+ private function requestGetQueryParameter(Request $request, 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 = $request->query->get((string) $rootName);
+
+ if (\is_array($queryParameter) && isset($queryParameter[$keyName])) {
+ return $queryParameter[$keyName];
+ }
+
+ return null;
+ }
+
+ return $request->query->get((string) $rootName);
+ }
+}
diff --git a/src/Filter/Validator/ValidatorInterface.php b/src/Filter/Validator/ValidatorInterface.php
new file mode 100644
index 00000000000..33740725059
--- /dev/null
+++ b/src/Filter/Validator/ValidatorInterface.php
@@ -0,0 +1,21 @@
+
+ *
+ * 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;
+
+use Symfony\Component\HttpFoundation\Request;
+
+interface ValidatorInterface
+{
+ public function validate(string $name, array $filterDescription, Request $request): array;
+}
diff --git a/tests/Bridge/Symfony/Bundle/DependencyInjection/ApiPlatformExtensionTest.php b/tests/Bridge/Symfony/Bundle/DependencyInjection/ApiPlatformExtensionTest.php
index f2a1b9043b5..32b2b8e38c3 100644
--- a/tests/Bridge/Symfony/Bundle/DependencyInjection/ApiPlatformExtensionTest.php
+++ b/tests/Bridge/Symfony/Bundle/DependencyInjection/ApiPlatformExtensionTest.php
@@ -744,6 +744,7 @@ private function getPartialContainerBuilderProphecy()
'api_platform.listener.view.respond',
'api_platform.listener.view.serialize',
'api_platform.listener.view.validate',
+ 'api_platform.validator.query_parameter_validator',
'api_platform.listener.view.validate_query_parameters',
'api_platform.listener.view.write',
'api_platform.metadata.extractor.xml',
diff --git a/tests/Filter/QueryParameterValidateListenerTest.php b/tests/EventListener/QueryParameterValidateListenerTest.php
similarity index 70%
rename from tests/Filter/QueryParameterValidateListenerTest.php
rename to tests/EventListener/QueryParameterValidateListenerTest.php
index 573e8d1ff72..6ff154ba7ed 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\GetResponseEvent;
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(GetResponseEvent::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'], $request)->shouldBeCalled();
$this->assertNull(
$this->testedInstance->onKernelRequest($eventProphecy->reveal())
@@ -81,27 +79,11 @@ public function testOnKernelRequestWithRequiredFilterNotSet()
$eventProphecy = $this->prophesize(GetResponseEvent::class);
$eventProphecy->getRequest()->willReturn($request)->shouldBeCalled();
- $this->filterLocatorProphecy
- ->has('some_filter')
+ $this->queryParameterValidor
+ ->validateFilters(Dummy::class, ['some_filter'], $request)
->shouldBeCalled()
- ->willReturn(true)
+ ->willThrow(new FilterValidationException(['Query parameter "required" is required']))
;
- $filterProphecy = $this->prophesize(FilterInterface::class);
- $filterProphecy
- ->getDescription(Dummy::class)
- ->shouldBeCalled()
- ->willReturn([
- 'required' => [
- 'required' => true,
- ],
- ])
- ;
- $this->filterLocatorProphecy
- ->get('some_filter')
- ->shouldBeCalled()
- ->willReturn($filterProphecy->reveal())
- ;
-
$this->expectException(FilterValidationException::class);
$this->expectExceptionMessage('Query parameter "required" is required');
$this->testedInstance->onKernelRequest($eventProphecy->reveal());
@@ -124,25 +106,9 @@ public function testOnKernelRequestWithRequiredFilter()
$eventProphecy = $this->prophesize(GetResponseEvent::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')
+ $this->queryParameterValidor
+ ->validateFilters(Dummy::class, ['some_filter'], $request)
->shouldBeCalled()
- ->willReturn($filterProphecy->reveal())
;
$this->assertNull(
@@ -163,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..3f1cdb1cc74
--- /dev/null
+++ b/tests/Filter/QueryParameterValidatorTest.php
@@ -0,0 +1,134 @@
+
+ *
+ * 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;
+use Symfony\Component\HttpFoundation\Request;
+
+/**
+ * Class QueryParameterValidatorTest.
+ *
+ * @author Julien Deniau
+ */
+class QueryParameterValidatorTest extends TestCase
+{
+ private $testedInstance;
+ private $filterLocatorProphecy;
+
+ public function setUp()
+ {
+ $this->filterLocatorProphecy = $this->prophesize(ContainerInterface::class);
+
+ $this->testedInstance = new QueryParameterValidator(
+ $this->filterLocatorProphecy->reveal()
+ );
+ }
+
+ /**
+ * unsafe method should not use filter validations.
+ */
+ public function testOnKernelRequestWithUnsafeMethod()
+ {
+ $request = new Request();
+
+ $this->assertNull(
+ $this->testedInstance->validateFilters(Dummy::class, [], $request)
+ );
+ }
+
+ /**
+ * If the tested filter is non-existant, then nothing should append.
+ */
+ public function testOnKernelRequestWithWrongFilter()
+ {
+ $request = new 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 = new 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 = new 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..6e4d2a3e9a4
--- /dev/null
+++ b/tests/Filter/Validator/ArrayItemsTest.php
@@ -0,0 +1,199 @@
+
+ *
+ * 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;
+use Symfony\Component\HttpFoundation\Request;
+
+/**
+ * @author Julien Deniau
+ */
+class ArrayItemsTest extends TestCase
+{
+ public function testNonDefinedFilter()
+ {
+ $request = new Request();
+ $filter = new ArrayItems();
+
+ $this->assertEmpty(
+ $filter->validate('some_filter', [], $request)
+ );
+ }
+
+ public function testEmptyQueryParameter()
+ {
+ $request = new 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 = new 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 = new 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 = new Request(['some_filter' => ['foo', 'bar']]);
+ $this->assertEmpty(
+ $filter->validate('some_filter', $filterDefinition, $request)
+ );
+
+ $request = new 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 = new 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 = new 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 = new 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 = new 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 = new 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 = new 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 = new 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..d993777a911
--- /dev/null
+++ b/tests/Filter/Validator/BoundsTest.php
@@ -0,0 +1,180 @@
+
+ *
+ * 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;
+use Symfony\Component\HttpFoundation\Request;
+
+/**
+ * @author Julien Deniau
+ */
+class BoundsTest extends TestCase
+{
+ public function testNonDefinedFilter()
+ {
+ $request = new Request();
+ $filter = new Bounds();
+
+ $this->assertEmpty(
+ $filter->validate('some_filter', [], $request)
+ );
+ }
+
+ public function testEmptyQueryParameter()
+ {
+ $request = new Request(['some_filter' => '']);
+ $filter = new Bounds();
+
+ $this->assertEmpty(
+ $filter->validate('some_filter', [], $request)
+ );
+ }
+
+ public function testNonMatchingMinimum()
+ {
+ $request = new 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 = new 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 = new 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 = new 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..e55697e4376
--- /dev/null
+++ b/tests/Filter/Validator/EnumTest.php
@@ -0,0 +1,77 @@
+
+ *
+ * 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;
+use Symfony\Component\HttpFoundation\Request;
+
+/**
+ * @author Julien Deniau
+ */
+class EnumTest extends TestCase
+{
+ public function testNonDefinedFilter()
+ {
+ $request = new Request();
+ $filter = new Enum();
+
+ $this->assertEmpty(
+ $filter->validate('some_filter', [], $request)
+ );
+ }
+
+ public function testEmptyQueryParameter()
+ {
+ $request = new Request(['some_filter' => '']);
+ $filter = new Enum();
+
+ $this->assertEmpty(
+ $filter->validate('some_filter', [], $request)
+ );
+ }
+
+ public function testNonMatchingParameter()
+ {
+ $request = new Request(['some_filter' => 'foobar']);
+ $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, $request)
+ );
+ }
+
+ public function testMatchingParameter()
+ {
+ $request = new Request(['some_filter' => 'foo']);
+ $filter = new Enum();
+
+ $filterDefinition = [
+ 'swagger' => [
+ 'enum' => ['foo', 'bar'],
+ ],
+ ];
+
+ $this->assertEmpty(
+ $filter->validate('some_filter', $filterDefinition, $request)
+ );
+ }
+}
diff --git a/tests/Filter/Validator/LengthTest.php b/tests/Filter/Validator/LengthTest.php
new file mode 100644
index 00000000000..76e60645af2
--- /dev/null
+++ b/tests/Filter/Validator/LengthTest.php
@@ -0,0 +1,142 @@
+
+ *
+ * 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;
+use Symfony\Component\HttpFoundation\Request;
+
+/**
+ * @author Julien Deniau
+ */
+class LengthTest extends TestCase
+{
+ public function testNonDefinedFilter()
+ {
+ $request = new Request();
+ $filter = new Length();
+
+ $this->assertEmpty(
+ $filter->validate('some_filter', [], $request)
+ );
+ }
+
+ public function testEmptyQueryParameter()
+ {
+ $request = new Request(['some_filter' => '']);
+ $filter = new Length();
+
+ $this->assertEmpty(
+ $filter->validate('some_filter', [], $request)
+ );
+ }
+
+ 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, new Request(['some_filter' => 'ab']))
+ );
+
+ $this->assertEquals(
+ ['Query parameter "some_filter" length must be lower than or equal to 5'],
+ $filter->validate('some_filter', $filterDefinition, new Request(['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, new Request(['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, new Request(['some_filter' => 'abcdef']))
+ );
+ }
+
+ public function testMatchingParameter()
+ {
+ $filter = new Length();
+
+ $filterDefinition = [
+ 'swagger' => [
+ 'minLength' => 3,
+ 'maxLength' => 5,
+ ],
+ ];
+
+ $this->assertEmpty(
+ $filter->validate('some_filter', $filterDefinition, new Request(['some_filter' => 'abc']))
+ );
+
+ $this->assertEmpty(
+ $filter->validate('some_filter', $filterDefinition, new Request(['some_filter' => 'abcd']))
+ );
+
+ $this->assertEmpty(
+ $filter->validate('some_filter', $filterDefinition, new Request(['some_filter' => 'abcde']))
+ );
+ }
+
+ public function testMatchingParameterWithOneDefinition()
+ {
+ $filter = new Length();
+
+ $filterDefinition = [
+ 'swagger' => [
+ 'minLength' => 3,
+ ],
+ ];
+
+ $this->assertEmpty(
+ $filter->validate('some_filter', $filterDefinition, new Request(['some_filter' => 'abc']))
+ );
+
+ $filterDefinition = [
+ 'swagger' => [
+ 'maxLength' => 5,
+ ],
+ ];
+
+ $this->assertEmpty(
+ $filter->validate('some_filter', $filterDefinition, new Request(['some_filter' => 'abcde']))
+ );
+ }
+}
diff --git a/tests/Filter/Validator/MultipleOfTest.php b/tests/Filter/Validator/MultipleOfTest.php
new file mode 100644
index 00000000000..a90386db765
--- /dev/null
+++ b/tests/Filter/Validator/MultipleOfTest.php
@@ -0,0 +1,77 @@
+
+ *
+ * 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;
+use Symfony\Component\HttpFoundation\Request;
+
+/**
+ * @author Julien Deniau
+ */
+class MultipleOfTest extends TestCase
+{
+ public function testNonDefinedFilter()
+ {
+ $request = new Request();
+ $filter = new MultipleOf();
+
+ $this->assertEmpty(
+ $filter->validate('some_filter', [], $request)
+ );
+ }
+
+ public function testEmptyQueryParameter()
+ {
+ $request = new Request(['some_filter' => '']);
+ $filter = new MultipleOf();
+
+ $this->assertEmpty(
+ $filter->validate('some_filter', [], $request)
+ );
+ }
+
+ public function testNonMatchingParameter()
+ {
+ $request = new 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 = new 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..02a924a8f5b
--- /dev/null
+++ b/tests/Filter/Validator/PatternTest.php
@@ -0,0 +1,102 @@
+
+ *
+ * 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;
+use Symfony\Component\HttpFoundation\Request;
+
+/**
+ * @author Julien Deniau
+ */
+class PatternTest extends TestCase
+{
+ public function testNonDefinedFilter()
+ {
+ $request = new Request();
+ $filter = new Pattern();
+
+ $this->assertEmpty(
+ $filter->validate('some_filter', [], $request)
+ );
+ }
+
+ public function testFilterWithEmptyValue()
+ {
+ $filter = new Pattern();
+
+ $explicitFilterDefinition = [
+ 'swagger' => [
+ 'pattern' => '/foo/',
+ ],
+ ];
+
+ $this->assertEmpty(
+ $filter->validate('some_filter', $explicitFilterDefinition, new Request(['some_filter' => '']))
+ );
+
+ $weirdParameter = new \stdClass();
+ $weirdParameter->foo = 'non string value should not exists';
+ $this->assertEmpty(
+ $filter->validate('some_filter', $explicitFilterDefinition, new Request(['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, new Request(['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, new Request(['some_filter' => 'bar']))
+ );
+ }
+
+ public function testFilterWithNonchingValue()
+ {
+ $filter = new Pattern();
+
+ $explicitFilterDefinition = [
+ 'swagger' => [
+ 'pattern' => '/foo \d+/',
+ ],
+ ];
+
+ $this->assertEmpty(
+ $filter->validate('some_filter', $explicitFilterDefinition, new Request(['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..b2c1596c144
--- /dev/null
+++ b/tests/Filter/Validator/RequiredTest.php
@@ -0,0 +1,105 @@
+
+ *
+ * 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;
+use Symfony\Component\HttpFoundation\Request;
+
+/**
+ * Class RequiredTest.
+ *
+ * @author Julien Deniau
+ */
+class RequiredTest extends TestCase
+{
+ public function testNonRequiredFilter()
+ {
+ $request = new Request();
+ $filter = new Required();
+
+ $this->assertEmpty(
+ $filter->validate('some_filter', [], $request)
+ );
+
+ $this->assertEmpty(
+ $filter->validate('some_filter', ['required' => false], $request)
+ );
+ }
+
+ public function testRequiredFilterNotInQuery()
+ {
+ $request = new Request();
+ $filter = new Required();
+
+ $this->assertEquals(
+ ['Query parameter "some_filter" is required'],
+ $filter->validate('some_filter', ['required' => true], $request)
+ );
+ }
+
+ public function testRequiredFilterIsPresent()
+ {
+ $request = new Request(['some_filter' => 'some_value']);
+ $filter = new Required();
+
+ $this->assertEmpty(
+ $filter->validate('some_filter', ['required' => true], $request)
+ );
+ }
+
+ public function testEmptyValueNotAllowed()
+ {
+ $request = new 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 = new 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/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..7feb9b3409f
--- /dev/null
+++ b/tests/Fixtures/TestBundle/Filter/ArrayItemsFilter.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\Tests\Fixtures\TestBundle\Filter;
+
+use ApiPlatform\Core\Bridge\Doctrine\Orm\Filter\AbstractFilter;
+use ApiPlatform\Core\Bridge\Doctrine\Orm\Util\QueryNameGeneratorInterface;
+use Doctrine\ORM\QueryBuilder;
+
+class ArrayItemsFilter extends AbstractFilter
+{
+ 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..ef46fc29f36
--- /dev/null
+++ b/tests/Fixtures/TestBundle/Filter/BoundsFilter.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\Tests\Fixtures\TestBundle\Filter;
+
+use ApiPlatform\Core\Bridge\Doctrine\Orm\Filter\AbstractFilter;
+use ApiPlatform\Core\Bridge\Doctrine\Orm\Util\QueryNameGeneratorInterface;
+use Doctrine\ORM\QueryBuilder;
+
+class BoundsFilter extends AbstractFilter
+{
+ 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..a2fe49b2598
--- /dev/null
+++ b/tests/Fixtures/TestBundle/Filter/EnumFilter.php
@@ -0,0 +1,40 @@
+
+ *
+ * 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\Orm\Filter\AbstractFilter;
+use ApiPlatform\Core\Bridge\Doctrine\Orm\Util\QueryNameGeneratorInterface;
+use Doctrine\ORM\QueryBuilder;
+
+class EnumFilter extends AbstractFilter
+{
+ 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..be9c30af0b2
--- /dev/null
+++ b/tests/Fixtures/TestBundle/Filter/LengthFilter.php
@@ -0,0 +1,48 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+declare(strict_types=1);
+
+namespace ApiPlatform\Core\Tests\Fixtures\TestBundle\Filter;
+
+use ApiPlatform\Core\Bridge\Doctrine\Orm\Filter\AbstractFilter;
+use ApiPlatform\Core\Bridge\Doctrine\Orm\Util\QueryNameGeneratorInterface;
+use Doctrine\ORM\QueryBuilder;
+
+class LengthFilter extends AbstractFilter
+{
+ 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..f806a99950d
--- /dev/null
+++ b/tests/Fixtures/TestBundle/Filter/MultipleOfFilter.php
@@ -0,0 +1,40 @@
+
+ *
+ * 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\Orm\Filter\AbstractFilter;
+use ApiPlatform\Core\Bridge\Doctrine\Orm\Util\QueryNameGeneratorInterface;
+use Doctrine\ORM\QueryBuilder;
+
+class MultipleOfFilter extends AbstractFilter
+{
+ 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..ccb9f56e731
--- /dev/null
+++ b/tests/Fixtures/TestBundle/Filter/PatternFilter.php
@@ -0,0 +1,40 @@
+
+ *
+ * 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\Orm\Filter\AbstractFilter;
+use ApiPlatform\Core\Bridge\Doctrine\Orm\Util\QueryNameGeneratorInterface;
+use Doctrine\ORM\QueryBuilder;
+
+class PatternFilter extends AbstractFilter
+{
+ 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..d0dc25882dd
--- /dev/null
+++ b/tests/Fixtures/TestBundle/Filter/RequiredAllowEmptyFilter.php
@@ -0,0 +1,40 @@
+
+ *
+ * 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\Orm\Filter\AbstractFilter;
+use ApiPlatform\Core\Bridge\Doctrine\Orm\Util\QueryNameGeneratorInterface;
+use Doctrine\ORM\QueryBuilder;
+
+class RequiredAllowEmptyFilter extends AbstractFilter
+{
+ 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 456baa29f73..68732fd3a1f 100644
--- a/tests/Fixtures/app/config/config_common.yml
+++ b/tests/Fixtures/app/config/config_common.yml
@@ -129,6 +129,34 @@ 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\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']