Skip to content

Commit 6517944

Browse files
committed
add validation for all uuid versions
1 parent a095c08 commit 6517944

6 files changed

Lines changed: 164 additions & 47 deletions

File tree

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ Heavily inspired by the well-known TypeScript library [zod](https://github.com/c
3535
Through [Composer](http://getcomposer.org) as [chubbyphp/chubbyphp-parsing][1].
3636

3737
```sh
38-
composer require chubbyphp/chubbyphp-parsing "^2.2"
38+
composer require chubbyphp/chubbyphp-parsing "^2.4"
3939
```
4040

4141
## Quick Start

composer.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@
5050
},
5151
"extra": {
5252
"branch-alias": {
53-
"dev-master": "2.3-dev"
53+
"dev-master": "2.4-dev"
5454
}
5555
},
5656
"scripts": {

doc/Schema/StringSchema.md

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -36,14 +36,16 @@ $schema->regexp('/^[a-z]+$/i'); // Must match regex pattern
3636
### Format Validations
3737

3838
```php
39-
$schema->domain(); // Valid domain
40-
$schema->email(); // Valid email address
41-
$schema->ipV4(); // Valid IPv4 address
42-
$schema->ipV6(); // Valid IPv6 address
43-
$schema->mac(); // Valid mac address
44-
$schema->url(); // Valid URL
45-
$schema->uuidV4(); // Valid UUID v4
46-
$schema->uuidV5(); // Valid UUID v5
39+
use Chubbyphp\Parsing\Enum\Uuid;
40+
41+
$schema->domain(); // Valid domain
42+
$schema->email(); // Valid email address
43+
$schema->ipV4(); // Valid IPv4 address
44+
$schema->ipV6(); // Valid IPv6 address
45+
$schema->mac(); // Valid mac address
46+
$schema->url(); // Valid URL
47+
$schema->uuid(); // Valid UUID v4
48+
$schema->uuid(Uuid::v5); // Valid UUID v5
4749
```
4850

4951
## Transformations

src/Enum/Uuid.php

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Chubbyphp\Parsing\Enum;
6+
7+
enum Uuid: int
8+
{
9+
case v1 = 1; // Gregorian Time
10+
case v2 = 2; // DCE Security
11+
case v3 = 3; // MD5 Hash
12+
case v4 = 4; // Random
13+
case v5 = 5; // SHA-1 Hash
14+
case v6 = 6; // Reordered Time (Sortable)
15+
case v7 = 7; // Unix Epoch Time (Sortable)
16+
case v8 = 8; // Custom
17+
}

src/Schema/StringSchema.php

Lines changed: 21 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
namespace Chubbyphp\Parsing\Schema;
66

7+
use Chubbyphp\Parsing\Enum\Uuid;
78
use Chubbyphp\Parsing\Error;
89
use Chubbyphp\Parsing\ErrorsException;
910

@@ -66,8 +67,7 @@ final class StringSchema extends AbstractSchemaInnerParse implements SchemaInter
6667
public const string ERROR_DATETIME_CODE = 'string.datetime';
6768
public const string ERROR_DATETIME_TEMPLATE = 'Cannot convert {{given}} to datetime';
6869

69-
private const string UUID_V4_PATTERN = '/^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-(8|9|a|b)[0-9a-f]{3}-[0-9a-f]{12}$/i';
70-
private const string UUID_V5_PATTERN = '/^[0-9a-f]{8}-[0-9a-f]{4}-5[0-9a-f]{3}-(8|9|a|b)[0-9a-f]{3}-[0-9a-f]{12}$/i';
70+
private const string UUID_PATTERN = '/^[0-9a-f]{8}-[0-9a-f]{4}-(\d{1})[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i';
7171

7272
public function length(int $length): static
7373
{
@@ -330,15 +330,18 @@ public function url(): static
330330
});
331331
}
332332

