Skip to content

Commit 3121516

Browse files
committed
Add StringUuidV7 type with strict UUID v7 validation and comprehensive tests
- Introduced `StringUuidV7` class to validate and normalize UUID v7 values (Unix time-based, RFC 4122). - Added detailed error messages for invalid formats, incorrect versions, empty values, or invalid variants. - Implemented comprehensive unit tests covering valid UUID preservation, normalization, and various error scenarios.
1 parent bb2ba13 commit 3121516

File tree

2 files changed

+106
-0
lines changed

2 files changed

+106
-0
lines changed

src/String/StringUuidV7.php

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace PhpTypedValues\String;
6+
7+
use PhpTypedValues\Code\Exception\StringTypeException;
8+
use PhpTypedValues\Code\String\StrType;
9+
10+
use function preg_match;
11+
use function sprintf;
12+
use function strtolower;
13+
14+
/**
15+
* UUID version 7 (time-ordered, Unix time-based).
16+
*
17+
* @psalm-immutable
18+
*/
19+
readonly class StringUuidV7 extends StrType
20+
{
21+
/** @var non-empty-string */
22+
protected string $value;
23+
24+
/**
25+
* @throws StringTypeException
26+
*/
27+
public function __construct(string $value)
28+
{
29+
// Normalize to lowercase for consistent representation
30+
$normalized = strtolower($value);
31+
32+
if ($normalized === '') {
33+
// Distinct message for empty input
34+
throw new StringTypeException(sprintf('Expected non-empty UUID v7 (xxxxxxxx-xxxx-7xxx-[89ab]xxx-xxxxxxxxxxxx), got "%s"', $value));
35+
}
36+
37+
// RFC 4122-style UUID v7:
38+
// xxxxxxxx-xxxx-7xxx-[89ab]xxx-xxxxxxxxxxxx (hex, case-insensitive)
39+
if (preg_match('/^[0-9a-f]{8}-[0-9a-f]{4}-7[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/', $normalized) !== 1) {
40+
throw new StringTypeException(sprintf('Expected UUID v7 (xxxxxxxx-xxxx-7xxx-[89ab]xxx-xxxxxxxxxxxx), got "%s"', $value));
41+
}
42+
43+
$this->value = $normalized;
44+
}
45+
46+
/**
47+
* @throws StringTypeException
48+
*/
49+
public static function fromString(string $value): static
50+
{
51+
return new static($value);
52+
}
53+
54+
/** @return non-empty-string */
55+
public function value(): string
56+
{
57+
return $this->value;
58+
}
59+
}
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
use PhpTypedValues\Code\Exception\StringTypeException;
6+
use PhpTypedValues\String\StringUuidV7;
7+
8+
it('accepts a valid lowercase UUID v7 and preserves value', function (): void {
9+
// Matches: xxxxxxxx-xxxx-7xxx-[89ab]xxx-xxxxxxxxxxxx
10+
$uuid = '01890f2a-5bcd-7def-8abc-1234567890ab';
11+
$s = new StringUuidV7($uuid);
12+
13+
expect($s->value())->toBe($uuid)
14+
->and($s->toString())->toBe($uuid);
15+
});
16+
17+
it('normalizes uppercase input to lowercase while preserving the UUID semantics', function (): void {
18+
$upper = '01890F2A-5BCD-7DEF-9ABC-1234567890AB';
19+
$s = StringUuidV7::fromString($upper);
20+
21+
expect($s->value())->toBe('01890f2a-5bcd-7def-9abc-1234567890ab')
22+
->and($s->toString())->toBe('01890f2a-5bcd-7def-9abc-1234567890ab');
23+
});
24+
25+
it('throws on empty string', function (): void {
26+
expect(fn() => new StringUuidV7(''))
27+
->toThrow(StringTypeException::class, 'Expected non-empty UUID v7 (xxxxxxxx-xxxx-7xxx-[89ab]xxx-xxxxxxxxxxxx), got ""');
28+
});
29+
30+
it('throws when UUID version is not 7 (e.g., version 4)', function (): void {
31+
$v4 = '550e8400-e29b-41d4-a716-446655440000';
32+
expect(fn() => StringUuidV7::fromString($v4))
33+
->toThrow(StringTypeException::class, 'Expected UUID v7 (xxxxxxxx-xxxx-7xxx-[89ab]xxx-xxxxxxxxxxxx), got "' . $v4 . '"');
34+
});
35+
36+
it('throws when UUID variant nibble is invalid (must be 8,9,a,b)', function (): void {
37+
// Fourth group starts with '7' which is invalid for RFC 4122 variant
38+
$badVariant = '550e8400-e29b-7d14-7716-446655440000';
39+
expect(fn() => new StringUuidV7($badVariant))
40+
->toThrow(StringTypeException::class, 'Expected UUID v7 (xxxxxxxx-xxxx-7xxx-[89ab]xxx-xxxxxxxxxxxx), got "' . $badVariant . '"');
41+
});
42+
43+
it('throws on invalid characters or format (non-hex character)', function (): void {
44+
$badChar = '01890f2a-5bcd-7def-8abc-1234567890ag';
45+
expect(fn() => StringUuidV7::fromString($badChar))
46+
->toThrow(StringTypeException::class, 'Expected UUID v7 (xxxxxxxx-xxxx-7xxx-[89ab]xxx-xxxxxxxxxxxx), got "' . $badChar . '"');
47+
});

0 commit comments

Comments
 (0)