master bd064471e6a 1/2: New package uuid.el

Eli Zaretskii <[email protected]>
Newsgroups gmane.emacs.diffs
Message-ID <[email protected]>
branch: master
commit bd064471e6aaf6348f52e66c3d34d7050695f21b
Author: Andrew Hyatt <[email protected]>
Commit: Eli Zaretskii <[email protected]>

    New package uuid.el
    
    * lisp/emacs-lisp/uuid.el: New file.
    * test/lisp/emacs-lisp/uuid-tests.el: New tests.
    * doc/lispref/processes.texi (UUIDs): New section.
---
 doc/lispref/processes.texi         | 152 +++++++++++++++++++++++
 lisp/emacs-lisp/uuid.el            | 245 +++++++++++++++++++++++++++++++++++++
 test/lisp/emacs-lisp/uuid-tests.el | 175 ++++++++++++++++++++++++++
 3 files changed, 572 insertions(+)

diff --git a/doc/lispref/processes.texi b/doc/lispref/processes.texi
index b340067592f..bc4fcdbe27a 100644
--- a/doc/lispref/processes.texi
+++ b/doc/lispref/processes.texi
@@ -68,6 +68,7 @@ Processes}.
 * Misc Network::             Additional relevant functions for net connections.
 * Serial Ports::             Communicating with serial ports.
 * Byte Packing::             Using bindat to pack and unpack binary data.
+* UUIDs::                    Generating and converting UUIDs.
 @end menu
 
 @node Subprocess Creation
@@ -3883,3 +3884,154 @@ arguments @var{args}.  Its behavior follows that of @code{defmacro},
 which the important difference that the new forms can only be used
 within Bindat type expressions.
 @end defmac