333-
public function uuidV4(): static
333+
public function uuid(Uuid $version = Uuid::v4): static
334334
{
335-
return $this->postParse(static function (string $string) {
336-
if (0 === preg_match(self::UUID_V4_PATTERN, $string)) {
335+
return $this->postParse(static function (string $string) use ($version) {
336+
$matches = [];
337+
preg_match(self::UUID_PATTERN, $string, $matches);
338+
339+
if ((int) ($matches[1] ?? '-1') !== $version->value) {
337340
throw new ErrorsException(
338341
new Error(
339342
self::ERROR_UUID_CODE,
340343
self::ERROR_UUID_TEMPLATE,
341-
['version' => 'v4', 'given' => $string]
344+
['version' => 'v'.$version->value, 'given' => $string]
342345
)
343346
);
344347
}
@@ -347,21 +350,20 @@ public function uuidV4(): static
347350
});
348351
}
349352

350-
public function uuidV5(): static
353+
/**
354+
* @deprecated use uuid()
355+
*/
356+
public function uuidV4(): static
351357
{
352-
return $this->postParse(static function (string $string) {
353-
if (0 === preg_match(self::UUID_V5_PATTERN, $string)) {
354-
throw new ErrorsException(
355-
new Error(
356-
self::ERROR_UUID_CODE,
357-
self::ERROR_UUID_TEMPLATE,
358-
['version' => 'v5', 'given' => $string]
359-
)
360-
);
361-
}
358+
return $this->uuid(Uuid::v4);
359+
}
362360

363-
return $string;
364-
});
361+
/**
362+
* @deprecated use uuid(Chubbyphp\Parsing\Enum\Uuid::v5)
363+
*/
364+
public function uuidV5(): static
365+
{
366+
return $this->uuid(Uuid::v5);
365367
}
366368

367369
public function trim(): static

tests/Unit/Schema/StringSchemaTest.php

Lines changed: 114 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,10 @@
44

55
namespace Chubbyphp\Tests\Parsing\Unit\Schema;
66

7+
use Chubbyphp\Parsing\Enum\Uuid;
78
use Chubbyphp\Parsing\ErrorsException;
89
use Chubbyphp\Parsing\Schema\StringSchema;
10+
use PHPUnit\Framework\Attributes\DataProvider;
911
use PHPUnit\Framework\TestCase;
1012

1113
/**
@@ -680,20 +682,20 @@ public function testParseWithInvalidUrl(): void
680682
}
681683
}
682684

683-
public function testParseWithValidUuidV4(): void
685+
public function testParseWithValidUuid(): void
684686
{
685687
$input = '960b0533-da17-42d8-a0a4-dd2ab7213caf';
686688

687-
$schema = (new StringSchema())->uuidV4();
689+
$schema = (new StringSchema())->uuid();
688690

689691
self::assertSame($input, $schema->parse($input));
690692
}
691693

692-
public function testParseWithInvalidUuidV4(): void
694+
public function testParseWithInvalidUuid(): void
693695
{
694696
$input = '960b0533-da17-52d8-a0a4-dd2ab7213caf';
695697

696-
$schema = (new StringSchema())->uuidV4();
698+
$schema = (new StringSchema())->uuid();
697699

698700
try {
699701
$schema->parse($input);
@@ -716,25 +718,60 @@ public function testParseWithInvalidUuidV4(): void
716718
}
717719
}
718720

719-
public function testParseWithValidUuidV5(): void
721+
#[DataProvider('provideParseWithValidUuidsCases')]
722+
public function testParseWithValidUuids(Uuid $version, string $uuid): void
720723
{
721-
$input = '960b0533-da17-52d8-a0a4-dd2ab7213caf';
722-
723-
$schema = (new StringSchema())->uuidV5();
724-
725-
self::assertSame($input, $schema->parse($input));
724+
self::assertSame($uuid, (new StringSchema())->uuid($version)->parse($uuid));
726725
}
727726

728-
public function testParseWithInvalidUuidV5(): void
727+
/**
728+
* @return array<string, array{Uuid, string}>
729+
*/
730+
public static function provideParseWithValidUuidsCases(): iterable
729731
{
730-
$input = '960b0533-da17-42d8-a0a4-dd2ab7213caf';
731-
732-
$schema = (new StringSchema())->uuidV5();
732+
return [
733+
'v1 timestamp + MAC' => [
734+
Uuid::v1,
735+
'6fa459ea-ee8a-1d13-a3ac-0800200c9a66',
736+
],
737+
'v2 DCE security' => [
738+
Uuid::v2,
739+
'000003e8-ee8a-2d13-8500-0800200c9a66',
740+
],
741+
'v3 MD5 hash' => [
742+
Uuid::v3,
743+
'5df41881-3aed-3515-88a7-2f4a814cf09e',
744+
],
745+
'v4 random' => [
746+
Uuid::v4,
747+
'f47ac10b-58cc-4372-a567-0e02b2c3d479',
748+
],
749+
'v5 SHA-1 hash' => [
750+
Uuid::v5,
751+
'a6e4eb18-bba0-5a2d-b0aa-b85e4718e89f',
752+
],
753+
'v6 reordered timestamp' => [
754+
Uuid::v6,
755+
'1d13ee8a-6fa4-659e-a3ac-0800200c9a66',
756+
],
757+
'v7 unix timestamp' => [
758+
Uuid::v7,
759+
'019490a9-5e00-7d34-b5f6-4a1b2c3d4e5f',
760+
],
761+
'v8 custom' => [
762+
Uuid::v8,
763+
'c0ffee00-cafe-8bad-beef-deaddeadbeef',
764+
],
765+
];
766+
}
733767

768+
#[DataProvider('provideParseWithInvalidUuidsCases')]
769+
public function testParseWithInvalidUuids(Uuid $version, string $uuid): void
770+
{
734771
try {
735-
$schema->parse($input);
772+
(new StringSchema())->uuid($version)->parse($uuid);
736773

737-
throw new \Exception('code should not be reached');
774+
self::fail('Expected ErrorsException was not thrown');
738775
} catch (ErrorsException $errorsException) {
739776
self::assertSame([
740777
[
@@ -743,15 +780,74 @@ public function testParseWithInvalidUuidV5(): void
743780
'code' => 'string.uuid',
744781
'template' => 'Invalid uuid {{version}} {{given}}',
745782
'variables' => [
746-
'version' => 'v5',
747-
'given' => $input,
783+
'version' => 'v'.$version->value,
784+
'given' => $uuid,
748785
],
749786
],
750787
],
751788
], $errorsException->errors->jsonSerialize());
752789
}
753790
}
754791

792+
/**
793+
* @return array<string, array{Uuid, string}>
794+
*/
795+
public static function provideParseWithInvalidUuidsCases(): iterable
796+
{
797+
return [
798+
'v1 with invalid variant c' => [
799+
Uuid::v1,
800+
'6fa459ea-ee8a-1d13-c3ac-0800200c9a66',
801+
],
802+
'v2 with invalid variant 0' => [
803+
Uuid::v2,
804+
'000003e8-ee8a-2d13-0500-0800200c9a66',
805+
],
806+
'v3 with invalid hex character g' => [
807+
Uuid::v3,
808+
'5df41881-3aed-3515-88a7-2f4a814cg09e',
809+
],
810+
'v4 with wrong version 5' => [
811+
Uuid::v4,
812+
'f47ac10b-58cc-5372-a567-0e02b2c3d479',
813+
],
814+
'v5 with invalid variant 7' => [
815+
Uuid::v5,
816+
'a6e4eb18-bba0-5a2d-70aa-b85e4718e89f',
817+
],
818+
'v6 with wrong version 1' => [
819+
Uuid::v6,
820+
'1d13ee8a-6fa4-159e-a3ac-0800200c9a66',
821+
],
822+
'v7 with invalid variant f' => [
823+
Uuid::v7,
824+
'019490a9-5e00-7d34-f5f6-4a1b2c3d4e5f',
825+
],
826+
'v8 with wrong version 0' => [
827+
Uuid::v8,
828+
'c0ffee00-cafe-0bad-beef-deaddeadbeef',
829+
],
830+
];
831+
}
832+
833+
public function testParseWithValidUuidV4(): void
834+
{
835+
$input = '960b0533-da17-42d8-a0a4-dd2ab7213caf';
836+
837+
$schema = (new StringSchema())->uuidV4();
838+
839+
self::assertSame($input, $schema->parse($input));
840+
}
841+
842+
public function testParseWithValidUuidV5(): void
843+
{
844+
$input = '960b0533-da17-52d8-a0a4-dd2ab7213caf';
845+
846+
$schema = (new StringSchema())->uuidV5();
847+
848+
self::assertSame($input, $schema->parse($input));
849+
}
850+
755851
public function testParseWithTrim(): void
756852
{
757853
$input = ' test ';

0 commit comments

Comments
 (0)