Skip to content

Commit 91336ca

Browse files
committed
Added support for ULID
1 parent 8e3e6d3 commit 91336ca

10 files changed

+951
-672
lines changed

README.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,7 @@ class MatcherTest extends TestCase
111111
* ``@*@`` || ``@wildcard@``
112112
* ``expr(expression)`` - **optional**, requires `symfony/expression-language: ^2.3|^3.0|^4.0|^5.0` to be present
113113
* ``@uuid@``
114+
* ``@ulid@``
114115
* ``@json@``
115116
* ``@string@||@integer@`` - string OR integer
116117

@@ -336,6 +337,18 @@ $matcher = new PHPMatcher();
336337
$matcher->match('9f4db639-0e87-4367-9beb-d64e3f42ae18', '@uuid@');
337338
```
338339

340+
### ULID matching
341+
342+
```php
343+
<?php
344+
345+
use Coduo\PHPMatcher\PHPMatcher;
346+
347+
$matcher = new PHPMatcher();
348+
349+
$matcher->match('01BX5ZZKBKACTAV9WEVGEMMVS0', '@ulid@');
350+
```
351+
339352
### Array matching
340353

341354
```php

src/Factory/MatcherFactory.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,7 @@ private function buildScalarMatchers(Parser $parser, Backtrace $backtrace) : Mat
8787
new Matcher\ScalarMatcher($backtrace),
8888
new Matcher\WildcardMatcher($backtrace),
8989
new Matcher\UuidMatcher($backtrace, $parser),
90+
new Matcher\UlidMatcher($backtrace, $parser),
9091
new Matcher\JsonObjectMatcher($backtrace, $parser),
9192
]
9293
);

src/Matcher/UlidMatcher.php

Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Coduo\PHPMatcher\Matcher;
6+
7+
use Coduo\PHPMatcher\Backtrace;
8+
use Coduo\PHPMatcher\Parser;
9+
use Coduo\ToString\StringConverter;
10+
use Symfony\Component\Validator\Constraints\Ulid;
11+
12+
final class UlidMatcher extends Matcher
13+
{
14+
/**
15+
* @var string
16+
*/
17+
public const PATTERN = 'ulid';
18+
19+
private Backtrace $backtrace;
20+
21+
private Parser $parser;
22+
23+
public function __construct(Backtrace $backtrace, Parser $parser)
24+
{
25+
$this->parser = $parser;
26+
$this->backtrace = $backtrace;
27+
}
28+
29+
public function match($value, $pattern) : bool
30+
{
31+
$this->backtrace->matcherEntrance(self::class, $value, $pattern);
32+
33+
if (!\is_string($value)) {
34+
$this->error = \sprintf(
35+
'%s "%s" is not a valid ULID: not a string.',
36+
\gettype($value),
37+
new StringConverter($value)
38+
);
39+
$this->backtrace->matcherFailed(self::class, $value, $pattern, $this->error);
40+
41+
return false;
42+
}
43+
44+
if (\strlen($value) !== strspn($value, '0123456789ABCDEFGHJKMNPQRSTVWXYZabcdefghjkmnpqrstvwxyz')) {
45+
$this->error = \sprintf(
46+
'%s "%s" is not a valid ULID: invalid characters.',
47+
\gettype($value),
48+
new StringConverter($value)
49+
);
50+
$this->backtrace->matcherFailed(self::class, $value, $pattern, $this->error);
51+
52+
return false;
53+
}
54+
55+
if (26 < \strlen($value)) {
56+
$this->error = \sprintf(
57+
'%s "%s" is not a valid ULID: too long.',
58+
\gettype($value),
59+
new StringConverter($value)
60+
);
61+
$this->backtrace->matcherFailed(self::class, $value, $pattern, $this->error);
62+
63+
return false;
64+
}
65+
66+
if (26 > \strlen($value)) {
67+
$this->error = \sprintf(
68+
'%s "%s" is not a valid ULID: too short.',
69+
\gettype($value),
70+
new StringConverter($value)
71+
);
72+
$this->backtrace->matcherFailed(self::class, $value, $pattern, $this->error);
73+
74+
return false;
75+
}
76+
77+
// Largest valid ULID is '7ZZZZZZZZZZZZZZZZZZZZZZZZZ'
78+
// Cf https://github.com/ulid/spec#overflow-errors-when-parsing-base32-strings
79+
if ($value[0] > '7') {
80+
$this->error = \sprintf(
81+
'%s "%s" is not a valid ULID: overflow.',
82+
\gettype($value),
83+
new StringConverter($value)
84+
);
85+
$this->backtrace->matcherFailed(self::class, $value, $pattern, $this->error);
86+
87+
return false;
88+
}
89+
90+
$this->backtrace->matcherSucceed(self::class, $value, $pattern);
91+
92+
return true;
93+
}
94+
95+
public function canMatch($pattern) : bool
96+
{
97+
if (!\is_string($pattern)) {
98+
$this->backtrace->matcherCanMatch(self::class, $pattern, false);
99+
100+
return false;
101+
}
102+
103+
$result = $this->parser->hasValidSyntax($pattern) && $this->parser->parse($pattern)->is(self::PATTERN);
104+
$this->backtrace->matcherCanMatch(self::class, $pattern, $result);
105+
106+
return $result;
107+
}
108+
}

