From 372b25f8d25cb8a1e9725007742c8a7e73dd92ea Mon Sep 17 00:00:00 2001 From: aegypius Date: Tue, 17 Oct 2023 15:00:20 +0200 Subject: [PATCH] fix(validation)!: validate parameters with multiple values Ref #4798 --- src/ParameterValidator/ParameterValidator.php | 84 ++++++++++++++++++- 1 file changed, 82 insertions(+), 2 deletions(-) diff --git a/src/ParameterValidator/ParameterValidator.php b/src/ParameterValidator/ParameterValidator.php index a915bd0381..f7e3d4ae90 100644 --- a/src/ParameterValidator/ParameterValidator.php +++ b/src/ParameterValidator/ParameterValidator.php @@ -59,8 +59,16 @@ public function validateFilters(string $resourceClass, array $resourceFilters, a } foreach ($filter->getDescription($resourceClass) as $name => $data) { - foreach ($this->validators as $validator) { - if ($errors = $validator->validate($name, $data, $queryParameters)) { + $collectionFormat = $this->getCollectionFormat($data); + + // validate simple values + if ($errors = $this->validate($name, $data, $queryParameters)) { + $errorList[] = $errors; + } + + // manipulate query data to validate each value + foreach ($this->iterateValue($name, $queryParameters, $collectionFormat) as $scalarQueryParameters) { + if ($errors = $this->validate($name, $data, $scalarQueryParameters)) { $errorList[] = $errors; } } @@ -71,4 +79,76 @@ public function validateFilters(string $resourceClass, array $resourceFilters, a throw new ValidationException(array_merge(...$errorList)); } } + + /** + * @param array> $filterDescription + */ + private static function getCollectionFormat(array $filterDescription): string + { + return $filterDescription['openapi']['collectionFormat'] ?? $filterDescription['swagger']['collectionFormat'] ?? 'csv'; + } + + /** + * @param array $queryParameters + * + * @throws \InvalidArgumentException + */ + private static function iterateValue(string $name, array $queryParameters, string $collectionFormat = 'csv'): \Generator + { + $candidates = array_filter( + $queryParameters, + static fn (string $key) => $key === $name || "{$key}[]" === $name, + \ARRAY_FILTER_USE_KEY + ); + + foreach ($candidates as $key => $value) { + $values = self::getValue($value, $collectionFormat); + foreach ($values as $v) { + yield [$key => $v]; + } + } + } + + /** + * @param int|int[]|string|string[] $value + * + * @return int[]|string[] + */ + private static function getValue(int|string|array $value, string $collectionFormat = 'csv'): array + { + if (\is_array($value)) { + return $value; + } + + if (\is_string($value)) { + return explode(self::getSeparator($collectionFormat), $value); + } + + return [$value]; + } + + /** @return non-empty-string */ + private static function getSeparator(string $collectionFormat): string + { + return match ($collectionFormat) { + 'csv' => ',', + 'ssv' => ' ', + 'tsv' => '\t', + 'pipes' => '|', + default => throw new \InvalidArgumentException(sprintf('Unknown collection format %s', $collectionFormat)), + }; + } + + private function validate(string $name, array $data, array $queryParameters): array + { + $errorList = []; + + foreach ($this->validators as $validator) { + if ($errors = $validator->validate($name, $data, $queryParameters)) { + $errorList[] = $errors; + } + } + + return array_merge(...$errorList); + } }