From 685c2f3f2316fa49185b54d6b36a2cceab26ac4a Mon Sep 17 00:00:00 2001 From: Ben Ramsey Date: Wed, 14 Sep 2022 20:44:24 -0500 Subject: [PATCH] feat: support max UUIDs --- psalm-baseline.xml | 4 +- src/Guid/Fields.php | 14 +++++-- src/Nonstandard/Fields.php | 5 +++ src/Rfc4122/Fields.php | 13 +++++-- src/Rfc4122/MaxTrait.php | 41 +++++++++++++++++++++ src/Rfc4122/MaxUuid.php | 27 ++++++++++++++ src/Rfc4122/UuidBuilder.php | 5 +++ src/Rfc4122/VariantTrait.php | 6 +++ src/Rfc4122/VersionTrait.php | 7 +++- src/Uuid.php | 8 ++++ tests/ExpectedBehaviorTest.php | 15 ++++++-- tests/Guid/FieldsTest.php | 21 ++++++++++- tests/Nonstandard/FieldsTest.php | 1 + tests/Rfc4122/FieldsTest.php | 23 +++++++++++- tests/Rfc4122/UuidBuilderTest.php | 15 +++++++- tests/Rfc4122/VariantTraitTest.php | 4 ++ tests/UuidTest.php | 49 +++++++++++++++++++++++-- tests/benchmark/UuidGenerationBench.php | 6 +++ 18 files changed, 247 insertions(+), 17 deletions(-) create mode 100644 src/Rfc4122/MaxTrait.php create mode 100644 src/Rfc4122/MaxUuid.php diff --git a/psalm-baseline.xml b/psalm-baseline.xml index 96f8617..85843cc 100644 --- a/psalm-baseline.xml +++ b/psalm-baseline.xml @@ -96,7 +96,9 @@ - + + $this + $this $this $this $this diff --git a/src/Guid/Fields.php b/src/Guid/Fields.php index d8a1a2b..9e47aff 100644 --- a/src/Guid/Fields.php +++ b/src/Guid/Fields.php @@ -17,6 +17,7 @@ namespace Ramsey\Uuid\Guid; use Ramsey\Uuid\Exception\InvalidArgumentException; use Ramsey\Uuid\Fields\SerializableFieldsTrait; use Ramsey\Uuid\Rfc4122\FieldsInterface; +use Ramsey\Uuid\Rfc4122\MaxTrait; use Ramsey\Uuid\Rfc4122\NilTrait; use Ramsey\Uuid\Rfc4122\VariantTrait; use Ramsey\Uuid\Rfc4122\VersionTrait; @@ -44,6 +45,7 @@ use const STR_PAD_LEFT; */ final class Fields implements FieldsInterface { + use MaxTrait; use NilTrait; use SerializableFieldsTrait; use VariantTrait; @@ -149,7 +151,13 @@ final class Fields implements FieldsInterface public function getClockSeq(): Hexadecimal { - $clockSeq = hexdec(bin2hex(substr($this->bytes, 8, 2))) & 0x3fff; + if ($this->isMax()) { + $clockSeq = 0xffff; + } elseif ($this->isNil()) { + $clockSeq = 0x0000; + } else { + $clockSeq = hexdec(bin2hex(substr($this->bytes, 8, 2))) & 0x3fff; + } return new Hexadecimal(str_pad(dechex($clockSeq), 4, '0', STR_PAD_LEFT)); } @@ -171,7 +179,7 @@ final class Fields implements FieldsInterface public function getVersion(): ?int { - if ($this->isNil()) { + if ($this->isNil() || $this->isMax()) { return null; } @@ -183,7 +191,7 @@ final class Fields implements FieldsInterface private function isCorrectVariant(): bool { - if ($this->isNil()) { + if ($this->isNil() || $this->isMax()) { return true; } diff --git a/src/Nonstandard/Fields.php b/src/Nonstandard/Fields.php index 927bc6a..d5b820f 100644 --- a/src/Nonstandard/Fields.php +++ b/src/Nonstandard/Fields.php @@ -130,4 +130,9 @@ final class Fields implements FieldsInterface { return false; } + + public function isMax(): bool + { + return false; + } } diff --git a/src/Rfc4122/Fields.php b/src/Rfc4122/Fields.php index 6103cdc..0a6ece5 100644 --- a/src/Rfc4122/Fields.php +++ b/src/Rfc4122/Fields.php @@ -40,6 +40,7 @@ use const STR_PAD_LEFT; */ final class Fields implements FieldsInterface { + use MaxTrait; use NilTrait; use SerializableFieldsTrait; use VariantTrait; @@ -88,7 +89,13 @@ final class Fields implements FieldsInterface public function getClockSeq(): Hexadecimal { - $clockSeq = hexdec(bin2hex(substr($this->bytes, 8, 2))) & 0x3fff; + if ($this->isMax()) { + $clockSeq = 0xffff; + } elseif ($this->isNil()) { + $clockSeq = 0x0000; + } else { + $clockSeq = hexdec(bin2hex(substr($this->bytes, 8, 2))) & 0x3fff; + } return new Hexadecimal(str_pad(dechex($clockSeq), 4, '0', STR_PAD_LEFT)); } @@ -184,7 +191,7 @@ final class Fields implements FieldsInterface public function getVersion(): ?int { - if ($this->isNil()) { + if ($this->isNil() || $this->isMax()) { return null; } @@ -196,7 +203,7 @@ final class Fields implements FieldsInterface private function isCorrectVariant(): bool { - if ($this->isNil()) { + if ($this->isNil() || $this->isMax()) { return true; } diff --git a/src/Rfc4122/MaxTrait.php b/src/Rfc4122/MaxTrait.php new file mode 100644 index 0000000..2ec3047 --- /dev/null +++ b/src/Rfc4122/MaxTrait.php @@ -0,0 +1,41 @@ + + * @license http://opensource.org/licenses/MIT MIT + */ + +declare(strict_types=1); + +namespace Ramsey\Uuid\Rfc4122; + +/** + * Provides common functionality for max UUIDs + * + * The max UUID is special form of UUID that is specified to have all 128 bits + * set to one. It is the inverse of the nil UUID. + * + * @link https://datatracker.ietf.org/doc/html/draft-peabody-dispatch-new-uuid-format-04#section-5.4 Max UUID + * + * @psalm-immutable + */ +trait MaxTrait +{ + /** + * Returns the bytes that comprise the fields + */ + abstract public function getBytes(): string; + + /** + * Returns true if the byte string represents a max UUID + */ + public function isMax(): bool + { + return $this->getBytes() === "\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff"; + } +} diff --git a/src/Rfc4122/MaxUuid.php b/src/Rfc4122/MaxUuid.php new file mode 100644 index 0000000..e5ffa72 --- /dev/null +++ b/src/Rfc4122/MaxUuid.php @@ -0,0 +1,27 @@ + + * @license http://opensource.org/licenses/MIT MIT + */ + +declare(strict_types=1); + +namespace Ramsey\Uuid\Rfc4122; + +use Ramsey\Uuid\Uuid; + +/** + * The max UUID is special form of UUID that is specified to have all 128 bits + * set to one + * + * @psalm-immutable + */ +final class MaxUuid extends Uuid implements UuidInterface +{ +} diff --git a/src/Rfc4122/UuidBuilder.php b/src/Rfc4122/UuidBuilder.php index 10eded9..895524d 100644 --- a/src/Rfc4122/UuidBuilder.php +++ b/src/Rfc4122/UuidBuilder.php @@ -73,12 +73,17 @@ class UuidBuilder implements UuidBuilderInterface public function build(CodecInterface $codec, string $bytes): UuidInterface { try { + /** @var Fields $fields */ $fields = $this->buildFields($bytes); if ($fields->isNil()) { return new NilUuid($fields, $this->numberConverter, $codec, $this->timeConverter); } + if ($fields->isMax()) { + return new MaxUuid($fields, $this->numberConverter, $codec, $this->timeConverter); + } + switch ($fields->getVersion()) { case Uuid::UUID_TYPE_TIME: return new UuidV1($fields, $this->numberConverter, $codec, $this->timeConverter); diff --git a/src/Rfc4122/VariantTrait.php b/src/Rfc4122/VariantTrait.php index 4c98165..74d12ef 100644 --- a/src/Rfc4122/VariantTrait.php +++ b/src/Rfc4122/VariantTrait.php @@ -58,6 +58,12 @@ trait VariantTrait throw new InvalidBytesException('Invalid number of bytes'); } + if ($this->isMax() || $this->isNil()) { + // RFC 4122 defines these special types of UUID, so we will consider + // them as belonging to the RFC 4122 variant. + return Uuid::RFC_4122; + } + /** @var array $parts */ $parts = unpack('n*', $this->getBytes()); diff --git a/src/Rfc4122/VersionTrait.php b/src/Rfc4122/VersionTrait.php index b65ca7b..fb3e3e5 100644 --- a/src/Rfc4122/VersionTrait.php +++ b/src/Rfc4122/VersionTrait.php @@ -28,6 +28,11 @@ trait VersionTrait */ abstract public function getVersion(): ?int; + /** + * Returns true if these fields represent a max UUID + */ + abstract public function isMax(): bool; + /** * Returns true if these fields represent a nil UUID */ @@ -40,7 +45,7 @@ trait VersionTrait */ private function isCorrectVersion(): bool { - if ($this->isNil()) { + if ($this->isNil() || $this->isMax()) { return true; } diff --git a/src/Uuid.php b/src/Uuid.php index 0397de1..171d126 100644 --- a/src/Uuid.php +++ b/src/Uuid.php @@ -84,6 +84,14 @@ class Uuid implements UuidInterface */ public const NIL = '00000000-0000-0000-0000-000000000000'; + /** + * The max UUID is a special form of UUID that is specified to have all 128 + * bits set to one + * + * @link https://datatracker.ietf.org/doc/html/draft-peabody-dispatch-new-uuid-format-04#section-5.4 Max UUID + */ + public const MAX = 'ffffffff-ffff-ffff-ffff-ffffffffffff'; + /** * Variant: reserved, NCS backward compatibility * diff --git a/tests/ExpectedBehaviorTest.php b/tests/ExpectedBehaviorTest.php index 82ae7eb..52a6d1e 100644 --- a/tests/ExpectedBehaviorTest.php +++ b/tests/ExpectedBehaviorTest.php @@ -198,6 +198,7 @@ class ExpectedBehaviorTest extends TestCase // Non RFC 4122 UUIDs ['ffffffff-ffff-ffff-ffff-ffffffffffff', true], + ['00000000-0000-0000-0000-000000000000', true], ['ff6f8cb0-c57d-01e1-0b21-0800200c9a66', true], ['ff6f8cb0-c57d-01e1-1b21-0800200c9a66', true], ['ff6f8cb0-c57d-01e1-2b21-0800200c9a66', true], @@ -254,7 +255,12 @@ class ExpectedBehaviorTest extends TestCase $value = BigInteger::fromBase($value, 16)->__toString(); }); - $clockSeq = (int) $components[3] & 0x3fff; + if (strtolower($string) === Uuid::MAX) { + $clockSeq = (int) $components[3]; + } else { + $clockSeq = (int) $components[3] & 0x3fff; + } + $clockSeqHiAndReserved = (int) $components[3] >> 8; $clockSeqLow = (int) $components[3] & 0x00ff; @@ -349,7 +355,7 @@ class ExpectedBehaviorTest extends TestCase public function provideFromStringInteger() { return [ - ['00000000-0000-0000-0000-000000000000', null, 0, '0'], + ['00000000-0000-0000-0000-000000000000', null, 2, '0'], ['ff6f8cb0-c57d-11e1-8b21-0800200c9a66', 1, 2, '339532337419071774304650190139318639206'], ['ff6f8cb0-c57d-11e1-9b21-0800200c9a66', 1, 2, '339532337419071774305803111643925486182'], ['ff6f8cb0-c57d-11e1-ab21-0800200c9a66', 1, 2, '339532337419071774306956033148532333158'], @@ -382,7 +388,7 @@ class ExpectedBehaviorTest extends TestCase ['ff6f8cb0-c57d-01e1-db21-0800200c9a66', null, 6, '339532337419071698752551071748029454950'], ['ff6f8cb0-c57d-01e1-eb21-0800200c9a66', null, 7, '339532337419071698753703993252636301926'], ['ff6f8cb0-c57d-01e1-fb21-0800200c9a66', null, 7, '339532337419071698754856914757243148902'], - ['ffffffff-ffff-ffff-ffff-ffffffffffff', null, 7, '340282366920938463463374607431768211455'], + ['ffffffff-ffff-ffff-ffff-ffffffffffff', null, 2, '340282366920938463463374607431768211455'], ]; } @@ -641,6 +647,7 @@ class ExpectedBehaviorTest extends TestCase ['NAMESPACE_OID', '6ba7b812-9dad-11d1-80b4-00c04fd430c8'], ['NAMESPACE_X500', '6ba7b814-9dad-11d1-80b4-00c04fd430c8'], ['NIL', '00000000-0000-0000-0000-000000000000'], + ['MAX', 'ffffffff-ffff-ffff-ffff-ffffffffffff'], ['RESERVED_NCS', 0], ['RFC_4122', 2], ['RESERVED_MICROSOFT', 6], @@ -651,6 +658,8 @@ class ExpectedBehaviorTest extends TestCase ['UUID_TYPE_HASH_MD5', 3], ['UUID_TYPE_RANDOM', 4], ['UUID_TYPE_HASH_SHA1', 5], + ['UUID_TYPE_REORDERED_TIME', 6], + ['UUID_TYPE_UNIX_TIME', 7], ]; } } diff --git a/tests/Guid/FieldsTest.php b/tests/Guid/FieldsTest.php index d0f0689..f3bd810 100644 --- a/tests/Guid/FieldsTest.php +++ b/tests/Guid/FieldsTest.php @@ -139,6 +139,7 @@ class FieldsTest extends TestCase ['b08c6fff7dc5e111cb210800200c9a66', 'getVariant', 6], ['b08c6fff7dc5e111cb210800200c9a66', 'getVersion', 1], ['b08c6fff7dc5e111cb210800200c9a66', 'isNil', false], + ['b08c6fff7dc5e111cb210800200c9a66', 'isMax', false], // For ff6f8cb0-c57d-41e1-db21-0800200c9a66 ['b08c6fff7dc5e141db210800200c9a66', 'getClockSeq', '1b21'], @@ -152,6 +153,7 @@ class FieldsTest extends TestCase ['b08c6fff7dc5e141db210800200c9a66', 'getVariant', 6], ['b08c6fff7dc5e141db210800200c9a66', 'getVersion', 4], ['b08c6fff7dc5e141db210800200c9a66', 'isNil', false], + ['b08c6fff7dc5e141db210800200c9a66', 'isMax', false], // For ff6f8cb0-c57d-31e1-8b21-0800200c9a66 ['b08c6fff7dc5e1318b210800200c9a66', 'getClockSeq', '0b21'], @@ -165,6 +167,7 @@ class FieldsTest extends TestCase ['b08c6fff7dc5e1318b210800200c9a66', 'getVariant', 2], ['b08c6fff7dc5e1318b210800200c9a66', 'getVersion', 3], ['b08c6fff7dc5e1318b210800200c9a66', 'isNil', false], + ['b08c6fff7dc5e1318b210800200c9a66', 'isMax', false], // For ff6f8cb0-c57d-51e1-9b21-0800200c9a66 ['b08c6fff7dc5e1519b210800200c9a66', 'getClockSeq', '1b21'], @@ -178,6 +181,7 @@ class FieldsTest extends TestCase ['b08c6fff7dc5e1519b210800200c9a66', 'getVariant', 2], ['b08c6fff7dc5e1519b210800200c9a66', 'getVersion', 5], ['b08c6fff7dc5e1519b210800200c9a66', 'isNil', false], + ['b08c6fff7dc5e1519b210800200c9a66', 'isMax', false], // For 00000000-0000-0000-0000-000000000000 ['00000000000000000000000000000000', 'getClockSeq', '0000'], @@ -188,9 +192,24 @@ class FieldsTest extends TestCase ['00000000000000000000000000000000', 'getTimeLow', '00000000'], ['00000000000000000000000000000000', 'getTimeMid', '0000'], ['00000000000000000000000000000000', 'getTimestamp', '000000000000000'], - ['00000000000000000000000000000000', 'getVariant', 0], + ['00000000000000000000000000000000', 'getVariant', 2], ['00000000000000000000000000000000', 'getVersion', null], ['00000000000000000000000000000000', 'isNil', true], + ['00000000000000000000000000000000', 'isMax', false], + + // For ffffffff-ffff-ffff-ffff-ffffffffffff + ['ffffffffffffffffffffffffffffffff', 'getClockSeq', 'ffff'], + ['ffffffffffffffffffffffffffffffff', 'getClockSeqHiAndReserved', 'ff'], + ['ffffffffffffffffffffffffffffffff', 'getClockSeqLow', 'ff'], + ['ffffffffffffffffffffffffffffffff', 'getNode', 'ffffffffffff'], + ['ffffffffffffffffffffffffffffffff', 'getTimeHiAndVersion', 'ffff'], + ['ffffffffffffffffffffffffffffffff', 'getTimeLow', 'ffffffff'], + ['ffffffffffffffffffffffffffffffff', 'getTimeMid', 'ffff'], + ['ffffffffffffffffffffffffffffffff', 'getTimestamp', 'fffffffffffffff'], + ['ffffffffffffffffffffffffffffffff', 'getVariant', 2], + ['ffffffffffffffffffffffffffffffff', 'getVersion', null], + ['ffffffffffffffffffffffffffffffff', 'isNil', false], + ['ffffffffffffffffffffffffffffffff', 'isMax', true], ]; } diff --git a/tests/Nonstandard/FieldsTest.php b/tests/Nonstandard/FieldsTest.php index bb51fcc..b0be7ad 100644 --- a/tests/Nonstandard/FieldsTest.php +++ b/tests/Nonstandard/FieldsTest.php @@ -63,6 +63,7 @@ class FieldsTest extends TestCase ['ff6f8cb0-c57d-91e1-0b21-0800200c9a66', 'getVariant', Uuid::RESERVED_NCS], ['ff6f8cb0-c57d-91e1-0b21-0800200c9a66', 'getVersion', null], ['ff6f8cb0-c57d-91e1-0b21-0800200c9a66', 'isNil', false], + ['ff6f8cb0-c57d-91e1-0b21-0800200c9a66', 'isMax', false], ]; } diff --git a/tests/Rfc4122/FieldsTest.php b/tests/Rfc4122/FieldsTest.php index 97f7497..125575c 100644 --- a/tests/Rfc4122/FieldsTest.php +++ b/tests/Rfc4122/FieldsTest.php @@ -130,6 +130,7 @@ class FieldsTest extends TestCase ['ff6f8cb0-c57d-11e1-9b21-0800200c9a66', 'getVariant', 2], ['ff6f8cb0-c57d-11e1-9b21-0800200c9a66', 'getVersion', 1], ['ff6f8cb0-c57d-11e1-9b21-0800200c9a66', 'isNil', false], + ['ff6f8cb0-c57d-11e1-9b21-0800200c9a66', 'isMax', false], ['ff6f8cb0-c57d-41e1-ab21-0800200c9a66', 'getClockSeq', '2b21'], ['ff6f8cb0-c57d-41e1-ab21-0800200c9a66', 'getClockSeqHiAndReserved', 'ab'], @@ -142,6 +143,7 @@ class FieldsTest extends TestCase ['ff6f8cb0-c57d-41e1-ab21-0800200c9a66', 'getVariant', 2], ['ff6f8cb0-c57d-41e1-ab21-0800200c9a66', 'getVersion', 4], ['ff6f8cb0-c57d-41e1-ab21-0800200c9a66', 'isNil', false], + ['ff6f8cb0-c57d-41e1-ab21-0800200c9a66', 'isMax', false], ['ff6f8cb0-c57d-31e1-bb21-0800200c9a66', 'getClockSeq', '3b21'], ['ff6f8cb0-c57d-31e1-bb21-0800200c9a66', 'getClockSeqHiAndReserved', 'bb'], @@ -154,6 +156,7 @@ class FieldsTest extends TestCase ['ff6f8cb0-c57d-31e1-bb21-0800200c9a66', 'getVariant', 2], ['ff6f8cb0-c57d-31e1-bb21-0800200c9a66', 'getVersion', 3], ['ff6f8cb0-c57d-31e1-bb21-0800200c9a66', 'isNil', false], + ['ff6f8cb0-c57d-31e1-bb21-0800200c9a66', 'isMax', false], ['ff6f8cb0-c57d-51e1-8b21-0800200c9a66', 'getClockSeq', '0b21'], ['ff6f8cb0-c57d-51e1-8b21-0800200c9a66', 'getClockSeqHiAndReserved', '8b'], @@ -166,6 +169,7 @@ class FieldsTest extends TestCase ['ff6f8cb0-c57d-51e1-8b21-0800200c9a66', 'getVariant', 2], ['ff6f8cb0-c57d-51e1-8b21-0800200c9a66', 'getVersion', 5], ['ff6f8cb0-c57d-51e1-8b21-0800200c9a66', 'isNil', false], + ['ff6f8cb0-c57d-51e1-8b21-0800200c9a66', 'isMax', false], ['ff6f8cb0-c57d-61e1-8b21-0800200c9a66', 'getClockSeq', '0b21'], ['ff6f8cb0-c57d-61e1-8b21-0800200c9a66', 'getClockSeqHiAndReserved', '8b'], @@ -178,6 +182,7 @@ class FieldsTest extends TestCase ['ff6f8cb0-c57d-61e1-8b21-0800200c9a66', 'getVariant', 2], ['ff6f8cb0-c57d-61e1-8b21-0800200c9a66', 'getVersion', 6], ['ff6f8cb0-c57d-61e1-8b21-0800200c9a66', 'isNil', false], + ['ff6f8cb0-c57d-61e1-8b21-0800200c9a66', 'isMax', false], ['00000000-0000-0000-0000-000000000000', 'getClockSeq', '0000'], ['00000000-0000-0000-0000-000000000000', 'getClockSeqHiAndReserved', '00'], @@ -187,9 +192,23 @@ class FieldsTest extends TestCase ['00000000-0000-0000-0000-000000000000', 'getTimeLow', '00000000'], ['00000000-0000-0000-0000-000000000000', 'getTimeMid', '0000'], ['00000000-0000-0000-0000-000000000000', 'getTimestamp', '000000000000000'], - ['00000000-0000-0000-0000-000000000000', 'getVariant', 0], + ['00000000-0000-0000-0000-000000000000', 'getVariant', 2], ['00000000-0000-0000-0000-000000000000', 'getVersion', null], ['00000000-0000-0000-0000-000000000000', 'isNil', true], + ['00000000-0000-0000-0000-000000000000', 'isMax', false], + + ['ffffffff-ffff-ffff-ffff-ffffffffffff', 'getClockSeq', 'ffff'], + ['ffffffff-ffff-ffff-ffff-ffffffffffff', 'getClockSeqHiAndReserved', 'ff'], + ['ffffffff-ffff-ffff-ffff-ffffffffffff', 'getClockSeqLow', 'ff'], + ['ffffffff-ffff-ffff-ffff-ffffffffffff', 'getNode', 'ffffffffffff'], + ['ffffffff-ffff-ffff-ffff-ffffffffffff', 'getTimeHiAndVersion', 'ffff'], + ['ffffffff-ffff-ffff-ffff-ffffffffffff', 'getTimeLow', 'ffffffff'], + ['ffffffff-ffff-ffff-ffff-ffffffffffff', 'getTimeMid', 'ffff'], + ['ffffffff-ffff-ffff-ffff-ffffffffffff', 'getTimestamp', 'fffffffffffffff'], + ['ffffffff-ffff-ffff-ffff-ffffffffffff', 'getVariant', 2], + ['ffffffff-ffff-ffff-ffff-ffffffffffff', 'getVersion', null], + ['ffffffff-ffff-ffff-ffff-ffffffffffff', 'isNil', false], + ['ffffffff-ffff-ffff-ffff-ffffffffffff', 'isMax', true], ['000001f5-5cde-21ea-8400-0242ac130003', 'getClockSeq', '0400'], ['000001f5-5cde-21ea-8400-0242ac130003', 'getClockSeqHiAndReserved', '84'], @@ -202,6 +221,7 @@ class FieldsTest extends TestCase ['000001f5-5cde-21ea-8400-0242ac130003', 'getVariant', 2], ['000001f5-5cde-21ea-8400-0242ac130003', 'getVersion', 2], ['000001f5-5cde-21ea-8400-0242ac130003', 'isNil', false], + ['000001f5-5cde-21ea-8400-0242ac130003', 'isMax', false], ['018339f0-1b83-71e1-9b21-0800200c9a66', 'getClockSeq', '1b21'], ['018339f0-1b83-71e1-9b21-0800200c9a66', 'getClockSeqHiAndReserved', '9b'], @@ -214,6 +234,7 @@ class FieldsTest extends TestCase ['018339f0-1b83-71e1-9b21-0800200c9a66', 'getVariant', 2], ['018339f0-1b83-71e1-9b21-0800200c9a66', 'getVersion', 7], ['018339f0-1b83-71e1-9b21-0800200c9a66', 'isNil', false], + ['018339f0-1b83-71e1-9b21-0800200c9a66', 'isMax', false], ]; } diff --git a/tests/Rfc4122/UuidBuilderTest.php b/tests/Rfc4122/UuidBuilderTest.php index bf8a816..07f2d07 100644 --- a/tests/Rfc4122/UuidBuilderTest.php +++ b/tests/Rfc4122/UuidBuilderTest.php @@ -13,6 +13,8 @@ use Ramsey\Uuid\Math\BrickMathCalculator; use Ramsey\Uuid\Nonstandard\UuidV6 as NonstandardUuidV6; use Ramsey\Uuid\Rfc4122\Fields; use Ramsey\Uuid\Rfc4122\FieldsInterface; +use Ramsey\Uuid\Rfc4122\MaxUuid; +use Ramsey\Uuid\Rfc4122\NilUuid; use Ramsey\Uuid\Rfc4122\UuidBuilder; use Ramsey\Uuid\Rfc4122\UuidV1; use Ramsey\Uuid\Rfc4122\UuidV2; @@ -33,7 +35,7 @@ class UuidBuilderTest extends TestCase * * @dataProvider provideBuildTestValues */ - public function testBuild(string $uuid, string $expectedClass, int $expectedVersion): void + public function testBuild(string $uuid, string $expectedClass, ?int $expectedVersion): void { $bytes = (string) hex2bin(str_replace('-', '', $uuid)); @@ -58,6 +60,16 @@ class UuidBuilderTest extends TestCase public function provideBuildTestValues(): array { return [ + [ + 'uuid' => '00000000-0000-0000-0000-000000000000', + 'expectedClass' => NilUuid::class, + 'expectedVersion' => null, + ], + [ + 'uuid' => 'ffffffff-ffff-ffff-ffff-ffffffffffff', + 'expectedClass' => MaxUuid::class, + 'expectedVersion' => null, + ], [ 'uuid' => 'ff6f8cb0-c57d-11e1-9b21-0800200c9a66', 'expectedClass' => UuidV1::class, @@ -127,6 +139,7 @@ class UuidBuilderTest extends TestCase { $fields = Mockery::mock(FieldsInterface::class, [ 'isNil' => false, + 'isMax' => false, 'getVersion' => 255, ]); diff --git a/tests/Rfc4122/VariantTraitTest.php b/tests/Rfc4122/VariantTraitTest.php index c0e2593..8883624 100644 --- a/tests/Rfc4122/VariantTraitTest.php +++ b/tests/Rfc4122/VariantTraitTest.php @@ -23,6 +23,8 @@ class VariantTraitTest extends TestCase /** @var Fields $trait */ $trait = Mockery::mock(VariantTrait::class, [ 'getBytes' => $bytes, + 'isMax' => false, + 'isNil' => false, ]); $this->expectException(InvalidBytesException::class); @@ -52,6 +54,8 @@ class VariantTraitTest extends TestCase /** @var Fields $trait */ $trait = Mockery::mock(VariantTrait::class, [ 'getBytes' => $bytes, + 'isMax' => false, + 'isNil' => false, ]); $this->assertSame($expectedVariant, $trait->getVariant()); diff --git a/tests/UuidTest.php b/tests/UuidTest.php index 5f71905..06dbb4d 100644 --- a/tests/UuidTest.php +++ b/tests/UuidTest.php @@ -29,6 +29,7 @@ use Ramsey\Uuid\Guid\Guid; use Ramsey\Uuid\Lazy\LazyUuidFromString; use Ramsey\Uuid\Provider\Node\RandomNodeProvider; use Ramsey\Uuid\Provider\Time\FixedTimeProvider; +use Ramsey\Uuid\Rfc4122\Fields; use Ramsey\Uuid\Rfc4122\FieldsInterface; use Ramsey\Uuid\Rfc4122\UuidV1; use Ramsey\Uuid\Type\Hexadecimal; @@ -146,6 +147,30 @@ class UuidTest extends TestCase $this->assertInstanceOf(LazyUuidFromString::class, Uuid::fromString('FF6F8CB0-C57D-11E1-9B21-0800200C9A66')); } + public function testFromStringWithNilUuid(): void + { + $uuid = Uuid::fromString(Uuid::NIL); + + /** @var Fields $fields */ + $fields = $uuid->getFields(); + + $this->assertSame('00000000-0000-0000-0000-000000000000', $uuid->toString()); + $this->assertTrue($fields->isNil()); + $this->assertFalse($fields->isMax()); + } + + public function testFromStringWithMaxUuid(): void + { + $uuid = Uuid::fromString(Uuid::MAX); + + /** @var Fields $fields */ + $fields = $uuid->getFields(); + + $this->assertSame('ffffffff-ffff-ffff-ffff-ffffffffffff', $uuid->toString()); + $this->assertFalse($fields->isNil()); + $this->assertTrue($fields->isMax()); + } + public function testGetBytes(): void { $uuid = Uuid::fromString('ff6f8cb0-c57d-11e1-9b21-0800200c9a66'); @@ -1297,7 +1322,13 @@ class UuidTest extends TestCase 'urn' => 'urn:uuid:00000000-0000-0000-0000-000000000000', 'time' => '0', 'clock_seq' => '0000', - 'variant' => Uuid::RESERVED_NCS, + // This is a departure from the Python tests. The Python tests + // are technically "correct" because all bits are set to zero, + // so it stands to reason that the variant is also zero, but + // that leads to this being considered a "Reserved NCS" variant, + // and that is not the case. RFC 4122 defines this special UUID, + // so it is an RFC 4122 variant. + 'variant' => Uuid::RFC_4122, 'version' => null, ], [ @@ -1556,8 +1587,20 @@ class UuidTest extends TestCase ], 'urn' => 'urn:uuid:ffffffff-ffff-ffff-ffff-ffffffffffff', 'time' => 'fffffffffffffff', - 'clock_seq' => '3fff', - 'variant' => Uuid::RESERVED_FUTURE, + // This is a departure from the Python tests. The Python tests + // are technically "correct" because all bits are set to one, + // which ends up calculating the variant as 7, or "Reserved + // Future," but that is not the case, and now that max UUIDs + // are defined as a special type, within the RFC 4122 variant + // rules, we also consider it an RFC 4122 variant. + // + // Similarly, Python's tests think the clock sequence should be + // 0x3fff because of the bit shifting performed on this field. + // However, since all the bits in this UUID are defined as being + // set to one, we will consider the clock sequence as 0xffff, + // which all bits set to one. + 'clock_seq' => 'ffff', + 'variant' => Uuid::RFC_4122, 'version' => null, ], ]; diff --git a/tests/benchmark/UuidGenerationBench.php b/tests/benchmark/UuidGenerationBench.php index c3838e1..b474993 100644 --- a/tests/benchmark/UuidGenerationBench.php +++ b/tests/benchmark/UuidGenerationBench.php @@ -14,6 +14,7 @@ declare(strict_types=1); namespace Ramsey\Uuid\Benchmark; +use DateTimeImmutable; use Ramsey\Uuid\Provider\Node\StaticNodeProvider; use Ramsey\Uuid\Type\Hexadecimal; use Ramsey\Uuid\Type\Integer as IntegerIdentifier; @@ -104,4 +105,9 @@ final class UuidGenerationBench { Uuid::uuid7(); } + + public function benchUuid7GenerationWithDateTime(): void + { + Uuid::uuid7(new DateTimeImmutable('@1663203901.667000')); + } }