tests/BacktraceTest/failed_complex_matching_expected_trace.txt

Lines changed: 302 additions & 296 deletions
Large diffs are not rendered by default.

tests/BacktraceTest/succeed_complex_matching_expected_trace.txt

Lines changed: 330 additions & 324 deletions
Large diffs are not rendered by default.

tests/ExpandersTest.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ public static function expanderExamples()
5353
[[], ['unexistent_key' => '@[email protected]()'], true],
5454
[[], ['unexistent_key' => '@[email protected]()'], true],
5555
[[], ['unexistent_key' => '@[email protected]()'], true],
56+
[[], ['unexistent_key' => '@[email protected]()'], true],
5657
[[], ['unexistent_key' => '@[email protected]()'], true],
5758
[[], ['unexistent_key' => '@[email protected]()', 'unexistent_second_key' => '@[email protected]()'], true],
5859
[[], ['unexistent_key' => '@[email protected]()', 'unexistent_second_key' => '@string@'], false],

tests/Matcher/TextMatcherTest.php

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,16 @@ public function matchingData()
127127
'/user/@uuid@/@string@',
128128
false,
129129
],
130+
[
131+
'/user/01BX5ZZKBKACTAV9WEVGEMMVS0/profile',
132+
'/user/@ulid@/@string@',
133+
true,
134+
],
135+
[
136+
'/user/12345/profile',
137+
'/user/@ulid@/@string@',
138+
false,
139+
],
130140
];
131141
}
132142
}

