packages feed

mmzk-typeid 0.3.1.0 → 0.4.0.0

raw patch · 23 files changed

+2139/−494 lines, 23 filesdep +randomdep +uuidPVP ok

version bump matches the API change (PVP)

Dependencies added: random, uuid

API changes (from Hackage documentation)

- Data.KindID: data KindID prefix
- Data.KindID: nilKindID :: KindID ""
- Data.TypeID: data TypeID
- Data.TypeID: nilTypeID :: TypeID
- Data.UUID.V7: nil :: UUID
+ Data.KindID: data KindID' (version :: UUIDVersion) prefix
+ Data.KindID: type KindID = KindID' 'V7
+ Data.KindID.V4: byteString2ID :: IDConv a => ByteString -> Either TypeIDError a
+ Data.KindID.V4: byteString2IDM :: (IDConv a, MonadIO m) => ByteString -> m a
+ Data.KindID.V4: checkID :: forall a. IDGen a => a -> Maybe TypeIDError
+ Data.KindID.V4: checkKindID :: (ToPrefix prefix, ValidPrefix (PrefixSymbol prefix)) => KindIDV4 prefix -> Maybe TypeIDError
+ Data.KindID.V4: decorate :: forall a. IDGen a => GenFunc (IDGenPrefix a) (UUID -> ResWithErr (IDGenPrefix a) a)
+ Data.KindID.V4: decorateKindID :: (ToPrefix prefix, ValidPrefix (PrefixSymbol prefix)) => UUID -> KindIDV4 prefix
+ Data.KindID.V4: fromTypeID :: (ToPrefix prefix, ValidPrefix (PrefixSymbol prefix)) => TypeIDV4 -> Maybe (KindIDV4 prefix)
+ Data.KindID.V4: genID :: forall a m. (IDGen a, MonadIO m) => GenFunc (IDGenPrefix a) (m a)
+ Data.KindID.V4: genID' :: forall a m. (IDGen a, MonadIO m) => GenFunc (IDGenPrefix a) (m a)
+ Data.KindID.V4: genKindID :: (ToPrefix prefix, ValidPrefix (PrefixSymbol prefix), MonadIO m) => m (KindIDV4 prefix)
+ Data.KindID.V4: genKindID' :: (ToPrefix prefix, ValidPrefix (PrefixSymbol prefix), MonadIO m) => m (KindIDV4 prefix)
+ Data.KindID.V4: getPrefix :: IDType a => a -> Text
+ Data.KindID.V4: getTime :: IDType a => a -> Word64
+ Data.KindID.V4: getUUID :: IDType a => a -> UUID
+ Data.KindID.V4: id2ByteString :: IDConv a => a -> ByteString
+ Data.KindID.V4: id2String :: IDConv a => a -> String
+ Data.KindID.V4: id2Text :: IDConv a => a -> Text
+ Data.KindID.V4: parseByteString :: forall prefix. (ToPrefix prefix, ValidPrefix (PrefixSymbol prefix)) => ByteString -> Either TypeIDError (KindIDV4 prefix)
+ Data.KindID.V4: parseByteStringM :: (ToPrefix prefix, ValidPrefix (PrefixSymbol prefix), MonadIO m) => ByteString -> m (KindIDV4 prefix)
+ Data.KindID.V4: parseString :: forall prefix. (ToPrefix prefix, ValidPrefix (PrefixSymbol prefix)) => String -> Either TypeIDError (KindIDV4 prefix)
+ Data.KindID.V4: parseStringM :: (ToPrefix prefix, ValidPrefix (PrefixSymbol prefix), MonadIO m) => String -> m (KindIDV4 prefix)
+ Data.KindID.V4: parseText :: forall prefix. (ToPrefix prefix, ValidPrefix (PrefixSymbol prefix)) => Text -> Either TypeIDError (KindIDV4 prefix)
+ Data.KindID.V4: parseTextM :: (ToPrefix prefix, ValidPrefix (PrefixSymbol prefix), MonadIO m) => Text -> m (KindIDV4 prefix)
+ Data.KindID.V4: string2ID :: IDConv a => String -> Either TypeIDError a
+ Data.KindID.V4: string2IDM :: (IDConv a, MonadIO m) => String -> m a
+ Data.KindID.V4: text2ID :: IDConv a => Text -> Either TypeIDError a
+ Data.KindID.V4: text2IDM :: (IDConv a, MonadIO m) => Text -> m a
+ Data.KindID.V4: toByteString :: (ToPrefix prefix, ValidPrefix (PrefixSymbol prefix)) => KindIDV4 prefix -> ByteString
+ Data.KindID.V4: toString :: (ToPrefix prefix, ValidPrefix (PrefixSymbol prefix)) => KindIDV4 prefix -> String
+ Data.KindID.V4: toText :: (ToPrefix prefix, ValidPrefix (PrefixSymbol prefix)) => KindIDV4 prefix -> Text
+ Data.KindID.V4: toTypeID :: (ToPrefix prefix, ValidPrefix (PrefixSymbol prefix)) => KindIDV4 prefix -> TypeIDV4
+ Data.KindID.V4: type KindIDV4 = KindID' 'V4
+ Data.KindID.V4.Unsafe: unsafeByteString2ID :: IDConv a => ByteString -> a
+ Data.KindID.V4.Unsafe: unsafeFromTypeID :: (ToPrefix prefix, ValidPrefix (PrefixSymbol prefix)) => TypeIDV4 -> KindIDV4 prefix
+ Data.KindID.V4.Unsafe: unsafeParseByteString :: (ToPrefix prefix, ValidPrefix (PrefixSymbol prefix)) => ByteString -> KindIDV4 prefix
+ Data.KindID.V4.Unsafe: unsafeParseString :: (ToPrefix prefix, ValidPrefix (PrefixSymbol prefix)) => String -> KindIDV4 prefix
+ Data.KindID.V4.Unsafe: unsafeParseText :: (ToPrefix prefix, ValidPrefix (PrefixSymbol prefix)) => Text -> KindIDV4 prefix
+ Data.KindID.V4.Unsafe: unsafeString2ID :: IDConv a => String -> a
+ Data.KindID.V4.Unsafe: unsafeText2ID :: IDConv a => Text -> a
+ Data.KindID.V7: byteString2ID :: IDConv a => ByteString -> Either TypeIDError a
+ Data.KindID.V7: byteString2IDM :: (IDConv a, MonadIO m) => ByteString -> m a
+ Data.KindID.V7: checkID :: forall a. IDGen a => a -> Maybe TypeIDError
+ Data.KindID.V7: checkIDWithEnv :: forall a m. (IDGen a, MonadIO m) => a -> m (Maybe TypeIDError)
+ Data.KindID.V7: checkKindID :: (ToPrefix prefix, ValidPrefix (PrefixSymbol prefix)) => KindID prefix -> Maybe TypeIDError
+ Data.KindID.V7: checkKindIDWithEnv :: (ToPrefix prefix, ValidPrefix (PrefixSymbol prefix), MonadIO m) => KindID' 'V7 prefix -> m (Maybe TypeIDError)
+ Data.KindID.V7: decorate :: forall a. IDGen a => GenFunc (IDGenPrefix a) (UUID -> ResWithErr (IDGenPrefix a) a)
+ Data.KindID.V7: decorateKindID :: (ToPrefix prefix, ValidPrefix (PrefixSymbol prefix)) => UUID -> KindID prefix
+ Data.KindID.V7: fromTypeID :: (ToPrefix prefix, ValidPrefix (PrefixSymbol prefix)) => TypeID -> Maybe (KindID prefix)
+ Data.KindID.V7: genID :: forall a m. (IDGen a, MonadIO m) => GenFunc (IDGenPrefix a) (m a)
+ Data.KindID.V7: genID' :: forall a m. (IDGen a, MonadIO m) => GenFunc (IDGenPrefix a) (m a)
+ Data.KindID.V7: genIDs :: forall a m. (IDGen a, MonadIO m) => GenFunc (IDGenPrefix a) (Word16 -> m [a])
+ Data.KindID.V7: genKindID :: (ToPrefix prefix, ValidPrefix (PrefixSymbol prefix), MonadIO m) => m (KindID prefix)
+ Data.KindID.V7: genKindID' :: (ToPrefix prefix, ValidPrefix (PrefixSymbol prefix), MonadIO m) => m (KindID prefix)
+ Data.KindID.V7: genKindIDs :: (ToPrefix prefix, ValidPrefix (PrefixSymbol prefix), MonadIO m) => Word16 -> m [KindID prefix]
+ Data.KindID.V7: getPrefix :: IDType a => a -> Text
+ Data.KindID.V7: getTime :: IDType a => a -> Word64
+ Data.KindID.V7: getUUID :: IDType a => a -> UUID
+ Data.KindID.V7: id2ByteString :: IDConv a => a -> ByteString
+ Data.KindID.V7: id2String :: IDConv a => a -> String
+ Data.KindID.V7: id2Text :: IDConv a => a -> Text
+ Data.KindID.V7: parseByteString :: forall prefix. (ToPrefix prefix, ValidPrefix (PrefixSymbol prefix)) => ByteString -> Either TypeIDError (KindID prefix)
+ Data.KindID.V7: parseByteStringM :: (ToPrefix prefix, ValidPrefix (PrefixSymbol prefix), MonadIO m) => ByteString -> m (KindID prefix)
+ Data.KindID.V7: parseString :: forall prefix. (ToPrefix prefix, ValidPrefix (PrefixSymbol prefix)) => String -> Either TypeIDError (KindID prefix)
+ Data.KindID.V7: parseStringM :: (ToPrefix prefix, ValidPrefix (PrefixSymbol prefix), MonadIO m) => String -> m (KindID prefix)
+ Data.KindID.V7: parseText :: forall prefix. (ToPrefix prefix, ValidPrefix (PrefixSymbol prefix)) => Text -> Either TypeIDError (KindID prefix)
+ Data.KindID.V7: parseTextM :: (ToPrefix prefix, ValidPrefix (PrefixSymbol prefix), MonadIO m) => Text -> m (KindID prefix)
+ Data.KindID.V7: string2ID :: IDConv a => String -> Either TypeIDError a
+ Data.KindID.V7: string2IDM :: (IDConv a, MonadIO m) => String -> m a
+ Data.KindID.V7: text2ID :: IDConv a => Text -> Either TypeIDError a
+ Data.KindID.V7: text2IDM :: (IDConv a, MonadIO m) => Text -> m a
+ Data.KindID.V7: toByteString :: (ToPrefix prefix, ValidPrefix (PrefixSymbol prefix)) => KindID prefix -> ByteString
+ Data.KindID.V7: toString :: (ToPrefix prefix, ValidPrefix (PrefixSymbol prefix)) => KindID prefix -> String
+ Data.KindID.V7: toText :: (ToPrefix prefix, ValidPrefix (PrefixSymbol prefix)) => KindID prefix -> Text
+ Data.KindID.V7: toTypeID :: (ToPrefix prefix, ValidPrefix (PrefixSymbol prefix)) => KindID prefix -> TypeID
+ Data.KindID.V7: type KindID = KindID' 'V7
+ Data.KindID.V7: type KindIDV7 = KindID
+ Data.KindID.V7.Unsafe: unsafeByteString2ID :: IDConv a => ByteString -> a
+ Data.KindID.V7.Unsafe: unsafeFromTypeID :: (ToPrefix prefix, ValidPrefix (PrefixSymbol prefix)) => TypeID -> KindID prefix
+ Data.KindID.V7.Unsafe: unsafeParseByteString :: (ToPrefix prefix, ValidPrefix (PrefixSymbol prefix)) => ByteString -> KindID prefix
+ Data.KindID.V7.Unsafe: unsafeParseString :: (ToPrefix prefix, ValidPrefix (PrefixSymbol prefix)) => String -> KindID prefix
+ Data.KindID.V7.Unsafe: unsafeParseText :: (ToPrefix prefix, ValidPrefix (PrefixSymbol prefix)) => Text -> KindID prefix
+ Data.KindID.V7.Unsafe: unsafeString2ID :: IDConv a => String -> a
+ Data.KindID.V7.Unsafe: unsafeText2ID :: IDConv a => Text -> a
+ Data.TypeID: data TypeID' (version :: UUIDVersion)
+ Data.TypeID: type TypeID = TypeID' 'V7
+ Data.TypeID.V4: byteString2ID :: IDConv a => ByteString -> Either TypeIDError a
+ Data.TypeID.V4: byteString2IDM :: (IDConv a, MonadIO m) => ByteString -> m a
+ Data.TypeID.V4: checkID :: forall a. IDGen a => a -> Maybe TypeIDError
+ Data.TypeID.V4: checkPrefix :: Text -> Maybe TypeIDError
+ Data.TypeID.V4: checkTypeID :: TypeIDV4 -> Maybe TypeIDError
+ Data.TypeID.V4: decorate :: forall a. IDGen a => GenFunc (IDGenPrefix a) (UUID -> ResWithErr (IDGenPrefix a) a)
+ Data.TypeID.V4: decorateTypeID :: Text -> UUID -> Either TypeIDError TypeIDV4
+ Data.TypeID.V4: genID :: forall a m. (IDGen a, MonadIO m) => GenFunc (IDGenPrefix a) (m a)
+ Data.TypeID.V4: genID' :: forall a m. (IDGen a, MonadIO m) => GenFunc (IDGenPrefix a) (m a)
+ Data.TypeID.V4: genTypeID :: MonadIO m => Text -> m TypeIDV4
+ Data.TypeID.V4: genTypeID' :: MonadIO m => Text -> m TypeIDV4
+ Data.TypeID.V4: getPrefix :: IDType a => a -> Text
+ Data.TypeID.V4: getUUID :: IDType a => a -> UUID
+ Data.TypeID.V4: id2ByteString :: IDConv a => a -> ByteString
+ Data.TypeID.V4: id2String :: IDConv a => a -> String
+ Data.TypeID.V4: id2Text :: IDConv a => a -> Text
+ Data.TypeID.V4: parseByteString :: ByteString -> Either TypeIDError TypeIDV4
+ Data.TypeID.V4: parseByteStringM :: MonadIO m => ByteString -> m TypeIDV4
+ Data.TypeID.V4: parseString :: String -> Either TypeIDError TypeIDV4
+ Data.TypeID.V4: parseStringM :: MonadIO m => String -> m TypeIDV4
+ Data.TypeID.V4: parseText :: Text -> Either TypeIDError TypeIDV4
+ Data.TypeID.V4: parseTextM :: MonadIO m => Text -> m TypeIDV4
+ Data.TypeID.V4: string2ID :: IDConv a => String -> Either TypeIDError a
+ Data.TypeID.V4: string2IDM :: (IDConv a, MonadIO m) => String -> m a
+ Data.TypeID.V4: text2ID :: IDConv a => Text -> Either TypeIDError a
+ Data.TypeID.V4: text2IDM :: (IDConv a, MonadIO m) => Text -> m a
+ Data.TypeID.V4: toByteString :: TypeIDV4 -> ByteString
+ Data.TypeID.V4: toString :: TypeIDV4 -> String
+ Data.TypeID.V4: toText :: TypeIDV4 -> Text
+ Data.TypeID.V4: type TypeIDV4 = TypeID' 'V4
+ Data.TypeID.V4.Unsafe: unsafeByteString2ID :: IDConv a => ByteString -> a
+ Data.TypeID.V4.Unsafe: unsafeGenTypeID :: MonadIO m => Text -> m TypeIDV4
+ Data.TypeID.V4.Unsafe: unsafeGenTypeID' :: MonadIO m => Text -> m TypeIDV4
+ Data.TypeID.V4.Unsafe: unsafeParseByteString :: ByteString -> TypeIDV4
+ Data.TypeID.V4.Unsafe: unsafeParseString :: String -> TypeIDV4
+ Data.TypeID.V4.Unsafe: unsafeParseText :: Text -> TypeIDV4
+ Data.TypeID.V4.Unsafe: unsafeString2ID :: IDConv a => String -> a
+ Data.TypeID.V4.Unsafe: unsafeText2ID :: IDConv a => Text -> a
+ Data.TypeID.V7: byteString2ID :: IDConv a => ByteString -> Either TypeIDError a
+ Data.TypeID.V7: byteString2IDM :: (IDConv a, MonadIO m) => ByteString -> m a
+ Data.TypeID.V7: checkID :: forall a. IDGen a => a -> Maybe TypeIDError
+ Data.TypeID.V7: checkIDWithEnv :: forall a m. (IDGen a, MonadIO m) => a -> m (Maybe TypeIDError)
+ Data.TypeID.V7: checkPrefix :: Text -> Maybe TypeIDError
+ Data.TypeID.V7: checkTypeID :: TypeID -> Maybe TypeIDError
+ Data.TypeID.V7: checkTypeIDWithEnv :: MonadIO m => TypeID -> m (Maybe TypeIDError)
+ Data.TypeID.V7: decorate :: forall a. IDGen a => GenFunc (IDGenPrefix a) (UUID -> ResWithErr (IDGenPrefix a) a)
+ Data.TypeID.V7: decorateTypeID :: Text -> UUID -> Either TypeIDError TypeID
+ Data.TypeID.V7: genID :: forall a m. (IDGen a, MonadIO m) => GenFunc (IDGenPrefix a) (m a)
+ Data.TypeID.V7: genID' :: forall a m. (IDGen a, MonadIO m) => GenFunc (IDGenPrefix a) (m a)
+ Data.TypeID.V7: genIDs :: forall a m. (IDGen a, MonadIO m) => GenFunc (IDGenPrefix a) (Word16 -> m [a])
+ Data.TypeID.V7: genTypeID :: MonadIO m => Text -> m TypeID
+ Data.TypeID.V7: genTypeID' :: MonadIO m => Text -> m TypeID
+ Data.TypeID.V7: genTypeIDs :: MonadIO m => Text -> Word16 -> m [TypeID]
+ Data.TypeID.V7: getPrefix :: IDType a => a -> Text
+ Data.TypeID.V7: getTime :: IDType a => a -> Word64
+ Data.TypeID.V7: getUUID :: IDType a => a -> UUID
+ Data.TypeID.V7: id2ByteString :: IDConv a => a -> ByteString
+ Data.TypeID.V7: id2String :: IDConv a => a -> String
+ Data.TypeID.V7: id2Text :: IDConv a => a -> Text
+ Data.TypeID.V7: parseByteString :: ByteString -> Either TypeIDError TypeID
+ Data.TypeID.V7: parseByteStringM :: MonadIO m => ByteString -> m TypeID
+ Data.TypeID.V7: parseString :: String -> Either TypeIDError TypeID
+ Data.TypeID.V7: parseStringM :: MonadIO m => String -> m TypeID
+ Data.TypeID.V7: parseText :: Text -> Either TypeIDError TypeID
+ Data.TypeID.V7: parseTextM :: MonadIO m => Text -> m TypeID
+ Data.TypeID.V7: string2ID :: IDConv a => String -> Either TypeIDError a
+ Data.TypeID.V7: string2IDM :: (IDConv a, MonadIO m) => String -> m a
+ Data.TypeID.V7: text2ID :: IDConv a => Text -> Either TypeIDError a
+ Data.TypeID.V7: text2IDM :: (IDConv a, MonadIO m) => Text -> m a
+ Data.TypeID.V7: toByteString :: TypeID -> ByteString
+ Data.TypeID.V7: toString :: TypeID -> String
+ Data.TypeID.V7: toText :: TypeID -> Text
+ Data.TypeID.V7: type TypeID = TypeID' 'V7
+ Data.TypeID.V7: type TypeIDV7 = TypeID
+ Data.TypeID.V7.Unsafe: unsafeByteString2ID :: IDConv a => ByteString -> a
+ Data.TypeID.V7.Unsafe: unsafeGenTypeID :: MonadIO m => Text -> m TypeID
+ Data.TypeID.V7.Unsafe: unsafeGenTypeID' :: MonadIO m => Text -> m TypeID
+ Data.TypeID.V7.Unsafe: unsafeGenTypeIDs :: MonadIO m => Text -> Word16 -> m [TypeID]
+ Data.TypeID.V7.Unsafe: unsafeParseByteString :: ByteString -> TypeID
+ Data.TypeID.V7.Unsafe: unsafeParseString :: String -> TypeID
+ Data.TypeID.V7.Unsafe: unsafeParseText :: Text -> TypeID
+ Data.TypeID.V7.Unsafe: unsafeString2ID :: IDConv a => String -> a
+ Data.TypeID.V7.Unsafe: unsafeText2ID :: IDConv a => Text -> a
+ Data.UUID.Versions: V1 :: UUIDVersion
+ Data.UUID.Versions: V4 :: UUIDVersion
+ Data.UUID.Versions: V5 :: UUIDVersion
+ Data.UUID.Versions: V7 :: UUIDVersion
+ Data.UUID.Versions: data UUIDVersion
+ Data.UUID.Versions: instance GHC.Classes.Eq Data.UUID.Versions.UUIDVersion
+ Data.UUID.Versions: instance GHC.Classes.Ord Data.UUID.Versions.UUIDVersion
+ Data.UUID.Versions: instance GHC.Enum.Bounded Data.UUID.Versions.UUIDVersion
+ Data.UUID.Versions: instance GHC.Enum.Enum Data.UUID.Versions.UUIDVersion
+ Data.UUID.Versions: instance GHC.Show.Show Data.UUID.Versions.UUIDVersion
+ Data.UUID.Versions: validateWithVersion :: UUID -> UUIDVersion -> Bool
- Data.KindID: checkKindIDWithEnv :: (ToPrefix prefix, ValidPrefix (PrefixSymbol prefix), MonadIO m) => KindID prefix -> m (Maybe TypeIDError)
+ Data.KindID: checkKindIDWithEnv :: (ToPrefix prefix, ValidPrefix (PrefixSymbol prefix), MonadIO m) => KindID' 'V7 prefix -> m (Maybe TypeIDError)
- Data.KindID: fromTypeID :: forall prefix. (ToPrefix prefix, ValidPrefix (PrefixSymbol prefix)) => TypeID -> Maybe (KindID prefix)
+ Data.KindID: fromTypeID :: (ToPrefix prefix, ValidPrefix (PrefixSymbol prefix)) => TypeID -> Maybe (KindID prefix)