+
+@node UUIDs
+@section UUIDs
+@cindex uuids
+
+  Emacs Lisp can generate and parse the most popular UUID variants to
+and from strings and binary.  It can generate UUIDv4, UUIDv5 and UUIDv7
+according to RFC 9562.  UUIDv4 is a randomly generated identifier,
+UUIDv5 is an identifier generated from a standard namespace and a name,
+and UUIDv7 is a combination of a timestamp and random identifier, with
+the advantage of having good index performance in databases.
+
+All UUIDs are 16 byte identifiers with a common string representation,
+consisting of hex digits and dashes of a predetermined length, such as
+@samp{919108f7-52d1-4320-9bac-f847db4148a8}.
+
+@menu
+* UUID Generation::
+* UUID Conversion::
+* Special UUID values::
+@end menu
+
+@node UUID Generation
+@subsection UUID Generation
+@cindex uuid generation
+
+  To generate UUIDs, call @code{uuid-v4}, @code{uuid-v5} or
+@code{uuid-v7}.  UUID v4 and v7 will by default use the normal elisp
+@dfn{random} for random numbers, but a different random number generator
+can be passed in, as long as it can be called with an arg of the end of
+the range of acceptable integers to generate (exclusive), and returns an
+integer.
+
+@defun uuid-v4 &key rng
+Return a @code{uuid-v4} Lisp object representing a new UUIDv4.
+@var{rng}, if provided, is a function that will, when called with a
+numeric argument, return a random number between 0 and that number
+(exclusive).
+@end defun
+
+@defun uuid-v7 &key rng
+Similar to @code{uuid-v4} but returns a @code{uuid-v7}, which is
+generated with a timestamp in addition to random numbers (generated from
+@var{rng} in a similar manner as @code{uuid-v4}).
+@end defun
+
+These use random numbers, which should be cryptographically secure.
+@xref{Random Numbers}, but generally these random numbers are not
+sufficient to generate sufficiently random UUIDs.  For private uses, the
+default behavior should be sufficient, but for professional uses,
+@var{rng} should be used, and provide cryptographically secure random
+numbers.
+
+@noindent
+UUIDv5s must be created with a namespace and a name.  The namespaces are
+defined in @code{uuid-namespace-alist} and are by default @code{dns},
+@code{url}, @code{oid}, and @code{x500}.
+
+@defun uuid-v5 namespace name
+This function returns a new @code{uuid-v5} Lisp object based on
+@var{namespace}, which is a symbol in the key of the
+@code{uuid-namespace-alist}: @code{dns}, @code{url}, @code{oid},
+@code{x500}, or any symbol added.  This is combined with @var{name} to
+construct a new UUIDv5.
+@end defun
+
+An example is:
+
+@cindex uuidv5
+@example
+(uuid-v5 'dns "www.example.com")
+@end example
+
+@defvar uuid-namespace-alist
+A list of namespace symbols and their corresponding UUIDs.  Each alist
+entry is a cons of the symbol and a UUID Lisp object representing the
+namespace.  This is used in @code{uuid-v5}, see above for more info on
+how this is used.
+@end defvar
+
+@node UUID Conversion
+@subsection UUID Conversion
+@cindex uuid conversion
+
+To use a UUID, typically it is converted to a string, and sometimes to a
+binary representation.  This is done using @code{uuid-to-string} function.
+
+@defun uuid-to-string uuid
+Return the standard string value, with hex values and dashes, of Lisp
+object @code{uuid}.
+@end defun
+
+@example
+(uuid-to-string (uuid-v4))     ; @r{"230156d4-488a-45d1-93c9-507ec8be37ca"}
+@end example
+
+@noindent
+Binary can also be generated, which generates a unibyte string, using
+@code{uuid-to-bytes}, and a numeric representation of a UUID can be
+generated with @code{uuid-to-number}.
+
+@defun uuid-to-bytes uuid
+Return a unibyte string representing the binary value of @code{uuid}.
+@end defun
+
+@defun uuid-to-number uuid
+Return a numeric representation of the value of @code{uuid}.
+@end defun
+
+@noindent
+String UUIDs can also be converted to a UUID Lisp object, with
+@code{uuid-from-string}, and bytes can be converted with
+@code{uuid-from-bytes}.  These work with all UUIDs, not just the ones
+this module can generate.
+
+@defun uuid-from-string uuid-str
+Return a UUID Lisp object that represents @var{uuid-str}.
+@end defun
+
+@defun uuid-from-bytes uuid-bytes
+Return a UUID Lisp object that represents @var{uuid-bytes}, which is
+expected to be a unibyte string.
+@end defun
+
+Valid UUIDs have types, which can be used with @code{cl-typep},
+@code{cl-check-type} and @code{cl-typecase}.  The type is specified with
+the type specifier @code{(uuid-v <version>)}.  @var{version} can be any
+version of UUID, but we make sure the UUID variant is correctly
+@code{2}; any other value is not a valid version.
+
+@example
+(when (cl-typep my-uuid '(uuid-v 4))
+    (process-v4-uuid my-uuid))
+
+(defun process-v4-uuid (uuid)
+    ;; Error if not the correct type
+    (cl-check-type uuid (uuid-v 4))
+    ;; Proceed to process the UUIDv4.
+    (more-processing uuid))
+@end example
+
+@node Special UUID values
+@subsection Special UUID values
+@cindex special uuid values
+@cindex uuid nil and uuid max
+@cindex nil uuid
+@cindex max uuid
+
+@code{uuid-nil} and @code{uuid-max} are constant values representing the
+UUID equivalents of @code{nil} and a max sentinel value, as defined in
+RFC 9562.
diff --git a/lisp/emacs-lisp/uuid.el b/lisp/emacs-lisp/uuid.el
new file mode 100644
index 00000000000..592751a427e
--- /dev/null
+++ b/lisp/emacs-lisp/uuid.el
@@ -0,0 +1,245 @@
+;;; uuid.el --- UUID creation and handling -*- lexical-binding: t; -*-
+
+;; Copyright (C) 2026 Free Software Foundation, Inc.
+
+;; Author: Andrew Hyatt <[email protected]>
+;; Keywords: tools
+
+;; This file is part of GNU Emacs.
+
+;; GNU Emacs is free software: you can redistribute it and/or modify
+;; it under the terms of the GNU General Public License as published by
+;; the Free Software Foundation, either version 3 of the License, or
+;; (at your option) any later version.
+
+;; GNU Emacs is distributed in the hope that it will be useful,
+;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+;; GNU General Public License for more details.
+
+;; You should have received a copy of the GNU General Public License
+;; along with GNU Emacs.  If not, see <https://www.gnu.org/licenses/>.
+
+
+;;; Commentary:
+;; This library provides useful code for handling UUIDs.  It provides
+;; methods for generating UUIDs, parsing them, and outputting them as
+;; a string, a unibyte binary string, or as a number.
+;;
+;;; Code:
+
+(require 'cl-lib)
+(require 'bindat)
+(require 'seq)
+
+(define-error
+ 'uuid-invalid-string
+ "Invalid UUID string not conforming to expected UUID shape.")
+
+(define-error
+ 'uuid-invalid-bytes
+ "Invalid UUID bytes not conforming to expected 16-byte length.")
+
+(define-error
+ 'uuid-invalid-namespace
+ "Namespace not found in `uuid-namespace-alist'.")
+
+(defconst uuid--bindat-type
+  (bindat-type (:high uint 48)
+               ;; We can't separate out ver and mid due to bindat
+               ;; limitations on byte alignment.
+               (:ver-mid uint 16)
+               ;; Same issue with var and low.
+               (:var-low uint 64))
+  "The bindat type for UUIDs, as defined in RFC 9562.")
+
+(cl-defstruct (uuid
+               (:constructor nil)
+               (:conc-name uuid--)
+               (:constructor
+                uuid--from-parts
+                (high mid low ver var)))
+  "The base type for all UUIDs.
+
+This will represent versions where we don't have a more specific version
+defined, so all versions except for v4, v5, and v7."
+  high mid low ver var)
+
+(defun uuid--upper-bits (num n bitsize)
+  "Return the upper N bits of NUM of BITSIZE.
+
+BITSIZE is the total size of the num."
+  (ash num (- n bitsize)))
+
+(defun uuid--lower-bits (num n)
+  "Return the lower N bits of NUM."
+  (logand num (- (ash 1 n) 1)))
+
+(defun uuid--concat-bits (&rest num-and-sizes)
+  "Return number doing bitwise concatenating NUM-AND-SIZES.
+
+NUM-AND-SIZES is a list of alternating numbers and their sizes in bits."
+  (cl-loop for num-and-size in (reverse
+                                (seq-partition num-and-sizes 2))
+           with shift = 0
+           sum (ash (car num-and-size) shift)
+           do (incf shift (cadr num-and-size))))
+
+(defun uuid-to-string (id)
+  "Convert the uuid object ID to a string."
+  (let ((low (uuid--low id))
+        (high (uuid--high id)))
+    (format "%08x-%04x-%04x-%04x-%012x"
+            (uuid--upper-bits high 32 48)
+            (uuid--lower-bits high 16)
+            (uuid--concat-bits
+             (uuid--ver id) 4
+             (uuid--mid id) 12)
+            (uuid--concat-bits
+             (uuid--var id) 2
+             (uuid--upper-bits low 14 62) 14)
+            (uuid--lower-bits low 48))))
+
+(defun uuid-to-bytes (id)
+  "Convert the uuid object ID to a 16-byte unibyte string."
+  (bindat-pack uuid--bindat-type
+               `((:high . ,(uuid--high id))
+                 (:ver-mid . ,(uuid--concat-bits
+                               (uuid--ver id) 4
+                               (uuid--mid id) 12))
+                 (:var-low . ,(uuid--concat-bits
+                               (uuid--var id) 2
+                               (uuid--low id) 62)))))
+
+(defun uuid-to-number (id)
+  "Convert ID, a `uuid' lisp object, to a numerical representation."
+  (uuid--concat-bits
+   (uuid--high id) 48
+   (uuid--ver id) 4
+   (uuid--mid id) 12
+   (uuid--var id) 2
+   (uuid--low id) 62))
+
+(defconst uuid-nil
+  (uuid--from-parts 0 0 0 0 0)
+  "A UUID representing `nil', as defined in RFC 9562.")
+
+(defconst uuid-max
+  (uuid--from-parts (1- (ash 1 48)) (1- (ash 1 12)) (1- (ash 1 62)) 15 3)
+  "A UUID representing the maximum UUID, per RFC 9562.")
+
+(cl-deftype uuid-v (var)
+  `(and uuid (satisfies
+              ,(lambda (id) (and (= (uuid--ver id) var)
+                                 (= (uuid--var id) 2))))))
+
+(defun uuid--random-bits (n &optional rng)
+  "Return a random number with N bits."
+  (funcall (or rng #'random)
+           (expt 2 n)))
+
+(defun uuid-from-string (uuid-str)
+  "Parse UUID-STR and return the appropriate UUID object."
+  (let* ((parts (split-string uuid-str "-"))
+         (_ (unless (and (= (length parts) 5)
+                         (= (length (nth 0 parts)) 8)
+                         (= (length (nth 1 parts)) 4)
+                         (= (length (nth 2 parts)) 4)
+                         (= (length (nth 3 parts)) 4)
+                         (= (length (nth 4 parts)) 12)
+                         (string-match "^[0-9a-fA-F-]+$" uuid-str))
+              (signal 'uuid-invalid-string (list uuid-str))))
+         (hex-parts (mapcar (lambda (part) (string-to-number part 16)) parts))
+         (version (uuid--upper-bits (nth 2 hex-parts) 4 16))
+         (variant (uuid--upper-bits (nth 3 hex-parts) 2 16))
+         (high (uuid--concat-bits
+                (nth 0 hex-parts) 32
+                (nth 1 hex-parts) 16))
+         (mid (uuid--lower-bits (nth 2 hex-parts) 12))
+         (low
+          (uuid--concat-bits
+           (uuid--lower-bits (nth 3 hex-parts) 14) 14
+           (nth 4 hex-parts) 48)))
+    (uuid--from-parts high mid low version variant)))
+
+(defun uuid-from-bytes (uuid-bytes)
+  "Parse unibyte string UUID-BYTES and return a UUID object.
+
+If UUID-BYTES are not unibyte, or not 16 bytes, a `uuid-invalid-bytes'
+error is signaled."
+  (unless (and
+           (not (multibyte-string-p uuid-bytes))
+           (= 16 (string-bytes uuid-bytes)))
+    (signal 'uuid-invalid-bytes (list uuid-bytes)))
+  (let* ((parts (bindat-unpack uuid--bindat-type uuid-bytes))
+         (version (uuid--upper-bits (assoc-default :ver-mid parts) 4 16))
+         (variant (uuid--upper-bits (assoc-default :var-low parts) 2 64))
+         (high (assoc-default :high parts))
+         (mid (uuid--lower-bits (assoc-default :ver-mid parts) 12))
+         (low (uuid--lower-bits (assoc-default :var-low parts) 62)))
+    (uuid--from-parts high mid low version variant)))
+
+(defconst uuid-namespace-alist
+  (mapcar (lambda (x) (cons (car x) (uuid-from-string (cdr x))))
+          '((dns . "6ba7b810-9dad-11d1-80b4-00c04fd430c8")
+            (url . "6ba7b811-9dad-11d1-80b4-00c04fd430c8")
+            (oid . "6ba7b812-9dad-11d1-80b4-00c04fd430c8")
+            (x500 . "6ba7b814-9dad-11d1-80b4-00c04fd430c8")))
+  "An alist of namespaces and their canonical UUIDs.
+ This is defined at https://www.rfc-editor.org/info/rfc9562/#namespaces.")
+
+(cl-defun uuid-v4 (&key rng)
+  "Return a new UUIDv4 ID.
+
+RNG is an alternate random number function which should take a single
+argument, the limit (exclusive) for the random number, and return an
+integer between 0 and that number.  To be valid according to RFC 9562,
+the random numbers should be cryptographically secure, which the default
+random number generator typically is not, so if these UUIDs are
+important, it's advised to use a better random number function, which
+typically requires getting random numbers from outside of Emacs."
+  (uuid--from-parts (uuid--random-bits 48 rng)
+                    (uuid--random-bits 12 rng)
+                    (uuid--random-bits 62 rng)
+                    4 2))
+
+(defun uuid-v5 (namespace name)
+  "Return a new UUIDv5 ID from the given NAMESPACE and NAME.
+
+NAMESPACE should be a symbol corresponding to a namespace in
+`uuid-namespace-alist'.  If this is not recognized it will signal an
+`uuid-invalid-namespace' signal.
+
+NAME is the name from which to generate the UUID, and should be a
+string."
+  (let* ((namespace-uuid (or
+                          (alist-get namespace uuid-namespace-alist)
+                          (signal 'uuid-invalid-namespace (list namespace))))
+         (hash-bytes (sha1
+                      (concat
+                       (uuid-to-bytes namespace-uuid)
+                       (encode-coding-string name 'utf-8))
+                      nil nil t))
+         (hash-vals (bindat-unpack uuid--bindat-type hash-bytes)))
+    (uuid--from-parts (assoc-default :high hash-vals)
+                      (uuid--lower-bits (assoc-default :ver-mid hash-vals) 12)
+                      (uuid--lower-bits (assoc-default :var-low hash-vals) 62)
+                      5 2)))
+
+(cl-defun uuid-v7 (&key rng)
+  "Return a new UUIDv7 ID.
+
+The UUIDv7 uses a timestamp instead of being purely random, which makes
+it more suitable for use cases such as database keys.
+
+RNG is an alternate random number function which should take a single
+argument, the limit (exclusive) for the random number, and return an
+integer between 0 and that number.  See `uuid-v4' for more details on
+the expected behavior of this function."
+  (uuid--from-parts (floor (* (float-time) 1000))
+                    (uuid--random-bits 12 rng)
+                    (uuid--random-bits 62 rng)
+                    7 2))
+
+(provide 'uuid)
+;;; uuid.el ends here
diff --git a/test/lisp/emacs-lisp/uuid-tests.el b/test/lisp/emacs-lisp/uuid-tests.el
new file mode 100644
index 00000000000..e2631e49079
--- /dev/null
+++ b/test/lisp/emacs-lisp/uuid-tests.el
@@ -0,0 +1,175 @@
+;; uuid-tests.el --- unit tests for uuid.el  -*- lexical-binding: t; -*-
+
+;; Copyright (C) 2026 Free Software Foundation, Inc.
+
+;; Author: Andrew Hyatt <[email protected]>
+
+;; This file is part of GNU Emacs.
+
+;; GNU Emacs is free software: you can redistribute it and/or modify
+;; it under the terms of the GNU General Public License as published by
+;; the Free Software Foundation, either version 3 of the License, or
+;; (at your option) any later version.
+
+;; GNU Emacs is distributed in the hope that it will be useful,
+;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+;; GNU General Public License for more details.
+
+;; You should have received a copy of the GNU General Public License
+;; along with GNU Emacs.  If not, see <https://www.gnu.org/licenses/>.
+
+;;; Commentary:
+;; Unit tests for uuid.el.
+
+;;; Code:
+
+(require 'uuid)
+
+(ert-deftest uuid-v4-input ()
+  "Verifies that a UUID-V4 can be parsed and detected correctly."
+  ;; Example comes from RFC 9562, A.3.
+  (let ((id (uuid-from-string "919108f7-52d1-4320-9bac-f847db4148a8")))
+    (should (cl-typep id '(uuid-v 4)))
+    (should (equal (uuid-to-string id) "919108f7-52d1-4320-9bac-f847db4148a8"))
+    (should (equal (uuid--high id) #x919108f752d1))
+    (should (equal (uuid--mid id) #x320))
+    (should (equal (uuid--low id) #x1bacf847db4148a8))
+    (should (equal (uuid--var id) 2))
+    (should (equal (uuid--ver id) 4))))
+
+(ert-deftest uuid-v4-generator ()
+  "Verifies that a generated UUID-V4 is valid."
+  (let ((id (uuid-v4)))
+    (should (cl-typep id '(uuid-v 4)))
+    (should (equal (uuid--var id) 2))
+    (should (equal (uuid--ver id) 4))))
+
+(ert-deftest uuid-v4-generator-custom-rnd ()
+  "Verifies that a UUID-V4 uses the RND function provided to it."
+  (let ((id (uuid-v4 :rng (lambda (n) 23))))
+    (should (cl-typep id '(uuid-v 4)))
+    (should (equal (uuid--var id) 2))
+    (should (equal (uuid--ver id) 4))
+    (should (equal (uuid--high id) 23))
+    (should (equal (uuid--mid id) 23))
+    (should (equal (uuid--low id) 23))))
+
+(ert-deftest uuid-v5-input ()
+  "Verifies that a UUIDv5 can be parsed and detected correctly."
+  ;; Example comes from RFC 9562, A.4.
+  (let ((id (uuid-from-string "2ed6657d-e927-568b-95e1-2665a8aea6a2")))
+    (should (cl-typep id '(uuid-v 5)))
+    (should (equal (uuid-to-string id) "2ed6657d-e927-568b-95e1-2665a8aea6a2"))
+    (should (equal (uuid--high id) #x2ed6657de927))
+    (should (equal (uuid--mid id) #x68b))
+    (should (equal (uuid--low id) #x015e12665a8aea6a2))
+    (should (equal (uuid--var id) 2))
+    (should (equal (uuid--ver id) 5))))
+
+(ert-deftest uuid-v5-generator ()
+  "Verifies that a UUIDv5 can be generated correctly."
+  ;; Example comes from RFC 9562, A.4.
+  (let ((id (uuid-v5 'dns "www.example.com")))
+    (should (cl-typep id '(uuid-v 5)))
+    (should (equal (uuid-to-string id) "2ed6657d-e927-568b-95e1-2665a8aea6a2"))
+    (should (equal (uuid--high id) #x2ed6657de927))
+    (should (equal (uuid--mid id) #x68b))
+    (should (equal (uuid--low id) #x015e12665a8aea6a2))))
+
+(ert-deftest uuid-v5-invalid-namespace ()
+  "Verifies an invalid namespace errors out."
+  (should-error (uuid-v5 'invalid "www.example.com")
+                :type 'uuid-invalid-namespace))
+
+(ert-deftest uuid-v7-input ()
+  "Verifies that a UUIDv7 can be parsed and detected correctly."
+  ;; Example comes from RFC 9562, A.5.
+  (let ((id (uuid-from-string "017f22e2-79b0-7cc3-98c4-dc0c0c07398f")))
+    (should (cl-typep id '(uuid-v 7)))
+    (should (equal (uuid-to-string id) "017f22e2-79b0-7cc3-98c4-dc0c0c07398f"))
+    (should (equal (uuid--high id) #x017f22e279b0))
+    (should (equal (uuid--mid id) #xcc3))
+    (should (equal (uuid--low id) #x18c4dc0c0c07398f))
+    (should (equal (uuid--var id) 2))
+    (should (equal (uuid--ver id) 7))))
+
+(ert-deftest uuid-v7-generator ()
+  "Verifies that a UUIDv7 can be generated correctly."
+  (let ((id (uuid-v7)))
+    (should (cl-typep id '(uuid-v 7)))
+    (should (equal (uuid--var id) 2))
+    (should (equal (uuid--ver id) 7))))
+
+(ert-deftest uuid-v7-generator-known-rnd-and-ts ()
+  "Verifies that a UUIDv7 uses random, timestamps correctly."
+  (cl-letf (((symbol-function 'float-time)
+             (lambda () 0.123)))
+    (let ((id (uuid-v7 :rng (lambda (n) 789))))
+      (should (cl-typep id '(uuid-v 7)))
+      (should (equal (uuid--var id) 2))
+      (should (equal (uuid--ver id) 7))
+      ;; 123 = 0x7b
+      ;; 789 = 0x315
+      (should (equal (uuid-to-string id) "00000000-007b-7315-8000-000000000315")))))
+
+(ert-deftest uuid-bytes ()
+  "Verifies that a UUID can be converted to bytes and back."
+  (let* ((id (uuid-v4))
+         (bytes (uuid-to-bytes id))
+         (id2 (uuid-from-bytes bytes)))
+    (should (equal id id2))
+    (should (= (length bytes) 16))
+    (should-not (multibyte-string-p bytes))))
+
+(ert-deftest uuid-to-number ()
+  "Test UUIDs can convert into the correct number."
+  (should (equal 193491124287564075115561252409011423400
+                 (uuid-to-number (uuid-from-string "919108f7-52d1-4320-9bac-f847db4148a8")))))
+
+(ert-deftest uuid-invalid-string ()
+  "Verifies that an invalid UUID string is rejected."
+  (should-error (uuid-from-string "invaluuid-string")
+                :type 'uuid-invalid-string)
+  ;; Too short
+  (should-error (uuid-from-string "017f22e2-79b0-7cc3-98c4-dc0c0c07398")
+                :type 'uuid-invalid-string)
+  ;; Too long
+  (should-error (uuid-from-string "017f22e2-79b0-7cc3-98c4-dc0c0c07398ff"))
+  ;; Not valid hex
+  (should-error (uuid-from-string "017f22e2-79b0-7cc3-98c4-dc0c0c07398zz")))
+
+(ert-deftest uuid-invalid-bytes ()
+  "Verifies that an invalid UUID byte string is rejected."
+  (should-error (uuid-from-bytes "short-bytes")
+                :type 'uuid-invalid-bytes)
+  ;; Too long
+  (should-error (uuid-from-bytes (make-string 17 ?\0)))
+  ;; Multibyte
+  (should-error (uuid-from-bytes (encode-coding-string (make-string 17 ?\0) 'utf-8))))
+
+(ert-deftest uuid-unsupported-version ()
+  "Verifies that non v4,5, or 7 versions are a usable UUID."
+  (dolist (uuid-str
+           '("017f22e2-79b0-8cc3-98c4-dc0c0c07398f"  ;; UUIDv8
+             "d5103018-6530-328e-b76b-9132edeba856"   ;; UUIDv3
+             "358f9528-5e27-11f1-b58b-def57c109056"  ;; UUIDv1
+             ))
+    (let* ((id (uuid-from-string uuid-str)))
+      (should (equal id (uuid-from-bytes (uuid-to-bytes id))))
+      (should (uuid-p id))
+      (dolist (v '(4 5 7))
+        (should-not (cl-typep id `(uuid-v ,v))))
+      (should (equal (uuid-to-string id) uuid-str)))))
+
+(ert-deftest uuid-nil ()
+  "Tests the nil UUID handling."
+  (should (equal (uuid-to-string uuid-nil) "00000000-0000-0000-0000-000000000000"))
+  (should (equal 0 (uuid-to-number uuid-nil))))
+
+(ert-deftest uuid-max ()
+  "Tests the max UUID handling."
+  (should (equal (uuid-to-string uuid-max) "ffffffff-ffff-ffff-ffff-ffffffffffff"))
+  (should (= 340282366920938463463374607431768211455 (uuid-to-number uuid-max))))
+
+;;; uuid-tests.el ends here
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.