diff --git a/src/PSR7/Validators/QueryArgumentsValidator.php b/src/PSR7/Validators/QueryArgumentsValidator.php index 20a492df..d18de4a3 100644 --- a/src/PSR7/Validators/QueryArgumentsValidator.php +++ b/src/PSR7/Validators/QueryArgumentsValidator.php @@ -15,7 +15,9 @@ use Psr\Http\Message\RequestInterface; use Psr\Http\Message\ServerRequestInterface; -use function parse_str; +use function array_filter; +use function explode; +use function urldecode; /** * @see https://swagger.io/docs/specification/describing-parameters/ @@ -69,11 +71,39 @@ private function validateQueryArguments(OperationAddress $addr, array $parsedQue private function parseQueryArguments(RequestInterface $message): array { if ($message instanceof ServerRequestInterface) { - $parsedQueryArguments = $message->getQueryParams(); - } else { - parse_str($message->getUri()->getQuery(), $parsedQueryArguments); + return $message->getQueryParams(); } - return $parsedQueryArguments; + return $this->parseQueryString($message->getUri()->getQuery()); + } + + /** + * @see https://www.php.net/manual/en/function.parse-str.php#76792 + * + * @return array + */ + private function parseQueryString(string $queryString): array + { + $queryParameterPairs = explode('&', urldecode($queryString)); + $filteredParameterPairs = array_filter( + $queryParameterPairs, + static function ($item) { + return $item !== ''; + } + ); + + $arr = []; + foreach ($filteredParameterPairs as $i) { + [$key, $value] = explode('=', $i); + + if (! isset($arr[$key])) { + $arr[$key] = $value; + continue; + } + + $arr[$key] = [$arr[$key], $value]; + } + + return $arr; } } diff --git a/tests/PSR7/BaseValidatorTest.php b/tests/PSR7/BaseValidatorTest.php index dd82c382..f1e0470c 100644 --- a/tests/PSR7/BaseValidatorTest.php +++ b/tests/PSR7/BaseValidatorTest.php @@ -54,12 +54,27 @@ protected function makeGoodServerRequest(string $path, string $method): ServerRe switch ($method . ' ' . $path) { case 'get /read': return $request - ->withUri(new Uri($path . '?filter=age&limit=10')) - ->withQueryParams(['filter' => 'age', 'limit' => 10, 'offset' => 0]); + ->withUri(new Uri($path)) + ->withQueryParams([ + 'filter' => 'age', + 'limit' => 10, + 'offset' => 0, + 'queryArgB[]' => [ + 'value1', + '20', + ], + ]); case 'get /path1': return $request - ->withUri(new Uri($path . '?queryArgA=20')) + ->withUri(new Uri($path)) + ->withQueryParams([ + 'queryArgA' => '20', + 'queryArgB[]' => [ + 'value1', + '20', + ], + ]) ->withHeader('Header-A', 'value A'); case 'post /cookies': @@ -86,11 +101,11 @@ protected function makeGoodRequest(string $path, string $method): RequestInterfa switch ($method . ' ' . $path) { case 'get /read': return $request - ->withUri(new Uri($path . '?filter=age&limit=10&offset=0')); + ->withUri(new Uri($path . '?filter=age&limit=10&offset=0&queryArgB[]=value1&queryArgB[]=value2')); case 'get /path1': return $request - ->withUri(new Uri($path . '?queryArgA=20')) + ->withUri(new Uri($path . '?queryArgA=20&queryArgB[]=value1&queryArgB[]=value2')) ->withHeader('Header-A', 'value A'); case 'post /cookies': diff --git a/tests/PSR7/HeadersTest.php b/tests/PSR7/HeadersTest.php index 8dc518f3..96c2eb60 100644 --- a/tests/PSR7/HeadersTest.php +++ b/tests/PSR7/HeadersTest.php @@ -15,7 +15,15 @@ final class HeadersTest extends BaseValidatorTest { public function testItValidatesRequestQueryArgumentsGreen(): void { - $request = (new ServerRequest('get', new Uri('/path1?queryArgA=20')))->withHeader('header-a', 'value A'); + $request = (new ServerRequest('get', new Uri('/path1'))) + ->withQueryParams([ + 'queryArgA' => 20, + 'queryArgB[]' => [ + 'value1', + 'value2', + ], + ]) + ->withHeader('header-a', 'value A'); $validator = (new ValidatorBuilder())->fromYamlFile($this->apiSpecFile)->getServerRequestValidator(); $validator->validate($request); diff --git a/tests/PSR7/RoutedServerRequestTest.php b/tests/PSR7/RoutedServerRequestTest.php index cc862c25..ada710b6 100644 --- a/tests/PSR7/RoutedServerRequestTest.php +++ b/tests/PSR7/RoutedServerRequestTest.php @@ -16,7 +16,13 @@ final class RoutedServerRequestTest extends BaseValidatorTest { public function testItValidatesMessageGreen(): void { - $request = $this->makeGoodServerRequest('/path1', 'get'); + $request = $this->makeGoodServerRequest('/path1', 'get') + ->withQueryParams([ + 'queryArgB[]' => [ + 'value1', + 'value2', + ], + ]); $validator = (new ValidatorBuilder())->fromYamlFile($this->apiSpecFile)->getRoutedRequestValidator(); $validator->validate(new OperationAddress('/path1', 'get'), $request); diff --git a/tests/PSR7/ServerRequestTest.php b/tests/PSR7/ServerRequestTest.php index 679d57e6..34feb91a 100644 --- a/tests/PSR7/ServerRequestTest.php +++ b/tests/PSR7/ServerRequestTest.php @@ -16,7 +16,8 @@ final class ServerRequestTest extends BaseValidatorTest { public function testItValidatesMessageGreen(): void { - $request = $this->makeGoodServerRequest('/path1', 'get'); + $request = $this->makeGoodServerRequest('/path1', 'get') + ->withQueryParams(['queryArgB[]' => ['value1']]); $validator = (new ValidatorBuilder())->fromYamlFile($this->apiSpecFile)->getServerRequestValidator(); $validator->validate($request); diff --git a/tests/stubs/api.yaml b/tests/stubs/api.yaml index 46e2a2ec..67614584 100644 --- a/tests/stubs/api.yaml +++ b/tests/stubs/api.yaml @@ -101,6 +101,7 @@ paths: parameters: - $ref: 'schemas.yaml#/components/parameters/HeaderA' - $ref: 'schemas.yaml#/components/parameters/QueryArgumentA' + - $ref: 'schemas.yaml#/components/parameters/QueryArgumentB' description: Get Path1 responses: 200: diff --git a/tests/stubs/schemas.yaml b/tests/stubs/schemas.yaml index ae7f00b8..2aaeaaa3 100644 --- a/tests/stubs/schemas.yaml +++ b/tests/stubs/schemas.yaml @@ -42,3 +42,13 @@ components: schema: type: number format: float + QueryArgumentB: + in: query + name: queryArgB[] + description: query argument B which is array + required: true + schema: + type: array + items: + - type: string + example: value1