Skip to content

Commit e14b90b

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

11 files changed

+968
-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/Pattern/RegexConverter.php

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
namespace Coduo\PHPMatcher\Matcher\Pattern;
66

77
use Coduo\PHPMatcher\Exception\UnknownTypeException;
8+
use Coduo\PHPMatcher\Matcher\UlidMatcher;
89
use Coduo\PHPMatcher\Matcher\UuidMatcher;
910

1011
final class RegexConverter
@@ -24,6 +25,8 @@ public function toRegex(TypePattern $typePattern) : string
2425
return '(\\-?[0-9]*[\\.|\\,][0-9]*)';
2526
case 'uuid':
2627
return '(' . UuidMatcher::UUID_PATTERN . ')';
28+
case 'ulid':
29+
return '(' . UlidMatcher::ULID_PATTERN . ')';
2730

2831
default:
2932
throw new UnknownTypeException($typePattern->getType());

src/Matcher/UlidMatcher.php

Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
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+
/**
20+
* ULID validation pattern highly inspired by the Symfony Uid Component.
21+
*
22+
* @see https://github.com/symfony/uid/blob/8311a3f6e14c21960e7955452fe52a462d58ad2b/Ulid.php#L41-L52
23+
*
24+
* @var string
25+
*/
26+
public const ULID_PATTERN = '[01234567]{1}[0123456789ABCDEFGHJKMNPQRSTVWXYZabcdefghjkmnpqrstvwxyz]{25}';
27+
28+
private Backtrace $backtrace;
29+
30+
private Parser $parser;
31+
32+
public function __construct(Backtrace $backtrace, Parser $parser)
33+
{
34+
$this->parser = $parser;
35+
$this->backtrace = $backtrace;
36+
}
37+
38+
public function match($value, $pattern) : bool
39+
{
40+
$this->backtrace->matcherEntrance(self::class, $value, $pattern);
41+
42+
if (!\is_string($value)) {
43+
$this->error = \sprintf(
44+
'%s "%s" is not a valid ULID: not a string.',
45+
\gettype($value),
46+
new StringConverter($value)
47+
);
48+
$this->backtrace->matcherFailed(self::class, $value, $pattern, $this->error);
49+
50+
return false;
51+
}
52+
53+
if (\strlen($value) !== \strspn($value, '0123456789ABCDEFGHJKMNPQRSTVWXYZabcdefghjkmnpqrstvwxyz')) {
54+
$this->error = \sprintf(
55+
'%s "%s" is not a valid ULID: invalid characters.',
56+
\gettype($value),
57+
new StringConverter($value)
58+
);
59+
$this->backtrace->matcherFailed(self::class, $value, $pattern, $this->error);
60+
61+
return false;
62+
}
63+
64+
if (26 < \strlen($value)) {
65+
$this->error = \sprintf(
66+
'%s "%s" is not a valid ULID: too long.',
67+
\gettype($value),
68+
new StringConverter($value)
69+
);
70+
$this->backtrace->matcherFailed(self::class, $value, $pattern, $this->error);
71+
72+
return false;
73+
}
74+
75+
if (26 > \strlen($value)) {
76+
$this->error = \sprintf(
77+
'%s "%s" is not a valid ULID: too short.',
78+
\gettype($value),
79+
new StringConverter($value)
80+
);
81+
$this->backtrace->matcherFailed(self::class, $value, $pattern, $this->error);
82+
83+
return false;
84+
}
85+
86+
// Largest valid ULID is '7ZZZZZZZZZZZZZZZZZZZZZZZZZ'
87+
// Cf https://github.com/ulid/spec#overflow-errors-when-parsing-base32-strings
88+
if ($value[0] > '7') {
89+
$this->error = \sprintf(
90+
'%s "%s" is not a valid ULID: overflow.',
91+
\gettype($value),
92+
new StringConverter($value)
93+
);
94+
$this->backtrace->matcherFailed(self::class, $value, $pattern, $this->error);
95+
96+
return false;
97+
}
98+
99+
$this->backtrace->matcherSucceed(self::class, $value, $pattern);
100+
101+
return true;
102+
}
103+
104+
public function canMatch($pattern) : bool
105+
{
106+
if (!\is_string($pattern)) {
107+
$this->backtrace->matcherCanMatch(self::class, $pattern, false);
108+
109+
return false;
110+
}
111+
112+
$result = $this->parser->hasValidSyntax($pattern) && $this->parser->parse($pattern)->is(self::PATTERN);
113+
$this->backtrace->matcherCanMatch(self::class, $pattern, $result);
114+
115+
return $result;
116+
}
117+
}

0 commit comments

Comments
 (0)