packages feed

zeromq4-simple-0.0.0: src/System/ZMQ4/Simple.hs

{-# LANGUAGE
    MultiParamTypeClasses
  , FunctionalDependencies
  , GeneralizedNewtypeDeriving
  , DeriveGeneric
  , TupleSections
  , OverloadedStrings
  , DataKinds
  , KindSignatures
  , TypeFamilies
  , ConstraintKinds
  , UndecidableInstances
  #-}

module System.ZMQ4.Simple where

import System.ZMQ4.Monadic
  ( ZMQ, SocketType
  , Req (..), Rep (..), Dealer (..), Router (..)
  , Pub (..), Sub (..), XPub (..), XSub (..)
  , Pull (..), Push (..), Pair (..))
import qualified System.ZMQ4.Monadic as Z

import Data.Restricted (toRestricted)
import Data.Hashable (Hashable)
import Data.ByteString (ByteString)
import qualified Data.UUID as UUID
import Data.UUID.V4 (nextRandom)
import qualified Data.ByteString.Lazy as LBS
import Data.List.NonEmpty (NonEmpty (..))
import Data.Constraint (Constraint)
import Data.Aeson (FromJSON (..), ToJSON (..), encode, decode)
import Control.Monad (when)
import Control.Monad.IO.Class (liftIO)

import GHC.Generics (Generic)
import GHC.TypeLits (TypeError, ErrorMessage (..))



-- * Types

data Ordinal = Ord1 | OrdN

-- | The numerity of `from`
type family Ordinance from to (loc :: Location) :: Ordinal where
  Ordinance Pair Pair     'Bound     = 'Ord1
  Ordinance Pair Pair     'Connected = 'Ord1
  Ordinance Pub Sub       'Bound     = 'Ord1
  Ordinance XPub Sub      'Bound     = 'Ord1
  Ordinance Sub Pub       'Connected = 'OrdN
  Ordinance Sub XPub      'Connected = 'OrdN
  Ordinance Pub XSub      'Connected = 'OrdN
  Ordinance XSub Pub      'Bound     = 'Ord1
  Ordinance Req Rep       'Connected = 'Ord1
  Ordinance Rep Req       'Bound     = 'Ord1
  Ordinance Req Router    'Connected = 'OrdN
  Ordinance Router Req    'Bound     = 'Ord1
  Ordinance Rep Dealer    'Connected = 'OrdN
  Ordinance Dealer Rep    'Bound     = 'Ord1
  Ordinance Dealer Router 'Connected = 'OrdN
  Ordinance Dealer Router 'Bound     = 'Ord1
  Ordinance Router Dealer 'Connected = 'OrdN
  Ordinance Router Dealer 'Bound     = 'Ord1
  Ordinance Pull Push     'Connected = 'OrdN
  Ordinance Pull Push     'Bound     = 'Ord1
  Ordinance Push Pull     'Connected = 'OrdN
  Ordinance Push Pull     'Bound     = 'Ord1


type family NeedsIdentity from to :: Constraint where
  NeedsIdentity Req Router = ()
  NeedsIdentity Dealer Rep = ()
  NeedsIdentity Dealer Router = ()


data Location = Connected | Bound

type family Bindable from :: Constraint where
  Bindable Pair = ()
  Bindable Rep = ()
  Bindable Router = ()
  Bindable Dealer = ()
  Bindable Pub = ()
  Bindable XPub = ()
  Bindable XSub = ()
  Bindable Pull = ()
  Bindable Push = ()

type family Connectable from :: Constraint where
  Connectable Pair = ()
  Connectable Req = ()
  Connectable Rep = ()
  Connectable Router = ()
  Connectable Dealer = ()
  Connectable Sub = ()
  Connectable Pub = ()
  Connectable XSub = ()
  Connectable XPub = ()
  Connectable Pull = ()
  Connectable Push = ()


newtype Socket z from to (loc :: Location)
  = Socket {getSocket :: Z.Socket z from}


type family IsLegal from to :: Constraint where
  IsLegal Pair Pair = ()
  IsLegal Sub Pub = ()
  IsLegal Pub Sub = ()
  IsLegal XSub Pub = TypeError (Text "Not legal ZeroMQ socket: For some reason xsub/pub isn't working")
  IsLegal XPub Sub = ()
  IsLegal Pub XSub = TypeError (Text "Not legal ZeroMQ socket: For some reason pub/xsub isn't working")
  IsLegal Sub XPub = ()
  IsLegal XPub XSub = ()
  IsLegal XSub XPub = ()
  IsLegal Push Pull = ()
  IsLegal Pull Push = ()
  IsLegal Req Rep = ()
  IsLegal Rep Req = ()
  IsLegal Req Router = ()
  IsLegal Router Req = ()
  IsLegal Rep Dealer = ()
  IsLegal Dealer Rep = ()
  IsLegal Router Dealer = ()
  IsLegal Dealer Router = ()
  IsLegal Router Router = ()
  IsLegal Dealer Dealer = ()
  IsLegal from to = TypeError (Text "Not legal ZeroMQ socket")


socket :: SocketType from
       => IsLegal from to
       => from -> to -> ZMQ z (Socket z from to loc)
socket from _ = Socket <$> Z.socket from

bind :: Bindable from => Socket z from to 'Bound -> String -> ZMQ z ()
bind (Socket s) x = Z.bind s x

connect :: Connectable from => Socket z from to 'Connected -> String -> ZMQ z ()
connect (Socket s) x = Z.connect s x


newtype ZMQIdent = ZMQIdent {getZMQIdent :: ByteString}
  deriving (Show, Eq, Ord, Generic, Hashable)

newUUIDIdentity :: IO ZMQIdent
newUUIDIdentity =
  (ZMQIdent . LBS.toStrict . UUID.toByteString) <$> nextRandom


setIdentity :: NeedsIdentity from to
            => Socket z from to loc
            -> ZMQIdent -> ZMQ z Bool
setIdentity (Socket s) (ZMQIdent clientId) =
  case toRestricted clientId of
    Nothing -> pure False
    Just ident -> True <$ Z.setIdentity ident s


setUUIDIdentity :: NeedsIdentity from to => Socket z from to loc -> ZMQ z ()
setUUIDIdentity s = do
  ident <- liftIO newUUIDIdentity
  worked <- setIdentity s ident
  when (not worked) (error "couldn't restrict uuid")


-- * Classes

-- | Send a message over a ZMQ socket
class Sendable from to aux
  | from to -> aux where
  send :: aux -> Socket z from to loc -> NonEmpty ByteString -> ZMQ z ()

sendJson :: Sendable from to aux => ToJSON a => aux -> Socket z from to loc -> a -> ZMQ z ()
sendJson a s x = send a s (LBS.toStrict (encode x) :| [])

instance Sendable Pub Sub () where
  send () (Socket s) xs = Z.sendMulti s xs

instance Sendable XPub Sub () where
  send () (Socket s) xs = Z.sendMulti s xs

instance Sendable Pub XSub () where
  send () (Socket s) xs = Z.sendMulti s xs

instance Sendable Req Rep () where
  send () (Socket s) xs = Z.sendMulti s xs

instance Sendable Rep Req () where
  send () (Socket s) xs = Z.sendMulti s xs

instance Sendable Req Router () where
  send () (Socket s) xs = Z.sendMulti s xs

instance Sendable Router Req ZMQIdent where
  send (ZMQIdent addr) (Socket s) (x:|xs) = Z.sendMulti s (addr :| "":x:xs)

instance Sendable Rep Dealer () where
  send () (Socket s) xs = Z.sendMulti s xs

instance Sendable Dealer Rep () where
  send () (Socket s) (x:|xs) = Z.sendMulti s ("" :| x:xs)

instance Sendable Dealer Router () where
  send () (Socket s) (x:|xs) = Z.sendMulti s ("" :| x:xs)

instance Sendable Router Dealer ZMQIdent where
  send (ZMQIdent addr) (Socket s) (x:|xs) = Z.sendMulti s (addr :| "":x:xs)


-- | Receive a message over a ZMQ socket
class Receivable from to aux
  | from to -> aux where
  receive :: Socket z from to loc -> ZMQ z (Maybe (aux, NonEmpty ByteString))

receiveJson :: Receivable from to aux => FromJSON a => Socket z from to loc -> ZMQ z (Maybe (aux, a))
receiveJson s = do
  mX <- receive s
  case mX of
    Nothing -> pure Nothing
    Just (aux, msg :| _) -> case decode (LBS.fromStrict msg) of
      Nothing -> pure Nothing
      Just x -> pure (Just (aux,x))

instance Receivable Sub Pub () where
  receive (Socket s) = receiveBasic s

instance Receivable Sub XPub () where
  receive (Socket s) = receiveBasic s

instance Receivable XSub Pub () where
  receive (Socket s) = receiveBasic s

instance Receivable Req Rep () where
  receive (Socket s) = receiveBasic s

instance Receivable Rep Req () where
  receive (Socket s) = receiveBasic s

instance Receivable Req Router () where
  receive (Socket s) = receiveBasic s

instance Receivable Router Req ZMQIdent where
  receive (Socket s) = do
    xs <- Z.receiveMulti s
    case xs of
      (addr:_:x:xs') -> pure (Just (ZMQIdent addr, x :| xs'))
      _ -> pure Nothing

instance Receivable Rep Dealer () where
  receive (Socket s) = receiveBasic s

instance Receivable Dealer Rep () where
  receive (Socket s) = do
    xs <- Z.receiveMulti s
    case xs of
      (_:x:xs') -> pure (Just ((), x :| xs'))
      _ -> pure Nothing

instance Receivable Dealer Router () where
  receive (Socket s) = do
    xs <- Z.receiveMulti s
    case xs of
      (_:x:xs') -> pure (Just ((), x :| xs'))
      _ -> pure Nothing

instance Receivable Router Dealer ZMQIdent where
  receive (Socket s) = do
    xs <- Z.receiveMulti s
    case xs of
      (addr:_:x:xs') -> pure (Just (ZMQIdent addr, x :| xs'))
      _ -> pure Nothing


receiveBasic :: Z.Receiver t => Z.Socket s t -> ZMQ s (Maybe ((), NonEmpty ByteString))
receiveBasic s = do
  xs <- Z.receiveMulti s
  case xs of
    (x:xs') -> pure (Just ((), x :| xs'))
    _ -> pure Nothing