I ran into a situation the other day where UUID was needed. Sadly, the UUID module in pursuit depends on an npm package. So, I rolled my own.
I’d be happy to hear of any holes in my implementation and any other critiques anyone has to offer.
Here’s the code:
module UUID where
import Prelude
import Data.Either (Either(..))
import Data.Int.Bits ((.|.))
import Data.Maybe (Maybe(..))
import Data.String (joinWith, length)
import Data.String.Regex (regex, test)
import Data.String.Regex.Flags (noFlags)
import Effect (Effect)
import Data.Int (floor, hexadecimal, toNumber, toStringAs)
import Effect.Random (random)
import Data.Array (replicate)
newtype UUID = UUID String
instance showUUID :: Show UUID where
show (UUID uuid) = uuid
derive instance eqUUID :: Eq UUID
derive instance ordUUID :: Ord UUID
randomInt :: Int -> Int -> Effect Int
randomInt min max = do
r <- random
pure $ floor $ r * toNumber (max - min + 1) + toNumber min
padStart :: Int -> String -> String
padStart targetLength str =
let
paddingLength = max 0 (targetLength - length str)
padding = replicate paddingLength "0"
in joinWith "" padding <> str
parseUUID :: String -> Maybe UUID
parseUUID str =
case regex "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$" noFlags of
Left _ -> Nothing
Right r -> if test r str
then Just $ UUID str
else Nothing
uuidToString :: UUID -> String
uuidToString (UUID uuid) = uuid
emptyUUID :: UUID
emptyUUID = UUID "00000000-0000-0000-0000-000000000000"
-- | Generate a UUID v4
genUUID :: Effect UUID
genUUID = do
-- Generate random 16-bit integers for smaller chunks
r1 <- randomInt 0 0xFFFF -- First half of time_low
r2 <- randomInt 0 0xFFFF -- Second half of time_low
r3 <- randomInt 0 0xFFFF -- time_mid
r4 <- randomInt 0 0x0FFF -- time_hi (12 bits for randomness)
r5 <- randomInt 0 0x3FFF -- clock_seq (14 bits for randomness)
r6 <- randomInt 0 0xFFFF -- First part of node
r7 <- randomInt 0 0xFFFF -- Second part of node
r8 <- randomInt 0 0xFFFF -- Third part of node
-- Set the version (4) and variant (10)
let versioned = r4 .|. 0x4000 -- Set version to 4 (binary OR with 0100 0000 0000 0000)
variant = r5 .|. 0x8000 -- Set variant to 10xx (binary OR with 1000 0000 0000 0000)
-- Convert to hex and pad as needed
let hex1 = padStart 4 (toHex r1) <> padStart 4 (toHex r2) -- time_low
hex2 = padStart 4 (toHex r3) -- time_mid
hex3 = padStart 4 (toHex versioned) -- time_hi_and_version
hex4 = padStart 4 (toHex variant) -- clock_seq
hex5 = padStart 4 (toHex r6) <> padStart 4 (toHex r7) <> padStart 4 (toHex r8) -- node
uuid = joinWith "-" [hex1, hex2, hex3, hex4, hex5]
pure $ UUID uuid
where
toHex = toStringAs hexadecimal
You must log in or register to comment.