A UUID looks something like e1631449-6321-4a58-920c-5440029b092e
, and can
be used as an identifier for anything. Each 128-bit number, usually represented
as 32 hexadecimal digits, is generally used under the assumption that it is
Universally Unique (hence the name).
This package supports variant 1 UUIDs (those outlined in RFC 4122), which covers the vast majority of those in use (versions 1-5).
This modules provides a UUID type, and functions to work with them. It is an opaque type, which basically means you have to use the provided functions if you want to do anything with it!
Here is an example of a Book
model, which uses UUIDs to identify both the book
and its authors:
type alias Book =
{ title : String
, uuid : UUID
, published : Maybe Date
, authors : List UUID
}
fromString : String -> Result Error UUID
You can attempt to create a UUID from a string. This function can interpret a fairly broad range of formatted (and mis-formatted) UUIDs, including ones with too much whitespace, too many (or not enough) hyphens, or uppercase characters.
fromString "c72c207b-0847-386d-bdbc-2e5def81cf811"
--> Err WrongLength
fromString "c72c207b-0847-386d-bdbc-2e5def81cg81"
--> Err WrongFormat
fromString "c72c207b-0847-386d-bdbc-2e5def81cf81"
|> Result.map version
--> Ok 3
fromString "6ba7b810-9dad-11d1-80b4-00c04fd430c8"
--> Ok dnsNamespace
This enumerates all the possible errors that can happen when reading existing UUIDs.
import Bytes.Extra exposing (fromByteValues)
-- Note the non-hexadecimal characters in this string!
fromString "12345678-9abc-4567-89ab-cdefghijklmn"
--> Err WrongFormat
fromBytes (fromByteValues [ 223 ])
--> Err WrongLength
-- This package only supports variant 1 UUIDs
fromString "urn:uuid:12345678-9abc-4567-c234-abcd12345678"
--> Err UnsupportedVariant
fromBytes (fromByteValues [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0])
--> Err IsNil
fromString "{00000000-0000-0000-0000-000000000000}"
--> Err IsNil
fromString "6ba7b814-9dad-71d1-80b4-00c04fd430c8"
--> Err NoVersion
generator : Random.Generator UUID
Generating a random UUID (version 4) is, I think, the most straightforward way of making a UUID, and I see them used all the time. There are a couple of ways of using a generator to create a value, which are described nicely in the elm/random docs.
NOTE: Random.Seed
s have either 32 or 54 bits of randomness. That means
that UUID
s generated with this Generator
will only ever be one of either
4.3 billion or 18 quadrillion UUIDs. If that is not enough for your use
case, check out step
. It is also recommended that you do not use
Random.generate
.
import Random
Random.step UUID.generator (Random.initialSeed 12345)
|> Tuple.first
|> UUID.toRepresentation Urn
--> "urn:uuid:5b58931d-bb69-406d-81a9-7746c300838c"
{ seed1 : Random.Seed
, seed2 : Random.Seed
, seed3 : Random.Seed
, seed4 : Random.Seed
}
step : Seeds -> ( UUID, Seeds )
Properly using all of a randomly-generated UUID’s bits requires up to four
Random.Seed
s. How you generate them is up to you. You might want to check out
Crypto.getRandomValues()
though.
Use four seeds to create a version 4 UUID and four new seeds.
import Random
s1 = Random.initialSeed 3684687
s2 = Random.initialSeed 3487532
s3 = Random.initialSeed 63374
s4 = Random.initialSeed 65483
step (Seeds s1 s2 s3 s4)
|> Tuple.first
|> toString
---> "3d938336-567b-43b9-88d5-3703c2e63a0b"
UUIDs can be created using a namespace UUID and a name, which is then hashed to create a new UUID. The hash function used depends on the version of UUID: verison 3 UUIDs use MD5, and version 5 UUIDs use SHA-1. Version 5 is the officially recommended version to use.
Although the RFC defining UUIDs defines some base namespaces, any UUID can be used as a namespace, making a hierarchy. I think this is pretty cool! You can use this method for consistently making the same UUID from the same data, which can be very useful in some situations.
forName : String -> UUID -> UUID
Create a version 5 UUID from a String
and a namespace, which should be a
UUID
. The same name and namespace will always produce the same UUID, which can
be used to your advantage. Furthermore, the UUID created from this can be used
as a namespace for another UUID, creating a hierarchy of sorts.
apiNamespace : UUID
apiNamespace =
forName "https://api.example.com/v2/" dnsNamespace
widgetNamespace : UUID
widgetNamespace =
forName "Widget" apiNamespace
toString apiNamespace
--> "bad122ad-b5b6-527c-b544-4406328d8b13"
toString widgetNamespace
--> "7b0db628-d793-550b-a883-937a276f4908"
forBytes : Bytes -> UUID -> UUID
Create a version 5 UUID from Bytes
and a namespace, which should be a
UUID
. The same name and namespace will always produce the same UUID, which can
be used to your advantage. Furthermore, the UUID created from this can be used
as a namespace for another UUID, creating a hierarchy of sorts.
import Bytes
import Bytes.Encode
type alias Widget = { name: String, value: Float }
apiNamespace : UUID
apiNamespace =
forName "https://api.example.com/v2/" dnsNamespace
widgetNamespace : UUID
widgetNamespace =
forName "Widget" apiNamespace
uuidForWidget : Widget -> UUID
uuidForWidget { name, value } =
let
bytes =
Bytes.Encode.encode <| Bytes.Encode.sequence
[ Bytes.Encode.unsignedInt32 Bytes.BE (String.length name)
, Bytes.Encode.string name
, Bytes.Encode.float64 Bytes.BE value
]
in
forBytes bytes widgetNamespace
Widget "Exponent" 0.233
|> uuidForWidget
|> toString
--> "46dab34b-5447-5c5c-8e24-fe4e3daab014"
forNameV3 : String -> UUID -> UUID
Similar to forName
, creates a UUID based on the given String
and namespace UUID, but creates a version 3 UUID. If you can choose between the
two, it is recommended that you choose version 5 UUIDs.
apiNamespace : UUID
apiNamespace =
forNameV3 "https://api.example.com/v1/" dnsNamespace
toString apiNamespace
--> "f72dd2ff-b8e7-3c58-afe1-352d5e273de4"
forBytesV3 : Bytes -> UUID -> UUID
Similar to forBytes
, creates a UUID based on the given Bytes
and namespace UUID, but creates a version 3 UUID. If you can choose between the
two, it is recommended that you choose version 5 UUIDs.
import Bytes
import Bytes.Encode
apiNamespace : UUID
apiNamespace =
forName "https://api.example.com/v2/" dnsNamespace
forBytesV3 (Bytes.Encode.encode (Bytes.Encode.unsignedInt8 245)) apiNamespace
|> toString
--> "4ad1f4bf-cf83-3c1d-b4f5-975f7d0fc9ba"
dnsNamespace : UUID
A UUID for the DNS namespace, "6ba7b810-9dad-11d1-80b4-00c04fd430c8".
forName "elm-lang.org" dnsNamespace
|> toString
--> "c6d62c23-3406-5fc7-836e-9d6bef13e18c"
urlNamespace : UUID
A UUID for the URL namespace, "6ba7b811-9dad-11d1-80b4-00c04fd430c8".
forName "https://package.elm-lang.org" urlNamespace
|> toString
--> "e1dba7a5-c338-53f3-bc90-353f045447be"
oidNamespace : UUID
A UUID for the ISO object ID (OID) namespace, "6ba7b812-9dad-11d1-80b4-00c04fd430c8".
forName "1.2.250.1" oidNamespace
|> toString
--> "87a2ac60-306f-5131-b748-3787f9f55685"
x500Namespace : UUID
A UUID for the X.500 Distinguished Name (DN) namespace, "6ba7b814-9dad-11d1-80b4-00c04fd430c8".
If you don't know what this is for, I can't help you, because I don't know either.
fromBytes : Bytes -> Result Error UUID
You can attempt to create a UUID from some Bytes
, the only contents of which much be the UUID.
import Bytes.Extra exposing (fromByteValues)
fromBytes (fromByteValues [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0])
--> Err IsNil
fromBytes (fromByteValues [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0])
--> Err WrongLength
fromBytes (fromByteValues [0x6b, 0xa7, 0xb8, 0x14, 0x9d, 0xad, 0x11, 0xd1, 0x80, 0xb4, 0x00, 0xc0, 0x4f, 0xd4, 0x30, 0xc8])
--> Ok x500Namespace
decoder : Bytes.Decode.Decoder UUID
A Bytes.Decode.Decoder
, for integrating with a broader decode.
import Bytes
import Bytes.Decode exposing (Decoder)
import Bytes.Extra exposing (fromByteValues)
type alias Record =
{ name : String
, value : Int
, id : UUID
}
recordDecoder : Decoder Record
recordDecoder =
Bytes.Decode.map3 Record
(Bytes.Decode.unsignedInt16 Bytes.BE |> Bytes.Decode.andThen Bytes.Decode.string)
(Bytes.Decode.unsignedInt32 Bytes.BE)
UUID.decoder
Bytes.Decode.decode recordDecoder <| fromByteValues
[ 0x00, 0x0b -- 11
, 0x68, 0x65, 0x6c, 0x6c, 0x6f, 0x20, 0x77, 0x6f, 0x72, 0x6c, 0x64 -- "hello world"
, 0x00, 0x00, 0x00, 0xFF -- 255
, 0x6b, 0xa7, 0xb8, 0x11, 0x9d, 0xad, 0x11, 0xd1, 0x80, 0xb4, 0x00, 0xc0, 0x4f, 0xd4, 0x30, 0xc8 -- UUID
]
--> Just
--> { name = "hello world"
--> , value = 255
--> , id = urlNamespace
--> }
toBytes : UUID -> Bytes
Convert a UUID to Bytes
.
import Bytes.Extra exposing (toByteValues)
toBytes dnsNamespace
|> toByteValues
--> [0x6b, 0xa7, 0xb8, 0x10, 0x9d, 0xad, 0x11, 0xd1, 0x80, 0xb4, 0x00, 0xc0, 0x4f, 0xd4, 0x30, 0xc8]
encoder : UUID -> Bytes.Encode.Encoder
An encoder for encoding data that includes a UUID.
import Bytes.Extra exposing (toByteValues)
import Bytes.Encode
import Bytes.Encode.Extra
defaultNamespaces : List UUID
defaultNamespaces =
[ dnsNamespace, urlNamespace, oidNamespace, x500Namespace ]
Bytes.Encode.Extra.list UUID.encoder defaultNamespaces
|> Bytes.Encode.encode
|> toByteValues
--> [ 0x6b, 0xa7, 0xb8, 0x10, 0x9d, 0xad, 0x11, 0xd1, 0x80, 0xb4, 0x00, 0xc0, 0x4f, 0xd4, 0x30, 0xc8
--> , 0x6b, 0xa7, 0xb8, 0x11, 0x9d, 0xad, 0x11, 0xd1, 0x80, 0xb4, 0x00, 0xc0, 0x4f, 0xd4, 0x30, 0xc8
--> , 0x6b, 0xa7, 0xb8, 0x12, 0x9d, 0xad, 0x11, 0xd1, 0x80, 0xb4, 0x00, 0xc0, 0x4f, 0xd4, 0x30, 0xc8
--> , 0x6b, 0xa7, 0xb8, 0x14, 0x9d, 0xad, 0x11, 0xd1, 0x80, 0xb4, 0x00, 0xc0, 0x4f, 0xd4, 0x30, 0xc8
--> ]
UUIDs are generally represented by 32 hexadecimal digits in the form
00112233-4455-M677-N899-aabbccddeeff
, where the four bits at position M
denote the UUID version, and the first two or three bits at position N
denote
the variant. This is the "canonical" representation, but there is also a URN
representation and a format used mostly by Microsoft, where they are more
commonly named GUIDs. Additionally, some APIs do not use the dashes.
toString : UUID -> String
The canonical representation of the UUID. This is the same as
toRepresentation Canonical
.
toString dnsNamespace
--> "6ba7b810-9dad-11d1-80b4-00c04fd430c8"
toRepresentation : Representation -> UUID -> String
Convert UUID to a given format
toRepresentation Canonical dnsNamespace
--> "6ba7b810-9dad-11d1-80b4-00c04fd430c8"
toRepresentation Urn dnsNamespace
--> "urn:uuid:6ba7b810-9dad-11d1-80b4-00c04fd430c8"
toRepresentation Guid dnsNamespace
--> "{6ba7b810-9dad-11d1-80b4-00c04fd430c8}"
toRepresentation Compact dnsNamespace
--> "6ba7b8109dad11d180b400c04fd430c8"
There are three typical human-readable representations of UUID: canonical, a
Uniform Resource Name and Microsoft's formatting for its GUIDs (which are just
version 4 UUIDs nowadays). Additionally, some APIs do not use the dashes, which
has been named Compact
here.
jsonDecoder : Json.Decode.Decoder UUID
Decode a UUID from a JSON string.
toValue : UUID -> Json.Encode.Value
Encode a UUID to a JSON Value (as a canonical string).
toValueWithRepresentation : Representation -> UUID -> Json.Encode.Value
Encode a UUID to a JSON Value (as a string with a given (Representation)[#Representation]).
version : UUID -> Basics.Int
Get the version number of a UUID
. Only versions 3, 4 and 5 are supported
in this package, so you should only expect the returned Int
to be 3
, 4
or
5
.
import Random
Random.step generator (Random.initialSeed 1)
|> Tuple.first
|> version
--> 4
fromString "{12345678-1234-5678-8888-0123456789ab}"
|> Result.map version
--> Ok 5
compare : UUID -> UUID -> Basics.Order
Returns the relative ordering of two UUID
s. The main use case of this
function is helping in binary-searching algorithms. Mimics elm/core
's
compare
.
One more, special UUID is the nil UUID. This is just
"00000000-0000-0000-0000-000000000000"
. I suppose it can work as a placeholder
in some cases, but elm has Maybe
for that! Nevertheless, you might need to
check for nil UUIDs, or print the nil UUID.
nilBytes : Bytes
128 bits of nothing.
isNilBytes : Bytes -> Basics.Bool
True
if the given bytes are the nil UUID (00000000-0000-0000-0000-000000000000).
isNilBytes nilBytes
--> True
nilString : String
nilString
--> "00000000-0000-0000-0000-000000000000"
isNilString : String -> Basics.Bool
True
if the given string represents the nil UUID (00000000-0000-0000-0000-000000000000).
nilRepresentation : Representation -> String
nilRepresentation Canonical
--> "00000000-0000-0000-0000-000000000000"
nilRepresentation Urn
--> "urn:uuid:00000000-0000-0000-0000-000000000000"
nilRepresentation Guid
--> "{00000000-0000-0000-0000-000000000000}"