Files

CHANGELOG.md view
@@ -1,6 +1,25 @@ # Revision history for mmzk-typeid  +## 0.4.0.0 -- 2023-08-08++* Support `TypeID` and `KindID` with `UUID` suffixes of version 4.+  * They are exported in `Data.TypeID.V4` and `Data.KindID.V4`.+  * By default, `TypeID` and `KindID` has a `UUID` suffix of version 7.+  * The default `TypeID` and `KindID` is also exported via `Data.TypeID.V7` and+    `Data.KindID.V7`.+  * The constructor shapes have been changed, but it should not cause any+    problems since they are not exported.++* Remove deprecated `nil` functions.++* Provide some default implementations for methods of `IDConv`.++* Fix typoes in the Haddock.+  +* Tests for V4 `TypeID` and `KindID`.++ ## 0.3.1.0 -- 2023-07-23  * Add `parseStringM`, `parseTextM`, and `parseByteStringM` to `IDConv`.
+ README.md view
@@ -0,0 +1,225 @@+# mmzk-typeid++## Introduction++A [TypeID](https://github.com/jetpack-io/typeid) implementation in Haskell. It is a "type-safe, K-sortable, globally unique identifier" extended on top of UUIDv7.++TypeIDs are canonically encoded as lowercase strings consisting of three parts:++1. A type prefix (at most 63 characters in all lowercase ASCII [a-z]);+2. An underscore '_' separator;+3. A 128-bit UUIDv7 encoded as a 26-character string using a modified base32 encoding.++For more information, please check out the [specification](https://github.com/jetpack-io/typeid/blob/main/README.md).++It also serves as a (temporary) UUIDv7 implementation in Haskell, since there are no official ones yet.++If you notice any issues or have any suggestions, please feel free to open an issue or contact me via email.++## Highlights++In addition to the features provided by [TypeID](https://github.com/jetpack-io/typeid), this implementation also supports:++1. Generating TypeIDs in a batch. They are guaranteed to have the same timestamp (up to the first 32768 ids) and of ascending order;+2. Encoding the prefix in the [type level](src/Data/KindID.hs), so that if you accidentally pass in a wrong prefix, the code won't compile, avoiding the need for runtime checks.+3. Support TypeID with other UUID versions. Currently v7 (default) and v4 are supported.++## Quick start++```Haskell+{-# LANGUAGE OverloadedStrings #-}++import           Control.Exception+import           Data.TypeID (TypeID)+import qualified Data.TypeID as TID++main :: IO ()+main = do++  -- Make a TypeID with prefix 'mmzk':+  typeID <- TID.genTypeID "mmzk"+  putStrLn $ TID.toString typeID++  -- Get components from the TypeID:+  let prefix = TID.getPrefix typeID -- "mmzk"+      uuid   = TID.getUUID typeID+      time   = TID.getTime typeID -- A 'Word64' representing the timestamp in milliseconds++  -- Make a TypeID without prefix:+  typeID' <- TID.genTypeID ""+  print typeID'++  -- Make 10 TypeIDs in a batch. They are guaranteed to have the same timestamp and of ascending order:+  typeIDs <- TID.genTypeIDs "mmzk" 10+  mapM_ print typeIDs++  -- Parse a TypeID from string:+  case TID.parseString "mmzk_01h455vb4pex5vsknk084sn02q" of+    Left err     -> throwIO err+    Right typeID -> print typeID+```++For a full list of functions on `TypeID`, see [Data.TypeID](src/Data/TypeID.hs).++## More Usages++### V4 TypeID++We also support TypeID using UUIDv4, which loses the monoticity property. To use it, simply import `Data.TypeID.V4` instead of `Data.TypeID`.++```Haskell+{-# LANGUAGE OverloadedStrings #-}++import           Control.Exception+import           Data.TypeIDV4 (TypeIDV4)+import qualified Data.TypeIDV4 as TID++main :: IO ()+main = do++  -- Make a TypeID with prefix 'mmzk':+  typeID <- TID.genTypeID "mmzk"+  putStrLn $ TID.toString typeID++  -- Get components from the TypeID:+  let prefix = TID.getPrefix typeID -- "mmzk"+      uuid   = TID.getUUID typeID++  -- Make a TypeID without prefix:+  typeID' <- TID.genTypeID ""+  print typeID'++  -- Parse a TypeID from string:+  case TID.parseString "mmzk_5hjpeh96458fct8t49fnf9farw" of+    Left err     -> throwIO err+    Right typeID -> print typeID+```++### Type-level TypeID (KindID)+When using `TypeID`, if we want to check if the type matches, we usually need to get the prefix of the `TypeID` and compare it with the desired prefix at runtime. However, with Haskell's type system, we can do this at compile time instead. We call this TypeID with compile-time prefix a KindID.++Of course, that would require the desired prefix to be known at compile time. This is actually quite common, especially when we are using one prefix for one table in the database.++For example, suppose we have a function that takes a KindID with the prefix "user", it may have a signature like this: `f :: KindID "user" -> IO ()`.++Then if we try to pass in a KindID with the prefix "post", the compiler will complain, thus removing the runtime check and the associated overhead.++All the prefixes are type-checked at compile time, so if we try to pass in invalid prefixes, the compiler (again) will complain.++```Haskell+{-# LANGUAGE DataKinds #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE TypeApplications #-}++import           Control.Exception+import           Data.KindID (KindID)+import qualified Data.KindID as KID++main :: IO ()+main = do++  -- Make a KindID with prefix 'mmzk':+  kindID <- KID.genKindID @"mmzk" -- Has type `KindID "mmzk"`+  putStrLn $ KID.toString kindID++  -- Get components from the KindID:+  let prefix = KID.getPrefix kindID -- "mmzk"+      uuid   = KID.getUUID kindID+      time   = KID.getTime kindID -- A 'Word64' representing the timestamp in milliseconds++  -- Make a KindID without prefix:+  kindID' <- KID.genKindID @"" -- Has type `KindID ""`+  print kindID'++  -- Make 10 KindIDs in a batch. They are guaranteed to have the same timestamp and of ascending order:+  kindIDs <- KID.genKindIDs @"mmzk" 10+  mapM_ print kindIDs++  -- Parse a KindID from string:+  case KID.parseString @"mmzk" "mmzk_01h455vb4pex5vsknk084sn02q" of+    Left err     -> throwIO err+    Right kindID -> print kindID+```++For a full list of functions on `KindID`, see [Data.KindID](src/Data/KindID.hs).++### Functions with More General Types+`TypeID` and `KindID` shares many functions with the same name and functionality. So far, we are using qualified imports to diffentiate them (*e.g* `KID.fromString` and `TID.fromString`). Alternatively, we can use the methods of `IDConv` to use the same functions for both `TypeID` and `KindID`.++```Haskell+{-# LANGUAGE DataKinds #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE TypeApplication #-}++import           Control.Exception+import           Data.KindID+import           Data.TypeID++main :: IO ()+main = do+  -- Make a TypeID with prefix 'mmzk':+  typeID <- genID @TypeID "mmzk"+  print typeID++  -- Make a KindID with prefix 'mmzk':+  kindID <- genID @(KindID "mmzk")+  print kindID++  -- Parse a TypeID from string:+  case string2ID "mmzk_01h455vb4pex5vsknk084sn02q" :: Maybe TypeID of+    Left err     -> throwIO err+    Right typeID -> print typeID++  -- Parse a KindID from string:+  case string2ID "mmzk_01h455vb4pex5vsknk084sn02q" :: Maybe (KindID "mmzk") of+    Left err     -> throwIO err+    Right kindID -> print kindID+```++We no longer need to use qualified imports, but on the down side, we need to add explicit type annotations. Therefore it is a matter of preference.++Note that with the class methods, the type application with `Symbol` no longer works as the full type must be provided. For example, `string2ID @"mmzk" "mmzk_01h455vb4pex5vsknk084sn02q"` will not compile.++For a full list of these functions, see [Data.TypeID.Class](src/Data/TypeID/Class.hs).++### KindID with Data Kinds+Instead of using raw `Symbol`s as `KindID` prefixes, we can also define our custom data type for better semantics.++For example, suppose we have three tables for users, posts, and comments, and each table has a unique prefix, we can design the structure as following:++```Haskell+{-# LANGUAGE DataKinds #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE TypeApplications #-}++import           Data.KindID+import           Data.KindID.Class++data Prefix = User | Post | Comment++instance ToPrefix 'User where+  type PrefixSymbol 'User = "user"++instance ToPrefix 'Post where+  type PrefixSymbol 'Post = "post"++instance ToPrefix 'Comment where+  type PrefixSymbol 'Comment = "comment"+```++Now we can use `Prefix` as a prefix for `KindID`s, e.g.++```Haskell+main :: IO ()+main = do+  -- ...+  userID <- genKindID @'User -- Same as genKindID @"user"+  postID <- genKindID @'Post -- Same as genKindID @"post"+  commentID <- genKindID @'Comment -- Same as genKindID @"comment"+  -- ...+```++For more information, see [Data.KindID.Class](src/Data/KindID/Class.hs).++## Note+Not explicitly exported functions are considered internal and are subjected to changes.
mmzk-typeid.cabal view
@@ -1,6 +1,6 @@ cabal-version:      2.4 name:               mmzk-typeid-version:            0.3.1.0+version:            0.4.0.0  synopsis:           A TypeID implementation for Haskell description:@@ -27,7 +27,6 @@   .   > {-# LANGUAGE OverloadedStrings #-}   .-  For a quick "how-to-use" guide, please refer to the README.md file at https://github.com/MMZK1526/mmzk-typeid#readme.  homepage:           https://github.com/MMZK1526/mmzk-typeid bug-reports:        https://github.com/MMZK1526/mmzk-typeid/issues@@ -41,6 +40,7 @@ extra-source-files:     CHANGELOG.md     LICENSE+    README.md     test/invalid.json     test/valid.json @@ -50,11 +50,20 @@         Data.KindID,         Data.KindID.Class,         Data.KindID.Unsafe,+        Data.KindID.V4,+        Data.KindID.V4.Unsafe,+        Data.KindID.V7,+        Data.KindID.V7.Unsafe,         Data.TypeID,         Data.TypeID.Class,         Data.TypeID.Error,         Data.TypeID.Unsafe,-        Data.UUID.V7+        Data.TypeID.V4,+        Data.TypeID.V4.Unsafe,+        Data.TypeID.V7,+        Data.TypeID.V7.Unsafe,+        Data.UUID.V7,+        Data.UUID.Versions     other-modules:         Data.KindID.Internal,         Data.TypeID.Internal@@ -62,6 +71,8 @@         BlockArguments         ConstraintKinds         DataKinds+        FlexibleContexts+        FlexibleInstances         InstanceSigs         MultiWayIf         OverloadedStrings@@ -79,8 +90,10 @@         bytestring ^>= 0.11,         entropy ^>=0.4,         hashable ^>=1.4,+        random ^>=1.2,         text ^>=2.0,         time >=1.11 && <1.13,+        uuid ^>=1.3,         uuid-types ^>=1.0,     hs-source-dirs:   src     default-language: Haskell2010@@ -94,16 +107,27 @@         Data.KindID.Class,         Data.KindID.Internal,         Data.KindID.Unsafe,+        Data.KindID.V4,+        Data.KindID.V4.Unsafe,+        Data.KindID.V7,+        Data.KindID.V7.Unsafe,         Data.TypeID,         Data.TypeID.Class,         Data.TypeID.Error,         Data.TypeID.Internal,         Data.TypeID.Unsafe,-        Data.UUID.V7+        Data.TypeID.V4,+        Data.TypeID.V4.Unsafe,+        Data.TypeID.V7,+        Data.TypeID.V7.Unsafe,+        Data.UUID.V7,+        Data.UUID.Versions     default-extensions:         BlockArguments         ConstraintKinds         DataKinds+        FlexibleContexts+        FlexibleInstances         InstanceSigs         MultiWayIf         OverloadedStrings@@ -123,8 +147,10 @@         entropy,         hashable,         hspec ^>=2.11,+        random,         text,         time,+        uuid,         uuid-types,     hs-source-dirs:         src
src/Data/KindID.hs view
@@ -34,15 +34,17 @@ -- >   kindID <- genKindID @"user" -- >   ... --+-- It is a re-export of "Data.KindID.V7".+-- module Data.KindID   (   -- * Data types     KindID+  , KindID'   , getPrefix   , getUUID   , getTime   -- * 'KindID' generation ('KindID'-specific)-  , nilKindID   , genKindID   , genKindID'   , genKindIDs@@ -83,6 +85,7 @@   , fromTypeID   ) where -import           Data.KindID.Internal+import           Data.KindID.Internal (KindID')+import           Data.KindID.V7 import           Data.TypeID.Class-import           Data.TypeID.Internal (TypeID)+import           Data.TypeID.V7 (TypeID)
src/Data/KindID/Class.hs view
@@ -1,4 +1,3 @@-{-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE UndecidableInstances #-}  -- |
src/Data/KindID/Internal.hs view
@@ -1,4 +1,3 @@-{-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE UndecidableInstances #-}  -- |@@ -21,29 +20,33 @@ import qualified Data.Text as T import           Data.TypeID.Class import           Data.TypeID.Error-import           Data.TypeID.Internal (TypeID)+import           Data.TypeID.Internal (TypeID'(..)) import qualified Data.TypeID.Internal as TID import           Data.UUID.Types.Internal (UUID(..))+import           Data.TypeID.V7 (TypeID)+import qualified Data.UUID.V4 as V4 import qualified Data.UUID.V7 as V7+import           Data.UUID.Versions import           Foreign import           GHC.TypeLits (symbolVal)+import           System.Random  -- | A TypeID with the prefix encoded at type level. ----- It is dubbed 'KindID' because we the prefix here is a data kind rather than--- a type.-newtype KindID prefix = KindID { _getUUID :: UUID }+-- It is dubbed 'Data.KindID.V7.KindID' because the prefix here is a data kind+-- rather than a type.+newtype KindID' (version :: UUIDVersion) prefix = KindID' UUID   deriving (Eq, Ord)  instance (ToPrefix prefix, ValidPrefix (PrefixSymbol prefix))-  => Show (KindID prefix) where-    show :: KindID prefix -> String+  => Show (KindID' version prefix) where+    show :: KindID' version prefix -> String     show = toString     {-# INLINE show #-}  instance (ToPrefix prefix, ValidPrefix (PrefixSymbol prefix))-  => Read (KindID prefix) where-    readsPrec :: Int -> String -> [(KindID prefix, String)]+  => Read (KindID' version prefix) where+    readsPrec :: Int -> String -> [(KindID' version prefix, String)]     readsPrec _ str = case TID.parseStringS str of       Left _           -> []       Right (tid, rem) -> case fromTypeID tid of@@ -52,14 +55,14 @@     {-# INLINE readsPrec #-}  instance (ToPrefix prefix, ValidPrefix (PrefixSymbol prefix))-  => ToJSON (KindID prefix) where-    toJSON :: KindID prefix -> Value+  => ToJSON (KindID' version prefix) where+    toJSON :: KindID' version prefix -> Value     toJSON = toJSON . toText     {-# INLINE toJSON #-}  instance (ToPrefix prefix, ValidPrefix (PrefixSymbol prefix))-  => FromJSON (KindID prefix) where-    parseJSON :: Value -> Parser (KindID prefix)+  => FromJSON (KindID' version prefix) where+    parseJSON :: Value -> Parser (KindID' version prefix)     parseJSON str = do       s <- parseJSON str       case parseText s of@@ -68,29 +71,29 @@     {-# INLINE parseJSON #-}  instance (ToPrefix prefix, ValidPrefix (PrefixSymbol prefix))-  => ToJSONKey (KindID prefix) where-    toJSONKey :: ToJSONKeyFunction (KindID prefix)+  => ToJSONKey (KindID' version prefix) where+    toJSONKey :: ToJSONKeyFunction (KindID' version prefix)     toJSONKey = toJSONKeyText toText     {-# INLINE toJSONKey #-}  instance (ToPrefix prefix, ValidPrefix (PrefixSymbol prefix))-  => FromJSONKey (KindID prefix) where-    fromJSONKey :: FromJSONKeyFunction (KindID prefix)+  => FromJSONKey (KindID' version prefix) where+    fromJSONKey :: FromJSONKeyFunction (KindID' version prefix)     fromJSONKey = FromJSONKeyTextParser \t -> case parseText t of       Left err  -> fail $ show err       Right kid -> pure kid     {-# INLINE fromJSONKey #-} --- | See The 'Binary' instance of 'TypeID'.+-- | See The 'Binary' instance of 'TypeID''. instance (ToPrefix prefix, ValidPrefix (PrefixSymbol prefix))-  => Binary (KindID prefix) where-    put :: KindID prefix -> Put+  => Binary (KindID' version prefix) where+    put :: KindID' version prefix -> Put     put = put . toTypeID     {-# INLINE put #-} -    get :: Get (KindID prefix)+    get :: Get (KindID' version prefix)     get = do-      tid <- get :: Get TypeID+      tid <- get :: Get (TypeID' version)       case fromTypeID tid of         Nothing  -> fail "Binary: Prefix mismatch"         Just kid -> pure kid@@ -98,202 +101,246 @@  -- | Similar to the 'Binary' instance, but the 'UUID' is stored in host endian. instance (ToPrefix prefix, ValidPrefix (PrefixSymbol prefix))-  => Storable (KindID prefix) where-    sizeOf :: KindID prefix -> Int+  => Storable (KindID' version prefix) where+    sizeOf :: KindID' version prefix -> Int     sizeOf = sizeOf . toTypeID     {-# INLINE sizeOf #-} -    alignment :: KindID prefix -> Int+    alignment :: KindID' version prefix -> Int     alignment = alignment . toTypeID     {-# INLINE alignment #-} -    peek :: Ptr (KindID prefix) -> IO (KindID prefix)+    peek :: Ptr (KindID' version prefix) -> IO (KindID' version prefix)     peek ptr = do-      tid <- peek $ castPtr ptr :: IO TypeID+      tid <- peek $ castPtr ptr :: IO (TypeID' version)       case fromTypeID tid of         Nothing  -> fail "Storable: Prefix mismatch"         Just kid -> pure kid     {-# INLINE peek #-} -    poke :: Ptr (KindID prefix) -> KindID prefix -> IO ()+    poke :: Ptr (KindID' version prefix) -> KindID' version prefix -> IO ()     poke ptr = poke (castPtr ptr) . toTypeID     {-# INLINE poke #-}  instance (ToPrefix prefix, ValidPrefix (PrefixSymbol prefix))-  => Hashable (KindID prefix) where-    hashWithSalt :: Int -> KindID prefix -> Int+  => Hashable (KindID' version prefix) where+    hashWithSalt :: Int -> KindID' version prefix -> Int     hashWithSalt salt = hashWithSalt salt . toTypeID     {-# INLINE hashWithSalt #-} --- | Get the prefix, 'UUID', and timestamp of a 'KindID'.+-- | Get the prefix, 'UUID', and timestamp of a 'KindID''. instance (ToPrefix prefix, ValidPrefix (PrefixSymbol prefix))-  => IDType (KindID prefix) where-    getPrefix :: KindID prefix -> Text+  => IDType (KindID' version prefix) where+    getPrefix :: KindID' version prefix -> Text     getPrefix _ = T.pack (symbolVal (Proxy @(PrefixSymbol prefix)))     {-# INLINE getPrefix #-} -    getUUID :: KindID prefix -> UUID-    getUUID = _getUUID+    getUUID :: KindID' version prefix -> UUID+    getUUID (KindID' uuid) = uuid     {-# INLINE getUUID #-} -    getTime :: KindID prefix -> Word64+    getTime :: KindID' version prefix -> Word64     getTime = V7.getTime . getUUID     {-# INLINE getTime #-} --- | Conversion between 'KindID' and 'String'/'Text'/'ByteString'.+-- | Conversion between 'KindID'' and 'String'/'Text'/'ByteString'. instance (ToPrefix prefix, ValidPrefix (PrefixSymbol prefix))-  => IDConv (KindID prefix) where-    string2ID :: String -> Either TypeIDError (KindID prefix)+  => IDConv (KindID' version prefix) where+    string2ID :: String -> Either TypeIDError (KindID' version prefix)     string2ID = parseString     {-# INLINE string2ID #-} -    text2ID :: Text -> Either TypeIDError (KindID prefix)+    text2ID :: Text -> Either TypeIDError (KindID' version prefix)     text2ID = parseText     {-# INLINE text2ID #-} -    byteString2ID :: ByteString -> Either TypeIDError (KindID prefix)+    byteString2ID :: ByteString -> Either TypeIDError (KindID' version prefix)     byteString2ID = parseByteString     {-# INLINE byteString2ID #-} -    id2String :: KindID prefix -> String+    id2String :: KindID' version prefix -> String     id2String = toString     {-# INLINE id2String #-} -    id2Text :: KindID prefix -> Text+    id2Text :: KindID' version prefix -> Text     id2Text = toText     {-# INLINE id2Text #-} -    id2ByteString :: KindID prefix -> ByteString+    id2ByteString :: KindID' version prefix -> ByteString     id2ByteString = toByteString     {-# INLINE id2ByteString #-} -    unsafeString2ID :: String -> KindID prefix+    unsafeString2ID :: String -> KindID' version prefix     unsafeString2ID = unsafeParseString     {-# INLINE unsafeString2ID #-} -    unsafeText2ID :: Text -> KindID prefix+    unsafeText2ID :: Text -> KindID' version prefix     unsafeText2ID = unsafeParseText     {-# INLINE unsafeText2ID #-} -    unsafeByteString2ID :: ByteString -> KindID prefix+    unsafeByteString2ID :: ByteString -> KindID' version prefix     unsafeByteString2ID = unsafeParseByteString     {-# INLINE unsafeByteString2ID #-} --- | Generate 'KindID's.+-- | Generate 'Data.KindID.V7.KindID's. instance (ToPrefix prefix, ValidPrefix (PrefixSymbol prefix))-  => IDGen (KindID prefix) where-    type IDGenPrefix (KindID prefix) = 'Nothing+  => IDGen (KindID' 'V7 prefix) where+    type IDGenPrefix (KindID' 'V7 prefix) = 'Nothing -    genID_ :: MonadIO m => Proxy (KindID prefix) -> m (KindID prefix)+    genID_ :: MonadIO m => Proxy (KindID' 'V7 prefix) -> m (KindID' 'V7 prefix)     genID_ _ = genKindID     {-# INLINE genID_ #-} -    genID'_ :: MonadIO m => Proxy (KindID prefix) -> m (KindID prefix)+    genID'_ :: MonadIO m => Proxy (KindID' 'V7 prefix) -> m (KindID' 'V7 prefix)     genID'_ _ = genKindID'     {-# INLINE genID'_ #-} -    genIDs_ :: MonadIO m => Proxy (KindID prefix) -> Word16 -> m [KindID prefix]+    genIDs_ :: MonadIO m+            => Proxy (KindID' 'V7 prefix) -> Word16 -> m [KindID' 'V7 prefix]     genIDs_ _ = genKindIDs     {-# INLINE genIDs_ #-} -    decorate_ :: Proxy (KindID prefix) -> UUID -> KindID prefix+    decorate_ :: Proxy (KindID' 'V7 prefix) -> UUID -> KindID' 'V7 prefix     decorate_ _ = decorateKindID     {-# INLINE decorate_ #-} -    checkID_ :: Proxy (KindID prefix) -> KindID prefix -> Maybe TypeIDError+    checkID_ :: Proxy (KindID' 'V7 prefix)+             -> KindID' 'V7 prefix+             -> Maybe TypeIDError     checkID_ _ = checkKindID     {-# INLINE checkID_ #-}      checkIDWithEnv_ :: MonadIO m-                    => Proxy (KindID prefix)-                    -> KindID prefix+                    => Proxy (KindID' 'V7 prefix)+                    -> KindID' 'V7 prefix                     -> m (Maybe TypeIDError)     checkIDWithEnv_ _ = checkKindIDWithEnv     {-# INLINE checkIDWithEnv_ #-} --- | Generate a new 'KindID' from a prefix.+-- | Generate 'KindID'' ''V4's.+instance (ToPrefix prefix, ValidPrefix (PrefixSymbol prefix))+  => IDGen (KindID' 'V4 prefix) where+    type IDGenPrefix (KindID' 'V4 prefix) = 'Nothing++    genID_ :: MonadIO m => Proxy (KindID' 'V4 prefix) -> m (KindID' 'V4 prefix)+    genID_ _ = genKindIDV4+    {-# INLINE genID_ #-}++    genID'_ :: MonadIO m => Proxy (KindID' 'V4 prefix) -> m (KindID' 'V4 prefix)+    genID'_ _ = genKindIDV4'+    {-# INLINE genID'_ #-}++    genIDs_ :: MonadIO m+            => Proxy (KindID' 'V4 prefix) -> Word16 -> m [KindID' 'V4 prefix]+    genIDs_ _ n+      = fmap KindID' <$> replicateM (fromIntegral n) (liftIO V4.nextRandom)+    {-# INLINE genIDs_ #-}++    decorate_ :: Proxy (KindID' 'V4 prefix) -> UUID -> KindID' 'V4 prefix+    decorate_ _ = decorateKindID+    {-# INLINE decorate_ #-}++    checkID_ :: Proxy (KindID' 'V4 prefix)+             -> KindID' 'V4 prefix+             -> Maybe TypeIDError+    checkID_ _ = checkKindIDV4+    {-# INLINE checkID_ #-}++-- | Generate a new 'Data.KindID.V7.KindID' from a prefix. -- -- It throws a 'TypeIDError' if the prefix does not match the specification, -- namely if it's longer than 63 characters or if it contains characters other -- than lowercase latin letters. genKindID :: (ToPrefix prefix, ValidPrefix (PrefixSymbol prefix), MonadIO m)-          => m (KindID prefix)-genKindID = KindID <$> V7.genUUID+          => m (KindID' 'V7 prefix)+genKindID = KindID' <$> V7.genUUID {-# INLINE genKindID #-} --- | Generate a new 'KindID' from a prefix based on statelesss 'UUID'v7.+-- | Generate a new 'Data.KindID.V7.KindID' from a prefix based on stateless+-- 'UUID'v7. -- -- See the documentation of 'V7.genUUID'' for more information. genKindID' :: (ToPrefix prefix, ValidPrefix (PrefixSymbol prefix), MonadIO m)-           => m (KindID prefix)-genKindID' = KindID <$> V7.genUUID'+           => m (KindID' 'V7 prefix)+genKindID' = KindID' <$> V7.genUUID' {-# INLINE genKindID' #-} --- | Generate a list of 'KindID's from a prefix.+-- | Generate a list of 'Data.KindID.V7.KindID's from a prefix. ----- It tries its best to generate 'KindID's at the same timestamp, but it may not--- be possible if we are asking too many 'UUID's at the same time.+-- It tries its best to generate 'Data.KindID.V7.KindID's at the same timestamp,+-- but it may not be possible if we are asking too many 'UUID's at the same+-- time. ----- It is guaranteed that the first 32768 'KindID's are generated at the same--- timestamp.+-- It is guaranteed that the first 32768 'Data.KindID.V7.KindID's are generated+-- at the same timestamp. genKindIDs :: (ToPrefix prefix, ValidPrefix (PrefixSymbol prefix), MonadIO m)-           => Word16 -> m [KindID prefix]-genKindIDs n = fmap KindID <$> V7.genUUIDs n+           => Word16 -> m [KindID' 'V7 prefix]+genKindIDs n = fmap KindID' <$> V7.genUUIDs n {-# INLINE genKindIDs #-} --- | The nil 'KindID'.-nilKindID :: KindID ""-nilKindID = KindID V7.nil-{-# INLINE nilKindID #-}-{-# DEPRECATED nilKindID "Will be removed in the next major release." #-}+-- | Generate a new 'KindID'' ''V4' from a prefix.+--+-- It throws a 'TypeIDError' if the prefix does not match the specification,+-- namely if it's longer than 63 characters or if it contains characters other+-- than lowercase latin letters.+genKindIDV4 :: (ToPrefix prefix, ValidPrefix (PrefixSymbol prefix), MonadIO m)+            => m (KindID' 'V4 prefix)+genKindIDV4 = KindID' <$> liftIO V4.nextRandom+{-# INLINE genKindIDV4 #-} --- | Obtain a 'KindID' from a prefix and a 'UUID'.+-- | Generate a new 'KindID'' ''V4' from a prefix with insecure 'UUID'v4.+genKindIDV4' :: (ToPrefix prefix, ValidPrefix (PrefixSymbol prefix), MonadIO m)+             => m (KindID' 'V4 prefix)+genKindIDV4' = KindID' <$> liftIO randomIO+{-# INLINE genKindIDV4' #-}++-- | Obtain a 'KindID'' from a prefix and a 'UUID'. decorateKindID :: (ToPrefix prefix, ValidPrefix (PrefixSymbol prefix))-               => UUID -> KindID prefix-decorateKindID = KindID+               => UUID -> KindID' version prefix+decorateKindID = KindID' {-# INLINE decorateKindID #-} --- | Convert a 'KindID' to a 'TypeID'.+-- | Convert a 'KindID'' to a 'TypeID''. toTypeID :: (ToPrefix prefix, ValidPrefix (PrefixSymbol prefix))-         => KindID prefix -> TypeID-toTypeID kid = TID.TypeID (getPrefix kid) (getUUID kid)+         => KindID' version prefix -> TypeID' version+toTypeID kid = TID.TypeID' (getPrefix kid) (getUUID kid) {-# INLINE toTypeID #-} --- | Convert a 'TypeID' to a 'KindID'. If the actual prefix does not match+-- | Convert a 'TypeID'' to a 'KindID''. If the actual prefix does not match -- with the expected one as defined by the type, it returns @Nothing@.-fromTypeID :: forall prefix+fromTypeID :: forall version prefix             . (ToPrefix prefix, ValidPrefix (PrefixSymbol prefix))-           => TypeID -> Maybe (KindID prefix)+           => TypeID' version -> Maybe (KindID' version prefix) fromTypeID tid = do   guard (T.pack (symbolVal (Proxy @(PrefixSymbol prefix))) == getPrefix tid)-  pure $ KindID (getUUID tid)+  pure $ KindID' (getUUID tid) {-# INLINE fromTypeID #-} --- | Pretty-print a 'KindID'. It is 'id2String' with concrete type.+-- | Pretty-print a 'KindID''. It is 'id2String' with concrete type. toString :: (ToPrefix prefix, ValidPrefix (PrefixSymbol prefix))-         => KindID prefix -> String+         => KindID' version prefix -> String toString = TID.toString . toTypeID {-# INLINE toString #-} --- | Pretty-print a 'KindID' to strict 'Text'. It is 'id2Text' with concrete+-- | Pretty-print a 'KindID'' to strict 'Text'. It is 'id2Text' with concrete -- type. toText :: (ToPrefix prefix, ValidPrefix (PrefixSymbol prefix))-       => KindID prefix -> Text+       => KindID' version prefix -> Text toText = TID.toText . toTypeID {-# INLINE toText #-} --- | Pretty-print a 'KindID' to lazy 'ByteString'. It is 'id2ByteString' with+-- | Pretty-print a 'KindID'' to lazy 'ByteString'. It is 'id2ByteString' with -- concrete type. toByteString :: (ToPrefix prefix, ValidPrefix (PrefixSymbol prefix))-             => KindID prefix -> ByteString+             => KindID' version prefix -> ByteString toByteString = TID.toByteString . toTypeID {-# INLINE toByteString #-} --- | Parse a 'KindID' from its 'String' representation. It is 'parseString'+-- | Parse a 'KindID'' from its 'String' representation. It is 'parseString' -- with concrete type.-parseString :: forall prefix+parseString :: forall version prefix              . (ToPrefix prefix, ValidPrefix (PrefixSymbol prefix))-            => String -> Either TypeIDError (KindID prefix)+            => String -> Either TypeIDError (KindID' version prefix) parseString str = do   tid <- TID.parseString str   case fromTypeID tid of@@ -303,10 +350,11 @@     Just kid -> pure kid {-# INLINE parseString #-} --- | Parse a 'KindID' from its string representation as a strict 'Text'. It is+-- | Parse a 'KindID'' from its string representation as a strict 'Text'. It is -- 'parseText' with concrete type.-parseText :: forall prefix. (ToPrefix prefix, ValidPrefix (PrefixSymbol prefix))-          => Text -> Either TypeIDError (KindID prefix)+parseText :: forall version prefix+           . (ToPrefix prefix, ValidPrefix (PrefixSymbol prefix))+          => Text -> Either TypeIDError (KindID' version prefix) parseText str = do   tid <- TID.parseText str   case fromTypeID tid of@@ -316,11 +364,11 @@     Just kid -> pure kid {-# INLINE parseText #-} --- | Parse a 'KindID' from its string representation as a lazy 'ByteString'. It+-- | Parse a 'KindID'' from its string representation as a lazy 'ByteString'. It -- is 'parseByteString' with concrete type.-parseByteString :: forall prefix+parseByteString :: forall version prefix                  . (ToPrefix prefix, ValidPrefix (PrefixSymbol prefix))-                => ByteString -> Either TypeIDError (KindID prefix)+                => ByteString -> Either TypeIDError (KindID' version prefix) parseByteString str = do   tid <- TID.parseByteString str   case fromTypeID tid of@@ -330,36 +378,36 @@     Just kid -> pure kid {-# INLINE parseByteString #-} --- | Parse a 'KindID' from its 'String' representation, throwing an error when+-- | Parse a 'KindID'' from its 'String' representation, throwing an error when -- the parsing fails. It is 'string2IDM' with concrete type. parseStringM :: (ToPrefix prefix, ValidPrefix (PrefixSymbol prefix), MonadIO m)-             => String -> m (KindID prefix)+             => String -> m (KindID' version prefix) parseStringM = string2IDM {-# INLINE parseStringM #-} --- | Parse a 'KindID' from its string representation as a strict 'Text',+-- | Parse a 'KindID'' from its string representation as a strict 'Text', -- throwing an error when the parsing fails. It is 'text2IDM' with concrete -- type. parseTextM :: (ToPrefix prefix, ValidPrefix (PrefixSymbol prefix), MonadIO m)-           => Text -> m (KindID prefix)+           => Text -> m (KindID' version prefix) parseTextM = text2IDM {-# INLINE parseTextM #-} --- | Parse a 'KindID' from its string representation as a lazy 'ByteString',+-- | Parse a 'KindID'' from its string representation as a lazy 'ByteString', -- throwing an error when the parsing fails. It is 'byteString2IDM' with -- concrete type. parseByteStringM :: ( ToPrefix prefix                     , ValidPrefix (PrefixSymbol prefix)                     , MonadIO m )                  => ByteString-                 -> m (KindID prefix)+                 -> m (KindID' version prefix) parseByteStringM = byteString2IDM {-# INLINE parseByteStringM #-}  -- | Check if the prefix is valid and the suffix 'UUID' has the correct v7 -- version and variant. checkKindID :: (ToPrefix prefix, ValidPrefix (PrefixSymbol prefix))-            => KindID prefix -> Maybe TypeIDError+            => KindID' 'V7 prefix -> Maybe TypeIDError checkKindID = TID.checkTypeID . toTypeID {-# INLINE checkKindID #-} @@ -368,44 +416,51 @@ checkKindIDWithEnv :: ( ToPrefix prefix                       , ValidPrefix (PrefixSymbol prefix)                       , MonadIO m )-                   => KindID prefix -> m (Maybe TypeIDError)+                   => KindID' 'V7 prefix -> m (Maybe TypeIDError) checkKindIDWithEnv = TID.checkTypeIDWithEnv . toTypeID {-# INLINE checkKindIDWithEnv #-} --- | Convert a 'TypeID' to a 'KindID'. If the actual prefix does not match+-- | Check if the prefix is valid and the suffix 'UUID' has the correct v4+-- version and variant.+checkKindIDV4 :: (ToPrefix prefix, ValidPrefix (PrefixSymbol prefix))+              => KindID' 'V4 prefix -> Maybe TypeIDError+checkKindIDV4 = TID.checkTypeIDV4 . toTypeID+{-# INLINE checkKindIDV4 #-}++-- | Convert a 'TypeID'' to a 'KindID''. If the actual prefix does not match -- with the expected one as defined by the type, it does not complain and--- produces a wrong 'KindID'.+-- produces a wrong 'KindID''. unsafeFromTypeID :: (ToPrefix prefix, ValidPrefix (PrefixSymbol prefix))-                 => TypeID -> KindID prefix-unsafeFromTypeID tid = KindID (getUUID tid)+                 => TypeID' version -> KindID' version prefix+unsafeFromTypeID tid = KindID' (getUUID tid) {-# INLINE unsafeFromTypeID #-} --- | Parse a 'KindID' from its 'String' representation, but does not behave+-- | Parse a 'KindID'' from its 'String' representation, but does not behave -- correctly when parsing fails. -- -- More specifically, if the prefix does not match, it will not complain and--- produce the wrong 'KindID'. If there are other parse errors, it will crash.+-- produce the wrong 'KindID''. If there are other parse errors, it will crash. unsafeParseString :: (ToPrefix prefix, ValidPrefix (PrefixSymbol prefix))-                  => String -> KindID prefix+                  => String -> KindID' version prefix unsafeParseString = unsafeFromTypeID . TID.unsafeParseString {-# INLINE unsafeParseString #-} --- | Parse a 'KindID' from its string representation as a strict 'Text', but+-- | Parse a 'KindID'' from its string representation as a strict 'Text', but -- does not behave correctly when parsing fails. -- -- More specifically, if the prefix does not match, it will not complain and--- produce the wrong 'KindID'. If there are other parse errors, it will crash.+-- produce the wrong 'KindID''. If there are other parse errors, it will crash. unsafeParseText :: (ToPrefix prefix, ValidPrefix (PrefixSymbol prefix))-                => Text -> KindID prefix+                => Text -> KindID' version prefix unsafeParseText = unsafeFromTypeID . TID.unsafeParseText {-# INLINE unsafeParseText #-} --- | Parse a 'KindID' from its string representation as a lazy 'ByteString', but+-- | Parse a 'KindID'' from its string representation as a lazy 'ByteString', but -- does not behave correctly when parsing fails. -- -- More specifically, if the prefix does not match, it will not complain and--- produce the wrong 'KindID'. If there are other parse errors, it will crash.+-- produce the wrong 'KindID''. If there are other parse errors, it will crash. unsafeParseByteString :: (ToPrefix prefix, ValidPrefix (PrefixSymbol prefix))-                      => ByteString -> KindID prefix+                      => ByteString -> KindID' version prefix unsafeParseByteString = unsafeFromTypeID . TID.unsafeParseByteString {-# INLINE unsafeParseByteString #-}
src/Data/KindID/Unsafe.hs view
@@ -6,6 +6,8 @@ -- -- Unsafe 'KindID' functions. --+-- It is a re-export of "Data.TypeID.V7.Unsafe".+-- module Data.KindID.Unsafe   (   -- * Unsafe 'KindID' decoding ('KindID'-specific)@@ -20,5 +22,6 @@   , unsafeFromTypeID   ) where -import           Data.KindID.Internal+import           Data.KindID.V7 (KindID)+import           Data.KindID.V7.Unsafe import           Data.TypeID.Class
+ src/Data/KindID/V4.hs view
@@ -0,0 +1,178 @@+-- |+-- Module      : Data.KindID.V4+-- License     : MIT+-- Maintainer  : mmzk1526@outlook.com+-- Portability : GHC+--+-- 'Data.KindID.V7.KindID' with 'UUID'v4.+--+module Data.KindID.V4+  (+  -- * Data types+    KindIDV4+  , getPrefix+  , getUUID+  , getTime+  -- * 'KindIDV4' generation ('KindIDV4'-specific)+  , genKindID+  , genKindID'+  , decorateKindID+  -- * 'KindIDV4' generation (class methods)+  , genID+  , genID'+  , decorate+  -- * Validation ('KindIDV4'-specific)+  , checkKindID+  -- * Validation (class methods)+  , checkID+  -- * Encoding & decoding ('KindIDV4'-specific)+  , toString+  , toText+  , toByteString+  , parseString+  , parseText+  , parseByteString+  , parseStringM+  , parseTextM+  , parseByteStringM+  -- * Encoding & decoding (class methods)+  , id2String+  , id2Text+  , id2ByteString+  , string2ID+  , text2ID+  , byteString2ID+  , string2IDM+  , text2IDM+  , byteString2IDM+  -- * Type-level & term-level conversion+  , toTypeID+  , fromTypeID+  ) where++import           Control.Monad.IO.Class+import           Data.ByteString.Lazy (ByteString)+import           Data.KindID.Class+import           Data.KindID.Internal (KindID'(..))+import qualified Data.KindID.Internal as KID+import           Data.Text (Text)+import           Data.TypeID.Class+import           Data.TypeID.Error+import           Data.TypeID.V4 (TypeIDV4)+import           Data.TypeID.V7 (TypeID)+import           Data.UUID.Types.Internal (UUID)+import           Data.UUID.Versions+import           Data.Word++-- | Similar to 'Data.KindID.V7.KindID', but uses 'UUID'v4.+type KindIDV4 = KID.KindID' 'V4++-- | Generate a new 'KindIDV4' from a prefix.+--+-- It throws a 'TypeIDError' if the prefix does not match the specification,+-- namely if it's longer than 63 characters or if it contains characters other+-- than lowercase latin letters.+genKindID :: (ToPrefix prefix, ValidPrefix (PrefixSymbol prefix), MonadIO m)+          => m (KindIDV4 prefix)+genKindID = KID.genKindIDV4+{-# INLINE genKindID #-}++-- | Generate a new 'KindIDV4' from a prefix based on insecure 'UUID'v4.+genKindID' :: (ToPrefix prefix, ValidPrefix (PrefixSymbol prefix), MonadIO m)+           => m (KindIDV4 prefix)+genKindID' = KID.genKindIDV4'+{-# INLINE genKindID' #-}++-- | Obtain a 'KindIDV4' from a prefix and a 'UUID'.+decorateKindID :: (ToPrefix prefix, ValidPrefix (PrefixSymbol prefix))+               => UUID -> KindIDV4 prefix+decorateKindID = KID.decorateKindID+{-# INLINE decorateKindID #-}++-- | Check if the prefix is valid and the suffix 'UUID' has the correct v4+-- version and variant.+checkKindID :: (ToPrefix prefix, ValidPrefix (PrefixSymbol prefix))+            => KindIDV4 prefix -> Maybe TypeIDError+checkKindID = KID.checkKindIDV4+{-# INLINE checkKindID #-}+++-- | Pretty-print a 'KindIDV4'. It is 'id2String' with concrete type.+toString :: (ToPrefix prefix, ValidPrefix (PrefixSymbol prefix))+         => KindIDV4 prefix -> String+toString = KID.toString+{-# INLINE toString #-}++-- | Pretty-print a 'KindIDV4' to strict 'Text'. It is 'id2Text' with concrete+-- type.+toText :: (ToPrefix prefix, ValidPrefix (PrefixSymbol prefix))+       => KindIDV4 prefix -> Text+toText = KID.toText+{-# INLINE toText #-}++-- | Pretty-print a 'KindIDV4' to lazy 'ByteString'. It is 'id2ByteString' with+-- concrete type.+toByteString :: (ToPrefix prefix, ValidPrefix (PrefixSymbol prefix))+             => KindIDV4 prefix -> ByteString+toByteString = KID.toByteString+{-# INLINE toByteString #-}++-- | Parse a 'KindIDV4' from its 'String' representation. It is 'parseString'+-- with concrete type.+parseString :: forall prefix+             . (ToPrefix prefix, ValidPrefix (PrefixSymbol prefix))+            => String -> Either TypeIDError (KindIDV4 prefix)+parseString = KID.parseString+{-# INLINE parseString #-}++-- | Parse a 'KindIDV4' from its string representation as a strict 'Text'. It is+-- 'parseText' with concrete type.+parseText :: forall prefix+           . (ToPrefix prefix, ValidPrefix (PrefixSymbol prefix))+          => Text -> Either TypeIDError (KindIDV4 prefix)+parseText = KID.parseText+{-# INLINE parseText #-}++-- | Parse a 'KindIDV4' from its string representation as a lazy 'ByteString'.+-- It is 'parseByteString' with concrete type.+parseByteString :: forall prefix+                 . (ToPrefix prefix, ValidPrefix (PrefixSymbol prefix))+                => ByteString -> Either TypeIDError (KindIDV4 prefix)+parseByteString = KID.parseByteString++-- | Parse a 'KindIDV4' from its 'String' representation, throwing an error when+-- the parsing fails. It is 'string2IDM' with concrete type.+parseStringM :: (ToPrefix prefix, ValidPrefix (PrefixSymbol prefix), MonadIO m)+             => String -> m (KindIDV4 prefix)+parseStringM = KID.parseStringM+{-# INLINE parseStringM #-}++-- | Parse a 'KindIDV4' from its string representation as a strict 'Text',+-- throwing an error when the parsing fails. It is 'text2IDM' with concrete+-- type. It is 'parseTextM' with concrete type.+parseTextM :: (ToPrefix prefix, ValidPrefix (PrefixSymbol prefix), MonadIO m)+           => Text -> m (KindIDV4 prefix)+parseTextM = KID.parseTextM+{-# INLINE parseTextM #-}++-- | Parse a 'KindIDV4' from its string representation as a lazy 'ByteString',+-- throwing an error when the parsing fails. It is 'byteString2IDM' with+-- concrete type. It is 'parseByteStringM' with concrete type.+parseByteStringM :: ( ToPrefix prefix+                    , ValidPrefix (PrefixSymbol prefix)+                    , MonadIO m )+                 => ByteString -> m (KindIDV4 prefix)+parseByteStringM = KID.parseByteStringM+{-# INLINE parseByteStringM #-}++-- | Convert a 'KindIDV4' to a 'Data.TypeID.V4.TypeIDV4'.+toTypeID :: (ToPrefix prefix, ValidPrefix (PrefixSymbol prefix))+         => KindIDV4 prefix -> TypeIDV4+toTypeID = KID.toTypeID+{-# INLINE toTypeID #-}++-- | Convert a 'TypeIDV4' to a 'KindIDV4'.+fromTypeID :: (ToPrefix prefix, ValidPrefix (PrefixSymbol prefix))+           => TypeIDV4 -> Maybe (KindIDV4 prefix)+fromTypeID = KID.fromTypeID+{-# INLINE fromTypeID #-}
+ src/Data/KindID/V4/Unsafe.hs view
@@ -0,0 +1,69 @@+-- |+-- Module      : Data.KindID.V4.Unsafe+-- License     : MIT+-- Maintainer  : mmzk1526@outlook.com+-- Portability : GHC+--+-- Unsafe 'KindIDV4' functions.+--+module Data.KindID.V4.Unsafe+  (+  -- * Unsafe 'KindIDV4' decoding ('KindIDV4'-specific)+    unsafeParseString+  , unsafeParseText+  , unsafeParseByteString+  -- * Unsafe 'KindIDV4' decoding (class methods)+  , unsafeString2ID+  , unsafeText2ID+  , unsafeByteString2ID+  -- * Unsafe conversion+  , unsafeFromTypeID+  ) where++import           Data.ByteString.Lazy (ByteString)+import           Data.KindID.Class+import           Data.KindID.Internal (KindID')+import qualified Data.KindID.Internal as KID+import           Data.KindID.V4 (KindIDV4)+import           Data.TypeID.Internal (TypeID')+import           Data.TypeID.V4 (TypeIDV4)+import           Data.Text (Text)+import           Data.TypeID.Class++-- | Parse a 'KindIDV4' from its 'String' representation, but does not behave+-- correctly when parsing fails.+--+-- More specifically, if the prefix does not match, it will not complain and+-- produce the wrong 'KindIDV4'. If there are other parse errors, it will crash.+unsafeParseString :: (ToPrefix prefix, ValidPrefix (PrefixSymbol prefix))+                  => String -> KindIDV4 prefix+unsafeParseString = KID.unsafeParseString+{-# INLINE unsafeParseString #-}++-- | Parse a 'KindIDV4' from its string representation as a strict 'Text', but+-- does not behave correctly when parsing fails.+--+-- More specifically, if the prefix does not match, it will not complain and+-- produce the wrong 'KindIDV4'. If there are other parse errors, it will crash.+unsafeParseText :: (ToPrefix prefix, ValidPrefix (PrefixSymbol prefix))+                => Text -> KindIDV4 prefix+unsafeParseText = KID.unsafeParseText+{-# INLINE unsafeParseText #-}++-- | Parse a 'KindIDV4' from its string representation as a lazy 'ByteString',+-- but does not behave correctly when parsing fails.+--+-- More specifically, if the prefix does not match, it will not complain and+-- produce the wrong 'KindIDV4'. If there are other parse errors, it will crash.+unsafeParseByteString :: (ToPrefix prefix, ValidPrefix (PrefixSymbol prefix))+                      => ByteString -> KindIDV4 prefix+unsafeParseByteString = KID.unsafeParseByteString+{-# INLINE unsafeParseByteString #-}++-- | Convert a 'TypeIDV4' to a 'KindIDV4'. If the actual prefix does not match+-- with the expected one as defined by the type, it does not complain and+-- produces a wrong 'KindIDV4'.+unsafeFromTypeID :: (ToPrefix prefix, ValidPrefix (PrefixSymbol prefix))+                 => TypeIDV4 -> KindIDV4 prefix+unsafeFromTypeID = KID.unsafeFromTypeID+{-# INLINE unsafeFromTypeID #-}
+ src/Data/KindID/V7.hs view
@@ -0,0 +1,208 @@+-- |+-- Module      : Data.KindID.V7+-- License     : MIT+-- Maintainer  : mmzk1526@outlook.com+-- Portability : GHC+--+-- Similar to "Data.TypeID", but the type is statically determined in the type+-- level.+--+module Data.KindID.V7+  (+  -- * Data types+    KindID+  , KindIDV7+  , getPrefix+  , getUUID+  , getTime+  -- * 'KindID' generation ('KindID'-specific)+  , genKindID+  , genKindID'+  , genKindIDs+  , decorateKindID+  -- * 'KindID' generation (class methods)+  , genID+  , genID'+  , genIDs+  , decorate+  -- * Validation ('KindID'-specific)+  , checkKindID+  , checkKindIDWithEnv+  -- * Validation (class methods)+  , checkID+  , checkIDWithEnv+  -- * Encoding & decoding ('KindID'-specific)+  , toString+  , toText+  , toByteString+  , parseString+  , parseText+  , parseByteString+  , parseStringM+  , parseTextM+  , parseByteStringM+  -- * Encoding & decoding (class methods)+  , id2String+  , id2Text+  , id2ByteString+  , string2ID+  , text2ID+  , byteString2ID+  , string2IDM+  , text2IDM+  , byteString2IDM+  -- * Type-level & term-level conversion+  , toTypeID+  , fromTypeID+  ) where++import           Control.Monad.IO.Class+import           Data.ByteString.Lazy (ByteString)+import           Data.KindID.Class+import           Data.KindID.Internal (KindID'(..))+import qualified Data.KindID.Internal as KID+import           Data.Text (Text)+import           Data.TypeID.Class+import           Data.TypeID.Error+import           Data.TypeID.V7 (TypeID)+import           Data.UUID.Types.Internal (UUID)+import           Data.UUID.Versions+import           Data.Word++-- | A type alias for the default 'KindID' implementation with 'UUID'v7.+type KindID = KID.KindID' 'V7++-- | A type alias for 'KindID'.+type KindIDV7 = KindID++-- | Generate a new 'KindID' from a prefix.+--+-- It throws a 'TypeIDError' if the prefix does not match the specification,+-- namely if it's longer than 63 characters or if it contains characters other+-- than lowercase latin letters.+genKindID :: (ToPrefix prefix, ValidPrefix (PrefixSymbol prefix), MonadIO m)+          => m (KindID prefix)+genKindID = KID.genKindID+{-# INLINE genKindID #-}++-- | Generate a new 'KindID' from a prefix based on stateless 'UUID'v7.+--+-- See the documentation of 'V7.genUUID'' for more information.+genKindID' :: (ToPrefix prefix, ValidPrefix (PrefixSymbol prefix), MonadIO m)+           => m (KindID prefix)+genKindID' = KID.genKindID'+{-# INLINE genKindID' #-}++-- | Generate a list of 'KindID's from a prefix.+--+-- It tries its best to generate 'KindID's at the same timestamp, but it may not+-- be possible if we are asking too many 'UUID's at the same time.+--+-- It is guaranteed that the first 32768 'KindID's are generated at the same+-- timestamp.+genKindIDs :: (ToPrefix prefix, ValidPrefix (PrefixSymbol prefix), MonadIO m)+           => Word16 -> m [KindID prefix]+genKindIDs = KID.genKindIDs+{-# INLINE genKindIDs #-}++-- | Obtain a 'KindID' from a prefix and a 'UUID'.+decorateKindID :: (ToPrefix prefix, ValidPrefix (PrefixSymbol prefix))+               => UUID -> KindID prefix+decorateKindID = KID.decorateKindID+{-# INLINE decorateKindID #-}++-- | Check if the prefix is valid and the suffix 'UUID' has the correct v7+-- version and variant.+checkKindID :: (ToPrefix prefix, ValidPrefix (PrefixSymbol prefix))+            => KindID prefix -> Maybe TypeIDError+checkKindID = KID.checkKindID+{-# INLINE checkKindID #-}++-- | Similar to 'checkKindID', but also checks if the suffix 'UUID' is+-- generated in the past.+checkKindIDWithEnv :: ( ToPrefix prefix+                      , ValidPrefix (PrefixSymbol prefix)+                      , MonadIO m )+                   => KindID' 'V7 prefix -> m (Maybe TypeIDError)+checkKindIDWithEnv = KID.checkKindIDWithEnv+{-# INLINE checkKindIDWithEnv #-}++-- | Pretty-print a 'KindID'. It is 'id2String' with concrete type.+toString :: (ToPrefix prefix, ValidPrefix (PrefixSymbol prefix))+         => KindID prefix -> String+toString = KID.toString+{-# INLINE toString #-}++-- | Pretty-print a 'KindID' to strict 'Text'. It is 'id2Text' with concrete+-- type.+toText :: (ToPrefix prefix, ValidPrefix (PrefixSymbol prefix))+       => KindID prefix -> Text+toText = KID.toText+{-# INLINE toText #-}++-- | Pretty-print a 'KindID' to lazy 'ByteString'. It is 'id2ByteString' with+-- concrete type.+toByteString :: (ToPrefix prefix, ValidPrefix (PrefixSymbol prefix))+             => KindID prefix -> ByteString+toByteString = KID.toByteString+{-# INLINE toByteString #-}++-- | Parse a 'KindID' from its 'String' representation. It is 'parseString'+-- with concrete type.+parseString :: forall prefix+             . (ToPrefix prefix, ValidPrefix (PrefixSymbol prefix))+            => String -> Either TypeIDError (KindID prefix)+parseString = KID.parseString+{-# INLINE parseString #-}++-- | Parse a 'KindID' from its string representation as a strict 'Text'. It is+-- 'parseText' with concrete type.+parseText :: forall prefix+           . (ToPrefix prefix, ValidPrefix (PrefixSymbol prefix))+          => Text -> Either TypeIDError (KindID prefix)+parseText = KID.parseText+{-# INLINE parseText #-}++-- | Parse a 'KindID' from its string representation as a lazy 'ByteString'. It+-- is 'parseByteString' with concrete type.+parseByteString :: forall prefix+                 . (ToPrefix prefix, ValidPrefix (PrefixSymbol prefix))+                => ByteString -> Either TypeIDError (KindID prefix)+parseByteString = KID.parseByteString++-- | Parse a 'KindID' from its 'String' representation, throwing an error when+-- the parsing fails. It is 'string2IDM' with concrete type.+parseStringM :: (ToPrefix prefix, ValidPrefix (PrefixSymbol prefix), MonadIO m)+             => String -> m (KindID prefix)+parseStringM = KID.parseStringM+{-# INLINE parseStringM #-}++-- | Parse a 'KindID' from its string representation as a strict 'Text',+-- throwing an error when the parsing fails. It is 'text2IDM' with concrete+-- type. It is 'parseTextM' with concrete type.+parseTextM :: (ToPrefix prefix, ValidPrefix (PrefixSymbol prefix), MonadIO m)+           => Text -> m (KindID prefix)+parseTextM = KID.parseTextM+{-# INLINE parseTextM #-}++-- | Parse a 'KindID' from its string representation as a lazy 'ByteString',+-- throwing an error when the parsing fails. It is 'byteString2IDM' with+-- concrete type. It is 'parseByteStringM' with concrete type.+parseByteStringM :: ( ToPrefix prefix+                    , ValidPrefix (PrefixSymbol prefix)+                    , MonadIO m )+                 => ByteString -> m (KindID prefix)+parseByteStringM = KID.parseByteStringM+{-# INLINE parseByteStringM #-}++-- | Convert a 'KindID' to a 'TypeID'.+toTypeID :: (ToPrefix prefix, ValidPrefix (PrefixSymbol prefix))+         => KindID prefix -> TypeID+toTypeID = KID.toTypeID+{-# INLINE toTypeID #-}++-- | Convert a 'TypeID' to a 'KindID'.+fromTypeID :: (ToPrefix prefix, ValidPrefix (PrefixSymbol prefix))+           => TypeID -> Maybe (KindID prefix)+fromTypeID = KID.fromTypeID+{-# INLINE fromTypeID #-}
+ src/Data/KindID/V7/Unsafe.hs view
@@ -0,0 +1,69 @@+-- |+-- Module      : Data.KindID.V7.Unsafe+-- License     : MIT+-- Maintainer  : mmzk1526@outlook.com+-- Portability : GHC+--+-- Unsafe 'KindID' functions.+--+module Data.KindID.V7.Unsafe+  (+  -- * Unsafe 'KindID' decoding ('KindID'-specific)+    unsafeParseString+  , unsafeParseText+  , unsafeParseByteString+  -- * Unsafe 'KindID' decoding (class methods)+  , unsafeString2ID+  , unsafeText2ID+  , unsafeByteString2ID+  -- * Unsafe conversion+  , unsafeFromTypeID+  ) where++import           Data.ByteString.Lazy (ByteString)+import           Data.KindID.Class+import           Data.KindID.Internal (KindID')+import qualified Data.KindID.Internal as KID+import           Data.KindID.V7 (KindID)+import           Data.TypeID.Internal (TypeID')+import           Data.TypeID.V7 (TypeID)+import           Data.Text (Text)+import           Data.TypeID.Class++-- | Parse a 'KindID' from its 'String' representation, but does not behave+-- correctly when parsing fails.+--+-- More specifically, if the prefix does not match, it will not complain and+-- produce the wrong 'KindID'. If there are other parse errors, it will crash.+unsafeParseString :: (ToPrefix prefix, ValidPrefix (PrefixSymbol prefix))+                  => String -> KindID prefix+unsafeParseString = KID.unsafeParseString+{-# INLINE unsafeParseString #-}++-- | Parse a 'KindID' from its string representation as a strict 'Text', but+-- does not behave correctly when parsing fails.+--+-- More specifically, if the prefix does not match, it will not complain and+-- produce the wrong 'KindID'. If there are other parse errors, it will crash.+unsafeParseText :: (ToPrefix prefix, ValidPrefix (PrefixSymbol prefix))+                => Text -> KindID prefix+unsafeParseText = KID.unsafeParseText+{-# INLINE unsafeParseText #-}++-- | Parse a 'KindID' from its string representation as a lazy 'ByteString', but+-- does not behave correctly when parsing fails.+--+-- More specifically, if the prefix does not match, it will not complain and+-- produce the wrong 'KindID'. If there are other parse errors, it will crash.+unsafeParseByteString :: (ToPrefix prefix, ValidPrefix (PrefixSymbol prefix))+                      => ByteString -> KindID prefix+unsafeParseByteString = KID.unsafeParseByteString+{-# INLINE unsafeParseByteString #-}++-- | Convert a 'TypeID'' to a 'KindID''. If the actual prefix does not match+-- with the expected one as defined by the type, it does not complain and+-- produces a wrong 'KindID''.+unsafeFromTypeID :: (ToPrefix prefix, ValidPrefix (PrefixSymbol prefix))+                 => TypeID -> KindID prefix+unsafeFromTypeID = KID.unsafeFromTypeID+{-# INLINE unsafeFromTypeID #-}
src/Data/TypeID.hs view
@@ -1,5 +1,5 @@ -- |--- Module      : Data.KindID+-- Module      : Data.TypeID -- License     : MIT -- Maintainer  : mmzk1526@outlook.com -- Portability : GHC@@ -7,15 +7,17 @@ -- An implementation of the TypeID specification: -- https://github.com/jetpack-io/typeid. --+-- It is a re-export of "Data.TypeID.V7".+-- module Data.TypeID   (   -- * Data types     TypeID+  , TypeID'   , getPrefix   , getUUID   , getTime   -- * 'TypeID' generation ('TypeID'-specific)-  , nilTypeID   , genTypeID   , genTypeID'   , genTypeIDs@@ -55,4 +57,5 @@   ) where  import           Data.TypeID.Class-import           Data.TypeID.Internal+import           Data.TypeID.Internal (TypeID')+import           Data.TypeID.V7
src/Data/TypeID/Class.hs view
@@ -6,7 +6,7 @@ -- Maintainer  : mmzk1526@outlook.com -- Portability : GHC ----- A module with the APIs for any 'Data.TypeID.TypeID'-ish identifier type.+-- A module with the APIs for any 'Data.TypeID.V7.TypeID'-ish identifier type. -- -- These type classes are useful to define custom TypeID-ish identifier types. -- For example, if one wishes to remove the constraints on prefix, or use a@@ -33,18 +33,21 @@ import           Control.Exception import           Control.Monad.IO.Class import           Data.ByteString.Lazy (ByteString)+import qualified Data.ByteString.Lazy as BSL import           Data.Kind (Type) import           Data.Proxy import           Data.Text (Text)+import qualified Data.Text as T+import           Data.Text.Encoding import           Data.TypeID.Error import           Data.UUID.V7 (UUID) import           Data.Word --- | A constraint synonym for a 'Data.TypeID.TypeID'-ish identifier type that+-- | A constraint synonym for a 'Data.TypeID.V7.TypeID'-ish identifier type that -- supports ID generation and string conversion. type TypeIDLike a = (IDType a, IDConv a, IDGen a) --- | A type class for a 'Data.TypeID.TypeID'-ish identifier type, which has a+-- | A type class for a 'Data.TypeID.V7.TypeID'-ish identifier type, which has a -- 'Text' prefix and a 'UUID' suffix. class IDType a where   -- | Get the prefix of the identifier.@@ -57,27 +60,39 @@   -- timestamp-based.   getTime :: a -> Word64 --- | A type class for converting between a 'Data.TypeID.TypeID'-ish identifier--- type and some string representations.+-- | A type class for converting between a 'Data.TypeID.V7.TypeID'-ish+-- identifier type and some string representations. class IDConv a where   -- | Parse the identifier from its 'String' representation.   string2ID :: String -> Either TypeIDError a+  string2ID = text2ID . T.pack+  {-# INLINE string2ID #-}    -- | Parse the identifier from its string representation as a strict 'Text'.   text2ID :: Text -> Either TypeIDError a+  text2ID = byteString2ID . BSL.fromStrict . encodeUtf8+  {-# INLINE text2ID #-}    -- | Parse the identifier from its string representation as a lazy   -- 'ByteString'.   byteString2ID :: ByteString -> Either TypeIDError a+  byteString2ID = string2ID . T.unpack . decodeUtf8 . BSL.toStrict+  {-# INLINE byteString2ID #-}    -- | Pretty-print the identifier to a 'String'.   id2String :: a -> String+  id2String = T.unpack . id2Text+  {-# INLINE id2String #-}    -- | Pretty-print the identifier to a strict 'Text'.   id2Text :: a -> Text+  id2Text = decodeUtf8 . BSL.toStrict . id2ByteString+  {-# INLINE id2Text #-}    -- | Pretty-print the identifier to a lazy 'ByteString'.   id2ByteString :: a -> ByteString+  id2ByteString = BSL.fromStrict . encodeUtf8 . T.pack . id2String+  {-# INLINE id2ByteString #-}    -- | Parse the identifier from its 'String' representation, throwing an error   -- when the parsing fails.@@ -114,6 +129,15 @@   unsafeByteString2ID :: ByteString -> a   unsafeByteString2ID = either (error . show) id . byteString2ID   {-# INLINE unsafeByteString2ID #-}+  {-# MINIMAL string2ID, id2String+            | string2ID, id2Text+            | string2ID, id2ByteString+            | text2ID, id2String+            | text2ID, id2Text+            | text2ID, id2ByteString+            | byteString2ID, id2String+            | byteString2ID, id2Text+            | byteString2ID, id2ByteString #-}  -- | Generate a new identifier with the given prefix. genID :: forall a m. (IDGen a, MonadIO m) => GenFunc (IDGenPrefix a) (m a)@@ -151,7 +175,7 @@ checkIDWithEnv = checkIDWithEnv_ @a @m Proxy {-# INLINE checkIDWithEnv #-} --- | A type class for generating 'Data.TypeID.TypeID'-ish identifiers.+-- | A type class for generating 'Data.TypeID.V7.TypeID'-ish identifiers. -- -- The methods in this type class are not directly used since each of them has -- a dummy 'Proxy' in order to compile. We implement the methods here and use
src/Data/TypeID/Error.hs view
@@ -26,7 +26,7 @@     -- | From a `Data.KindID.KindID` conversion. The prefix doesn't match with     -- the expected.   | TypeIDErrorPrefixMismatch Text Text-    -- | The 'Data.UUID.V7.UUID' suffix has errors.+    -- | The 'Data.UUID.Types.Internal.UUID' suffix has errors.   | TypeIDErrorUUIDError   deriving (Eq, Ord) 
src/Data/TypeID/Internal.hs view
@@ -33,34 +33,39 @@ import           Data.TypeID.Error import           Data.UUID.Types.Internal (UUID(..)) import qualified Data.UUID.Types.Internal as UUID+import qualified Data.UUID.V4 as V4 import qualified Data.UUID.V7 as V7+import           Data.UUID.Versions+import           System.Random import           Foreign --- | The constructor is not exposed to the public API to prevent generating--- invalid @TypeID@s.-data TypeID = TypeID { _getPrefix :: Text-                     , _getUUID   :: UUID }+-- | This data type also supports 'Data.TypeID.V7.TypeID's with 'UUID' versions+-- other than v7.+--+--  The constructor is not exposed to the public API to prevent generating+-- invalid 'TypeID''s.+data TypeID' (version :: UUIDVersion) = TypeID' Text UUID   deriving (Eq, Ord) -instance Show TypeID where-  show :: TypeID -> String+instance Show (TypeID' version) where+  show :: TypeID' version -> String   show = toString   {-# INLINE show #-} -instance Read TypeID where-  readsPrec :: Int -> String -> [(TypeID, String)]+instance Read (TypeID' version) where+  readsPrec :: Int -> String -> [(TypeID' version, String)]   readsPrec _ str = case parseStringS str of-    Left _      -> []-    Right (x,y) -> [(x, y)]+    Left _       -> []+    Right (x, y) -> [(x, y)]   {-# INLINE readsPrec #-} -instance ToJSON TypeID where-  toJSON :: TypeID -> Value+instance ToJSON (TypeID' version) where+  toJSON :: TypeID' version -> Value   toJSON = toJSON . toText   {-# INLINE toJSON #-} -instance FromJSON TypeID where-  parseJSON :: Value -> Parser TypeID+instance FromJSON (TypeID' version) where+  parseJSON :: Value -> Parser (TypeID' version)   parseJSON str = do     s <- parseJSON str     case parseText s of@@ -68,13 +73,13 @@       Right tid -> pure tid   {-# INLINE parseJSON #-} -instance ToJSONKey TypeID where-  toJSONKey :: ToJSONKeyFunction TypeID+instance ToJSONKey (TypeID' version) where+  toJSONKey :: ToJSONKeyFunction (TypeID' version)   toJSONKey = toJSONKeyText toText   {-# INLINE toJSONKey #-} -instance FromJSONKey TypeID where-  fromJSONKey :: FromJSONKeyFunction TypeID+instance FromJSONKey (TypeID' version) where+  fromJSONKey :: FromJSONKeyFunction (TypeID' version)   fromJSONKey = FromJSONKeyTextParser \t -> case parseText t of     Left err  -> fail $ show err     Right tid -> pure tid@@ -93,9 +98,9 @@ -- suffix 'UUID' is required. Because of this, the sorting order may be -- different from the string representation, but they are guaranteed to be the -- same if the same prefix is used.-instance Binary TypeID where-  put :: TypeID -> Put-  put (TypeID prefix uuid) = do+instance Binary (TypeID' version) where+  put :: TypeID' version -> Put+  put (TypeID' prefix uuid) = do     put uuid     let encodedPrefix = concat5BitInts . fmap (subtract 96) . BS.unpack                       $ encodeUtf8 prefix@@ -103,7 +108,7 @@     forM_ encodedPrefix putWord8   {-# INLINE put #-} -  get :: Get TypeID+  get :: Get (TypeID' version)   get = do     uuid          <- get     len           <- getWord8@@ -111,20 +116,20 @@     when (length encodedPrefix > 63) $ fail "Binary: Prefix too long"     when (any (liftM2 (&&) (< 1) (> 25)) encodedPrefix)          (fail "Binary: Invalid prefix")-    pure $ TypeID (decodeUtf8 . BS.pack $ fmap (+ 96) encodedPrefix) uuid+    pure $ TypeID' (decodeUtf8 . BS.pack $ fmap (+ 96) encodedPrefix) uuid   {-# INLINE get #-}  -- | Similar to the 'Binary' instance, but the 'UUID' is stored in host endian.-instance Storable TypeID where-  sizeOf :: TypeID -> Int+instance Storable (TypeID' version) where+  sizeOf :: TypeID' version -> Int   sizeOf _ = 60   {-# INLINE sizeOf #-} -  alignment :: TypeID -> Int+  alignment :: TypeID' version -> Int   alignment _ = 4   {-# INLINE alignment #-} -  peek :: Ptr TypeID -> IO TypeID+  peek :: Ptr (TypeID' version) -> IO (TypeID' version)   peek ptr = do     uuid          <- peek (castPtr ptr :: Ptr UUID)     len           <- fromIntegral <$> (peekByteOff ptr 16 :: IO Word8)@@ -133,11 +138,11 @@     when (length encodedPrefix > 63) $ fail "Storable: Prefix too long"     when (any (liftM2 (&&) (< 1) (> 25)) encodedPrefix)          (fail "Storable: Invalid prefix")-    pure $ TypeID (decodeUtf8 . BS.pack $ fmap (+ 96) encodedPrefix) uuid+    pure $ TypeID' (decodeUtf8 . BS.pack $ fmap (+ 96) encodedPrefix) uuid   {-# INLINE peek #-} -  poke :: Ptr TypeID -> TypeID -> IO ()-  poke ptr (TypeID prefix uuid) = do+  poke :: Ptr (TypeID' version) -> TypeID' version -> IO ()+  poke ptr (TypeID' prefix uuid) = do     poke (castPtr ptr) uuid     let encodedPrefix = concat5BitInts . fmap (subtract 96) . BS.unpack                       $ encodeUtf8 prefix@@ -145,257 +150,287 @@     zipWithM_ (pokeByteOff ptr . (+ 16)) [1..] encodedPrefix   {-# INLINE poke #-} -instance Hashable TypeID where-  hashWithSalt :: Int -> TypeID -> Int-  hashWithSalt salt (TypeID prefix uuid)+instance Hashable (TypeID' version) where+  hashWithSalt :: Int -> TypeID' version -> Int+  hashWithSalt salt (TypeID' prefix uuid)     = salt `hashWithSalt` prefix `hashWithSalt` uuid   {-# INLINE hashWithSalt #-} --- | Get the prefix, 'UUID', and timestamp of a 'TypeID'.-instance IDType TypeID where-  getPrefix :: TypeID -> Text-  getPrefix = _getPrefix+-- | Get the prefix, 'UUID', and timestamp of a 'TypeID''.+instance IDType (TypeID' version) where+  getPrefix :: TypeID' version -> Text+  getPrefix (TypeID' prefix _) = prefix   {-# INLINE getPrefix #-} -  getUUID :: TypeID -> UUID-  getUUID = _getUUID+  getUUID :: TypeID' version -> UUID+  getUUID (TypeID' _ uuid) = uuid   {-# INLINE getUUID #-} -  getTime :: TypeID -> Word64+  getTime :: TypeID' version -> Word64   getTime = V7.getTime . getUUID   {-# INLINE getTime #-} --- | Conversion between 'TypeID' and 'String'/'Text'/'ByteString'.-instance IDConv TypeID where-  string2ID :: String -> Either TypeIDError TypeID+-- | Conversion between 'TypeID'' and 'String'/'Text'/'ByteString'.+instance IDConv (TypeID' version) where+  string2ID :: String -> Either TypeIDError (TypeID' version)   string2ID = parseString   {-# INLINE string2ID #-} -  text2ID :: Text -> Either TypeIDError TypeID+  text2ID :: Text -> Either TypeIDError (TypeID' version)   text2ID = parseText   {-# INLINE text2ID #-} -  byteString2ID :: ByteString -> Either TypeIDError TypeID+  byteString2ID :: ByteString -> Either TypeIDError (TypeID' version)   byteString2ID = parseByteString   {-# INLINE byteString2ID #-} -  id2String :: TypeID -> String+  id2String :: TypeID' version -> String   id2String = toString   {-# INLINE id2String #-} -  id2Text :: TypeID -> Text+  id2Text :: TypeID' version -> Text   id2Text = toText   {-# INLINE id2Text #-} -  id2ByteString :: TypeID -> ByteString+  id2ByteString :: TypeID' version -> ByteString   id2ByteString = toByteString   {-# INLINE id2ByteString #-} -  unsafeString2ID :: String -> TypeID+  unsafeString2ID :: String -> TypeID' version   unsafeString2ID = unsafeParseString   {-# INLINE unsafeString2ID #-} -  unsafeText2ID :: Text -> TypeID+  unsafeText2ID :: Text -> TypeID' version   unsafeText2ID = unsafeParseText   {-# INLINE unsafeText2ID #-} -  unsafeByteString2ID :: ByteString -> TypeID+  unsafeByteString2ID :: ByteString -> TypeID' version   unsafeByteString2ID = unsafeParseByteString   {-# INLINE unsafeByteString2ID #-} --- | Generate 'TypeID's.-instance IDGen TypeID where-  type IDGenPrefix TypeID = 'Just Text+-- | Generate 'Data.TypeIDs.V7.TypeIDs'.+instance IDGen (TypeID' 'V7) where+  type IDGenPrefix (TypeID' 'V7) = 'Just Text -  genID_ :: MonadIO m => Proxy TypeID -> Text -> m TypeID+  genID_ :: MonadIO m => Proxy (TypeID' 'V7) -> Text -> m (TypeID' 'V7)   genID_ _ = genTypeID   {-# INLINE genID_ #-} -  genID'_ :: MonadIO m => Proxy TypeID -> Text -> m TypeID+  genID'_ :: MonadIO m => Proxy (TypeID' 'V7) -> Text -> m (TypeID' 'V7)   genID'_ _ = genTypeID'   {-# INLINE genID'_ #-} -  genIDs_ :: MonadIO m => Proxy TypeID -> Text -> Word16 -> m [TypeID]+  genIDs_ :: MonadIO m+          => Proxy (TypeID' 'V7)+          -> Text+          -> Word16+          -> m [TypeID' 'V7]   genIDs_ _ = genTypeIDs   {-# INLINE genIDs_ #-} -  decorate_ :: Proxy TypeID -> Text -> UUID -> Either TypeIDError TypeID+  decorate_ :: Proxy (TypeID' 'V7)+            -> Text+            -> UUID+            -> Either TypeIDError (TypeID' 'V7)   decorate_ _ = decorateTypeID   {-# INLINE decorate_ #-} -  checkID_ :: Proxy TypeID -> TypeID -> Maybe TypeIDError+  checkID_ :: Proxy (TypeID' 'V7) -> TypeID' 'V7 -> Maybe TypeIDError   checkID_ _ = checkTypeID   {-# INLINE checkID_ #-}    checkIDWithEnv_ :: MonadIO m-                  => Proxy TypeID -> TypeID -> m (Maybe TypeIDError)+                  => Proxy (TypeID' 'V7)+                  -> TypeID' 'V7+                  -> m (Maybe TypeIDError)   checkIDWithEnv_ _ = checkTypeIDWithEnv   {-# INLINE checkIDWithEnv_ #-} --- | Generate a new 'TypeID' from a prefix.+instance IDGen (TypeID' 'V4) where+  type IDGenPrefix (TypeID' 'V4) = 'Just Text++  genID_ :: MonadIO m => Proxy (TypeID' 'V4) -> Text -> m (TypeID' 'V4)+  genID_ _ = genTypeIDV4+  {-# INLINE genID_ #-}++  genID'_ :: MonadIO m => Proxy (TypeID' 'V4) -> Text -> m (TypeID' 'V4)+  genID'_ _ = genTypeIDV4'+  {-# INLINE genID'_ #-}++  genIDs_ :: MonadIO m+          => Proxy (TypeID' 'V4)+          -> Text+          -> Word16+          -> m [TypeID' 'V4]+  genIDs_ _ prefix n = case checkPrefix prefix of+    Nothing  -> map (TypeID' prefix) <$> replicateM (fromIntegral n) randomIO+    Just err -> liftIO $ throwIO err+  {-# INLINE genIDs_ #-}++  decorate_ :: Proxy (TypeID' 'V4)+            -> Text+            -> UUID+            -> Either TypeIDError (TypeID' 'V4)+  decorate_ _ = decorateTypeID+  {-# INLINE decorate_ #-}++  checkID_ :: Proxy (TypeID' 'V4) -> TypeID' 'V4 -> Maybe TypeIDError+  checkID_ _ = checkTypeIDV4+  {-# INLINE checkID_ #-}++-- | Generate a new 'Data.TypeID.V7.TypeID' from a prefix. -- -- It throws a 'TypeIDError' if the prefix does not match the specification, -- namely if it's longer than 63 characters or if it contains characters other -- than lowercase latin letters.-genTypeID :: MonadIO m => Text -> m TypeID+genTypeID :: MonadIO m => Text -> m (TypeID' 'V7) genTypeID = fmap head . (`genTypeIDs` 1) {-# INLINE genTypeID #-} --- | Generate a new 'TypeID' from a prefix based on statelesss 'UUID'v7.+-- | Generate a new 'Data.TypeID.V7.TypeID' from a prefix based on stateless+-- 'UUID'v7. -- -- See the documentation of 'V7.genUUID'' for more information.-genTypeID' :: MonadIO m => Text -> m TypeID+genTypeID' :: MonadIO m => Text -> m (TypeID' 'V7) genTypeID' prefix = case checkPrefix prefix of   Nothing  -> unsafeGenTypeID' prefix   Just err -> liftIO $ throwIO err {-# INLINE genTypeID' #-} --- | Generate a list of 'TypeID's from a prefix.+-- | Generate a list of 'Data.TypeID.V7.TypeID's from a prefix. ----- It tries its best to generate 'TypeID's at the same timestamp, but it may not--- be possible if we are asking too many 'UUID's at the same time.+-- It tries its best to generate 'Data.TypeID.V7.TypeID's at the same timestamp,+-- but it may not be possible if we are asking too many 'UUID's at the same+-- time. ----- It is guaranteed that the first 32768 'TypeID's are generated at the same--- timestamp.-genTypeIDs :: MonadIO m => Text -> Word16 -> m [TypeID]+-- It is guaranteed that the first 32768 'Data.TypeID.V7.TypeID's are generated+-- at the same timestamp.+genTypeIDs :: MonadIO m => Text -> Word16 -> m [TypeID' 'V7] genTypeIDs prefix n = case checkPrefix prefix of   Nothing  -> unsafeGenTypeIDs prefix n   Just err -> liftIO $ throwIO err {-# INLINE genTypeIDs #-} --- | Generate a new 'TypeID' from a prefix, but without checking if the prefix--- is valid.-unsafeGenTypeID :: MonadIO m => Text -> m TypeID-unsafeGenTypeID = fmap head . (`unsafeGenTypeIDs` 1)-{-# INLINE unsafeGenTypeID #-}---- | Generate a new 'TypeID' from a prefix based on statelesss 'UUID'v7, but--- without checking if the prefix is valid.-unsafeGenTypeID' :: MonadIO m => Text -> m TypeID-unsafeGenTypeID' prefix = TypeID prefix <$> V7.genUUID'-{-# INLINE unsafeGenTypeID' #-}---- | Generate n 'TypeID's from a prefix, but without checking if the prefix is--- valid.------ It tries its best to generate 'TypeID's at the same timestamp, but it may not--- be possible if we are asking too many 'UUID's at the same time.+-- | Generate a new 'TypeID'' ''V4' from a prefix. ----- It is guaranteed that the first 32768 'TypeID's are generated at the same--- timestamp.-unsafeGenTypeIDs :: MonadIO m => Text -> Word16 -> m [TypeID]-unsafeGenTypeIDs prefix n = map (TypeID prefix) <$> V7.genUUIDs n-{-# INLINE unsafeGenTypeIDs #-}+-- It throws a 'TypeIDError' if the prefix does not match the specification,+-- namely if it's longer than 63 characters or if it contains characters other+-- than lowercase latin letters.+genTypeIDV4 :: MonadIO m => Text -> m (TypeID' 'V4)+genTypeIDV4 prefix = case checkPrefix prefix of+  Nothing  -> unsafeGenTypeIDV4 prefix+  Just err -> liftIO $ throwIO err+{-# INLINE genTypeIDV4 #-} --- | The nil 'TypeID'.-nilTypeID :: TypeID-nilTypeID = TypeID "" UUID.nil-{-# INLINE nilTypeID #-}-{-# DEPRECATED nilTypeID "Will be removed in the next major release." #-}+-- | Generate a new 'TypeID'' ''V4' from a prefix based on insecure 'UUID'v4.+genTypeIDV4' :: MonadIO m => Text -> m (TypeID' 'V4)+genTypeIDV4' prefix = case checkPrefix prefix of+  Nothing  -> unsafeGenTypeIDV4' prefix+  Just err -> liftIO $ throwIO err+{-# INLINE genTypeIDV4' #-} --- | Obtain a 'TypeID' from a prefix and a 'UUID'.-decorateTypeID :: Text -> UUID -> Either TypeIDError TypeID+-- | Obtain a 'TypeID'' from a prefix and a 'UUID'.+decorateTypeID :: Text -> UUID -> Either TypeIDError (TypeID' version) decorateTypeID prefix uuid = case checkPrefix prefix of-  Nothing  -> Right $ TypeID prefix uuid+  Nothing  -> Right $ TypeID' prefix uuid   Just err -> Left err {-# INLINE decorateTypeID #-} --- | Pretty-print a 'TypeID'. It is 'id2String' with concrete type.-toString :: TypeID -> String-toString (TypeID prefix (UUID w1 w2)) = if T.null prefix+-- | Pretty-print a 'TypeID''. It is 'id2String' with concrete type.+toString :: TypeID' version -> String+toString (TypeID' prefix (UUID w1 w2)) = if T.null prefix   then suffixEncode bs   else T.unpack prefix ++ "_" ++ suffixEncode bs   where     bs = runPut $ mapM_ putWord64be [w1, w2] {-# INLINE toString #-} --- | Pretty-print a 'TypeID' to strict 'Text'. It is 'id2Text' with concrete+-- | Pretty-print a 'TypeID'' to strict 'Text'. It is 'id2Text' with concrete -- type.-toText :: TypeID -> Text-toText (TypeID prefix (UUID w1 w2)) = if T.null prefix+toText :: TypeID' version -> Text+toText (TypeID' prefix (UUID w1 w2)) = if T.null prefix   then T.pack (suffixEncode bs)   else prefix <> "_" <> T.pack (suffixEncode bs)   where     bs = runPut $ mapM_ putWord64be [w1, w2] {-# INLINE toText #-} --- | Pretty-print a 'TypeID' to lazy 'ByteString'. It is 'id2ByteString' with+-- | Pretty-print a 'TypeID'' to lazy 'ByteString'. It is 'id2ByteString' with -- concrete type.-toByteString :: TypeID -> ByteString+toByteString :: TypeID' version -> ByteString toByteString = fromString . toString {-# INLINE toByteString #-} --- | Parse a 'TypeID' from its 'String' representation. It is 'string2ID' with+-- | Parse a 'TypeID'' from its 'String' representation. It is 'string2ID' with -- concrete type.-parseString :: String -> Either TypeIDError TypeID+parseString :: String -> Either TypeIDError (TypeID' version) parseString str = case parseStringS str of   Left err        -> Left err   Right (tid, "") -> Right tid   _               -> Left TypeIDErrorUUIDError {-# INLINE parseString #-} -parseStringS :: String -> Either TypeIDError (TypeID, String)+parseStringS :: String -> Either TypeIDError (TypeID' version, String) parseStringS str = case span (/= '_') str of   ("", _)              -> Left TypeIDExtraSeparator   (_, "")              -> do     let (uuid, rem) = splitAt 26 str         bs          = fromString uuid-    (, rem) . TypeID "" <$> decodeUUID bs+    (, rem) . TypeID' "" <$> decodeUUID bs   (prefix, _ : suffix) -> do     let prefix'     = T.pack prefix         (uuid, rem) = splitAt 26 suffix         bs          = fromString uuid     case checkPrefix prefix' of-      Nothing  -> (, rem) . TypeID prefix' <$> decodeUUID bs+      Nothing  -> (, rem) . TypeID' prefix' <$> decodeUUID bs       Just err -> Left err --- | Parse a 'TypeID' from its string representation as a strict 'Text'. It is+-- | Parse a 'TypeID'' from its string representation as a strict 'Text'. It is -- 'text2ID' with concrete type.-parseText :: Text -> Either TypeIDError TypeID+parseText :: Text -> Either TypeIDError (TypeID' version) parseText text = case second T.uncons $ T.span (/= '_') text of   ("", _)                    -> Left TypeIDExtraSeparator-  (_, Nothing)               -> TypeID "" <$> decodeUUID bs+  (_, Nothing)               -> TypeID' ""+                            <$> decodeUUID (BSL.fromStrict $ encodeUtf8 text)   (prefix, Just (_, suffix)) -> do     case checkPrefix prefix of-      Nothing  -> TypeID prefix+      Nothing  -> TypeID' prefix               <$> decodeUUID (BSL.fromStrict $ encodeUtf8 suffix)       Just err -> Left err-  where-    bs = BSL.fromStrict $ encodeUtf8 text --- | Parse a 'TypeID' from its string representation as a lazy 'ByteString'. It+-- | Parse a 'TypeID'' from its string representation as a lazy 'ByteString'. It -- is 'byteString2ID' with concrete type.-parseByteString :: ByteString -> Either TypeIDError TypeID+parseByteString :: ByteString -> Either TypeIDError (TypeID' version) parseByteString bs = case second BSL.uncons $ BSL.span (/= 95) bs of   ("", _)                    -> Left TypeIDExtraSeparator-  (_, Nothing)               -> TypeID "" <$> decodeUUID bs+  (_, Nothing)               -> TypeID' "" <$> decodeUUID bs   (prefix, Just (_, suffix)) -> do     let prefix' = decodeUtf8 $ BSL.toStrict prefix     case checkPrefix prefix' of-      Nothing  -> TypeID prefix' <$> decodeUUID suffix+      Nothing  -> TypeID' prefix' <$> decodeUUID suffix       Just err -> Left err --- | Parse a 'TypeID' from its 'String' representation, throwing an error when+-- | Parse a 'TypeID'' from its 'String' representation, throwing an error when -- the parsing fails. It is 'string2IDM' with concrete type.-parseStringM :: MonadIO m => String -> m TypeID+parseStringM :: MonadIO m => String -> m (TypeID' version) parseStringM = string2IDM {-# INLINE parseStringM #-} --- | Parse a 'TypeID' from its string representation as a strict 'Text',+-- | Parse a 'TypeID'' from its string representation as a strict 'Text', -- throwing an error when the parsing fails. It is 'text2IDM' with concrete -- type.-parseTextM :: MonadIO m => Text -> m TypeID+parseTextM :: MonadIO m => Text -> m (TypeID' version) parseTextM = text2IDM {-# INLINE parseTextM #-} --- | Parse a 'TypeID' from its string representation as a lazy 'ByteString',+-- | Parse a 'TypeID'' from its string representation as a lazy 'ByteString', -- throwing an error when the parsing fails. It is 'byteString2IDM' with -- concrete type.-parseByteStringM :: MonadIO m => ByteString -> m TypeID+parseByteStringM :: MonadIO m => ByteString -> m (TypeID' version) parseByteStringM = byteString2IDM {-# INLINE parseByteStringM #-} --- | Check if the given prefix is a valid TypeID prefix.+-- | Check if the given prefix is a valid 'TypeID'' prefix. checkPrefix :: Text -> Maybe TypeIDError checkPrefix prefix   | T.length prefix > 63 = Just $ TypeIDErrorPrefixTooLong (T.length prefix)@@ -407,45 +442,90 @@  -- | Check if the prefix is valid and the suffix 'UUID' has the correct v7 -- version and variant.-checkTypeID :: TypeID -> Maybe TypeIDError-checkTypeID (TypeID prefix uuid)+checkTypeID :: TypeID' 'V7 -> Maybe TypeIDError+checkTypeID (TypeID' prefix uuid)   = msum [ checkPrefix prefix          , TypeIDErrorUUIDError <$ guard (not $ V7.validate uuid) ] {-# INLINE checkTypeID #-} +-- | Check if the prefix is valid and the suffix 'UUID' has the correct v4+-- version and variant.+checkTypeIDV4 :: TypeID' 'V4 -> Maybe TypeIDError+checkTypeIDV4 (TypeID' prefix uuid)+  = msum [ checkPrefix prefix+         , TypeIDErrorUUIDError <$ guard (not $ validateWithVersion uuid V4) ]+{-# INLINE checkTypeIDV4 #-}+ -- | Similar to 'checkTypeID', but also checks if the suffix 'UUID' is -- generated in the past.-checkTypeIDWithEnv :: MonadIO m => TypeID -> m (Maybe TypeIDError)-checkTypeIDWithEnv tid@(TypeID _ uuid)+checkTypeIDWithEnv :: MonadIO m => TypeID' 'V7 -> m (Maybe TypeIDError)+checkTypeIDWithEnv tid@(TypeID' _ uuid)   = fmap (checkTypeID tid `mplus`)          ((TypeIDErrorUUIDError <$) . guard . not <$> V7.validateWithTime uuid) {-# INLINE checkTypeIDWithEnv #-} --- | Parse a 'TypeID' from its 'String' representation, but crashes when+-- | Generate a new 'Data.TypeID.V7.TypeID' from a prefix, but without checking+-- if the prefix is valid.+unsafeGenTypeID :: MonadIO m => Text -> m (TypeID' 'V7)+unsafeGenTypeID = fmap head . (`unsafeGenTypeIDs` 1)+{-# INLINE unsafeGenTypeID #-}++-- | Generate a new 'TypeID'' ''V4' from a prefix, but without checking if the+-- prefix is valid.+unsafeGenTypeIDV4 :: MonadIO m => Text -> m (TypeID' 'V4)+unsafeGenTypeIDV4 prefix = TypeID' prefix <$> liftIO V4.nextRandom+{-# INLINE unsafeGenTypeIDV4 #-}++-- | Generate a new 'Data.TypeID.V7.TypeID' from a prefix based on stateless+-- 'UUID'v7, but without checking if the prefix is valid.+unsafeGenTypeID' :: MonadIO m => Text -> m (TypeID' V7)+unsafeGenTypeID' prefix = TypeID' prefix <$> V7.genUUID'+{-# INLINE unsafeGenTypeID' #-}++-- | Generate a new 'TypeID'' ''V4' from a prefix based on insecure 'UUID'v4,+-- but without checking if the prefix is valid.+unsafeGenTypeIDV4' :: MonadIO m => Text -> m (TypeID' V4)+unsafeGenTypeIDV4' prefix = TypeID' prefix <$> liftIO randomIO+{-# INLINE unsafeGenTypeIDV4' #-}++-- | Generate n 'Data.TypeID.V7.TypeID's from a prefix, but without checking if+-- the prefix is valid.+--+-- It tries its best to generate 'Data.TypeID.V7.TypeID's at the same timestamp,+-- but it may not be possible if we are asking too many 'UUID's at the same+-- time.+--+-- It is guaranteed that the first 32768 'Data.TypeID.V7.TypeID's are generated+-- at the same timestamp.+unsafeGenTypeIDs :: MonadIO m => Text -> Word16 -> m [TypeID' V7]+unsafeGenTypeIDs prefix n = map (TypeID' prefix) <$> V7.genUUIDs n+{-# INLINE unsafeGenTypeIDs #-}++-- | Parse a 'TypeID'' from its 'String' representation, but crashes when -- parsing fails.-unsafeParseString :: String -> TypeID+unsafeParseString :: String -> TypeID' version unsafeParseString str = case span (/= '_') str of-  (_, "")              -> TypeID "" . unsafeDecodeUUID $ fromString str-  (prefix, _ : suffix) -> TypeID (T.pack prefix)+  (_, "")              -> TypeID' "" . unsafeDecodeUUID $ fromString str+  (prefix, _ : suffix) -> TypeID' (T.pack prefix)                         . unsafeDecodeUUID $ fromString suffix {-# INLINE unsafeParseString #-} --- | Parse a 'TypeID' from its string representation as a strict 'Text', but+-- | Parse a 'TypeID'' from its string representation as a strict 'Text', but -- crashes when parsing fails.-unsafeParseText :: Text -> TypeID+unsafeParseText :: Text -> TypeID' version unsafeParseText text = case second T.uncons $ T.span (/= '_') text of-  (_, Nothing)               -> TypeID "" . unsafeDecodeUUID+  (_, Nothing)               -> TypeID' "" . unsafeDecodeUUID                               . BSL.fromStrict $ encodeUtf8 text-  (prefix, Just (_, suffix)) -> TypeID prefix . unsafeDecodeUUID+  (prefix, Just (_, suffix)) -> TypeID' prefix . unsafeDecodeUUID                               . BSL.fromStrict . encodeUtf8 $ suffix {-# INLINE unsafeParseText #-} --- | Parse a 'TypeID' from its string representation as a lazy 'ByteString',+-- | Parse a 'TypeID'' from its string representation as a lazy 'ByteString', -- but crashes when parsing fails.-unsafeParseByteString :: ByteString -> TypeID+unsafeParseByteString :: ByteString -> TypeID' version unsafeParseByteString bs = case second BSL.uncons $ BSL.span (/= 95) bs of-  (_, Nothing)               -> TypeID "" $ unsafeDecodeUUID bs-  (prefix, Just (_, suffix)) -> TypeID (decodeUtf8 $ BSL.toStrict prefix)+  (_, Nothing)               -> TypeID' "" $ unsafeDecodeUUID bs+  (prefix, Just (_, suffix)) -> TypeID' (decodeUtf8 $ BSL.toStrict prefix)                               . unsafeDecodeUUID $ suffix {-# INLINE unsafeParseByteString #-} 
src/Data/TypeID/Unsafe.hs view
@@ -6,6 +6,8 @@ -- -- Unsafe 'TypeID' functions. --+-- It is a re-export of "Data.TypeID.V7.Unsafe".+-- module Data.TypeID.Unsafe   (   -- * Unsafe 'TypeID' generation@@ -23,4 +25,5 @@   ) where  import           Data.TypeID.Class-import           Data.TypeID.Internal+import           Data.TypeID.V7 (TypeID)+import           Data.TypeID.V7.Unsafe
+ src/Data/TypeID/V4.hs view
@@ -0,0 +1,146 @@+-- |+-- Module      : Data.TypeID.V4+-- License     : MIT+-- Maintainer  : mmzk1526@outlook.com+-- Portability : GHC+--+-- 'Data.TypeID.V7.TypeID' with 'UUID'v4.+--+module Data.TypeID.V4+  (+  -- * Data types+    TypeIDV4+  , getPrefix+  , getUUID+  -- * 'TypeIDV4' generation ('TypeIDV4'-specific)+  , genTypeID+  , genTypeID'+  , decorateTypeID+  -- * 'TypeIDV4' generation (class methods)+  , genID+  , genID'+  , decorate+  -- * Validation ('TypeIDV4'-specific)+  , checkPrefix+  , checkTypeID+  -- * Validation (class methods)+  , checkID+  -- * Encoding & decoding ('TypeIDV4'-specific)+  , toString+  , toText+  , toByteString+  , parseString+  , parseText+  , parseByteString+  , parseStringM+  , parseTextM+  , parseByteStringM+  -- * Encoding & decoding (class methods)+  , id2String+  , id2Text+  , id2ByteString+  , string2ID+  , text2ID+  , byteString2ID+  , string2IDM+  , text2IDM+  , byteString2IDM+  ) where++import           Control.Monad.IO.Class+import           Data.ByteString.Lazy (ByteString)+import           Data.Text (Text)+import           Data.TypeID.Class+import           Data.TypeID.Error+import qualified Data.TypeID.Internal as TID+import           Data.UUID.Types (UUID)+import           Data.UUID.Versions+import           Data.Word++-- | Similar to 'Data.TypeID.V7.TypeID', but uses 'UUID'v4.+type TypeIDV4 = TID.TypeID' 'V4++-- | Generate a new 'TypeIDV4' from a prefix.+--+-- It throws a 'TypeIDError' if the prefix does not match the specification,+-- namely if it's longer than 63 characters or if it contains characters other+-- than lowercase latin letters.+genTypeID :: MonadIO m => Text -> m TypeIDV4+genTypeID = TID.genTypeIDV4+{-# INLINE genTypeID #-}++-- | Generate a new 'TypeIDV4' from a prefix based on insecure 'UUID'v4.+genTypeID' :: MonadIO m => Text -> m TypeIDV4+genTypeID' = TID.genTypeIDV4'+{-# INLINE genTypeID' #-}++-- | Obtain a 'TypeIDV4' from a prefix and a 'UUID'.+decorateTypeID :: Text -> UUID -> Either TypeIDError TypeIDV4+decorateTypeID = TID.decorateTypeID+{-# INLINE decorateTypeID #-}++-- | Check if the given prefix is a valid 'TypeIDV4' prefix.+checkPrefix :: Text -> Maybe TypeIDError+checkPrefix = TID.checkPrefix+{-# INLINE checkPrefix #-}++-- | Check if the prefix is valid and the suffix 'UUID' has the correct v4+-- version and variant.+checkTypeID :: TypeIDV4 -> Maybe TypeIDError+checkTypeID = TID.checkTypeIDV4+{-# INLINE checkTypeID #-}++-- | Pretty-print a 'TypeIDV4'. It is 'id2String' with concrete type.+toString :: TypeIDV4 -> String+toString = TID.toString+{-# INLINE toString #-}++-- | Pretty-print a 'TypeIDV4' to strict 'Text'. It is 'id2Text' with concrete+-- type.+toText :: TypeIDV4 -> Text+toText = TID.toText+{-# INLINE toText #-}++-- | Pretty-print a 'TypeIDV4' to lazy 'ByteString'. It is 'id2ByteString'+-- with concrete type.+toByteString :: TypeIDV4 -> ByteString+toByteString = TID.toByteString+{-# INLINE toByteString #-}++-- | Parse a 'TypeIDV4' from its 'String' representation. It is 'string2ID' with+-- concrete type.+parseString :: String -> Either TypeIDError TypeIDV4+parseString = TID.parseString+{-# INLINE parseString #-}++-- | Parse a 'TypeIDV4' from its string representation as a strict 'Text'. It+-- is 'text2ID' with concrete type.+parseText :: Text -> Either TypeIDError TypeIDV4+parseText = TID.parseText+{-# INLINE parseText #-}++-- | Parse a 'TypeIDV4' from its string representation as a lazy 'ByteString'.+-- It is 'byteString2ID' with concrete type.+parseByteString :: ByteString -> Either TypeIDError TypeIDV4+parseByteString = TID.parseByteString+{-# INLINE parseByteString #-}++-- | Parse a 'TypeIDV4' from its 'String' representation, throwing an error when+-- the parsing fails. It is 'string2IDM' with concrete type.+parseStringM :: MonadIO m => String -> m TypeIDV4+parseStringM = TID.parseStringM+{-# INLINE parseStringM #-}++-- | Parse a 'TypeIDV4' from its string representation as a strict 'Text',+-- throwing an error when the parsing fails. It is 'text2IDM' with concrete+-- type.+parseTextM :: MonadIO m => Text -> m TypeIDV4+parseTextM = TID.parseTextM+{-# INLINE parseTextM #-}++-- | Parse a 'TypeIDV4' from its string representation as a lazy 'ByteString',+-- throwing an error when the parsing fails. It is 'byteString2IDM' with+-- concrete type.+parseByteStringM :: MonadIO m => ByteString -> m TypeIDV4+parseByteStringM = TID.parseByteStringM+{-# INLINE parseByteStringM #-}
+ src/Data/TypeID/V4/Unsafe.hs view
@@ -0,0 +1,59 @@+-- |+-- Module      : Data.TypeID.V4.Unsafe+-- License     : MIT+-- Maintainer  : mmzk1526@outlook.com+-- Portability : GHC+--+-- Unsafe 'TypeIDV4' functions.+--+module Data.TypeID.V4.Unsafe+  (+  -- * Unsafe 'TypeIDV4' generation+    unsafeGenTypeID+  , unsafeGenTypeID'+  -- * Unsafe decoding ('TypeIDV4'-specific)+  , unsafeParseString+  , unsafeParseText+  , unsafeParseByteString+  -- * Unsafe decoding (class methods)+  , unsafeString2ID+  , unsafeText2ID+  , unsafeByteString2ID+  ) where++import           Control.Monad.IO.Class+import           Data.ByteString.Lazy (ByteString)+import           Data.Text (Text)+import           Data.TypeID.Class+import qualified Data.TypeID.Internal as TID+import           Data.TypeID.V4 (TypeIDV4)+import           Data.UUID.Types.Internal (UUID)++-- | Generate a new 'TypeIDV4' from a prefix, but without checking if the prefix+-- is valid.+unsafeGenTypeID :: MonadIO m => Text -> m TypeIDV4+unsafeGenTypeID = TID.unsafeGenTypeIDV4+{-# INLINE unsafeGenTypeID #-}++-- | Generate a new 'TypeIDV4' from a prefix based on insecure 'UUID'v4.+unsafeGenTypeID' :: MonadIO m => Text -> m TypeIDV4+unsafeGenTypeID' = TID.unsafeGenTypeIDV4'+{-# INLINE unsafeGenTypeID' #-}++-- | Parse a 'TypeIDV4' from its 'String' representation, but crashes when+-- parsing fails.+unsafeParseString :: String -> TypeIDV4+unsafeParseString = TID.unsafeParseString+{-# INLINE unsafeParseString #-}++-- | Parse a 'TypeIDV4' from its string representation as a strict 'Text', but+-- crashes when parsing fails.+unsafeParseText :: Text -> TypeIDV4+unsafeParseText = TID.unsafeParseText+{-# INLINE unsafeParseText #-}++-- | Parse a 'TypeIDV4' from its string representation as a lazy 'ByteString',+-- but crashes when parsing fails.+unsafeParseByteString :: ByteString -> TypeIDV4+unsafeParseByteString = TID.unsafeParseByteString+{-# INLINE unsafeParseByteString #-}
+ src/Data/TypeID/V7.hs view
@@ -0,0 +1,175 @@+-- |+-- Module      : Data.TypeID.V7+-- License     : MIT+-- Maintainer  : mmzk1526@outlook.com+-- Portability : GHC+--+-- An implementation of the 'TypeID' specification:+-- https://github.com/jetpack-io/typeid.+--+module Data.TypeID.V7+  (+  -- * Data types+    TypeID+  , TypeIDV7+  , getPrefix+  , getUUID+  , getTime+  -- * 'TypeID' generation ('TypeID'-specific)+  , genTypeID+  , genTypeID'+  , genTypeIDs+  , decorateTypeID+  -- * 'TypeID' generation (class methods)+  , genID+  , genID'+  , genIDs+  , decorate+  -- * Validation ('TypeID'-specific)+  , checkPrefix+  , checkTypeID+  , checkTypeIDWithEnv+  -- * Validation (class methods)+  , checkID+  , checkIDWithEnv+  -- * Encoding & decoding ('TypeID'-specific)+  , toString+  , toText+  , toByteString+  , parseString+  , parseText+  , parseByteString+  , parseStringM+  , parseTextM+  , parseByteStringM+  -- * Encoding & decoding (class methods)+  , id2String+  , id2Text+  , id2ByteString+  , string2ID+  , text2ID+  , byteString2ID+  , string2IDM+  , text2IDM+  , byteString2IDM+  ) where++import           Control.Monad.IO.Class+import           Data.ByteString.Lazy (ByteString)+import           Data.Text (Text)+import           Data.TypeID.Class+import           Data.TypeID.Error+import qualified Data.TypeID.Internal as TID+import           Data.UUID.Types (UUID)+import           Data.UUID.Versions+import           Data.Word++-- | A type alias for the default 'TypeID' implementation with 'UUID'v7.+type TypeID = TID.TypeID' 'V7++-- | A type alias for 'TypeID'.+type TypeIDV7 = TypeID++-- | Generate a new 'TypeID' from a prefix.+--+-- It throws a 'TypeIDError' if the prefix does not match the specification,+-- namely if it's longer than 63 characters or if it contains characters other+-- than lowercase latin letters.+genTypeID :: MonadIO m => Text -> m TypeID+genTypeID = TID.genTypeID+{-# INLINE genTypeID #-}++-- | Generate a new 'TypeID' from a prefix based on stateless 'UUID'v7.+--+-- See the documentation of 'V7.genUUID'' for more information.+genTypeID' :: MonadIO m => Text -> m TypeID+genTypeID' = TID.genTypeID'+{-# INLINE genTypeID' #-}++-- | Generate a list of 'TypeID's from a prefix.+--+-- It tries its best to generate 'TypeID's at the same timestamp, but it may not+-- be possible if we are asking too many 'UUID's at the same time.+--+-- It is guaranteed that the first 32768 'TypeID's are generated at the same+-- timestamp.+genTypeIDs :: MonadIO m => Text -> Word16 -> m [TypeID]+genTypeIDs = TID.genTypeIDs+{-# INLINE genTypeIDs #-}++-- | Obtain a 'TypeID' from a prefix and a 'UUID'.+decorateTypeID :: Text -> UUID -> Either TypeIDError TypeID+decorateTypeID = TID.decorateTypeID+{-# INLINE decorateTypeID #-}++-- | Check if the given prefix is a valid 'TypeID' prefix.+checkPrefix :: Text -> Maybe TypeIDError+checkPrefix = TID.checkPrefix+{-# INLINE checkPrefix #-}++-- | Check if the prefix is valid and the suffix 'UUID' has the correct v7+-- version and variant.+checkTypeID :: TypeID -> Maybe TypeIDError+checkTypeID = TID.checkTypeID+{-# INLINE checkTypeID #-}++-- | Similar to 'checkTypeID', but also checks if the suffix 'UUID' is+-- generated in the past.+checkTypeIDWithEnv :: MonadIO m => TypeID -> m (Maybe TypeIDError)+checkTypeIDWithEnv = TID.checkTypeIDWithEnv+{-# INLINE checkTypeIDWithEnv #-}++-- | Pretty-print a 'TypeID'. It is 'id2String' with concrete type.+toString :: TypeID -> String+toString = TID.toString+{-# INLINE toString #-}++-- | Pretty-print a 'TypeID' to strict 'Text'. It is 'id2Text' with concrete+-- type.+toText :: TypeID -> Text+toText = TID.toText+{-# INLINE toText #-}++-- | Pretty-print a 'TypeID' to lazy 'ByteString'. It is 'id2ByteString' with+-- concrete type.+toByteString :: TypeID -> ByteString+toByteString = TID.toByteString+{-# INLINE toByteString #-}++-- | Parse a 'TypeID' from its 'String' representation. It is 'string2ID' with+-- concrete type.+parseString :: String -> Either TypeIDError TypeID+parseString = TID.parseString+{-# INLINE parseString #-}++-- | Parse a 'TypeID' from its string representation as a strict 'Text'. It is+-- 'text2ID' with concrete type.+parseText :: Text -> Either TypeIDError TypeID+parseText = TID.parseText+{-# INLINE parseText #-}++-- | Parse a 'TypeID' from its string representation as a lazy 'ByteString'. It+-- is 'byteString2ID' with concrete type.+parseByteString :: ByteString -> Either TypeIDError TypeID+parseByteString = TID.parseByteString+{-# INLINE parseByteString #-}++-- | Parse a 'TypeID' from its 'String' representation, throwing an error when+-- the parsing fails. It is 'string2IDM' with concrete type.+parseStringM :: MonadIO m => String -> m TypeID+parseStringM = TID.parseStringM+{-# INLINE parseStringM #-}++-- | Parse a 'TypeID' from its string representation as a strict 'Text',+-- throwing an error when the parsing fails. It is 'text2IDM' with concrete+-- type.+parseTextM :: MonadIO m => Text -> m TypeID+parseTextM = TID.parseTextM+{-# INLINE parseTextM #-}++-- | Parse a 'TypeID' from its string representation as a lazy 'ByteString',+-- throwing an error when the parsing fails. It is 'byteString2IDM' with+-- concrete type.+parseByteStringM :: MonadIO m => ByteString -> m TypeID+parseByteStringM = TID.parseByteStringM+{-# INLINE parseByteStringM #-}
+ src/Data/TypeID/V7/Unsafe.hs view
@@ -0,0 +1,74 @@+-- |+-- Module      : Data.TypeID.V7.Unsafe+-- License     : MIT+-- Maintainer  : mmzk1526@outlook.com+-- Portability : GHC+--+-- Unsafe 'TypeID' functions.+--+module Data.TypeID.V7.Unsafe+  (+  -- * Unsafe 'TypeID' generation+    unsafeGenTypeID+  , unsafeGenTypeID'+  , unsafeGenTypeIDs+  -- * Unsafe decoding ('TypeID'-specific)+  , unsafeParseString+  , unsafeParseText+  , unsafeParseByteString+  -- * Unsafe decoding (class methods)+  , unsafeString2ID+  , unsafeText2ID+  , unsafeByteString2ID+  ) where++import           Control.Monad.IO.Class+import           Data.ByteString.Lazy (ByteString)+import           Data.Text (Text)+import           Data.TypeID.Class+import qualified Data.TypeID.Internal as TID+import           Data.TypeID.V7 (TypeID)+import           Data.UUID.Types.Internal (UUID)+import           Data.Word++-- | Generate a new 'TypeID' from a prefix, but without checking if the prefix+-- is valid.+unsafeGenTypeID :: MonadIO m => Text -> m TypeID+unsafeGenTypeID = TID.unsafeGenTypeID+{-# INLINE unsafeGenTypeID #-}++-- | Generate a new 'TypeID' from a prefix based on stateless 'UUID'v7, but+-- without checking if the prefix is valid.+unsafeGenTypeID' :: MonadIO m => Text -> m TypeID+unsafeGenTypeID' = TID.unsafeGenTypeID'+{-# INLINE unsafeGenTypeID' #-}++-- | Generate n 'TypeID's from a prefix, but without checking if the prefix is+-- valid.+--+-- It tries its best to generate 'TypeID's at the same timestamp, but it may not+-- be possible if we are asking too many 'UUID's at the same time.+--+-- It is guaranteed that the first 32768 'TypeID's are generated at the same+-- timestamp.+unsafeGenTypeIDs :: MonadIO m => Text -> Word16 -> m [TypeID]+unsafeGenTypeIDs = TID.unsafeGenTypeIDs+{-# INLINE unsafeGenTypeIDs #-}++-- | Parse a 'TypeID' from its 'String' representation, but crashes when+-- parsing fails.+unsafeParseString :: String -> TypeID+unsafeParseString = TID.unsafeParseString+{-# INLINE unsafeParseString #-}++-- | Parse a 'TypeID' from its string representation as a strict 'Text', but+-- crashes when parsing fails.+unsafeParseText :: Text -> TypeID+unsafeParseText = TID.unsafeParseText+{-# INLINE unsafeParseText #-}++-- | Parse a 'TypeID' from its string representation as a lazy 'ByteString',+-- but crashes when parsing fails.+unsafeParseByteString :: ByteString -> TypeID+unsafeParseByteString = TID.unsafeParseByteString+{-# INLINE unsafeParseByteString #-}
src/Data/UUID/V7.hs view
@@ -13,14 +13,11 @@ -- implementation may change in the future according to the potential -- adjustments in the specification. ----- WARNING: The 'nil' re-export will be removed in the next major version.--- module Data.UUID.V7   (   -- * Data type     UUID   -- * 'UUID'v7 generation-  , nil   , genUUID   , genUUID'   , genUUIDs@@ -42,6 +39,8 @@ import           Data.IORef import           Data.Time.Clock.POSIX import           Data.UUID.Types.Internal++import           Data.UUID.Versions import           System.Entropy import           System.IO.Unsafe (unsafePerformIO) @@ -112,8 +111,7 @@  -- | Validate the version and variant of the 'UUID'v7. validate :: UUID -> Bool-validate (UUID w1 w2)-  = (w1 `shiftR` 12) .&. 0xF == 0x7 && (w2 `shiftR` 62) .&. 0x3 == 0x2+validate = flip validateWithVersion V7 {-# INLINE validate #-}  -- | Validate the version and variant of the 'UUID'v7 as well as its timestamp
+ src/Data/UUID/Versions.hs view
@@ -0,0 +1,40 @@+-- |+-- Module      : Data.UUID.Versions+-- License     : MIT+-- Maintainer  : mmzk1526@outlook.com+-- Portability : GHC+--+-- Supported 'UUID' versions for 'Data.TypeID.TypeID''.+--+module Data.UUID.Versions+ (+  -- * Supported 'UUID' versions+    UUIDVersion(..)+  -- * Validation+  , validateWithVersion+ ) where++import           Data.Bits+import           Data.UUID.Types.Internal++-- | The supported 'UUID' versions. These constructors are used as type-level+-- tags for 'Data.TypeID.TypeID''.+--+-- 'V1' and 'V5' are not supported yet.+data UUIDVersion = V1 | V4 | V5 | V7+  deriving (Eq, Ord, Bounded, Enum, Show)++toInt :: Num a => UUIDVersion -> a+toInt V1 = 1+toInt V4 = 4+toInt V5 = 5+toInt V7 = 7+{-# INLINE toInt #-}++-- | Validate the given 'UUID' with the given 'UUIDVersion'.+--+-- The variant is supposed to be 0x2.+validateWithVersion :: UUID -> UUIDVersion -> Bool+validateWithVersion (UUID w1 w2) version+  = (w1 `shiftR` 12) .&. 0xF == toInt version && (w2 `shiftR` 62) .&. 0x3 == 0x2+{-# INLINE validateWithVersion #-}
test/Spec.hs view
@@ -8,6 +8,7 @@ import           Data.Binary.Get import           Data.Binary.Put import qualified Data.ByteString.Lazy as BSL+import           Data.KindID.V4 (KindIDV4) import           Data.KindID import           Data.KindID.Class import           Data.Map (Map)@@ -16,8 +17,10 @@ import           Data.Text (Text) import qualified Data.Text as T import           Data.TypeID+import           Data.TypeID.V4 (TypeIDV4) import           Data.TypeID.Class import           Data.TypeID.Error+import           Data.UUID.Types (nil) import           Data.UUID.V7 (UUID) import qualified Data.UUID.V7 as V7 import           Foreign@@ -30,10 +33,10 @@                          , uuid   :: Maybe String }   deriving (Generic, FromJSON, ToJSON) -data TestDataUUID = TestDataUUID { name   :: String-                                 , typeid :: TypeID-                                 , prefix :: Text-                                 , uuid   :: UUID }+data TestDataUUID tid = TestDataUUID { name   :: String+                                     , typeid :: tid+                                     , prefix :: Text+                                     , uuid   :: UUID }   deriving (Generic, FromJSON, ToJSON)  data Prefix = User | Post | Comment@@ -69,218 +72,404 @@     Nothing -> pure tids  main :: IO ()-main = do-  invalid   <- BSL.readFile "test/invalid.json" >>= throwDecode :: IO [TestData]-  valid     <- BSL.readFile "test/valid.json" >>= throwDecode :: IO [TestData]-  validUUID <- BSL.readFile "test/valid.json" >>= throwDecode :: IO [TestDataUUID]--  hspec do-    describe "Generate TypeID" do-      it "can generate TypeID with prefix" do-        start <- V7.getEpochMilli-        tid   <- withCheck $ genID @TypeID "mmzk"-        getPrefix tid `shouldBe` "mmzk"-        getTime tid `shouldSatisfy` \t -> t >= start-      it "can generate TypeID without prefix" do-        start <- V7.getEpochMilli-        tid   <- withCheck $ genID @TypeID ""-        getPrefix tid `shouldBe` ""-        getTime tid `shouldSatisfy` \t -> t >= start-      it "can generate TypeID with stateless UUIDv7" do-        start <- V7.getEpochMilli-        tid   <- withCheck $ genID' @TypeID "mmzk"-        getPrefix tid `shouldBe` "mmzk"-        getTime tid `shouldSatisfy` \t -> t >= start-      it "can generate in batch with same timestamp and in ascending order" do-        start <- V7.getEpochMilli-        tids  <- withChecks $ genIDs @TypeID "mmzk" 1526-        all ((== "mmzk") . getPrefix) tids `shouldBe` True-        let timestamp = getTime $ head tids-        all ((== timestamp) . getTime) tids `shouldBe` True-        all (uncurry (<)) (zip tids $ tail tids) `shouldBe` True-        timestamp `shouldSatisfy` \t -> t >= start-      it "can parse TypeID from String" do-        case string2ID @TypeID "mmzk_00041061050r3gg28a1c60t3gf" of-          Left err  -> expectationFailure $ "Parse error: " ++ show err-          Right tid -> getPrefix tid `shouldBe` "mmzk"+main = hspec do+  v7Test+  v4Test -    describe "Parse TypeID" do-      let invalidPrefixes = [ ("caps", "PREFIX")-                            , ("numeric", "12323")-                            , ("symbols", "pre.fix")-                            , ("spaces", "  ")-                            , ("long", "abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz")-                            , ("ascii", "château") ]-      describe "can detect invalid prefix" do-        forM_ invalidPrefixes \(reason, prefix) -> it reason do-          genID @TypeID prefix `shouldThrow` anyTypeIDError-          case decorate @TypeID prefix V7.nil of-            Left _  -> pure ()-            Right _ -> expectationFailure "Should not be able to decorate with invalid prefix"-      let invalidSuffixes = [ ("spaces", " ")-                            , ("short", "01234")-                            , ("long", "012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789")-                            , ("caps", "00041061050R3GG28A1C60T3GF") -- Would be valid in lowercase-                            , ("hyphens", "00041061050-3gg28a1-60t3gf")-                            , ("crockford_ambiguous", "ooo41o61o5or3gg28a1c6ot3gi") -- Would be valid if we followed Crockford's substitution rules-                            , ("symbols", "00041061050.3gg28a1_60t3gf")-                            , ("wrong_alphabet", "ooooooiiiiiiuuuuuuulllllll") ]-      describe "can detect invalid suffix" do-        forM_ invalidSuffixes \(reason, suffix) -> it reason do-          case string2ID @TypeID suffix of-            Left _    -> pure ()-            Right tid -> expectationFailure $ "Parsed TypeID: " ++ show tid+v7Test :: Spec+v7Test = do+  invalid   <- runIO (BSL.readFile "test/invalid.json" >>= throwDecode :: IO [TestData])+  valid     <- runIO (BSL.readFile "test/valid.json" >>= throwDecode :: IO [TestData])+  validUUID <- runIO (BSL.readFile "test/valid.json" >>= throwDecode :: IO [TestDataUUID TypeID])+  describe "Generate TypeID" do+    it "can generate TypeID with prefix" do+      start <- V7.getEpochMilli+      tid   <- withCheck $ genID @TypeID "mmzk"+      getPrefix tid `shouldBe` "mmzk"+      getTime tid `shouldSatisfy` \t -> t >= start+    it "can generate TypeID without prefix" do+      start <- V7.getEpochMilli+      tid   <- withCheck $ genID @TypeID ""+      getPrefix tid `shouldBe` ""+      getTime tid `shouldSatisfy` \t -> t >= start+    it "can generate TypeID with stateless UUIDv7" do+      start <- V7.getEpochMilli+      tid   <- withCheck $ genID' @TypeID "mmzk"+      getPrefix tid `shouldBe` "mmzk"+      getTime tid `shouldSatisfy` \t -> t >= start+    it "can generate in batch with same timestamp and in ascending order" do+      start <- V7.getEpochMilli+      tids  <- withChecks $ genIDs @TypeID "mmzk" 1526+      all ((== "mmzk") . getPrefix) tids `shouldBe` True+      let timestamp = getTime $ head tids+      all ((== timestamp) . getTime) tids `shouldBe` True+      all (uncurry (<)) (zip tids $ tail tids) `shouldBe` True+      timestamp `shouldSatisfy` \t -> t >= start+    it "can parse TypeID from String" do+      case string2ID @TypeID "mmzk_00041061050r3gg28a1c60t3gf" of+        Left err  -> expectationFailure $ "Parse error: " ++ show err+        Right tid -> getPrefix tid `shouldBe` "mmzk" -    describe "Parse special values" do-      let specialValues = [ ("nil", "00000000000000000000000000", "00000000-0000-0000-0000-000000000000")-                          , ("one", "00000000000000000000000001", "00000000-0000-0000-0000-000000000001")-                          , ("ten", "0000000000000000000000000a", "00000000-0000-0000-0000-00000000000a")-                          , ("sixteen", "0000000000000000000000000g", "00000000-0000-0000-0000-000000000010")-                          , ("thirty-two", "00000000000000000000000010", "00000000-0000-0000-0000-000000000020") ]-      forM_ specialValues \(reason, tid, uuid) -> it reason do-        case string2ID @TypeID tid of-          Left err  -> expectationFailure $ "Parse error: " ++ show err-          Right tid -> show (getUUID tid) `shouldBe` uuid+  describe "Parse TypeID" do+    let invalidPrefixes = [ ("caps", "PREFIX")+                          , ("numeric", "12323")+                          , ("symbols", "pre.fix")+                          , ("spaces", "  ")+                          , ("long", "abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz")+                          , ("ascii", "château") ]+    describe "can detect invalid prefix" do+      forM_ invalidPrefixes \(reason, prefix) -> it reason do+        genID @TypeID prefix `shouldThrow` anyTypeIDError+        case decorate @TypeID prefix nil of+          Left _  -> pure ()+          Right _ -> expectationFailure "Should not be able to decorate with invalid prefix"+    let invalidSuffixes = [ ("spaces", " ")+                          , ("short", "01234")+                          , ("long", "012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789")+                          , ("caps", "00041061050R3GG28A1C60T3GF") -- Would be valid in lowercase+                          , ("hyphens", "00041061050-3gg28a1-60t3gf")+                          , ("crockford_ambiguous", "ooo41o61o5or3gg28a1c6ot3gi") -- Would be valid if we followed Crockford's substitution rules+                          , ("symbols", "00041061050.3gg28a1_60t3gf")+                          , ("wrong_alphabet", "ooooooiiiiiiuuuuuuulllllll") ]+    describe "can detect invalid suffix" do+      forM_ invalidSuffixes \(reason, suffix) -> it reason do+        case string2ID @TypeID suffix of+          Left _    -> pure ()+          Right tid -> expectationFailure $ "Parsed TypeID: " ++ show tid -    describe "TypeID valid JSON instances" do-      it "Decode and then encode should be identity" do-        tid  <- genID @TypeID "mmzk"-        tid' <- genID @TypeID "foo"-        let mapping = M.fromList [(tid, tid')]-        let json    = encode mapping-        decode json `shouldBe` Just mapping-        fmap encode (decode @(Map TypeID TypeID) json) `shouldBe` Just json-      describe "Valid JSON value" do-        forM_ valid \(TestData name tid (Just prefix) (Just uuid)) -> it name do-          case decode @TypeID (fromString $ show tid) of-            Nothing  -> expectationFailure "Parse JSON failed!"-            Just tid -> do-              getPrefix tid `shouldBe` prefix-              show (getUUID tid) `shouldBe` uuid-      describe "Valid JSON key" do-        forM_ valid \(TestData name tid (Just prefix) (Just uuid)) -> it name do-          case decode @(Map TypeID Int) (fromString $ "{" ++ show tid ++ ":" ++ "114514" ++ "}") of-            Nothing      -> expectationFailure "Parse JSON failed!"-            Just mapping -> do-              let (tid, _) = M.elemAt 0 mapping-              getPrefix tid `shouldBe` prefix-              show (getUUID tid) `shouldBe` uuid+  describe "Parse special values" do+    let specialValues = [ ("nil", "00000000000000000000000000", "00000000-0000-0000-0000-000000000000")+                        , ("one", "00000000000000000000000001", "00000000-0000-0000-0000-000000000001")+                        , ("ten", "0000000000000000000000000a", "00000000-0000-0000-0000-00000000000a")+                        , ("sixteen", "0000000000000000000000000g", "00000000-0000-0000-0000-000000000010")+                        , ("thirty-two", "00000000000000000000000010", "00000000-0000-0000-0000-000000000020") ]+    forM_ specialValues \(reason, tid, uuid) -> it reason do+      case string2ID @TypeID tid of+        Left err  -> expectationFailure $ "Parse error: " ++ show err+        Right tid -> show (getUUID tid) `shouldBe` uuid -    describe "TypeID invalid JSON instances" do-      describe "Invalid JSON value" do-        forM_ invalid \(TestData name tid _ _) -> it name do-          case decode @TypeID (fromString $ show tid) of-            Nothing  -> pure ()-            Just tid -> expectationFailure $ "Parsed TypeID: " ++ show tid-      describe "Invalid JSON key" do-        forM_ invalid \(TestData name tid _ _) -> it name do-          case decode @(Map TypeID Int) (fromString $ "{" ++ show tid ++ ":" ++ "114514" ++ "}") of-            Nothing  -> pure ()-            Just tid -> expectationFailure "Invalid TypeID key shouldn't be parsed!"+  describe "TypeID valid JSON instances" do+    it "Decode and then encode should be identity" do+      tid  <- genID @TypeID "mmzk"+      tid' <- genID @TypeID "foo"+      let mapping = M.fromList [(tid, tid')]+      let json    = encode mapping+      decode json `shouldBe` Just mapping+      fmap encode (decode @(Map TypeID TypeID) json) `shouldBe` Just json+    describe "Valid JSON value" do+      forM_ valid \(TestData name tid (Just prefix) (Just uuid)) -> it name do+        case decode @TypeID (fromString $ show tid) of+          Nothing  -> expectationFailure "Parse JSON failed!"+          Just tid -> do+            getPrefix tid `shouldBe` prefix+            show (getUUID tid) `shouldBe` uuid+    describe "Valid JSON key" do+      forM_ valid \(TestData name tid (Just prefix) (Just uuid)) -> it name do+        case decode @(Map TypeID Int) (fromString $ "{" ++ show tid ++ ":" ++ "114514" ++ "}") of+          Nothing      -> expectationFailure "Parse JSON failed!"+          Just mapping -> do+            let (tid, _) = M.elemAt 0 mapping+            getPrefix tid `shouldBe` prefix+            show (getUUID tid) `shouldBe` uuid -    describe "Test invalid.json" do+  describe "TypeID invalid JSON instances" do+    describe "Invalid JSON value" do       forM_ invalid \(TestData name tid _ _) -> it name do-        case string2ID @TypeID tid of+        case decode @TypeID (fromString $ show tid) of+          Nothing  -> pure ()+          Just tid -> expectationFailure $ "Parsed TypeID: " ++ show tid+    describe "Invalid JSON key" do+      forM_ invalid \(TestData name tid _ _) -> it name do+        case decode @(Map TypeID Int) (fromString $ "{" ++ show tid ++ ":" ++ "114514" ++ "}") of+          Nothing  -> pure ()+          Just tid -> expectationFailure "Invalid TypeID key shouldn't be parsed!"++  describe "Test invalid.json" do+    forM_ invalid \(TestData name tid _ _) -> it name do+      case string2ID @TypeID tid of+        Left _    -> pure ()+        Right tid -> expectationFailure $ "Parsed TypeID: " ++ show tid++  describe "Test valid.json (TypeID as literal)" do+    forM_ valid \(TestData name tid (Just prefix) (Just uuid)) -> it name do+      case string2ID @TypeID tid of+        Left err  -> expectationFailure $ "Parse error: " ++ show err+        Right tid -> do+          getPrefix tid `shouldBe` prefix+          show (getUUID tid) `shouldBe` uuid++  describe "Test valid.json (TypeID as JSON)" do+    forM_ validUUID \(TestDataUUID name tid prefix uuid) -> it name do+      getPrefix tid `shouldBe` prefix+      getUUID tid `shouldBe` uuid++  describe "Generate KindID with 'Symbol' prefixes" do+    it "can generate KindID with prefix" do+      start <- V7.getEpochMilli+      kid   <- withCheck $ genID @(KindID "mmzk")+      getPrefix kid `shouldBe` "mmzk"+      getTime kid `shouldSatisfy` \t -> start <= t+    it "can generate KindID without prefix" do+      start <- V7.getEpochMilli+      kid   <- withCheck $ genID @(KindID "")+      getPrefix kid `shouldBe` ""+      getTime kid `shouldSatisfy` \t -> start <= t+    it "can generate KindID with stateless UUID v7" do+      start <- V7.getEpochMilli+      kid   <- withCheck $ genID' @(KindID "mmzk")+      getPrefix kid `shouldBe` "mmzk"+      getTime kid `shouldSatisfy` \t -> start <= t+    it "can generate in batch with same timestamp and in ascending order" do+      start <- V7.getEpochMilli+      kids  <- withChecks $ genIDs @(KindID "mmzk") 1526+      all ((== "mmzk") . getPrefix) kids `shouldBe` True+      let timestamp = getTime $ head kids+      all ((== timestamp) . getTime) kids `shouldBe` True+      all (uncurry (<)) (zip kids $ tail kids) `shouldBe` True+      timestamp `shouldSatisfy` \t -> start <= t+    it "can parse KindID from String" do+      case string2ID @(KindID "mmzk") "mmzk_00041061050r3gg28a1c60t3gf" of+        Left err  -> expectationFailure $ "Parse error: " ++ show err+        Right kid -> getPrefix kid `shouldBe` "mmzk"+    it "cannot parse KindID into wrong prefix" do+      case string2ID @(KindID "foo") "mmzk_00041061050r3gg28a1c60t3gf" of+        Left err  -> pure ()+        Right kid -> expectationFailure $ "Parsed TypeID: " ++ show kid++  describe "Generate KindID with custom data kind prefixes" do+    it "can generate KindID with prefix" do+        kid <- withCheck $ genID @(KindID 'Post)+        getPrefix kid `shouldBe` "post"+    it "can parse KindID from String" do+      case string2ID @(KindID 'User) "user_00041061050r3gg28a1c60t3gf" of+        Left err  -> expectationFailure $ "Parse error: " ++ show err+        Right kid -> getPrefix kid `shouldBe` "user"+    it "cannot parse KindID into wrong prefix" do+      case string2ID @(KindID 'Comment) "user_00041061050r3gg28a1c60t3gf" of+        Left err  -> pure ()+        Right kid -> expectationFailure $ "Parsed KindID: " ++ show kid+    it "can generate in batch with same timestamp and in ascending order" do+      kids <- withChecks $ genIDs @(KindID 'Comment) 1526+      all ((== "comment") . getPrefix) kids `shouldBe` True+      let timestamp = getTime $ head kids+      all ((== timestamp) . getTime) kids `shouldBe` True+      all (uncurry (<)) (zip kids $ tail kids) `shouldBe` True++  describe "Binary instance for TypeID and KindID" do+    it "has correct binary instance for TypeID" do+      tids <- withChecks $ genIDs @TypeID "abcdefghijklmnopqrstuvwxyz" 114+      forM_ tids \tid -> do+        let bytes = runPut (put tid)+        let tid'  = runGet get bytes+        tid' `shouldBe` tid+    it "has correct binary instance for KindID" do+      kids <- withChecks $ genIDs @(KindID "abcdefghijklmnopqrstuvwxyz") 114+      forM_ kids \kid -> do+        let bytes = runPut (put kid)+        let kid'  = runGet get bytes+        kid' `shouldBe` kid++  describe "Storable instance for TypeID and KindID" do+    it "has correct Storable instance for TypeID" do+      tids <- withChecks $ genIDs @TypeID "abcdefghijklmnopqrstuvwxyz" 114+      forM_ tids \tid -> do+        ptr   <- new tid+        tid'  <- peek ptr+        tid' `shouldBe` tid+        poke ptr tid'+        tid'' <- peek ptr+        tid'' `shouldBe` tid'+        free ptr+    it "has correct Storable instance for KindID" do+      kids <- withChecks $ genIDs @(KindID "abcdefghijklmnopqrstuvwxyz") 114+      forM_ kids \kid -> do+        ptr   <- new kid+        kid'  <- peek ptr+        kid' `shouldBe` kid+        poke ptr kid'+        kid'' <- peek ptr+        kid'' `shouldBe` kid'+        free ptr+++v4Test :: Spec+v4Test = do+  invalid   <- runIO (BSL.readFile "test/invalid.json" >>= throwDecode :: IO [TestData])+  valid     <- runIO (BSL.readFile "test/valid.json" >>= throwDecode :: IO [TestData])+  validUUID <- runIO (BSL.readFile "test/valid.json" >>= throwDecode :: IO [TestDataUUID TypeIDV4])+  describe "Generate TypeIDV4" do+    it "can generate TypeIDV4 with prefix" do+      tid <- withCheck $ genID @TypeIDV4 "mmzk"+      getPrefix tid `shouldBe` "mmzk"+    it "can generate TypeIDV4 without prefix" do+      tid <- withCheck $ genID @TypeIDV4 ""+      getPrefix tid `shouldBe` ""+    it "can generate TypeIDV4 with insecure UUIDv4" do+      start <- V7.getEpochMilli+      tid   <- withCheck $ genID' @TypeIDV4 "mmzk"+      getPrefix tid `shouldBe` "mmzk"+    it "can parse TypeIDV4 from String" do+      case string2ID @TypeIDV4 "mmzk_5hjpeh96458fct8t49fnf9farw" of+        Left err  -> expectationFailure $ "Parse error: " ++ show err+        Right tid -> getPrefix tid `shouldBe` "mmzk"++  describe "Parse TypeIDV4" do+    let invalidPrefixes = [ ("caps", "PREFIX")+                          , ("numeric", "12323")+                          , ("symbols", "pre.fix")+                          , ("spaces", "  ")+                          , ("long", "abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz")+                          , ("ascii", "château") ]+    describe "can detect invalid prefix" do+      forM_ invalidPrefixes \(reason, prefix) -> it reason do+        genID @TypeIDV4 prefix `shouldThrow` anyTypeIDError+        case decorate @TypeIDV4 prefix nil of+          Left _  -> pure ()+          Right _ -> expectationFailure "Should not be able to decorate with invalid prefix"+    let invalidSuffixes = [ ("spaces", " ")+                          , ("short", "01234")+                          , ("long", "012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789")+                          , ("caps", "00041061050R3GG28A1C60T3GF") -- Would be valid in lowercase+                          , ("hyphens", "00041061050-3gg28a1-60t3gf")+                          , ("crockford_ambiguous", "ooo41o61o5or3gg28a1c6ot3gi") -- Would be valid if we followed Crockford's substitution rules+                          , ("symbols", "00041061050.3gg28a1_60t3gf")+                          , ("wrong_alphabet", "ooooooiiiiiiuuuuuuulllllll") ]+    describe "can detect invalid suffix" do+      forM_ invalidSuffixes \(reason, suffix) -> it reason do+        case string2ID @TypeIDV4 suffix of           Left _    -> pure ()           Right tid -> expectationFailure $ "Parsed TypeID: " ++ show tid -    describe "Test valid.json (TypeID as literal)" do+  describe "Parse special values" do+    let specialValues = [ ("nil", "00000000000000000000000000", "00000000-0000-0000-0000-000000000000")+                        , ("one", "00000000000000000000000001", "00000000-0000-0000-0000-000000000001")+                        , ("ten", "0000000000000000000000000a", "00000000-0000-0000-0000-00000000000a")+                        , ("sixteen", "0000000000000000000000000g", "00000000-0000-0000-0000-000000000010")+                        , ("thirty-two", "00000000000000000000000010", "00000000-0000-0000-0000-000000000020") ]+    forM_ specialValues \(reason, tid, uuid) -> it reason do+      case string2ID @TypeIDV4 tid of+        Left err  -> expectationFailure $ "Parse error: " ++ show err+        Right tid -> show (getUUID tid) `shouldBe` uuid++  describe "TypeIDV4 valid JSON instances" do+    it "Decode and then encode should be identity" do+      tid  <- genID @TypeIDV4 "mmzk"+      tid' <- genID @TypeIDV4 "foo"+      let mapping = M.fromList [(tid, tid')]+      let json    = encode mapping+      decode json `shouldBe` Just mapping+      fmap encode (decode @(Map TypeIDV4 TypeIDV4) json) `shouldBe` Just json+    describe "Valid JSON value" do       forM_ valid \(TestData name tid (Just prefix) (Just uuid)) -> it name do-        case string2ID @TypeID tid of-          Left err  -> expectationFailure $ "Parse error: " ++ show err-          Right tid -> do+        case decode @TypeIDV4 (fromString $ show tid) of+          Nothing  -> expectationFailure "Parse JSON failed!"+          Just tid -> do             getPrefix tid `shouldBe` prefix             show (getUUID tid) `shouldBe` uuid+    describe "Valid JSON key" do+      forM_ valid \(TestData name tid (Just prefix) (Just uuid)) -> it name do+        case decode @(Map TypeIDV4 Int) (fromString $ "{" ++ show tid ++ ":" ++ "114514" ++ "}") of+          Nothing      -> expectationFailure "Parse JSON failed!"+          Just mapping -> do+            let (tid, _) = M.elemAt 0 mapping+            getPrefix tid `shouldBe` prefix+            show (getUUID tid) `shouldBe` uuid -    describe "Test valid.json (TypeID as JSON)" do-      forM_ validUUID \(TestDataUUID name tid prefix uuid) -> it name do-        getPrefix tid `shouldBe` prefix-        getUUID tid `shouldBe` uuid+  describe "TypeIDV4 invalid JSON instances" do+    describe "Invalid JSON value" do+      forM_ invalid \(TestData name tid _ _) -> it name do+        case decode @TypeIDV4 (fromString $ show tid) of+          Nothing  -> pure ()+          Just tid -> expectationFailure $ "Parsed TypeID: " ++ show tid+    describe "Invalid JSON key" do+      forM_ invalid \(TestData name tid _ _) -> it name do+        case decode @(Map TypeIDV4 Int) (fromString $ "{" ++ show tid ++ ":" ++ "114514" ++ "}") of+          Nothing  -> pure ()+          Just tid -> expectationFailure "Invalid TypeID key shouldn't be parsed!" -    describe "Generate type-level TypeID (KindID) with 'Symbol' prefixes" do-      it "can generate KindID with prefix" do-        start <- V7.getEpochMilli-        kid   <- withCheck $ genID @(KindID "mmzk")-        getPrefix kid `shouldBe` "mmzk"-        getTime kid `shouldSatisfy` \t -> start <= t-      it "can generate KindID without prefix" do-        start <- V7.getEpochMilli-        kid   <- withCheck $ genID @(KindID "")-        getPrefix kid `shouldBe` ""-        getTime kid `shouldSatisfy` \t -> start <= t-      it "can generate KindID with stateless UUID v7" do-        start <- V7.getEpochMilli-        kid   <- withCheck $ genID' @(KindID "mmzk")-        getPrefix kid `shouldBe` "mmzk"-        getTime kid `shouldSatisfy` \t -> start <= t-      it "can generate in batch with same timestamp and in ascending order" do-        start <- V7.getEpochMilli-        kids  <- withChecks $ genIDs @(KindID "mmzk") 1526-        all ((== "mmzk") . getPrefix) kids `shouldBe` True-        let timestamp = getTime $ head kids-        all ((== timestamp) . getTime) kids `shouldBe` True-        all (uncurry (<)) (zip kids $ tail kids) `shouldBe` True-        timestamp `shouldSatisfy` \t -> start <= t-      it "can parse KindID from String" do-        case string2ID @(KindID "mmzk") "mmzk_00041061050r3gg28a1c60t3gf" of-          Left err  -> expectationFailure $ "Parse error: " ++ show err-          Right kid -> getPrefix kid `shouldBe` "mmzk"-      it "cannot parse KindID into wrong prefix" do-        case string2ID @(KindID "foo") "mmzk_00041061050r3gg28a1c60t3gf" of-          Left err  -> pure ()-          Right kid -> expectationFailure $ "Parsed TypeID: " ++ show kid+  describe "Test invalid.json" do+    forM_ invalid \(TestData name tid _ _) -> it name do+      case string2ID @TypeIDV4 tid of+        Left _    -> pure ()+        Right tid -> expectationFailure $ "Parsed TypeID: " ++ show tid -    describe "Generate type-level TypeID with custom data kind prefixes" do-      it "can generate TypeID with prefix" do-          kid <- withCheck $ genID @(KindID 'Post)-          getPrefix kid `shouldBe` "post"-      it "can parse TypeID from String" do-        case string2ID @(KindID User) "user_00041061050r3gg28a1c60t3gf" of-          Left err  -> expectationFailure $ "Parse error: " ++ show err-          Right kid -> getPrefix kid `shouldBe` "user"-      it "cannot parse TypeID into wrong prefix" do-        case string2ID @(KindID Comment) "user_00041061050r3gg28a1c60t3gf" of-          Left err  -> pure ()-          Right kid -> expectationFailure $ "Parsed TypeID: " ++ show kid-      it "can generate in batch with same timestamp and in ascending order" do-        kids <- withChecks $ genIDs @(KindID 'Comment) 1526-        all ((== "comment") . getPrefix) kids `shouldBe` True-        let timestamp = getTime $ head kids-        all ((== timestamp) . getTime) kids `shouldBe` True-        all (uncurry (<)) (zip kids $ tail kids) `shouldBe` True+  describe "Test valid.json (TypeIDV4 as literal)" do+    forM_ valid \(TestData name tid (Just prefix) (Just uuid)) -> it name do+      case string2ID @TypeIDV4 tid of+        Left err  -> expectationFailure $ "Parse error: " ++ show err+        Right tid -> do+          getPrefix tid `shouldBe` prefix+          show (getUUID tid) `shouldBe` uuid -    describe "Binary instance for TypeID and KindID" do-      it "has correct binary instance for TypeID" do-        tids <- withChecks $ genIDs @TypeID "abcdefghijklmnopqrstuvwxyz" 114-        forM_ tids \tid -> do-          let bytes = runPut (put tid)-          let tid'  = runGet get bytes-          tid' `shouldBe` tid-      it "has correct binary instance for KindID" do-        kids <- withChecks $ genIDs @(KindID "abcdefghijklmnopqrstuvwxyz") 114-        forM_ kids \kid -> do-          let bytes = runPut (put kid)-          let kid'  = runGet get bytes-          kid' `shouldBe` kid+  describe "Test valid.json (TypeIDV4 as JSON)" do+    forM_ validUUID \(TestDataUUID name tid prefix uuid) -> it name do+      getPrefix tid `shouldBe` prefix+      getUUID tid `shouldBe` uuid -    describe "Storable instance for TypeID and KindID" do-      it "has correct Storable instance for TypeID" do-        tids <- withChecks $ genIDs @TypeID "abcdefghijklmnopqrstuvwxyz" 114-        forM_ tids \tid -> do-          ptr   <- new tid-          tid'  <- peek ptr-          tid' `shouldBe` tid-          poke ptr tid'-          tid'' <- peek ptr-          tid'' `shouldBe` tid'-          free ptr-      it "has correct Storable instance for KindID" do-        kids <- withChecks $ genIDs @(KindID "abcdefghijklmnopqrstuvwxyz") 114-        forM_ kids \kid -> do-          ptr   <- new kid-          kid'  <- peek ptr-          kid' `shouldBe` kid-          poke ptr kid'-          kid'' <- peek ptr-          kid'' `shouldBe` kid'-          free ptr+  describe "Generate KindIDV4 with 'Symbol' prefixes" do+    it "can generate KindIDV4 with prefix" do+      kid <- withCheck $ genID @(KindIDV4 "mmzk")+      getPrefix kid `shouldBe` "mmzk"+    it "can generate KindIDV4 without prefix" do+      kid <- withCheck $ genID @(KindIDV4 "")+      getPrefix kid `shouldBe` ""+    it "can generate KindIDV4 with stateless UUID v7" do+      kid <- withCheck $ genID' @(KindIDV4 "mmzk")+      getPrefix kid `shouldBe` "mmzk"+    it "can parse KindID from String" do+      case string2ID @(KindIDV4 "mmzk") "mmzk_5hjpeh96458fct8t49fnf9farw" of+        Left err  -> expectationFailure $ "Parse error: " ++ show err+        Right kid -> getPrefix kid `shouldBe` "mmzk"+    it "cannot parse KindID into wrong prefix" do+      case string2ID @(KindIDV4 "foo") "mmzk_5hjpeh96458fct8t49fnf9farw" of+        Left err  -> pure ()+        Right kid -> expectationFailure $ "Parsed TypeID: " ++ show kid++  describe "Generate KindIDV4 with custom data kind prefixes" do+    it "can generate KindIDV4 with prefix" do+        kid <- withCheck $ genID @(KindID 'Post)+        getPrefix kid `shouldBe` "post"+    it "can parse KindIDV4 from String" do+      case string2ID @(KindIDV4 'User) "user_00041061050r3gg28a1c60t3gf" of+        Left err  -> expectationFailure $ "Parse error: " ++ show err+        Right kid -> getPrefix kid `shouldBe` "user"+    it "cannot parse KindIDV4 into wrong prefix" do+      case string2ID @(KindIDV4 'Comment) "user_00041061050r3gg28a1c60t3gf" of+        Left err  -> pure ()+        Right kid -> expectationFailure $ "Parsed KindIDV4: " ++ show kid++  describe "Binary instance for TypeIDV4 and KindIDV4" do+    it "has correct binary instance for TypeIDV4" do+      tids <- withChecks $ genIDs @TypeIDV4 "abcdefghijklmnopqrstuvwxyz" 114+      forM_ tids \tid -> do+        let bytes = runPut (put tid)+        let tid'  = runGet get bytes+        tid' `shouldBe` tid+    it "has correct binary instance for KindIDV4" do+      kids <- withChecks $ genIDs @(KindIDV4 "abcdefghijklmnopqrstuvwxyz") 114+      forM_ kids \kid -> do+        let bytes = runPut (put kid)+        let kid'  = runGet get bytes+        kid' `shouldBe` kid++  describe "Storable instance for TypeIDV4 and KindIDV4" do+    it "has correct Storable instance for TypeIDV4" do+      tids <- withChecks $ genIDs @TypeIDV4 "abcdefghijklmnopqrstuvwxyz" 114+      forM_ tids \tid -> do+        ptr   <- new tid+        tid'  <- peek ptr+        tid' `shouldBe` tid+        poke ptr tid'+        tid'' <- peek ptr+        tid'' `shouldBe` tid'+        free ptr+    it "has correct Storable instance for KindIDV4" do+      kids <- withChecks $ genIDs @(KindIDV4 "abcdefghijklmnopqrstuvwxyz") 114+      forM_ kids \kid -> do+        ptr   <- new kid+        kid'  <- peek ptr+        kid' `shouldBe` kid+        poke ptr kid'+        kid'' <- peek ptr+        kid'' `shouldBe` kid'+        free ptr