8000 [Validator] Add the `format` option to the `Uuid` constraint to allow… · symfony/symfony@febb9b7 · GitHub
[go: up one dir, main page]

Skip to content

Commit febb9b7

Browse files
[Validator] Add the format option to the Uuid constraint to allow accepting different ULID formats
1 parent 9ed27d0 commit febb9b7

File tree

5 files changed

+79
-11
lines changed

5 files changed

+79
-11
lines changed

src/Symfony/Component/Validator/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ CHANGELOG
66

77
* Make `PasswordStrengthValidator::estimateStrength()` public
88
* Add the `Yaml` constraint for validating YAML content
9+
* Add the `format` option to the `Uuid` constraint to allow accepting different ULID formats
910

1011
7.1
1112
---

src/Symfony/Component/Validator/Constraints/Ulid.php

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
namespace Symfony\Component\Validator\Constraints;
1313

1414
use Symfony\Component\Validator\Constraint;
15+
use Symfony\Component\Validator\Exception\ConstraintDefinitionException;
1516

1617
/**
1718
* Validates that a value is a valid Universally Unique Lexicographically Sortable Identifier (ULID).
@@ -35,20 +36,31 @@ class Ulid extends Constraint
3536
self::TOO_LARGE_ERROR => 'TOO_LARGE_ERROR',
3637
];
3738

39+
public const FORMAT_BASE_32 = 'base32';
40+
public const FORMAT_BASE_58 = 'base58';
41+
3842
public string $message = 'This is not a valid ULID.';
43+
public string $format = self::FORMAT_BASE_32;
3944

4045
/**
4146
* @param array<string,mixed>|null $options
4247
* @param string[]|null $groups
48+
* @param self::FORMAT_*|null $format
4349
*/
4450
public function __construct(
4551
?array $options = null,
4652
?string $message = null,
4753
?array $groups = null,
4854
mixed $payload = null,
55+
?string $format = null,
4956
) {
5057
parent::__construct($options, $groups, $payload);
5158

5259
$this->message = $message ?? $this->message;
60+
$this->format = $format ?? $this->format;
61+
62+
if (!\in_array($this->format, [self::FORMAT_BASE_32, self::FORMAT_BASE_58], true)) {
63+
throw new ConstraintDefinitionException(sprintf('The "%s" validation format is not supported.', $format));
64+
}
5365
}
5466
}

src/Symfony/Component/Validator/Constraints/UlidValidator.php

Lines changed: 24 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -40,16 +40,28 @@ public function validate(mixed $value, Constraint $constraint): void
4040

4141
$value = (string) $value;
4242

