From 923c182a1766d3f1cd585496cdb885c95ec31e96 Mon Sep 17 00:00:00 2001 From: Torsten Dittmann Date: Tue, 28 Feb 2023 18:04:43 +0530 Subject: [PATCH 01/10] feat: handle null value params --- src/App.php | 34 ++++++++++---------- src/Validator/Nullable.php | 65 ++++++++++++++++++++++++++++++++++++++ tests/AppTest.php | 18 +++++------ 3 files changed, 92 insertions(+), 25 deletions(-) create mode 100644 src/Validator/Nullable.php diff --git a/src/App.php b/src/App.php index 9b4a37ea..7e2a0d07 100755 --- a/src/App.php +++ b/src/App.php @@ -645,7 +645,15 @@ protected function getArguments(Hook $hook, array $values, array $requestParams) { $arguments = []; foreach ($hook->getParams() as $key => $param) { // Get value from route or request object - $arg = (isset($requestParams[$key])) ? $requestParams[$key] : $param['default']; + if (\array_key_exists($key, $requestParams)) { + $arg = $requestParams[$key]; + } else { + if (!$param['optional']) { + throw new Exception('Param "'.$key.'" is not optional.', 400); + } + $arg = $param['default']; + } + $value = (isset($values[$key])) ? $values[$key] : $arg; if ($hook instanceof Route) { @@ -654,8 +662,6 @@ protected function getArguments(Hook $hook, array $values, array $requestParams) } } - $value = ($value === '' || is_null($value)) ? $param['default'] : $value; - $this->validate($key, $param, $value); $hook->setParamValue($key, $value); $arguments[$param['order']] = $value; @@ -798,22 +804,18 @@ public function run(Request $request, Response $response): static */ protected function validate(string $key, array $param, mixed $value): void { - if ('' !== $value && ! is_null($value)) { - $validator = $param['validator']; // checking whether the class exists + $validator = $param['validator']; // checking whether the class exists - if (\is_callable($validator)) { - $validator = \call_user_func_array($validator, $this->getResources($param['injections'])); - } + if (\is_callable($validator)) { + $validator = \call_user_func_array($validator, $this->getResources($param['injections'])); + } - if (! $validator instanceof Validator) { // is the validator object an instance of the Validator class - throw new Exception('Validator object is not an instance of the Validator class', 500); - } + if (! $validator instanceof Validator) { // is the validator object an instance of the Validator class + throw new Exception('Validator object is not an instance of the Validator class', 500); + } - if (! $validator->isValid($value)) { - throw new Exception('Invalid '.$key.': '.$validator->getDescription(), 400); - } - } elseif (! $param['optional']) { - throw new Exception('Param "'.$key.'" is not optional.', 400); + if (! $validator->isValid($value)) { + throw new Exception('Invalid '.$key.': '.$validator->getDescription(), 400); } } diff --git a/src/Validator/Nullable.php b/src/Validator/Nullable.php new file mode 100644 index 00000000..1e787cf2 --- /dev/null +++ b/src/Validator/Nullable.php @@ -0,0 +1,65 @@ +validator->getDescription() . ' or null'; + } + + /** + * Is array + * + * Function will return true if object is array. + * + * @return bool + */ + public function isArray(): bool + { + return false; + } + + /** + * Get Type + * + * Returns validator type. + * + * @return string + */ + public function getType(): string + { + return $this->validator->getType(); + } + + /** + * Is valid + * + * Validation will pass when $value is text with valid length. + * + * @param mixed $value + * @return bool + */ + public function isValid(mixed $value): bool + { + if (\is_null($value)) { + return true; + } + + return $this->validator->isValid($value); + } +} diff --git a/tests/AppTest.php b/tests/AppTest.php index 21e47372..c09f6023 100755 --- a/tests/AppTest.php +++ b/tests/AppTest.php @@ -100,8 +100,8 @@ public function testCanGetResources(): void $route ->inject('rand') - ->param('x', 'x-def', new Text(200), 'x param', false) - ->param('y', 'y-def', new Text(200), 'y param', false) + ->param('x', 'x-def', new Text(200), 'x param', true) + ->param('y', 'y-def', new Text(200), 'y param', true) ->action(function ($x, $y, $rand) { echo $x.'-'.$y.'-'.$rand; }); @@ -144,8 +144,8 @@ public function testCanExecuteRoute(): void $route ->alias('/path1', ['x' => 'x-def-1', 'y' => 'y-def-1']) - ->param('x', 'x-def', new Text(200), 'x param', false) - ->param('y', 'y-def', new Text(200), 'y param', false) + ->param('x', 'x-def', new Text(200), 'x param', true) + ->param('y', 'y-def', new Text(200), 'y param', true) ->action(function ($x, $y) { echo $x.'-'.$y; }); @@ -178,7 +178,7 @@ public function testCanExecuteRoute(): void echo $rand.'-'; return new Text(200); - }, 'z param', false, ['rand']) + }, 'z param', true, ['rand']) ->action(function ($x, $y, $z, $rand) { echo $x.'-', $y; }); @@ -311,7 +311,7 @@ public function testCanAddAndExecuteHooks() // Default Params $route = new Route('GET', '/path'); $route - ->param('x', 'x-def', new Text(200), 'x param', false) + ->param('x', 'x-def', new Text(200), 'x param', true) ->action(function ($x) { echo $x; }); @@ -326,7 +326,7 @@ public function testCanAddAndExecuteHooks() // Default Params $route = new Route('GET', '/path'); $route - ->param('x', 'x-def', new Text(200), 'x param', false) + ->param('x', 'x-def', new Text(200), 'x param', true) ->hook(false) ->action(function ($x) { echo $x; @@ -365,7 +365,7 @@ public function testCanHookThrowExceptions() // param not provided for init $route = new Route('GET', '/path'); $route - ->param('x', 'x-def', new Text(200), 'x param', false) + ->param('x', 'x-def', new Text(200), 'x param', true) ->action(function ($x) { echo $x; }); @@ -547,7 +547,7 @@ public function testMultipleAliases(string $path, string $expected): void ->alias('/param2', [ 'param1' => 'param2', ]) - ->param('param1', '', new Text(100), 'a param', false) + ->param('param1', '', new Text(100), 'a param', true) ->inject('response') ->action(function ($param1, $response) { echo $param1; From a939c91d400ca3c30112b7a3ff0f4001db2f328e Mon Sep 17 00:00:00 2001 From: Torsten Dittmann Date: Tue, 28 Feb 2023 18:16:29 +0530 Subject: [PATCH 02/10] fix: stuff --- src/App.php | 8 +++----- tests/AppTest.php | 10 ++++++---- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/App.php b/src/App.php index 7e2a0d07..1c1ee203 100755 --- a/src/App.php +++ b/src/App.php @@ -645,16 +645,14 @@ protected function getArguments(Hook $hook, array $values, array $requestParams) { $arguments = []; foreach ($hook->getParams() as $key => $param) { // Get value from route or request object - if (\array_key_exists($key, $requestParams)) { - $arg = $requestParams[$key]; - } else { + if (!\array_key_exists($key, $requestParams) && !\array_key_exists($key, $values)) { if (!$param['optional']) { throw new Exception('Param "'.$key.'" is not optional.', 400); } - $arg = $param['default']; } - $value = (isset($values[$key])) ? $values[$key] : $arg; + $arg = (\array_key_exists($key, $requestParams)) ? $requestParams[$key] : $param['default']; + $value = (\array_key_exists($key, $values)) ? $values[$key] : $arg; if ($hook instanceof Route) { if ($hook->getIsAlias() && isset($hook->getAliasParams($hook->getAliasPath())[$key])) { diff --git a/tests/AppTest.php b/tests/AppTest.php index c09f6023..24c3bc3f 100755 --- a/tests/AppTest.php +++ b/tests/AppTest.php @@ -4,6 +4,7 @@ use PHPUnit\Framework\TestCase; use Utopia\Tests\UtopiaRequestTest; +use Utopia\Validator\Nullable; use Utopia\Validator\Text; class AppTest extends TestCase @@ -171,6 +172,7 @@ public function testCanExecuteRoute(): void $route = new Route('GET', '/path'); $route + ->param('a', 'a-def', new Nullable(new Text(200)), 'a param', false) ->param('x', 'x-def', new Text(200), 'x param', false) ->param('y', 'y-def', new Text(200), 'y param', false) ->inject('rand') @@ -179,13 +181,13 @@ public function testCanExecuteRoute(): void return new Text(200); }, 'z param', true, ['rand']) - ->action(function ($x, $y, $z, $rand) { - echo $x.'-', $y; + ->action(function ($a, $x, $y, $z, $rand) { + echo (\is_null($a) ? '' : 'fail') . $x.'-', $y; }); \ob_start(); $request = new UtopiaRequestTest(); - $request::_setParams(['x' => 'param-x', 'y' => 'param-y']); + $request::_setParams(['x' => 'param-x', 'y' => 'param-y', 'a' => null]); $this->app->execute($route, $request); $result = \ob_get_contents(); \ob_end_clean(); @@ -547,7 +549,7 @@ public function testMultipleAliases(string $path, string $expected): void ->alias('/param2', [ 'param1' => 'param2', ]) - ->param('param1', '', new Text(100), 'a param', true) + ->param('param1', '', new Text(100), 'a param', false) ->inject('response') ->action(function ($param1, $response) { echo $param1; From bb9365308f3b1d07e0ab65c53bf0f59b671ad36c Mon Sep 17 00:00:00 2001 From: Torsten Dittmann Date: Tue, 28 Feb 2023 18:28:22 +0530 Subject: [PATCH 03/10] fix: nullable optional param --- src/App.php | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/App.php b/src/App.php index 1c1ee203..f87c6161 100755 --- a/src/App.php +++ b/src/App.php @@ -802,17 +802,21 @@ public function run(Request $request, Response $response): static */ protected function validate(string $key, array $param, mixed $value): void { + if ($param['optional'] && \is_null($value)) { + return; + } + $validator = $param['validator']; // checking whether the class exists if (\is_callable($validator)) { $validator = \call_user_func_array($validator, $this->getResources($param['injections'])); } - if (! $validator instanceof Validator) { // is the validator object an instance of the Validator class + if (!$validator instanceof Validator) { // is the validator object an instance of the Validator class throw new Exception('Validator object is not an instance of the Validator class', 500); } - if (! $validator->isValid($value)) { + if (!$validator->isValid($value)) { throw new Exception('Invalid '.$key.': '.$validator->getDescription(), 400); } } From bb0a26cd798de9a27fd973101b601dbead059c2e Mon Sep 17 00:00:00 2001 From: Torsten Dittmann Date: Tue, 28 Feb 2023 19:26:23 +0530 Subject: [PATCH 04/10] cho --- composer.lock | 197 +++++++++++++++++++++++++------------------------- 1 file changed, 99 insertions(+), 98 deletions(-) diff --git a/composer.lock b/composer.lock index e51069a1..a2e54ba4 100644 --- a/composer.lock +++ b/composer.lock @@ -503,30 +503,30 @@ }, { "name": "doctrine/instantiator", - "version": "1.4.1", + "version": "2.0.0", "source": { "type": "git", "url": "https://github.com/doctrine/instantiator.git", - "reference": "10dcfce151b967d20fde1b34ae6640712c3891bc" + "reference": "c6222283fa3f4ac679f8b9ced9a4e23f163e80d0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/instantiator/zipball/10dcfce151b967d20fde1b34ae6640712c3891bc", - "reference": "10dcfce151b967d20fde1b34ae6640712c3891bc", + "url": "https://api.github.com/repos/doctrine/instantiator/zipball/c6222283fa3f4ac679f8b9ced9a4e23f163e80d0", + "reference": "c6222283fa3f4ac679f8b9ced9a4e23f163e80d0", "shasum": "" }, "require": { - "php": "^7.1 || ^8.0" + "php": "^8.1" }, "require-dev": { - "doctrine/coding-standard": "^9", + "doctrine/coding-standard": "^11", "ext-pdo": "*", "ext-phar": "*", - "phpbench/phpbench": "^0.16 || ^1", - "phpstan/phpstan": "^1.4", - "phpstan/phpstan-phpunit": "^1", - "phpunit/phpunit": "^7.5 || ^8.5 || ^9.5", - "vimeo/psalm": "^4.22" + "phpbench/phpbench": "^1.2", + "phpstan/phpstan": "^1.9.4", + "phpstan/phpstan-phpunit": "^1.3", + "phpunit/phpunit": "^9.5.27", + "vimeo/psalm": "^5.4" }, "type": "library", "autoload": { @@ -553,7 +553,7 @@ ], "support": { "issues": "https://github.com/doctrine/instantiator/issues", - "source": "https://github.com/doctrine/instantiator/tree/1.4.1" + "source": "https://github.com/doctrine/instantiator/tree/2.0.0" }, "funding": [ { @@ -569,7 +569,7 @@ "type": "tidelift" } ], - "time": "2022-03-03T08:28:38+00:00" + "time": "2022-12-30T00:23:10+00:00" }, { "name": "felixfbecker/advanced-json-rpc", @@ -674,16 +674,16 @@ }, { "name": "laravel/pint", - "version": "v1.2.0", + "version": "v1.6.0", "source": { "type": "git", "url": "https://github.com/laravel/pint.git", - "reference": "1d276e4c803397a26cc337df908f55c2a4e90d86" + "reference": "e48e3fadd7863d6b7d03464f5c4f211a828b890f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/pint/zipball/1d276e4c803397a26cc337df908f55c2a4e90d86", - "reference": "1d276e4c803397a26cc337df908f55c2a4e90d86", + "url": "https://api.github.com/repos/laravel/pint/zipball/e48e3fadd7863d6b7d03464f5c4f211a828b890f", + "reference": "e48e3fadd7863d6b7d03464f5c4f211a828b890f", "shasum": "" }, "require": { @@ -691,16 +691,16 @@ "ext-mbstring": "*", "ext-tokenizer": "*", "ext-xml": "*", - "php": "^8.0" + "php": "^8.1.0" }, "require-dev": { - "friendsofphp/php-cs-fixer": "^3.11.0", - "illuminate/view": "^9.27", - "laravel-zero/framework": "^9.1.3", - "mockery/mockery": "^1.5.0", - "nunomaduro/larastan": "^2.2", - "nunomaduro/termwind": "^1.14.0", - "pestphp/pest": "^1.22.1" + "friendsofphp/php-cs-fixer": "^3.14.4", + "illuminate/view": "^10.0.0", + "laravel-zero/framework": "^10.0.0", + "mockery/mockery": "^1.5.1", + "nunomaduro/larastan": "^2.4.0", + "nunomaduro/termwind": "^1.15.1", + "pestphp/pest": "^1.22.4" }, "bin": [ "builds/pint" @@ -736,7 +736,7 @@ "issues": "https://github.com/laravel/pint/issues", "source": "https://github.com/laravel/pint" }, - "time": "2022-09-13T15:07:15+00:00" + "time": "2023-02-21T15:44:57+00:00" }, { "name": "myclabs/deep-copy", @@ -799,16 +799,16 @@ }, { "name": "netresearch/jsonmapper", - "version": "v4.0.0", + "version": "v4.1.0", "source": { "type": "git", "url": "https://github.com/cweiske/jsonmapper.git", - "reference": "8bbc021a8edb2e4a7ea2f8ad4fa9ec9dce2fcb8d" + "reference": "cfa81ea1d35294d64adb9c68aa4cb9e92400e53f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/cweiske/jsonmapper/zipball/8bbc021a8edb2e4a7ea2f8ad4fa9ec9dce2fcb8d", - "reference": "8bbc021a8edb2e4a7ea2f8ad4fa9ec9dce2fcb8d", + "url": "https://api.github.com/repos/cweiske/jsonmapper/zipball/cfa81ea1d35294d64adb9c68aa4cb9e92400e53f", + "reference": "cfa81ea1d35294d64adb9c68aa4cb9e92400e53f", "shasum": "" }, "require": { @@ -844,22 +844,22 @@ "support": { "email": "cweiske@cweiske.de", "issues": "https://github.com/cweiske/jsonmapper/issues", - "source": "https://github.com/cweiske/jsonmapper/tree/v4.0.0" + "source": "https://github.com/cweiske/jsonmapper/tree/v4.1.0" }, - "time": "2020-12-01T19:48:11+00:00" + "time": "2022-12-08T20:46:14+00:00" }, { "name": "nikic/php-parser", - "version": "v4.15.2", + "version": "v4.15.3", "source": { "type": "git", "url": "https://github.com/nikic/PHP-Parser.git", - "reference": "f59bbe44bf7d96f24f3e2b4ddc21cd52c1d2adbc" + "reference": "570e980a201d8ed0236b0a62ddf2c9cbb2034039" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/f59bbe44bf7d96f24f3e2b4ddc21cd52c1d2adbc", - "reference": "f59bbe44bf7d96f24f3e2b4ddc21cd52c1d2adbc", + "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/570e980a201d8ed0236b0a62ddf2c9cbb2034039", + "reference": "570e980a201d8ed0236b0a62ddf2c9cbb2034039", "shasum": "" }, "require": { @@ -900,9 +900,9 @@ ], "support": { "issues": "https://github.com/nikic/PHP-Parser/issues", - "source": "https://github.com/nikic/PHP-Parser/tree/v4.15.2" + "source": "https://github.com/nikic/PHP-Parser/tree/v4.15.3" }, - "time": "2022-11-12T15:38:23+00:00" + "time": "2023-01-16T22:05:37+00:00" }, { "name": "openlss/lib-array2xml", @@ -1235,23 +1235,23 @@ }, { "name": "phpunit/php-code-coverage", - "version": "9.2.19", + "version": "9.2.25", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-code-coverage.git", - "reference": "c77b56b63e3d2031bd8997fcec43c1925ae46559" + "reference": "0e2b40518197a8c0d4b08bc34dfff1c99c508954" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/c77b56b63e3d2031bd8997fcec43c1925ae46559", - "reference": "c77b56b63e3d2031bd8997fcec43c1925ae46559", + "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/0e2b40518197a8c0d4b08bc34dfff1c99c508954", + "reference": "0e2b40518197a8c0d4b08bc34dfff1c99c508954", "shasum": "" }, "require": { "ext-dom": "*", "ext-libxml": "*", "ext-xmlwriter": "*", - "nikic/php-parser": "^4.14", + "nikic/php-parser": "^4.15", "php": ">=7.3", "phpunit/php-file-iterator": "^3.0.3", "phpunit/php-text-template": "^2.0.2", @@ -1300,7 +1300,7 @@ ], "support": { "issues": "https://github.com/sebastianbergmann/php-code-coverage/issues", - "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/9.2.19" + "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/9.2.25" }, "funding": [ { @@ -1308,7 +1308,7 @@ "type": "github" } ], - "time": "2022-11-18T07:47:47+00:00" + "time": "2023-02-25T05:32:00+00:00" }, { "name": "phpunit/php-file-iterator", @@ -1553,20 +1553,20 @@ }, { "name": "phpunit/phpunit", - "version": "9.5.26", + "version": "9.6.4", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "851867efcbb6a1b992ec515c71cdcf20d895e9d2" + "reference": "9125ee085b6d95e78277dc07aa1f46f9e0607b8d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/851867efcbb6a1b992ec515c71cdcf20d895e9d2", - "reference": "851867efcbb6a1b992ec515c71cdcf20d895e9d2", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/9125ee085b6d95e78277dc07aa1f46f9e0607b8d", + "reference": "9125ee085b6d95e78277dc07aa1f46f9e0607b8d", "shasum": "" }, "require": { - "doctrine/instantiator": "^1.3.1", + "doctrine/instantiator": "^1.3.1 || ^2", "ext-dom": "*", "ext-json": "*", "ext-libxml": "*", @@ -1604,7 +1604,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "9.5-dev" + "dev-master": "9.6-dev" } }, "autoload": { @@ -1635,7 +1635,7 @@ ], "support": { "issues": "https://github.com/sebastianbergmann/phpunit/issues", - "source": "https://github.com/sebastianbergmann/phpunit/tree/9.5.26" + "source": "https://github.com/sebastianbergmann/phpunit/tree/9.6.4" }, "funding": [ { @@ -1651,7 +1651,7 @@ "type": "tidelift" } ], - "time": "2022-10-28T06:00:21+00:00" + "time": "2023-02-27T13:06:37+00:00" }, { "name": "psr/container", @@ -2122,16 +2122,16 @@ }, { "name": "sebastian/environment", - "version": "5.1.4", + "version": "5.1.5", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/environment.git", - "reference": "1b5dff7bb151a4db11d49d90e5408e4e938270f7" + "reference": "830c43a844f1f8d5b7a1f6d6076b784454d8b7ed" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/1b5dff7bb151a4db11d49d90e5408e4e938270f7", - "reference": "1b5dff7bb151a4db11d49d90e5408e4e938270f7", + "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/830c43a844f1f8d5b7a1f6d6076b784454d8b7ed", + "reference": "830c43a844f1f8d5b7a1f6d6076b784454d8b7ed", "shasum": "" }, "require": { @@ -2173,7 +2173,7 @@ ], "support": { "issues": "https://github.com/sebastianbergmann/environment/issues", - "source": "https://github.com/sebastianbergmann/environment/tree/5.1.4" + "source": "https://github.com/sebastianbergmann/environment/tree/5.1.5" }, "funding": [ { @@ -2181,7 +2181,7 @@ "type": "github" } ], - "time": "2022-04-03T09:37:03+00:00" + "time": "2023-02-03T06:03:51+00:00" }, { "name": "sebastian/exporter", @@ -2495,16 +2495,16 @@ }, { "name": "sebastian/recursion-context", - "version": "4.0.4", + "version": "4.0.5", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/recursion-context.git", - "reference": "cd9d8cf3c5804de4341c283ed787f099f5506172" + "reference": "e75bd0f07204fec2a0af9b0f3cfe97d05f92efc1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/cd9d8cf3c5804de4341c283ed787f099f5506172", - "reference": "cd9d8cf3c5804de4341c283ed787f099f5506172", + "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/e75bd0f07204fec2a0af9b0f3cfe97d05f92efc1", + "reference": "e75bd0f07204fec2a0af9b0f3cfe97d05f92efc1", "shasum": "" }, "require": { @@ -2543,10 +2543,10 @@ } ], "description": "Provides functionality to recursively process PHP variables", - "homepage": "http://www.github.com/sebastianbergmann/recursion-context", + "homepage": "https://github.com/sebastianbergmann/recursion-context", "support": { "issues": "https://github.com/sebastianbergmann/recursion-context/issues", - "source": "https://github.com/sebastianbergmann/recursion-context/tree/4.0.4" + "source": "https://github.com/sebastianbergmann/recursion-context/tree/4.0.5" }, "funding": [ { @@ -2554,7 +2554,7 @@ "type": "github" } ], - "time": "2020-10-26T13:17:30+00:00" + "time": "2023-02-03T06:07:39+00:00" }, { "name": "sebastian/resource-operations", @@ -2613,16 +2613,16 @@ }, { "name": "sebastian/type", - "version": "3.2.0", + "version": "3.2.1", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/type.git", - "reference": "fb3fe09c5f0bae6bc27ef3ce933a1e0ed9464b6e" + "reference": "75e2c2a32f5e0b3aef905b9ed0b179b953b3d7c7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/type/zipball/fb3fe09c5f0bae6bc27ef3ce933a1e0ed9464b6e", - "reference": "fb3fe09c5f0bae6bc27ef3ce933a1e0ed9464b6e", + "url": "https://api.github.com/repos/sebastianbergmann/type/zipball/75e2c2a32f5e0b3aef905b9ed0b179b953b3d7c7", + "reference": "75e2c2a32f5e0b3aef905b9ed0b179b953b3d7c7", "shasum": "" }, "require": { @@ -2657,7 +2657,7 @@ "homepage": "https://github.com/sebastianbergmann/type", "support": { "issues": "https://github.com/sebastianbergmann/type/issues", - "source": "https://github.com/sebastianbergmann/type/tree/3.2.0" + "source": "https://github.com/sebastianbergmann/type/tree/3.2.1" }, "funding": [ { @@ -2665,7 +2665,7 @@ "type": "github" } ], - "time": "2022-09-12T14:47:03+00:00" + "time": "2023-02-03T06:13:03+00:00" }, { "name": "sebastian/version", @@ -2722,16 +2722,16 @@ }, { "name": "symfony/console", - "version": "v6.1.7", + "version": "v6.2.5", "source": { "type": "git", "url": "https://github.com/symfony/console.git", - "reference": "a1282bd0c096e0bdb8800b104177e2ce404d8815" + "reference": "3e294254f2191762c1d137aed4b94e966965e985" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/console/zipball/a1282bd0c096e0bdb8800b104177e2ce404d8815", - "reference": "a1282bd0c096e0bdb8800b104177e2ce404d8815", + "url": "https://api.github.com/repos/symfony/console/zipball/3e294254f2191762c1d137aed4b94e966965e985", + "reference": "3e294254f2191762c1d137aed4b94e966965e985", "shasum": "" }, "require": { @@ -2798,7 +2798,7 @@ "terminal" ], "support": { - "source": "https://github.com/symfony/console/tree/v6.1.7" + "source": "https://github.com/symfony/console/tree/v6.2.5" }, "funding": [ { @@ -2814,20 +2814,20 @@ "type": "tidelift" } ], - "time": "2022-10-26T21:42:49+00:00" + "time": "2023-01-01T08:38:09+00:00" }, { "name": "symfony/deprecation-contracts", - "version": "v3.1.1", + "version": "v3.2.0", "source": { "type": "git", "url": "https://github.com/symfony/deprecation-contracts.git", - "reference": "07f1b9cc2ffee6aaafcf4b710fbc38ff736bd918" + "reference": "1ee04c65529dea5d8744774d474e7cbd2f1206d3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/07f1b9cc2ffee6aaafcf4b710fbc38ff736bd918", - "reference": "07f1b9cc2ffee6aaafcf4b710fbc38ff736bd918", + "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/1ee04c65529dea5d8744774d474e7cbd2f1206d3", + "reference": "1ee04c65529dea5d8744774d474e7cbd2f1206d3", "shasum": "" }, "require": { @@ -2836,7 +2836,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "3.1-dev" + "dev-main": "3.3-dev" }, "thanks": { "name": "symfony/contracts", @@ -2865,7 +2865,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.1.1" + "source": "https://github.com/symfony/deprecation-contracts/tree/v3.2.0" }, "funding": [ { @@ -2881,7 +2881,7 @@ "type": "tidelift" } ], - "time": "2022-02-25T11:15:52+00:00" + "time": "2022-11-25T10:21:52+00:00" }, { "name": "symfony/polyfill-ctype", @@ -3298,16 +3298,16 @@ }, { "name": "symfony/service-contracts", - "version": "v3.1.1", + "version": "v3.2.0", "source": { "type": "git", "url": "https://github.com/symfony/service-contracts.git", - "reference": "925e713fe8fcacf6bc05e936edd8dd5441a21239" + "reference": "aac98028c69df04ee77eb69b96b86ee51fbf4b75" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/service-contracts/zipball/925e713fe8fcacf6bc05e936edd8dd5441a21239", - "reference": "925e713fe8fcacf6bc05e936edd8dd5441a21239", + "url": "https://api.github.com/repos/symfony/service-contracts/zipball/aac98028c69df04ee77eb69b96b86ee51fbf4b75", + "reference": "aac98028c69df04ee77eb69b96b86ee51fbf4b75", "shasum": "" }, "require": { @@ -3323,7 +3323,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "3.1-dev" + "dev-main": "3.3-dev" }, "thanks": { "name": "symfony/contracts", @@ -3363,7 +3363,7 @@ "standards" ], "support": { - "source": "https://github.com/symfony/service-contracts/tree/v3.1.1" + "source": "https://github.com/symfony/service-contracts/tree/v3.2.0" }, "funding": [ { @@ -3379,20 +3379,20 @@ "type": "tidelift" } ], - "time": "2022-05-30T19:18:58+00:00" + "time": "2022-11-25T10:21:52+00:00" }, { "name": "symfony/string", - "version": "v6.1.7", + "version": "v6.2.5", "source": { "type": "git", "url": "https://github.com/symfony/string.git", - "reference": "823f143370880efcbdfa2dbca946b3358c4707e5" + "reference": "b2dac0fa27b1ac0f9c0c0b23b43977f12308d0b0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/string/zipball/823f143370880efcbdfa2dbca946b3358c4707e5", - "reference": "823f143370880efcbdfa2dbca946b3358c4707e5", + "url": "https://api.github.com/repos/symfony/string/zipball/b2dac0fa27b1ac0f9c0c0b23b43977f12308d0b0", + "reference": "b2dac0fa27b1ac0f9c0c0b23b43977f12308d0b0", "shasum": "" }, "require": { @@ -3408,6 +3408,7 @@ "require-dev": { "symfony/error-handler": "^5.4|^6.0", "symfony/http-client": "^5.4|^6.0", + "symfony/intl": "^6.2", "symfony/translation-contracts": "^2.0|^3.0", "symfony/var-exporter": "^5.4|^6.0" }, @@ -3448,7 +3449,7 @@ "utf8" ], "support": { - "source": "https://github.com/symfony/string/tree/v6.1.7" + "source": "https://github.com/symfony/string/tree/v6.2.5" }, "funding": [ { @@ -3464,7 +3465,7 @@ "type": "tidelift" } ], - "time": "2022-10-10T09:34:31+00:00" + "time": "2023-01-01T08:38:09+00:00" }, { "name": "theseer/tokenizer", From d8d1f68c712ad30b754a1f40a922cf994d64b7d5 Mon Sep 17 00:00:00 2001 From: Torsten Dittmann Date: Wed, 1 Mar 2023 01:49:49 +0530 Subject: [PATCH 05/10] fix: all tests --- src/App.php | 16 ++++++++++------ tests/AppTest.php | 14 ++++++-------- 2 files changed, 16 insertions(+), 14 deletions(-) diff --git a/src/App.php b/src/App.php index f87c6161..3a2c2e9e 100755 --- a/src/App.php +++ b/src/App.php @@ -645,11 +645,7 @@ protected function getArguments(Hook $hook, array $values, array $requestParams) { $arguments = []; foreach ($hook->getParams() as $key => $param) { // Get value from route or request object - if (!\array_key_exists($key, $requestParams) && !\array_key_exists($key, $values)) { - if (!$param['optional']) { - throw new Exception('Param "'.$key.'" is not optional.', 400); - } - } + $paramExists = \array_key_exists($key, $requestParams) || \array_key_exists($key, $values); $arg = (\array_key_exists($key, $requestParams)) ? $requestParams[$key] : $param['default']; $value = (\array_key_exists($key, $values)) ? $values[$key] : $arg; @@ -657,10 +653,18 @@ protected function getArguments(Hook $hook, array $values, array $requestParams) if ($hook instanceof Route) { if ($hook->getIsAlias() && isset($hook->getAliasParams($hook->getAliasPath())[$key])) { $value = $hook->getAliasParams($hook->getAliasPath())[$key]; + $paramExists = true; } } - $this->validate($key, $param, $value); + if (!$paramExists && !$param['optional']) { + throw new Exception('Param "'.$key.'" is not optional.', 400); + } + + if ($paramExists) { + $this->validate($key, $param, $value); + } + $hook->setParamValue($key, $value); $arguments[$param['order']] = $value; } diff --git a/tests/AppTest.php b/tests/AppTest.php index 24c3bc3f..369e0916 100755 --- a/tests/AppTest.php +++ b/tests/AppTest.php @@ -4,7 +4,6 @@ use PHPUnit\Framework\TestCase; use Utopia\Tests\UtopiaRequestTest; -use Utopia\Validator\Nullable; use Utopia\Validator\Text; class AppTest extends TestCase @@ -172,22 +171,21 @@ public function testCanExecuteRoute(): void $route = new Route('GET', '/path'); $route - ->param('a', 'a-def', new Nullable(new Text(200)), 'a param', false) - ->param('x', 'x-def', new Text(200), 'x param', false) - ->param('y', 'y-def', new Text(200), 'y param', false) + ->param('x', 'x-def', new Text(200), 'x param', true) + ->param('y', 'y-def', new Text(200), 'y param', true) ->inject('rand') ->param('z', 'z-def', function ($rand) { echo $rand.'-'; return new Text(200); }, 'z param', true, ['rand']) - ->action(function ($a, $x, $y, $z, $rand) { - echo (\is_null($a) ? '' : 'fail') . $x.'-', $y; + ->action(function ($x, $y, $z, $rand) { + echo $x.'-', $y; }); \ob_start(); $request = new UtopiaRequestTest(); - $request::_setParams(['x' => 'param-x', 'y' => 'param-y', 'a' => null]); + $request::_setParams(['x' => 'param-x', 'y' => 'param-y', 'z' => 'param-z']); $this->app->execute($route, $request); $result = \ob_get_contents(); \ob_end_clean(); @@ -593,4 +591,4 @@ public function testWildcardRoute(): void $this->assertEquals('HELLO', $result); } -} +} \ No newline at end of file From 089f49856358f06a2c958d9bfcf2ce4dbda27eb8 Mon Sep 17 00:00:00 2001 From: Torsten Dittmann Date: Wed, 1 Mar 2023 02:03:14 +0530 Subject: [PATCH 06/10] fix: improved performance --- src/App.php | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/App.php b/src/App.php index 3a2c2e9e..40006d0b 100755 --- a/src/App.php +++ b/src/App.php @@ -645,10 +645,12 @@ protected function getArguments(Hook $hook, array $values, array $requestParams) { $arguments = []; foreach ($hook->getParams() as $key => $param) { // Get value from route or request object - $paramExists = \array_key_exists($key, $requestParams) || \array_key_exists($key, $values); + $existsInRequest = \array_key_exists($key, $requestParams); + $existsInValues = \array_key_exists($key, $values); + $paramExists = $existsInRequest || $existsInValues; - $arg = (\array_key_exists($key, $requestParams)) ? $requestParams[$key] : $param['default']; - $value = (\array_key_exists($key, $values)) ? $values[$key] : $arg; + $arg = $existsInRequest ? $requestParams[$key] : $param['default']; + $value = $existsInValues ? $values[$key] : $arg; if ($hook instanceof Route) { if ($hook->getIsAlias() && isset($hook->getAliasParams($hook->getAliasPath())[$key])) { From 27d4327b23b4d9680eb77128bc578a2491f4514c Mon Sep 17 00:00:00 2001 From: Torsten Dittmann Date: Wed, 1 Mar 2023 02:41:59 +0530 Subject: [PATCH 07/10] draft: add option to skip validation --- src/App.php | 12 +++++++----- src/Hook.php | 4 +++- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/src/App.php b/src/App.php index 40006d0b..5829a497 100755 --- a/src/App.php +++ b/src/App.php @@ -659,12 +659,14 @@ protected function getArguments(Hook $hook, array $values, array $requestParams) } } - if (!$paramExists && !$param['optional']) { - throw new Exception('Param "'.$key.'" is not optional.', 400); - } + if (!$param['skipValidation']) { + if (!$paramExists && !$param['optional']) { + throw new Exception('Param "'.$key.'" is not optional.', 400); + } - if ($paramExists) { - $this->validate($key, $param, $value); + if ($paramExists) { + $this->validate($key, $param, $value); + } } $hook->setParamValue($key, $value); diff --git a/src/Hook.php b/src/Hook.php index 528f8799..d59942d1 100644 --- a/src/Hook.php +++ b/src/Hook.php @@ -196,9 +196,10 @@ public function inject(string $injection): static * @param string $description * @param bool $optional * @param array $injections + * @param bool $skipValidation * @return static */ - public function param(string $key, mixed $default, Validator|callable $validator, string $description = '', bool $optional = false, array $injections = []): static + public function param(string $key, mixed $default, Validator|callable $validator, string $description = '', bool $optional = false, array $injections = [], bool $skipValidation = false): static { $this->params[$key] = [ 'default' => $default, @@ -206,6 +207,7 @@ public function param(string $key, mixed $default, Validator|callable $validator 'description' => $description, 'optional' => $optional, 'injections' => $injections, + 'skipValidation' => $skipValidation, 'value' => null, 'order' => count($this->params) + count($this->injections), ]; From fbfad97d6304e25d479df67851ebf2d14e8f8933 Mon Sep 17 00:00:00 2001 From: Torsten Dittmann Date: Wed, 1 Mar 2023 13:24:44 +0530 Subject: [PATCH 08/10] fix: make change backwards compatible --- src/App.php | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/App.php b/src/App.php index 5829a497..ab174f1a 100755 --- a/src/App.php +++ b/src/App.php @@ -660,13 +660,16 @@ protected function getArguments(Hook $hook, array $values, array $requestParams) } if (!$param['skipValidation']) { - if (!$paramExists && !$param['optional']) { - throw new Exception('Param "'.$key.'" is not optional.', 400); + if ( + (!$paramExists && !$param['optional']) + || $value === '' // this condition is added for backwards compatibility + ) { + throw new Exception('Param "' . $key . '" is not optional.', 400); } if ($paramExists) { $this->validate($key, $param, $value); - } + } } $hook->setParamValue($key, $value); From e1fdeee78dcf3c5711d4e9f7836ca240a6209c7c Mon Sep 17 00:00:00 2001 From: Torsten Dittmann Date: Wed, 1 Mar 2023 14:03:06 +0530 Subject: [PATCH 09/10] feat: text validator with min lnegth --- src/App.php | 5 +---- src/Validator/Text.php | 31 +++++++++++++++++++++++++------ tests/AppTest.php | 4 ++-- tests/Validator/TextTest.php | 17 +++++++++++++---- 4 files changed, 41 insertions(+), 16 deletions(-) diff --git a/src/App.php b/src/App.php index ab174f1a..244a0f47 100755 --- a/src/App.php +++ b/src/App.php @@ -660,10 +660,7 @@ protected function getArguments(Hook $hook, array $values, array $requestParams) } if (!$param['skipValidation']) { - if ( - (!$paramExists && !$param['optional']) - || $value === '' // this condition is added for backwards compatibility - ) { + if (!$paramExists && !$param['optional']) { throw new Exception('Param "' . $key . '" is not optional.', 400); } diff --git a/src/Validator/Text.php b/src/Validator/Text.php index 2fd828b6..5fb8e9f7 100644 --- a/src/Validator/Text.php +++ b/src/Validator/Text.php @@ -20,12 +20,17 @@ class Text extends Validator /** * @var int */ - protected int $length = 0; + protected int $length; + + /** + * @var int + */ + protected int $min; /** * @var string[] */ - protected array $allowList = []; + protected array $allowList; /** * Text constructor. @@ -34,11 +39,13 @@ class Text extends Validator * Optionally, provide allowList characters array $allowList to only allow specific character. * * @param int $length + * @param int $min * @param string[] $allowList */ - public function __construct(int $length, array $allowList = []) + public function __construct(int $length, int $min = 1, array $allowList = []) { $this->length = $length; + $this->min = $min; $this->allowList = $allowList; } @@ -53,8 +60,16 @@ public function getDescription(): string { $message = 'Value must be a valid string'; - if ($this->length) { - $message .= ' and no longer than '.$this->length.' chars'; + if ($this->min === $this->length) { + $message .= ' and exactly '.$this->length.' chars'; + } else { + if ($this->min) { + $message .= ' and at least '.$this->length.' chars'; + } + + if ($this->length) { + $message .= ' and no longer than '.$this->length.' chars'; + } } if ($this->allowList) { @@ -98,7 +113,11 @@ public function getType(): string */ public function isValid(mixed $value): bool { - if (! \is_string($value)) { + if (!\is_string($value)) { + return false; + } + + if (\mb_strlen($value) < $this->min) { return false; } diff --git a/tests/AppTest.php b/tests/AppTest.php index 369e0916..79bc9b91 100755 --- a/tests/AppTest.php +++ b/tests/AppTest.php @@ -197,8 +197,8 @@ public function testCanExecuteRoute(): void $route = new Route('GET', '/path'); $route - ->param('x', 'x-def', new Text(1), 'x param', false) - ->param('y', 'y-def', new Text(1), 'y param', false) + ->param('x', 'x-def', new Text(1, min: 0), 'x param', false) + ->param('y', 'y-def', new Text(1, min: 0), 'y param', false) ->action(function ($x, $y) { echo $x.'-', $y; }); diff --git a/tests/Validator/TextTest.php b/tests/Validator/TextTest.php index 3ec62ba6..cb987043 100755 --- a/tests/Validator/TextTest.php +++ b/tests/Validator/TextTest.php @@ -26,12 +26,21 @@ public function testCanValidateBoundaries(): void $this->assertTrue($validator->isValid('hell')); $this->assertTrue($validator->isValid('hello')); $this->assertFalse($validator->isValid('hellow')); + $this->assertFalse($validator->isValid('')); + + $validator = new Text(5, 3); + $this->assertTrue($validator->isValid('hel')); + $this->assertTrue($validator->isValid('hell')); + $this->assertTrue($validator->isValid('hello')); + $this->assertFalse($validator->isValid('hellow')); + $this->assertFalse($validator->isValid('he')); + $this->assertFalse($validator->isValid('h')); } public function testCanValidateTextWithAllowList(): void { // Test lowercase alphabet - $validator = new Text(100, Text::ALPHABET_LOWER); + $validator = new Text(100, allowList: Text::ALPHABET_LOWER); $this->assertFalse($validator->isArray()); $this->assertTrue($validator->isValid('qwertzuiopasdfghjklyxcvbnm')); $this->assertTrue($validator->isValid('hello')); @@ -42,7 +51,7 @@ public function testCanValidateTextWithAllowList(): void $this->assertFalse($validator->isValid('hello123')); // Test uppercase alphabet - $validator = new Text(100, Text::ALPHABET_UPPER); + $validator = new Text(100, allowList: Text::ALPHABET_UPPER); $this->assertFalse($validator->isArray()); $this->assertTrue($validator->isValid('QWERTZUIOPASDFGHJKLYXCVBNM')); $this->assertTrue($validator->isValid('HELLO')); @@ -53,7 +62,7 @@ public function testCanValidateTextWithAllowList(): void $this->assertFalse($validator->isValid('HELLO123')); // Test numbers - $validator = new Text(100, Text::NUMBERS); + $validator = new Text(100, allowList: Text::NUMBERS); $this->assertFalse($validator->isArray()); $this->assertTrue($validator->isValid('1234567890')); $this->assertTrue($validator->isValid('123')); @@ -61,7 +70,7 @@ public function testCanValidateTextWithAllowList(): void $this->assertFalse($validator->isValid('hello123')); // Test combination of allowLists - $validator = new Text(100, [ + $validator = new Text(100, allowList: [ ...Text::ALPHABET_LOWER, ...Text::ALPHABET_UPPER, ...Text::NUMBERS, From ba66e5abec744bfbaace765a53c939f180b44ed8 Mon Sep 17 00:00:00 2001 From: Torsten Dittmann Date: Wed, 1 Mar 2023 14:19:10 +0530 Subject: [PATCH 10/10] fix: typo --- src/Validator/Text.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Validator/Text.php b/src/Validator/Text.php index 5fb8e9f7..18e6b5ea 100644 --- a/src/Validator/Text.php +++ b/src/Validator/Text.php @@ -64,7 +64,7 @@ public function getDescription(): string $message .= ' and exactly '.$this->length.' chars'; } else { if ($this->min) { - $message .= ' and at least '.$this->length.' chars'; + $message .= ' and at least '.$this->min.' chars'; } if ($this->length) {