Skip to content

Commit 0cdb94f

Browse files
ellisglNaktibalda
authored andcommitted
JsonType: Allow to use : in regex filter (#5273)
* ISSUE-2501 Adding additional tests for JsonType->match:regex. * ISSUE-2501 Fixes regex filter handling. * ISSUE-2501 Styling. * ISSUE-2501 Styling. * ISSUE-2501 Added comment about the regex pattern. Also change intval() to (int). * Attempt to break this change * Fixed mistake in the test * ISSUE-2501 Fixed Test. * ISSUE-2501 Removed FQDNs. * ISSUE-2501 Improved the regex pattern matcher, and refilled the type back in with the regex pattern. * ISSUE-2501 Fixes based on static analysis feedback on what I was working on. * ISSUE-2501 Typos. * ISSUE-2501 More fixes based on static analysis. * ISSUE-2501 Fixed based on @Naktibalda CR comment.
1 parent 5c51fbe commit 0cdb94f

File tree

2 files changed

+53
-11
lines changed

2 files changed

+53
-11
lines changed

src/Codeception/Util/JsonType.php

Lines changed: 39 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
<?php
2+
23
namespace Codeception\Util;
34

45
/**
@@ -59,7 +60,6 @@ public function __construct($jsonArray)
5960
* return strpos(' ', $value) !== false;
6061
* });
6162
* // => use it as 'string:slug'
62-
6363
*
6464
* // add custom function to matcher with `len($val)` syntax
6565
* // parameter matching patterns should be valid regex and start with `/` char
@@ -70,7 +70,7 @@ public function __construct($jsonArray)
7070
* ?>
7171
* ```
7272
*
73-
* @param $name
73+
* @param $name
7474
* @param callable $callable
7575
*/
7676
public static function addCustomFilter($name, callable $callable)
@@ -119,35 +119,64 @@ protected function typeComparison($data, $jsonType)
119119
if (!array_key_exists($key, $data)) {
120120
return "Key `$key` doesn't exist in " . json_encode($data);
121121
}
122+
122123
if (is_array($jsonType[$key])) {
123124
$message = $this->typeComparison($data[$key], $jsonType[$key]);
125+
124126
if (is_string($message)) {
125127
return $message;
126128
}
129+
127130
continue;
128131
}
129-
$matchTypes = preg_split("#(?![^]\(]*\))\|#", $type);
130-
$matched = false;
132+
133+
$regexMatcher = '/:regex\((.*?)\)(?:\|(boolean|integer|double|float|string|array|object|resource|resource \(closed\)|null|unknown type)|$)/';
134+
$regexes = [];
135+
136+
// Match the string ':regex(' and any characters until a regex delimiter (matches 99.999% use cases) followed by character ')'
137+
// Place the 'any character' + delimiter matches in to an array.
138+
preg_match_all($regexMatcher, $type, $regexes);
139+
140+
// Do the same match as above, but replace the the 'any character' + delimiter with a place holder ($${count}).
141+
$filterType = preg_replace_callback($regexMatcher, function () {
142+
static $count = 0;
143+
return ':regex($$' . $count++ . ')';
144+
}, $type);
145+
146+
$matchTypes = preg_split("#(?![^]\(]*\))\|#", $filterType);
147+
$matched = false;
131148
$currentType = strtolower(gettype($data[$key]));
132-
if ($currentType == 'double') {
149+
150+
if ($currentType === 'double') {
133151
$currentType = 'float';
134152
}
153+
135154
foreach ($matchTypes as $matchType) {
136-
$filters = preg_split("#(?![^]\(]*\))\:#", $matchType);
137-
$expectedType = trim(strtolower(array_shift($filters)));
155+
$filters = preg_split("#(?![^]\(]*\))\:#", $matchType);
156+
$expectedType = strtolower(trim(array_shift($filters)));
138157

139-
if ($expectedType != $currentType) {
158+
if ($expectedType !== $currentType) {
140159
continue;
141160
}
161+
142162
$matched = true;
143163

144164
foreach ($filters as $filter) {
165+
// Fill regex pattern back into the filter.
166+
$filter = preg_replace_callback('/\$\$\d+/', function ($m) use ($regexes) {
167+
$pos = (int)substr($m[0], 2);
168+
169+
return $regexes[1][$pos];
170+
}, $filter);
171+
145172
$matched = $matched && $this->matchFilter($filter, $data[$key]);
146173
}
174+
147175
if ($matched) {
148176
break;
149177
}
150178
}
179+
151180
if (!$matched) {
152181
return sprintf("`$key: %s` is of type `$type`", var_export($data[$key], true));
153182
}
@@ -189,7 +218,8 @@ protected function matchFilter($filter, $value)
189218
}
190219
if ($filter === 'email') { // from http://emailregex.com/
191220
// @codingStandardsIgnoreStart
192-
return preg_match('/^(?!(?:(?:\x22?\x5C[\x00-\x7E]\x22?)|(?:\x22?[^\x5C\x22]\x22?)){255,})(?!(?:(?:\x22?\x5C[\x00-\x7E]\x22?)|(?:\x22?[^\x5C\x22]\x22?)){65,}@)(?:(?:[\x21\x23-\x27\x2A\x2B\x2D\x2F-\x39\x3D\x3F\x5E-\x7E]+)|(?:\x22(?:[\x01-\x08\x0B\x0C\x0E-\x1F\x21\x23-\x5B\x5D-\x7F]|(?:\x5C[\x00-\x7F]))*\x22))(?:\.(?:(?:[\x21\x23-\x27\x2A\x2B\x2D\x2F-\x39\x3D\x3F\x5E-\x7E]+)|(?:\x22(?:[\x01-\x08\x0B\x0C\x0E-\x1F\x21\x23-\x5B\x5D-\x7F]|(?:\x5C[\x00-\x7F]))*\x22)))*@(?:(?:(?!.*[^.]{64,})(?:(?:(?:xn--)?[a-z0-9]+(?:-[a-z0-9]+)*\.){1,126}){1,}(?:(?:[a-z][a-z0-9]*)|(?:(?:xn--)[a-z0-9]+))(?:-[a-z0-9]+)*)|(?:\[(?:(?:IPv6:(?:(?:[a-f0-9]{1,4}(?::[a-f0-9]{1,4}){7})|(?:(?!(?:.*[a-f0-9][:\]]){7,})(?:[a-f0-9]{1,4}(?::[a-f0-9]{1,4}){0,5})?::(?:[a-f0-9]{1,4}(?::[a-f0-9]{1,4}){0,5})?)))|(?:(?:IPv6:(?:(?:[a-f0-9]{1,4}(?::[a-f0-9]{1,4}){5}:)|(?:(?!(?:.*[a-f0-9]:){5,})(?:[a-f0-9]{1,4}(?::[a-f0-9]{1,4}){0,3})?::(?:[a-f0-9]{1,4}(?::[a-f0-9]{1,4}){0,3}:)?)))?(?:(?:25[0-5])|(?:2[0-4][0-9])|(?:1[0-9]{2})|(?:[1-9]?[0-9]))(?:\.(?:(?:25[0-5])|(?:2[0-4][0-9])|(?:1[0-9]{2})|(?:[1-9]?[0-9]))){3}))\]))$/iD', $value);
221+
return preg_match('/^(?!(?:(?:\x22?\x5C[\x00-\x7E]\x22?)|(?:\x22?[^\x5C\x22]\x22?)){255,})(?!(?:(?:\x22?\x5C[\x00-\x7E]\x22?)|(?:\x22?[^\x5C\x22]\x22?)){65,}@)(?:(?:[\x21\x23-\x27\x2A\x2B\x2D\x2F-\x39\x3D\x3F\x5E-\x7E]+)|(?:\x22(?:[\x01-\x08\x0B\x0C\x0E-\x1F\x21\x23-\x5B\x5D-\x7F]|(?:\x5C[\x00-\x7F]))*\x22))(?:\.(?:(?:[\x21\x23-\x27\x2A\x2B\x2D\x2F-\x39\x3D\x3F\x5E-\x7E]+)|(?:\x22(?:[\x01-\x08\x0B\x0C\x0E-\x1F\x21\x23-\x5B\x5D-\x7F]|(?:\x5C[\x00-\x7F]))*\x22)))*@(?:(?:(?!.*[^.]{64,})(?:(?:(?:xn--)?[a-z0-9]+(?:-[a-z0-9]+)*\.){1,126}){1,}(?:(?:[a-z][a-z0-9]*)|(?:(?:xn--)[a-z0-9]+))(?:-[a-z0-9]+)*)|(?:\[(?:(?:IPv6:(?:(?:[a-f0-9]{1,4}(?::[a-f0-9]{1,4}){7})|(?:(?!(?:.*[a-f0-9][:\]]){7,})(?:[a-f0-9]{1,4}(?::[a-f0-9]{1,4}){0,5})?::(?:[a-f0-9]{1,4}(?::[a-f0-9]{1,4}){0,5})?)))|(?:(?:IPv6:(?:(?:[a-f0-9]{1,4}(?::[a-f0-9]{1,4}){5}:)|(?:(?!(?:.*[a-f0-9]:){5,})(?:[a-f0-9]{1,4}(?::[a-f0-9]{1,4}){0,3})?::(?:[a-f0-9]{1,4}(?::[a-f0-9]{1,4}){0,3}:)?)))?(?:(?:25[0-5])|(?:2[0-4][0-9])|(?:1[0-9]{2})|(?:[1-9]?[0-9]))(?:\.(?:(?:25[0-5])|(?:2[0-4][0-9])|(?:1[0-9]{2})|(?:[1-9]?[0-9]))){3}))\]))$/iD',
222+
$value);
193223
// @codingStandardsIgnoreEnd
194224
}
195225
if ($filter === 'empty') {

tests/unit/Codeception/Util/JsonTypeTest.php

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ public function testRegexFilter()
6262
$this->assertTrue($jsonType->matches(['numbers' => 'string:regex(~1-2-3~)']));
6363
$this->assertTrue($jsonType->matches(['numbers' => 'string:regex(~\d-\d-\d~)']));
6464
$this->assertNotTrue($jsonType->matches(['numbers' => 'string:regex(~^\d-\d$~)']));
65-
65+
6666
$jsonType = new JsonType(['published' => 1]);
6767
$this->assertTrue($jsonType->matches(['published' => 'integer:regex(~1~)']));
6868
$this->assertTrue($jsonType->matches(['published' => 'integer:regex(~1|2~)']));
@@ -76,6 +76,18 @@ public function testRegexFilter()
7676
$this->assertNotTrue(
7777
$jsonType->matches(['date' => 'string:regex(~2015-11-30T04:06:44Z|2016-11-30T05:07:00Z~)'])
7878
);
79+
80+
$jsonType = new JsonType(['code' => 'xyz']);
81+
$this->assertTrue($jsonType->matches(['code' => 'string:regex(~((xyz)|(abc))~)']));
82+
83+
$jsonType = new JsonType(['time' => '21:00']);
84+
$this->assertTrue($jsonType->matches(['time' => 'string:regex(~^([0-1]\d|2[0-3]):[0-5]\d$~)']));
85+
86+
$jsonType = new JsonType(['text' => '21@:00']);
87+
$this->assertTrue($jsonType->matches(['text' => 'string:regex(~^(\d\d@):\d\d$~)']));
88+
89+
$jsonType = new JsonType(['text' => '21@:aa']);
90+
$this->assertNotTrue($jsonType->matches(['text' => 'string:regex(~^(\d\d@):\d\d$~)']));
7991
}
8092

8193
public function testDateTimeFilter()
@@ -182,7 +194,7 @@ public function testCollection()
182194
$this->assertNotTrue($res = $jsonType->matches([
183195
'id' => 'integer:<3'
184196
]));
185-
197+
186198
$this->assertContains('3` is of type `integer:<3', $res);
187199
$this->assertContains('5` is of type `integer:<3', $res);
188200
}

0 commit comments

Comments
 (0)