43-
if (26 !== \strlen($value)) {
43+
switch ($constraint->format) {
44+
case Ulid::FORMAT_BASE_32:
45+
$requiredLength = 26;
46+
$requiredCharset = '0123456789ABCDEFGHJKMNPQRSTVWXYZabcdefghjkmnpqrstvwxyz';
47+
break;
48+
case Ulid::FORMAT_BASE_58:
49+
$requiredLength = 22;
50+
$requiredCharset = '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz';
51+
break;
52+
// no default, the constraint constructor already ensures a valid format
53+
}
54+
55+
if ($requiredLength !== \strlen($value)) {
4456
$this->context->buildViolation($constraint->message)
4557
->setParameter('{{ value }}', $this->formatValue($value))
46-
->setCode(26 > \strlen($value) ? Ulid::TOO_SHORT_ERROR : Ulid::TOO_LONG_ERROR)
58+
->setCode($requiredLength > \strlen($value) ? Ulid::TOO_SHORT_ERROR : Ulid::TOO_LONG_ERROR)
4759
->addViolation();
4860

4961
return;
5062
}
5163

52-
if (\strlen($value) !== strspn($value, '0123456789ABCDEFGHJKMNPQRSTVWXYZabcdefghjkmnpqrstvwxyz')) {
64+
if (\strlen($value) !== strspn($value, $requiredCharset)) {
5365
$this->context->buildViolation($constraint->message)
5466
->setParameter('{{ value }}', $this->formatValue($value))
5567
->setCode(Ulid::INVALID_CHARACTERS_ERROR)
@@ -58,13 +70,15 @@ public function validate(mixed $value, Constraint $constraint): void
5870
return;
5971
}
6072

61-
// Largest valid ULID is '7ZZZZZZZZZZZZZZZZZZZZZZZZZ'
62-
// Cf https://github.com/ulid/spec#overflow-errors-when-parsing-base32-strings
63-
if ($value[0] > '7') {
64-
$this->context->buildViolation($constraint->message)
65-
->setParameter('{{ value }}', $this->formatValue($value))
66-
->setCode(Ulid::TOO_LARGE_ERROR)
67-
->addViolation();
73+
if (Ulid::FORMAT_BASE_32 === $constraint->format) {
74+
// Largest valid ULID is '7ZZZZZZZZZZZZZZZZZZZZZZZZZ'
75+
// Cf https://github.com/ulid/spec#overflow-errors-when-parsing-base32-strings
76+
if ($value[0] > '7') {
77+
$this->context->buildViolation($constraint->message)
78+
->setParameter('{{ value }}', $this->formatValue($value))
79+
->setCode(Ulid::TOO_LARGE_ERROR)
80+
->addViolation();
81+
}
6882
}
6983
}
7084
}

src/Symfony/Component/Validator/Tests/Constraints/UlidTest.php

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313

1414
use PHPUnit\Framework\TestCase;
1515
use Symfony\Component\Validator\Constraints\Ulid;
16+
use Symfony\Component\Validator\Exception\ConstraintDefinitionException;
1617
use Symfony\Component\Validator\Mapping\ClassMetadata;
1718
use Symfony\Component\Validator\Mapping\Loader\AttributeLoader;
1819

@@ -32,6 +33,14 @@ public function testAttributes()
3233
self::assertSame(['my_group'], $cConstraint->groups);
3334
self::assertSame('some attached data', $cConstraint->payload);
3435
}
36+
37+
public function testUnexpectedValidationFormat()
38+
{
39+
$this->expectException(ConstraintDefinitionException::class);
40+
$this->expectExceptionMessage('The "invalid" validation format is not supported.');
41+
42+
new Ulid(format: 'invalid');
43+
}
3544
}
3645

3746
class UlidDummy

src/Symfony/Component/Validator/Tests/Constraints/UlidValidatorTest.php

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,13 @@ public function testValidUlid()
5353
$this->assertNoViolation();
5454
}
5555

56+
public function testValidUlidAsBase58()
57+
{
58+
$this->validator->validate('1CCD2w4mK2m455S2BAXFht', new Ulid(format: Ulid::FORMAT_BASE_58));
59+
60+
$this->assertNoViolation();
61+
}
62+
5663
/**
5764
* @dataProvider getInvalidUlids
5865
*/
@@ -70,7 +77,7 @@ public function testInvalidUlid(string $ulid, string $code)
7077
->assertRaised();
7178
}
7279

73-
public static function getInvalidUlids()
80+
public static function getInvalidUlids(): array
7481
{
7582
return [
7683
['01ARZ3NDEKTSV4RRFFQ69G5FA', Ulid::TOO_SHORT_ERROR],
@@ -81,6 +88,31 @@ public static function getInvalidUlids()
8188
];
8289
}
8390

91+
/**
92+
* @dataProvider getInvalidBase58Ulids
93+
*/
94+
public function testInvalidBase58Ulid(string $ulid, string $code)
95+
{
96+
$constraint = new Ulid(message: 'testMessage', format: Ulid::FORMAT_BASE_58);
97+
98+
$this->validator->validate($ulid, $constraint);
99+
100+
$this->buildViolation('testMessage')
101+
->setParameter('{{ value }}', '"'.$ulid.'"')
102+
->setCode($code)
103+
->assertRaised();
104+
}
105+
106+
public static function getInvalidBase58Ulids(): array
107+
{
108+
return [
109+
['1CCD2w4mK2m455S2BAXFh', Ulid::TOO_SHORT_ERROR],
110+
['1CCD2w4mK2m455S2BAXFhttt', Ulid::TOO_LONG_ERROR],
111+
['1CCD2w4mK2m455S2BAXFhO', Ulid::INVALID_CHARACTERS_ERROR],
112+
['not-even-ulid-like', Ulid::TOO_SHORT_ERROR],
113+
];
114+
}
115+
84116
public function testInvalidUlidNamed()
85117
{
86118
$constraint = new Ulid(message: 'testMessage');

0 commit comments

Comments
 (0)
0