diff --git a/src/Assert.php b/src/Assert.php index 317cb258..31c861b9 100644 --- a/src/Assert.php +++ b/src/Assert.php @@ -2442,24 +2442,33 @@ public static function uuid(mixed $value, string|callable $message = ''): string { static::string($value, $message); - $originalValue = $value; - $value = \str_replace(['urn:', 'uuid:', '{', '}'], '', $value); + $uuid = '[0-9A-Fa-f]{8}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{12}'; - // The nil UUID is special form of UUID that is specified to have all - // 128 bits set to zero. - if ('00000000-0000-0000-0000-000000000000' === $value) { - return $originalValue; + // URN form as specified by RFC 9562, e.g. "urn:uuid:ff6f8cb0-...". + if (\str_starts_with($value, 'urn:uuid:') && \preg_match('/^urn:uuid:'.$uuid.'$/D', $value)) { + return $value; } - if (!\preg_match('/^[0-9A-Fa-f]{8}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{12}$/D', $value)) { - $message = self::resolveMessage($message); - static::reportInvalidArgument(\sprintf( - $message ?: 'Value %s is not a valid UUID.', - static::valueToString($value) - )); + // "uuid:" prefix, optionally combined with the curly-braced form. + if (\str_starts_with($value, 'uuid:') && \preg_match('/^uuid:(?:'.$uuid.'|\{'.$uuid.'\})$/D', $value)) { + return $value; + } + + // Curly-braced form; the braces must be a matching pair. + if (\str_starts_with($value, '{') && \str_ends_with($value, '}') && \preg_match('/^\{'.$uuid.'\}$/D', $value)) { + return $value; } - return $originalValue; + // Plain form, including the nil UUID with all 128 bits set to zero. + if (\preg_match('/^'.$uuid.'$/D', $value)) { + return $value; + } + + $message = self::resolveMessage($message); + static::reportInvalidArgument(\sprintf( + $message ?: 'Value %s is not a valid UUID.', + static::valueToString($value) + )); } /** diff --git a/tests/AssertTest.php b/tests/AssertTest.php index 6c947c5b..f0ec064d 100644 --- a/tests/AssertTest.php +++ b/tests/AssertTest.php @@ -587,8 +587,9 @@ public static function getTests(): array ['isNonEmptyMap', [[]], false], ['isNonEmptyMap', [[1, 2, 3]], false], ['uuid', ['00000000-0000-0000-0000-000000000000'], true], - ['uuid', ['urn:ff6f8cb0-c57d-21e1-9b21-0800200c9a66'], true], + ['uuid', ['urn:uuid:ff6f8cb0-c57d-21e1-9b21-0800200c9a66'], true], ['uuid', ['uuid:{ff6f8cb0-c57d-21e1-9b21-0800200c9a66}'], true], + ['uuid', ['{ff6f8cb0-c57d-21e1-9b21-0800200c9a66}'], true], ['uuid', ['ff6f8cb0-c57d-21e1-9b21-0800200c9a66'], true], ['uuid', ['ff6f8cb0-c57d-11e1-9b21-0800200c9a66'], true], ['uuid', ['ff6f8cb0-c57d-31e1-9b21-0800200c9a66'], true], @@ -603,6 +604,14 @@ public static function getTests(): array ['uuid', ['ff6f8cb0-c57da-51e1-9b21-0800200c9a66'], false], ['uuid', ['af6f8cb-c57d-11e1-9b21-0800200c9a66'], false], ['uuid', ['3f6f8cb0-c57d-11e1-9b21-0800200c9a6'], false], + ['uuid', ['f{}f6f8cb0-c57d-21e1-9b21-0800200c9a66'], false], + ['uuid', ['ff6f8cb0-c57d-21e1-9b21-08002urn:00c9a66'], false], + ['uuid', ['urn:ff6f8cb0-c57d-21e1-9b21-0800200c9a66uuid:'], false], + ['uuid', ['urn:ff6f8cb0-c57d-21e1-9b21-0800200c9a66'], false], + ['uuid', ['urn:uuid:{ff6f8cb0-c57d-21e1-9b21-0800200c9a66}'], false], + ['uuid', ['{}{}ff6f8cb0-c57d-21e1-9b21-0800200c9a66{}{}'], false], + ['uuid', ['{ff6f8cb0-c57d-21e1-9b21-0800200c9a66'], false], + ['uuid', ['ff6f8cb0-c57d-21e1-9b21-0800200c9a66}'], false], ['throws', [function () { throw new LogicException('test'); }, 'LogicException'], true], ['throws', [function () { throw new LogicException('test'); }, 'IllogicException'], false], ['throws', [function () { throw new Exception('test'); }], true],