diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index cdca0c074..f8baedae9 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -33,7 +33,6 @@ jobs: code_coverage: name: Code Coverage runs-on: ubuntu-latest - continue-on-error: true steps: - uses: actions/checkout@v6 - uses: shivammathur/setup-php@v2 @@ -44,7 +43,8 @@ jobs: - run: composer install --no-progress --prefer-dist - run: composer tester -- -p phpdbg --coverage ./coverage.xml --coverage-src ./src - - run: wget https://github.com/php-coveralls/php-coveralls/releases/download/v2.4.3/php-coveralls.phar - - env: - COVERALLS_REPO_TOKEN: ${{ secrets.GITHUB_TOKEN }} - run: php php-coveralls.phar --verbose --config tests/.coveralls.yml + continue-on-error: true + - uses: coverallsapp/github-action@v2 + with: + file: coverage.xml + format: clover diff --git a/src/Utils/Validators.php b/src/Utils/Validators.php index b11d7e1e6..ef624eb98 100644 --- a/src/Utils/Validators.php +++ b/src/Utils/Validators.php @@ -137,7 +137,9 @@ public static function assertField( */ public static function is(mixed $value, string $expected): bool { - foreach (explode('|', $expected) as $item) { + $items = explode('|', $expected); + for ($i = 0, $count = count($items); $i < $count; $i++) { + $item = $items[$i]; if (str_ends_with($item, '[]')) { if (is_iterable($value) && self::everyIs($value, substr($item, 0, -2))) { return true; @@ -152,7 +154,16 @@ public static function is(mixed $value, string $expected): bool } [$type] = $item = explode(':', $item, 2); - if (isset(static::$validators[$type])) { + if ($type === 'pattern') { + // A pattern may contain pipes, so the rest belongs to it and must not be + // split into further validators. + $pattern = implode('|', array_merge(array_slice($item, 1), array_slice($items, $i + 1))); + if (Strings::match($value, '~^(?:' . $pattern . ')$~D')) { + return true; + } + + break; + } elseif (isset(static::$validators[$type])) { try { if (!static::$validators[$type]($value)) { continue; @@ -160,12 +171,6 @@ public static function is(mixed $value, string $expected): bool } catch (\TypeError) { continue; } - } elseif ($type === 'pattern') { - if (Strings::match($value, '|^' . ($item[1] ?? '') . '$|D')) { - return true; - } - - continue; } elseif (!$value instanceof $type) { continue; } diff --git a/tests/.coveralls.yml b/tests/.coveralls.yml deleted file mode 100644 index 845038250..000000000 --- a/tests/.coveralls.yml +++ /dev/null @@ -1,4 +0,0 @@ -# for php-coveralls -service_name: github-actions -coverage_clover: coverage.xml -json_path: coverage.json diff --git a/tests/Utils/Validators.is().phpt b/tests/Utils/Validators.is().phpt index c15eaf439..012c04246 100644 --- a/tests/Utils/Validators.is().phpt +++ b/tests/Utils/Validators.is().phpt @@ -259,6 +259,25 @@ test('validates string against a regular expression pattern', function () { }); +test('allows pipe inside a regular expression pattern', function () { + // grouped alternation, https://github.com/nette/utils/issues/206 + Assert::true(Validators::is('a', 'pattern:(a|b)')); + Assert::true(Validators::is('b', 'pattern:(a|b)')); + Assert::false(Validators::is('c', 'pattern:(a|b)')); + + // bare alternation must no longer throw and is anchored as a whole + Assert::true(Validators::is('a', 'pattern:a|b')); + Assert::true(Validators::is('b', 'pattern:a|b')); + Assert::false(Validators::is('c', 'pattern:a|b')); + Assert::false(Validators::is('ab', 'pattern:a|b')); + + // a pattern combined with other validators + Assert::true(Validators::is(5, 'int|pattern:(a|b)')); + Assert::true(Validators::is('a', 'int|pattern:(a|b)')); + Assert::false(Validators::is('c', 'int|pattern:(a|b)')); +}); + + test('ensures alphanumeric string meets minimum length', function () { Assert::false(Validators::is('', 'alnum')); Assert::false(Validators::is('a-1', 'alnum'));