{- | This module contains the PartitionKey type. -} module Network.Legion.PartitionKey ( PartitionKey(..), toHex, fromHex ) where import Data.Attoparsec.ByteString (parseOnly, atEnd) import Data.Attoparsec.ByteString.Char8 (hexadecimal) import Data.Binary (Binary(put, get)) import Data.Bits (testBit) import Data.Bool (bool) import Data.ByteString.Char8 (pack) import Data.DoubleWord (Word256(Word256), Word128(Word128)) import Data.Ranged (DiscreteOrdered(adjacent, adjacentBelow)) import Data.Word (Word64) {- | This is how partitions are identified and referenced. -} newtype PartitionKey = K {unKey :: Word256} deriving (Eq, Ord, Show, Bounded) instance Binary PartitionKey where put (K (Word256 (Word128 a b) (Word128 c d))) = put (a, b, c, d) get = do (a, b, c, d) <- get return (K (Word256 (Word128 a b) (Word128 c d))) instance DiscreteOrdered PartitionKey where adjacent (K a) (K b) = a < b && succ a == b adjacentBelow (K k) = if k == minBound then Nothing else Just (K (pred k)) {- | Convert a `PartitionKey` into a hex string. -} toHex :: PartitionKey -> String toHex (K (Word256 (Word128 a b) (Word128 c d))) = concatMap toHex64 [a, b, c, d] {- | Convert a `Word64` into a hex string. I know I'm going to hell for this, but I just can't abide the @hexstring@ package pulling @aeson@ into our dependency tree. -} toHex64 :: Word64 -> String toHex64 w = fmap (digit . quad) [15, 14..0] where quad :: Int -> (Int, Int, Int, Int) quad n = let base = n * 4 in (base + 3, base + 2, base + 1, base) digit :: (Int, Int, Int, Int) -> Char digit (a, b, c, d) = case (testBit w a, testBit w b, testBit w c, testBit w d) of (False, False, False, False) -> '0' (False, False, False, True) -> '1' (False, False, True, False) -> '2' (False, False, True, True) -> '3' (False, True, False, False) -> '4' (False, True, False, True) -> '5' (False, True, True, False) -> '6' (False, True, True, True) -> '7' (True, False, False, False) -> '8' (True, False, False, True) -> '9' (True, False, True, False) -> 'a' (True, False, True, True) -> 'b' (True, True, False, False) -> 'c' (True, True, False, True) -> 'd' (True, True, True, False) -> 'e' (True, True, True, True) -> 'f' {- | Maybe convert a hex string into a partition key -} fromHex :: String -> Either String PartitionKey fromHex str | length str > 64 = Left "trailing characters while parsing hex PartitionKey" | otherwise = K <$> parseOnly parser (pack str) where parser = do w <- hexadecimal atEnd >>= bool (fail "not a valid hex string") (return w)