diff --git a/composer.lock b/composer.lock index b726c90..0156c10 100644 --- a/composer.lock +++ b/composer.lock @@ -8,16 +8,16 @@ "packages": [ { "name": "brick/math", - "version": "0.12.1", + "version": "0.12.3", "source": { "type": "git", "url": "https://github.com/brick/math.git", - "reference": "f510c0a40911935b77b86859eb5223d58d660df1" + "reference": "866551da34e9a618e64a819ee1e01c20d8a588ba" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/brick/math/zipball/f510c0a40911935b77b86859eb5223d58d660df1", - "reference": "f510c0a40911935b77b86859eb5223d58d660df1", + "url": "https://api.github.com/repos/brick/math/zipball/866551da34e9a618e64a819ee1e01c20d8a588ba", + "reference": "866551da34e9a618e64a819ee1e01c20d8a588ba", "shasum": "" }, "require": { @@ -26,7 +26,7 @@ "require-dev": { "php-coveralls/php-coveralls": "^2.2", "phpunit/phpunit": "^10.1", - "vimeo/psalm": "5.16.0" + "vimeo/psalm": "6.8.8" }, "type": "library", "autoload": { @@ -56,7 +56,7 @@ ], "support": { "issues": "https://github.com/brick/math/issues", - "source": "https://github.com/brick/math/tree/0.12.1" + "source": "https://github.com/brick/math/tree/0.12.3" }, "funding": [ { @@ -64,7 +64,7 @@ "type": "github" } ], - "time": "2023-11-29T23:19:16+00:00" + "time": "2025-02-28T13:11:00+00:00" }, { "name": "composer/semver", @@ -1082,16 +1082,16 @@ }, { "name": "ramsey/collection", - "version": "2.0.0", + "version": "2.1.0", "source": { "type": "git", "url": "https://github.com/ramsey/collection.git", - "reference": "a4b48764bfbb8f3a6a4d1aeb1a35bb5e9ecac4a5" + "reference": "3c5990b8a5e0b79cd1cf11c2dc1229e58e93f109" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/ramsey/collection/zipball/a4b48764bfbb8f3a6a4d1aeb1a35bb5e9ecac4a5", - "reference": "a4b48764bfbb8f3a6a4d1aeb1a35bb5e9ecac4a5", + "url": "https://api.github.com/repos/ramsey/collection/zipball/3c5990b8a5e0b79cd1cf11c2dc1229e58e93f109", + "reference": "3c5990b8a5e0b79cd1cf11c2dc1229e58e93f109", "shasum": "" }, "require": { @@ -1099,25 +1099,22 @@ }, "require-dev": { "captainhook/plugin-composer": "^5.3", - "ergebnis/composer-normalize": "^2.28.3", - "fakerphp/faker": "^1.21", + "ergebnis/composer-normalize": "^2.45", + "fakerphp/faker": "^1.24", "hamcrest/hamcrest-php": "^2.0", - "jangregor/phpstan-prophecy": "^1.0", - "mockery/mockery": "^1.5", + "jangregor/phpstan-prophecy": "^2.1", + "mockery/mockery": "^1.6", "php-parallel-lint/php-console-highlighter": "^1.0", - "php-parallel-lint/php-parallel-lint": "^1.3", - "phpcsstandards/phpcsutils": "^1.0.0-rc1", - "phpspec/prophecy-phpunit": "^2.0", - "phpstan/extension-installer": "^1.2", - "phpstan/phpstan": "^1.9", - "phpstan/phpstan-mockery": "^1.1", - "phpstan/phpstan-phpunit": "^1.3", - "phpunit/phpunit": "^9.5", - "psalm/plugin-mockery": "^1.1", - "psalm/plugin-phpunit": "^0.18.4", - "ramsey/coding-standard": "^2.0.3", - "ramsey/conventional-commits": "^1.3", - "vimeo/psalm": "^5.4" + "php-parallel-lint/php-parallel-lint": "^1.4", + "phpspec/prophecy-phpunit": "^2.3", + "phpstan/extension-installer": "^1.4", + "phpstan/phpstan": "^2.1", + "phpstan/phpstan-mockery": "^2.0", + "phpstan/phpstan-phpunit": "^2.0", + "phpunit/phpunit": "^10.5", + "ramsey/coding-standard": "^2.3", + "ramsey/conventional-commits": "^1.6", + "roave/security-advisories": "dev-latest" }, "type": "library", "extra": { @@ -1155,19 +1152,9 @@ ], "support": { "issues": "https://github.com/ramsey/collection/issues", - "source": "https://github.com/ramsey/collection/tree/2.0.0" + "source": "https://github.com/ramsey/collection/tree/2.1.0" }, - "funding": [ - { - "url": "https://github.com/ramsey", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/ramsey/collection", - "type": "tidelift" - } - ], - "time": "2022-12-31T21:50:55+00:00" + "time": "2025-03-02T04:48:29+00:00" }, { "name": "ramsey/uuid", @@ -1330,16 +1317,16 @@ }, { "name": "symfony/http-client", - "version": "v7.2.3", + "version": "v7.2.4", "source": { "type": "git", "url": "https://github.com/symfony/http-client.git", - "reference": "7ce6078c79a4a7afff931c413d2959d3bffbfb8d" + "reference": "78981a2ffef6437ed92d4d7e2a86a82f256c6dc6" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-client/zipball/7ce6078c79a4a7afff931c413d2959d3bffbfb8d", - "reference": "7ce6078c79a4a7afff931c413d2959d3bffbfb8d", + "url": "https://api.github.com/repos/symfony/http-client/zipball/78981a2ffef6437ed92d4d7e2a86a82f256c6dc6", + "reference": "78981a2ffef6437ed92d4d7e2a86a82f256c6dc6", "shasum": "" }, "require": { @@ -1405,7 +1392,7 @@ "http" ], "support": { - "source": "https://github.com/symfony/http-client/tree/v7.2.3" + "source": "https://github.com/symfony/http-client/tree/v7.2.4" }, "funding": [ { @@ -1421,7 +1408,7 @@ "type": "tidelift" } ], - "time": "2025-01-28T15:51:35+00:00" + "time": "2025-02-13T10:27:23+00:00" }, { "name": "symfony/http-client-contracts", @@ -4650,16 +4637,16 @@ }, { "name": "symfony/process", - "version": "v7.2.0", + "version": "v7.2.4", "source": { "type": "git", "url": "https://github.com/symfony/process.git", - "reference": "d34b22ba9390ec19d2dd966c40aa9e8462f27a7e" + "reference": "d8f411ff3c7ddc4ae9166fb388d1190a2df5b5cf" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/process/zipball/d34b22ba9390ec19d2dd966c40aa9e8462f27a7e", - "reference": "d34b22ba9390ec19d2dd966c40aa9e8462f27a7e", + "url": "https://api.github.com/repos/symfony/process/zipball/d8f411ff3c7ddc4ae9166fb388d1190a2df5b5cf", + "reference": "d8f411ff3c7ddc4ae9166fb388d1190a2df5b5cf", "shasum": "" }, "require": { @@ -4691,7 +4678,7 @@ "description": "Executes commands in sub-processes", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/process/tree/v7.2.0" + "source": "https://github.com/symfony/process/tree/v7.2.4" }, "funding": [ { @@ -4707,7 +4694,7 @@ "type": "tidelift" } ], - "time": "2024-11-06T14:24:19+00:00" + "time": "2025-02-05T08:33:46+00:00" }, { "name": "symfony/string", diff --git a/src/App.php b/src/App.php index 63965ec..aa2249a 100755 --- a/src/App.php +++ b/src/App.php @@ -579,7 +579,9 @@ public function execute(Route $route, Request $request, Response $response): sta { $arguments = []; $groups = $route->getGroups(); - $pathValues = $route->getPathValues($request); + + $preparedPath = Router::preparePath($route->getMatchedPath()); + $pathValues = $route->getPathValues($request, $preparedPath[0]); try { if ($route->getHook()) { diff --git a/src/Route.php b/src/Route.php index fa4b25f..aa06a29 100755 --- a/src/Route.php +++ b/src/Route.php @@ -28,7 +28,7 @@ class Route extends Hook /** * Path params. * - * @var array + * @var array> */ protected array $pathParams = []; @@ -46,6 +46,8 @@ class Route extends Hook */ protected int $order; + protected string $matchedPath = ''; + public function __construct(string $method, string $path) { $this->path($path); @@ -55,6 +57,17 @@ public function __construct(string $method, string $path) }; } + public function setMatchedPath(string $path): self + { + $this->matchedPath = $path; + return $this; + } + + public function getMatchedPath(): string + { + return $this->matchedPath; + } + /** * Get Route Order ID * @@ -141,9 +154,9 @@ public function getHook(): bool * @param int $index * @return void */ - public function setPathParam(string $key, int $index): void + public function setPathParam(string $path, string $key, int $index): void { - $this->pathParams[$key] = $index; + $this->pathParams[$path][$key] = $index; } /** @@ -152,12 +165,12 @@ public function setPathParam(string $key, int $index): void * @param \Utopia\Request $request * @return array */ - public function getPathValues(Request $request): array + public function getPathValues(Request $request, string $path): array { $pathValues = []; $parts = explode('/', ltrim($request->getURI(), '/')); - foreach ($this->pathParams as $key => $index) { + foreach (($this->pathParams[$path] ?? []) as $key => $index) { if (array_key_exists($index, $parts)) { $pathValues[$key] = $parts[$index]; } diff --git a/src/Router.php b/src/Router.php index a2ad9b9..bc6e63c 100644 --- a/src/Router.php +++ b/src/Router.php @@ -86,7 +86,7 @@ public static function addRoute(Route $route): void } foreach ($params as $key => $index) { - $route->setPathParam($key, $index); + $route->setPathParam($path, $key, $index); } self::$routes[$route->getMethod()][$path] = $route; @@ -101,12 +101,16 @@ public static function addRoute(Route $route): void */ public static function addRouteAlias(string $path, Route $route): void { - [$alias] = self::preparePath($path); + [$alias, $params] = self::preparePath($path); if (array_key_exists($alias, self::$routes[$route->getMethod()]) && !self::$allowOverride) { throw new Exception("Route for ({$route->getMethod()}:{$alias}) already registered."); } + foreach ($params as $key => $index) { + $route->setPathParam($alias, $key, $index); + } + self::$routes[$route->getMethod()][$alias] = $route; } @@ -138,7 +142,9 @@ public static function match(string $method, string $path): Route|null ); if (array_key_exists($match, self::$routes[$method])) { - return self::$routes[$method][$match]; + $route = self::$routes[$method][$match]; + $route->setMatchedPath($match); + return $route; } } @@ -147,7 +153,9 @@ public static function match(string $method, string $path): Route|null */ $match = self::WILDCARD_TOKEN; if (array_key_exists($match, self::$routes[$method])) { - return self::$routes[$method][$match]; + $route = self::$routes[$method][$match]; + $route->setMatchedPath($match); + return $route; } /** @@ -157,7 +165,9 @@ public static function match(string $method, string $path): Route|null $current = ($current ?? '') . "{$part}/"; $match = $current . self::WILDCARD_TOKEN; if (array_key_exists($match, self::$routes[$method])) { - return self::$routes[$method][$match]; + $route = self::$routes[$method][$match]; + $route->setMatchedPath($match); + return $route; } } @@ -192,7 +202,7 @@ protected static function combinations(array $set): iterable * @param string $path * @return array */ - protected static function preparePath(string $path): array + public static function preparePath(string $path): array { $parts = array_values(array_filter(explode('/', $path))); $prepare = ''; diff --git a/tests/e2e/Client.php b/tests/e2e/Client.php index 4052689..cedd4cf 100644 --- a/tests/e2e/Client.php +++ b/tests/e2e/Client.php @@ -54,17 +54,35 @@ public function __construct() public function call(string $method, string $path = '', array $headers = [], array $params = []) { usleep(50000); + $url = $this->baseUrl.$path.(($method == self::METHOD_GET && !empty($params)) ? '?'.http_build_query($params) : ''); $ch = curl_init($this->baseUrl.$path.(($method == self::METHOD_GET && !empty($params)) ? '?'.http_build_query($params) : '')); $responseHeaders = []; $responseStatus = -1; $responseType = ''; $responseBody = ''; + $query = match ($headers['content-type'] ?? '') { + 'application/json' => \json_encode($params), + 'text/plain' => $params, + default => \http_build_query($params), + }; + + $formattedHeaders = []; + foreach ($headers as $key => $value) { + if (strtolower($key) === 'accept-encoding') { + curl_setopt($ch, CURLOPT_ENCODING, $value); + continue; + } else { + $formattedHeaders[] = $key . ': ' . $value; + } + } + + curl_setopt($ch, CURLOPT_PATH_AS_IS, 1); curl_setopt($ch, CURLOPT_CUSTOMREQUEST, $method); curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true); curl_setopt($ch, CURLOPT_USERAGENT, 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.77 Safari/537.36'); - curl_setopt($ch, CURLOPT_HTTPHEADER, $headers); + curl_setopt($ch, CURLOPT_HTTPHEADER, $formattedHeaders); curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 0); curl_setopt($ch, CURLOPT_TIMEOUT, 15); curl_setopt($ch, CURLOPT_HEADERFUNCTION, function ($curl, $header) use (&$responseHeaders) { @@ -80,6 +98,10 @@ public function call(string $method, string $path = '', array $headers = [], arr return $len; }); + if ($method !== self::METHOD_GET) { + curl_setopt($ch, CURLOPT_POSTFIELDS, $query); + } + $responseBody = curl_exec($ch); $responseStatus = curl_getinfo($ch, CURLINFO_HTTP_CODE); diff --git a/tests/e2e/ResponseTest.php b/tests/e2e/ResponseTest.php index bff46a5..ecbb976 100644 --- a/tests/e2e/ResponseTest.php +++ b/tests/e2e/ResponseTest.php @@ -56,4 +56,30 @@ public function testEarlyResponse() $this->assertEquals(200, $response['headers']['status-code']); $this->assertEquals('Init response. Actioned before: no', $response['body']); } + + public function testAliasWithParameter(): void + { + $response = $this->client->call(Client::METHOD_POST, '/functions/deployment', [ + 'content-type' => 'application/json' + ], [ + 'deploymentId' => 'deployment1' + ]); + $this->assertEquals(200, $response['headers']['status-code']); + $this->assertEquals('ID:deployment1', $response['body']); + + $response = $this->client->call(Client::METHOD_POST, '/functions/deployment'); + $this->assertEquals(204, $response['headers']['status-code']); + + $response = $this->client->call(Client::METHOD_POST, '/functions/deployment/deployment2'); + $this->assertEquals(200, $response['headers']['status-code']); + $this->assertEquals('ID:deployment2', $response['body']); + + $response = $this->client->call(Client::METHOD_POST, '/database/collections/col1'); + $this->assertEquals(200, $response['headers']['status-code']); + $this->assertEquals(';col1', $response['body']); + + $response = $this->client->call(Client::METHOD_POST, '/databases/db2/collections/col2'); + $this->assertEquals(200, $response['headers']['status-code']); + $this->assertEquals('db2;col2', $response['body']); + } } diff --git a/tests/e2e/server.php b/tests/e2e/server.php index a10d58b..179767c 100644 --- a/tests/e2e/server.php +++ b/tests/e2e/server.php @@ -46,6 +46,27 @@ $response->noContent(); }); +App::post('/functions/deployment') + ->alias('/functions/deployment/:deploymentId') + ->param('deploymentId', '', new Text(64, 0), '', true) + ->inject('response') + ->action(function (string $deploymentId, Response $response) { + if (empty($deploymentId)) { + $response->noContent(); + return; + } + + $response->send('ID:' . $deploymentId); + }); + +App::post('/databases/:databaseId/collections/:collectionId') + ->alias('/database/collections/:collectionId') + ->param('databaseId', '', new Text(64, 0), '', true) + ->param('collectionId', '', new Text(64, 0), '', true) + ->inject('response') + ->action(function (string $databaseId, string $collectionId, Response $response) { + $response->send($databaseId . ';' . $collectionId); + }); // Endpoints for early response // Meant to run twice, so init hook can know if action ran