packages feed

packstream-0.1.0.2: src/Data/PackStream/Internal/Type.hs

{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE DeriveFunctor #-}
{-# LANGUAGE TypeApplications #-}
module Data.PackStream.Internal.Type where

import Control.Monad.Except (ExceptT, MonadError (..), runExceptT)
import Control.Monad.State (State, MonadState, evalState)
import Data.ByteString (ByteString)
import Data.Binary (Word8)
import Data.Map.Strict (Map)
import Data.Text (Text)

-- * PackStream basics

-- PackStream is a general purpose data serialisation format, originally inspired 
-- by (but incompatible with) MessagePack. This module provides basic types
-- and typeclasses to help you to parse or to serialize.

-- |Basic 'PackStream' error type that is used to handle parsing errors.
data PackStreamError = NotNull             -- ^This 'ByteString' doesn't represent null object
                     | NotBool             -- ^This 'ByteString' doesn't represent any boolean
                     | NotWord             -- ^This 'ByteString' doesn't represent any unsigned integer
                     | NotInt              -- ^This 'ByteString' doesn't represent any integer
                     | NotFloat            -- ^This 'ByteString' doesn't represent any floating-point number
                     | NotString           -- ^This 'ByteString' doesn't represent any 'Text' string
                     | NotBytes            -- ^This 'ByteString' doesn't represent any 'ByteString' array
                     | NotList             -- ^This 'ByteString' doesn't represent any list of 'PackStream' values
                     | NotDict             -- ^This 'ByteString' doesn't represent any dictionary of 'PackStream' values
                     | NotStructure        -- ^This 'ByteString' doesn't represent any 'Structure'
                     | NotValue            -- ^This 'ByteString' doesn't represent any 'Value'
                     | WrongStructure Text -- ^This 'ByteString' doesn't represent specific 'Structure'
                     | DictHasNoKey Text   -- ^The dictionary doesn't have a specified 'Text' key
  deriving (Show, Eq, Ord)

-- |Basic parser type. It works like parser combinators for binary data that represents PackStream.
newtype PackStream a = PackStream { runUnpackS :: ExceptT PackStreamError (State ByteString) a }
  deriving (Functor, Applicative, Monad, MonadState ByteString, MonadError PackStreamError)

-- |Use specific parser combinator to parse the 'ByteString' that represents any 'PackStream' data.
unpackStream :: PackStream a -> ByteString -> Either PackStreamError a
unpackStream action = evalState (runExceptT $ runUnpackS action)

-- |PackStream offers a number of core data types, many supported by multiple binary representations, as well as a flexible extension mechanism.
data Value = N                   -- ^Missing or empty value
           | B Bool              -- ^True or False
           | I Int               -- ^Signed 64-bit integer
           | F Double            -- ^64-bit floating point number
           | U ByteString        -- ^Byte array
           | T Text              -- ^Unicode text, UTF-8
           | L [Value]           -- ^Ordered collection of 'Value's
           | D (Map Text Value)  -- ^Collection of key-value entries (no order guaranteed)
           | S Structure         -- ^Composite value with a type signature
  deriving (Show, Eq)

-- |A structure is a composite value, comprised of fields and a unique type code.
-- Structure encodings consist, beyond the marker, of a single byte, the tag byte, followed 
-- by a sequence of up to 15 fields, each an individual value. The size of a structure is 
-- measured as the number of fields and not the total byte size. This count does not include the tag.
data Structure = Structure { signature :: Word8   -- ^Type code
                           , fields    :: [Value] -- ^Structure fields
                           }
  deriving (Show, Eq)

-- |The data types that can be serialized as 'PackStream'
class ToValue a where
    -- |Convert data type to the generic 'Value'
    toValue :: a -> Value

instance ToValue () where
    toValue = const N

instance ToValue Bool where
    toValue = B

instance ToValue Int where
    toValue = I

instance ToValue Integer where
    toValue = toValue @Int . fromIntegral

instance ToValue Double where
    toValue = F

instance ToValue ByteString where
    toValue = U

instance ToValue Text where
    toValue = T

instance ToValue a => ToValue [a] where
    toValue = L . fmap toValue

instance ToValue a => ToValue (Map Text a) where
    toValue = D . fmap toValue

instance ToValue Structure where
    toValue = S

instance ToValue Value where
    toValue = id

-- |Represent a 'Text' key and some 'ToValue' data into the 'Map' pair.
-- Can be useful to work with 'PackStream' dictionaries.
--
-- > fromList ["hello" =: 1, "world" =: False]
(=:) :: ToValue a => Text -> a -> (Text, Value)
(=:) key val = (key, toValue val)

-- |The data types taht can be read from 'PackStream' representation
class FromValue a where
    -- |Converts generic 'Value' type to a specific one or raises 'PackStreamError'
    fromValue :: Value -> Either PackStreamError a

instance FromValue () where
    fromValue N = pure ()
    fromValue _ = throwError NotNull

instance FromValue Bool where
    fromValue (B x) = pure x
    fromValue _     = throwError NotBool

instance FromValue Int where
    fromValue (I x) = pure x
    fromValue _     = throwError NotInt

instance FromValue Integer where
    fromValue = fmap fromIntegral . fromValue @Int

instance FromValue Double where
    fromValue (F x) = pure x
    fromValue _     = throwError NotFloat

instance FromValue ByteString where
    fromValue (U x) = pure x
    fromValue _     = throwError NotBytes

instance FromValue Text where
    fromValue (T x) = pure x
    fromValue _     = throwError NotString

instance FromValue a => FromValue [a] where
    fromValue (L xs) = traverse fromValue xs
    fromValue _      = throwError NotList

instance FromValue a => FromValue (Map Text a) where
    fromValue (D mp) = traverse fromValue mp
    fromValue _      = throwError NotDict

instance FromValue Structure where
    fromValue (S x) = pure x
    fromValue _     = throwError NotStructure

instance FromValue Value where
    fromValue = pure