tests/Matcher/UlidMatcherTest.php

Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Coduo\PHPMatcher\Tests\Matcher;
6+
7+
use Coduo\PHPMatcher\Backtrace;
8+
use Coduo\PHPMatcher\Lexer;
9+
use Coduo\PHPMatcher\Matcher\UlidMatcher;
10+
use Coduo\PHPMatcher\Parser;
11+
use PHPUnit\Framework\TestCase;
12+
13+
class UlidMatcherTest extends TestCase
14+
{
15+
private ?UlidMatcher $matcher = null;
16+
17+
public static function positiveCanMatchData()
18+
{
19+
return [
20+
['@ulid@'],
21+
];
22+
}
23+
24+
public static function positiveMatchData()
25+
{
26+
return [
27+
['01BX5ZZKBKACTAV9WEVGEMMVRY', '@ulid@'],
28+
['01BX5ZZKBKACTAV9WEVGEMMVS0', '@ulid@'],
29+
['01BX5ZZKBKACTAV9WEVGEMMVS1', '@ulid@'],
30+
['01BX5ZZKBKACTAV9WEVGEMMVRZ', '@ulid@'],
31+
['7ZZZZZZZZZZZZZZZZZZZZZZZZZ', '@ulid@'],
32+
];
33+
}
34+
35+
public static function negativeCanMatchData()
36+
{
37+
return [
38+
['@ulid'],
39+
['ulid'],
40+
[1],
41+
];
42+
}
43+
44+
public static function negativeMatchData()
45+
{
46+
return [
47+
[1, '@ulid@'],
48+
[0, '@ulid@'],
49+
['7b44804e-37d5-4df4-9bdd-b738d4a45bb4', '@ulid@'],
50+
['01BX5ZZKBKACTAV9WEVGEMMVR=', '@ulid@'],
51+
['01BX5ZZKBKACTAV9WEVGEMMVS', '@ulid@'],
52+
['01BX5ZZKBKACTAV9WEVGEMMVS00', '@ulid@'],
53+
['8ZZZZZZZZZZZZZZZZZZZZZZZZZ', '@ulid@'],
54+
];
55+
}
56+
57+
public static function negativeMatchDescription()
58+
{
59+
return [
60+
[new \stdClass, '@ulid@', 'object "\\stdClass" is not a valid ULID: not a string.'],
61+
[1.1, '@ulid@', 'double "1.1" is not a valid ULID: not a string.'],
62+
[false, '@ulid@', 'boolean "false" is not a valid ULID: not a string.'],
63+
[1, '@ulid@', 'integer "1" is not a valid ULID: not a string.'],
64+
['lorem ipsum', '@ulid@', 'string "lorem ipsum" is not a valid ULID: invalid characters.'],
65+
['7b44804e-37d5-4df4-9bdd-b738d4a45bb4', '@ulid@', 'string "7b44804e-37d5-4df4-9bdd-b738d4a45bb4" is not a valid ULID: invalid characters.'],
66+
['01BX5ZZKBKACTAV9WEVGEMMVR=', '@ulid@', 'string "01BX5ZZKBKACTAV9WEVGEMMVR=" is not a valid ULID: invalid characters.'],
67+
['01BX5ZZKBKACTAV9WEVGEMMVS', '@ulid@', 'string "01BX5ZZKBKACTAV9WEVGEMMVS" is not a valid ULID: too short.'],
68+
['01BX5ZZKBKACTAV9WEVGEMMVS00', '@ulid@', 'string "01BX5ZZKBKACTAV9WEVGEMMVS00" is not a valid ULID: too long.'],
69+
['8ZZZZZZZZZZZZZZZZZZZZZZZZZ', '@ulid@', 'string "8ZZZZZZZZZZZZZZZZZZZZZZZZZ" is not a valid ULID: overflow.'],
70+
];
71+
}
72+
73+
public function setUp() : void
74+
{
75+
$this->matcher = new UlidMatcher(
76+
$backtrace = new Backtrace\InMemoryBacktrace(),
77+
new Parser(new Lexer(), new Parser\ExpanderInitializer($backtrace))
78+
);
79+
}
80+
81+
/**
82+
* @dataProvider positiveCanMatchData
83+
*/
84+
public function test_positive_can_matches($pattern) : void
85+
{
86+
$this->assertTrue($this->matcher->canMatch($pattern));
87+
}
88+
89+
/**
90+
* @dataProvider negativeCanMatchData
91+
*/
92+
public function test_negative_can_matches($pattern) : void
93+
{
94+
$this->assertFalse($this->matcher->canMatch($pattern));
95+
}
96+
97+
/**
98+
* @dataProvider positiveMatchData
99+
*/
100+
public function test_positive_match($value, $pattern) : void
101+
{
102+
$this->assertTrue($this->matcher->match($value, $pattern));
103+
}
104+
105+
/**
106+
* @dataProvider negativeMatchData
107+
*/
108+
public function test_negative_match($value, $pattern) : void
109+
{
110+
$this->assertFalse($this->matcher->match($value, $pattern));
111+
}
112+
113+
/**
114+
* @dataProvider negativeMatchDescription
115+
*/
116+
public function test_negative_match_description($value, $pattern, $error) : void
117+
{
118+
$this->matcher->match($value, $pattern);
119+
$this->assertEquals($error, $this->matcher->getError());
120+
}
121+
}

tests/MatcherTest.php

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ public function scalarValueExamples()
2525
[1, '@integer@'],
2626
[['foo'], '@array@'],
2727
['9f4db639-0e87-4367-9beb-d64e3f42ae18', '@uuid@'],
28+
['01BX5ZZKBKACTAV9WEVGEMMVS0', '@ulid@'],
2829
];
2930
}
3031

@@ -170,6 +171,16 @@ public function jsonDataProvider()
170171
"url": "/accounts/@uuid@"
171172
}',
172173
],
174+
[
175+
/* @lang JSON */
176+
'{
177+
"url": "/profile/01BX5ZZKBKACTAV9WEVGEMMVS0"
178+
}',
179+
/* @lang JSON */
180+
'{
181+
"url": "/profile/@ulid@"
182+
}',
183+
],
173184
[
174185
/* @lang JSON */
175186
'{

0 commit comments

Comments
 (0)