diff --git a/composer.lock b/composer.lock index e1c2aab..f8a2288 100644 --- a/composer.lock +++ b/composer.lock @@ -1549,16 +1549,16 @@ }, { "name": "symfony/deprecation-contracts", - "version": "v3.6.0", + "version": "v3.7.0", "source": { "type": "git", "url": "https://github.com/symfony/deprecation-contracts.git", - "reference": "63afe740e99a13ba87ec199bb07bbdee937a5b62" + "reference": "50f59d1f3ca46d41ac911f97a78626b6756af35b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/63afe740e99a13ba87ec199bb07bbdee937a5b62", - "reference": "63afe740e99a13ba87ec199bb07bbdee937a5b62", + "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/50f59d1f3ca46d41ac911f97a78626b6756af35b", + "reference": "50f59d1f3ca46d41ac911f97a78626b6756af35b", "shasum": "" }, "require": { @@ -1571,7 +1571,7 @@ "name": "symfony/contracts" }, "branch-alias": { - "dev-main": "3.6-dev" + "dev-main": "3.7-dev" } }, "autoload": { @@ -1596,7 +1596,7 @@ "description": "A generic function and convention to trigger deprecation notices", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/deprecation-contracts/tree/v3.6.0" + "source": "https://github.com/symfony/deprecation-contracts/tree/v3.7.0" }, "funding": [ { @@ -1607,12 +1607,16 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2024-09-25T14:21:43+00:00" + "time": "2026-04-13T15:52:40+00:00" }, { "name": "symfony/http-client", @@ -1717,16 +1721,16 @@ }, { "name": "symfony/http-client-contracts", - "version": "v3.6.0", + "version": "v3.7.0", "source": { "type": "git", "url": "https://github.com/symfony/http-client-contracts.git", - "reference": "75d7043853a42837e68111812f4d964b01e5101c" + "reference": "4a2d00c37651c0bdc2b9e1c773487a8bf4edb12d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-client-contracts/zipball/75d7043853a42837e68111812f4d964b01e5101c", - "reference": "75d7043853a42837e68111812f4d964b01e5101c", + "url": "https://api.github.com/repos/symfony/http-client-contracts/zipball/4a2d00c37651c0bdc2b9e1c773487a8bf4edb12d", + "reference": "4a2d00c37651c0bdc2b9e1c773487a8bf4edb12d", "shasum": "" }, "require": { @@ -1739,7 +1743,7 @@ "name": "symfony/contracts" }, "branch-alias": { - "dev-main": "3.6-dev" + "dev-main": "3.7-dev" } }, "autoload": { @@ -1775,7 +1779,7 @@ "standards" ], "support": { - "source": "https://github.com/symfony/http-client-contracts/tree/v3.6.0" + "source": "https://github.com/symfony/http-client-contracts/tree/v3.7.0" }, "funding": [ { @@ -1786,12 +1790,16 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2025-04-29T11:18:49+00:00" + "time": "2026-03-06T13:17:50+00:00" }, { "name": "symfony/polyfill-mbstring", @@ -2040,16 +2048,16 @@ }, { "name": "symfony/service-contracts", - "version": "v3.6.1", + "version": "v3.7.0", "source": { "type": "git", "url": "https://github.com/symfony/service-contracts.git", - "reference": "45112560a3ba2d715666a509a0bc9521d10b6c43" + "reference": "d25d82433a80eba6aa0e6c24b61d7370d99e444a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/service-contracts/zipball/45112560a3ba2d715666a509a0bc9521d10b6c43", - "reference": "45112560a3ba2d715666a509a0bc9521d10b6c43", + "url": "https://api.github.com/repos/symfony/service-contracts/zipball/d25d82433a80eba6aa0e6c24b61d7370d99e444a", + "reference": "d25d82433a80eba6aa0e6c24b61d7370d99e444a", "shasum": "" }, "require": { @@ -2067,7 +2075,7 @@ "name": "symfony/contracts" }, "branch-alias": { - "dev-main": "3.6-dev" + "dev-main": "3.7-dev" } }, "autoload": { @@ -2103,7 +2111,7 @@ "standards" ], "support": { - "source": "https://github.com/symfony/service-contracts/tree/v3.6.1" + "source": "https://github.com/symfony/service-contracts/tree/v3.7.0" }, "funding": [ { @@ -2123,7 +2131,7 @@ "type": "tidelift" } ], - "time": "2025-07-15T11:30:57+00:00" + "time": "2026-03-28T09:44:51+00:00" }, { "name": "tbachert/spi", @@ -2327,16 +2335,16 @@ }, { "name": "utopia-php/http", - "version": "2.0.0-rc1", + "version": "2.0.0-rc2", "source": { "type": "git", "url": "https://github.com/utopia-php/http.git", - "reference": "3e3b431d443844c6bf810120dee735f45880856f" + "reference": "17f3d5e966ada8a5c041717436f069f269aef2b3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/http/zipball/3e3b431d443844c6bf810120dee735f45880856f", - "reference": "3e3b431d443844c6bf810120dee735f45880856f", + "url": "https://api.github.com/repos/utopia-php/http/zipball/17f3d5e966ada8a5c041717436f069f269aef2b3", + "reference": "17f3d5e966ada8a5c041717436f069f269aef2b3", "shasum": "" }, "require": { @@ -2377,9 +2385,9 @@ ], "support": { "issues": "https://github.com/utopia-php/http/issues", - "source": "https://github.com/utopia-php/http/tree/2.0.0-rc1" + "source": "https://github.com/utopia-php/http/tree/2.0.0-rc2" }, - "time": "2026-05-05T15:00:03+00:00" + "time": "2026-05-20T11:13:49+00:00" }, { "name": "utopia-php/pools", @@ -2436,16 +2444,16 @@ }, { "name": "utopia-php/queue", - "version": "0.18.2", + "version": "0.18.3", "source": { "type": "git", "url": "https://github.com/utopia-php/queue.git", - "reference": "f85ca003c99ff475708c05466643d067403c0c22" + "reference": "141aad162b90728353f3aa834684b1f2affed045" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/queue/zipball/f85ca003c99ff475708c05466643d067403c0c22", - "reference": "f85ca003c99ff475708c05466643d067403c0c22", + "url": "https://api.github.com/repos/utopia-php/queue/zipball/141aad162b90728353f3aa834684b1f2affed045", + "reference": "141aad162b90728353f3aa834684b1f2affed045", "shasum": "" }, "require": { @@ -2496,9 +2504,9 @@ ], "support": { "issues": "https://github.com/utopia-php/queue/issues", - "source": "https://github.com/utopia-php/queue/tree/0.18.2" + "source": "https://github.com/utopia-php/queue/tree/0.18.3" }, - "time": "2026-05-05T04:38:59+00:00" + "time": "2026-05-14T08:53:35+00:00" }, { "name": "utopia-php/servers", @@ -2611,16 +2619,16 @@ }, { "name": "utopia-php/validators", - "version": "0.2.2", + "version": "0.2.4", "source": { "type": "git", "url": "https://github.com/utopia-php/validators.git", - "reference": "5d7d494e64457cd4eb67fdcfd9481f2c89796aa6" + "reference": "b4ee60db4dbae5ffbe53968d01f69b6941251576" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/validators/zipball/5d7d494e64457cd4eb67fdcfd9481f2c89796aa6", - "reference": "5d7d494e64457cd4eb67fdcfd9481f2c89796aa6", + "url": "https://api.github.com/repos/utopia-php/validators/zipball/b4ee60db4dbae5ffbe53968d01f69b6941251576", + "reference": "b4ee60db4dbae5ffbe53968d01f69b6941251576", "shasum": "" }, "require": { @@ -2650,9 +2658,9 @@ ], "support": { "issues": "https://github.com/utopia-php/validators/issues", - "source": "https://github.com/utopia-php/validators/tree/0.2.2" + "source": "https://github.com/utopia-php/validators/tree/0.2.4" }, - "time": "2026-04-27T16:30:24+00:00" + "time": "2026-05-21T12:47:43+00:00" } ], "packages-dev": [ diff --git a/src/Platform/Action.php b/src/Platform/Action.php index bdf6afa..55e24bc 100644 --- a/src/Platform/Action.php +++ b/src/Platform/Action.php @@ -170,9 +170,10 @@ public function getParams(): array * @param bool $deprecated * @param string $example * @param array $aliases + * @param Enum|null $enum * @return self */ - public function param(string $key, mixed $default, Validator|callable $validator, string $description = '', bool $optional = false, array $injections = [], bool $skipValidation = false, bool $deprecated = false, string $example = '', array $aliases = []): self + public function param(string $key, mixed $default, Validator|callable $validator, string $description = '', bool $optional = false, array $injections = [], bool $skipValidation = false, bool $deprecated = false, string $example = '', array $aliases = [], ?Enum $enum = null): self { $param = [ 'default' => $default, @@ -184,6 +185,7 @@ public function param(string $key, mixed $default, Validator|callable $validator 'deprecated' => $deprecated, // TODO: @Meldiron implement tests 'example' => $example, 'aliases' => $aliases, + 'enum' => $enum, ]; $this->options['param:'.$key] = array_merge($param, ['type' => 'param']); $this->params[$key] = $param; diff --git a/src/Platform/Enum.php b/src/Platform/Enum.php new file mode 100644 index 0000000..ab613e5 --- /dev/null +++ b/src/Platform/Enum.php @@ -0,0 +1,21 @@ +|null $map Mapping of whitelist values to generated enum case names. + * @param list|null $exclude Whitelist values to omit from generated enums. + */ + public function __construct( + public ?string $name = null, + public ?array $map = null, + public ?array $exclude = null, + ) { + } +} diff --git a/src/Platform/Platform.php b/src/Platform/Platform.php index ef2b3b6..4b1249f 100644 --- a/src/Platform/Platform.php +++ b/src/Platform/Platform.php @@ -115,7 +115,7 @@ protected function initHttp(array $services): void switch ($option['type']) { case 'param': $key = substr($key, stripos($key, ':') + 1); - $hook->param($key, $option['default'], $option['validator'], $option['description'], $option['optional'], $option['injections'], $option['skipValidation'], $option['deprecated'], $option['example'], aliases: $option['aliases'] ?? []); + $hook->param($key, $option['default'], $option['validator'], $option['description'], $option['optional'], $option['injections'], $option['skipValidation'], $option['deprecated'], $option['example'], aliases: $option['aliases'] ?? [], enum: $option['enum'] ?? null); break; case 'injection': $hook->inject($option['name']); @@ -165,7 +165,7 @@ protected function initTasks(array $services): void switch ($option['type']) { case 'param': $key = substr($key, stripos($key, ':') + 1); - $hook->param($key, $option['default'], $option['validator'], $option['description'], $option['optional'], $option['injections'], $option['skipValidation'], $option['deprecated'], $option['example'], aliases: $option['aliases'] ?? []); + $hook->param($key, $option['default'], $option['validator'], $option['description'], $option['optional'], $option['injections'], $option['skipValidation'], $option['deprecated'], $option['example'], aliases: $option['aliases'] ?? [], enum: $option['enum'] ?? null); break; case 'injection': $hook->inject($option['name']); @@ -222,7 +222,7 @@ protected function initWorker(array $services, string $workerName): void switch ($option['type']) { case 'param': $key = substr($key, stripos($key, ':') + 1); - $hook->param($key, $option['default'], $option['validator'], $option['description'], $option['optional'], $option['injections'], $option['skipValidation'], $option['deprecated'], $option['example'], aliases: $option['aliases'] ?? []); + $hook->param($key, $option['default'], $option['validator'], $option['description'], $option['optional'], $option['injections'], $option['skipValidation'], $option['deprecated'], $option['example'], aliases: $option['aliases'] ?? [], enum: $option['enum'] ?? null); break; case 'injection': $hook->inject($option['name']); diff --git a/tests/Platform/TestActionWithParams.php b/tests/Platform/TestActionWithParams.php index 34cea91..cf39707 100644 --- a/tests/Platform/TestActionWithParams.php +++ b/tests/Platform/TestActionWithParams.php @@ -3,9 +3,11 @@ namespace Utopia\Tests; use Utopia\Platform\Action; +use Utopia\Platform\Enum; use Utopia\Validator\Boolean; use Utopia\Validator\Range; use Utopia\Validator\Text; +use Utopia\Validator\WhiteList; class TestActionWithParams extends Action { @@ -19,8 +21,9 @@ public function __construct() ->param('age', 0, new Range(0, 150), 'User age.', true, example: '25') ->param('active', false, new Boolean(true), 'Is active.', true, deprecated: true, example: 'true') ->param('email', '', new Text(256), 'User email.', true, aliases: ['emailAddress', 'userEmail'], example: 'user@example.com') + ->param('status', 'draft', new WhiteList(['draft', 'published']), 'Status.', optional: true, enum: new Enum(name: 'ArticleStatus', map: ['draft' => 'Draft', 'published' => 'Published'])) ->inject('response') - ->callback(function ($name, $age, $active, $email, $response) { + ->callback(function ($name, $age, $active, $email, $status, $response) { $response->send('OK'); }); } diff --git a/tests/e2e/HTTPServicesTest.php b/tests/e2e/HTTPServicesTest.php index 82ad833..832b82b 100644 --- a/tests/e2e/HTTPServicesTest.php +++ b/tests/e2e/HTTPServicesTest.php @@ -167,5 +167,22 @@ public function testActionParamFieldsForwardedToRoute() // Verify params without aliases have empty array $this->assertArrayHasKey('aliases', $params['name'], 'Param name should have aliases key'); $this->assertEquals([], $params['name']['aliases']); + + // Verify enum is forwarded to Route params + $this->assertArrayHasKey('enum', $params['status'], 'Param status should have enum key on Route'); + $this->assertInstanceOf(\Utopia\Platform\Enum::class, $params['status']['enum']); + $this->assertEquals('ArticleStatus', $params['status']['enum']->name); + $this->assertEquals(['draft' => 'Draft', 'published' => 'Published'], $params['status']['enum']->map); + + // Verify enum is stored on Action params + $action = new TestActionWithParams(); + $actionParams = $action->getParams(); + + $this->assertInstanceOf(\Utopia\Platform\Enum::class, $actionParams['status']['enum']); + $this->assertEquals('ArticleStatus', $actionParams['status']['enum']->name); + $this->assertEquals(['draft' => 'Draft', 'published' => 'Published'], $actionParams['status']['enum']->map); + + // Verify params without enum are null on Action + $this->assertNull($actionParams['name']['enum']); } }