packages feed

zyre2-0.1.1.0: src/Network/Zyre2/ZMsg.hs

{-# LANGUAGE OverloadedStrings #-}

-- | Message types for sending and receiving from a Zyre peer network.
module Network.Zyre2.ZMsg
  ( ZMsg (..),
    ZFrame,
    pop,
    pushText,
    addText,
    popText,
    pushMem,
    addMem,
    popMem,
    mkFrame,
    frameData,
    msgWhisper,
    msgShout,
  )
where

import Control.Exception (throw)
import qualified Data.Bifunctor
import Data.ByteString (ByteString)
import qualified Data.ByteString.Char8 as BSC
import qualified Data.List as List
import Data.String (fromString)
import Data.Text (Text)
import qualified Data.Text as T
import Network.Zyre2.Types (ZyreException (ZyreMsgDontSupportFramesException))

-- | Message-types supported and published by zyre.
data ZMsg
  = -- | A peer has joined the peer network.
    Enter
      { -- | The UUID of the sending node.
        _zmsgFromNode :: Text,
        -- | The name of the sending node.
        _zmsgName :: Text,
        -- | Dictionary containing the headers.
        _zmsgHeaders :: [(Text, Text)],
        -- | The ip and port of the sending node. E.g. tcp://127.0.0.1:8344
        _zmsgIpPort :: Text
      }
  | -- | A peer has not sent a message within the 'evasive' interval period.
    -- The peer will be pinged.
    Evasive
      { _zmsgFromNode :: Text,
        _zmsgName :: Text
      }
  | -- | A peer has been silent and not responded to PING messages for the 'silent' interval period.
    Silent
      { _zmsgFromNode :: Text,
        _zmsgName :: Text
      }
  | -- | A peer has exited the peer network.
    Exit
      { _zmsgFromNode :: Text,
        _zmsgName :: Text
      }
  | -- | A peer has joined a group.
    Join
      { _zmsgFromNode :: Text,
        _zmsgName :: Text,
        _zmsgGroupName :: Text
      }
  | -- | A peer has left a group.
    Leave
      { _zmsgFromNode :: Text,
        _zmsgName :: Text,
        -- | The name of the group which the message concerns.
        _zmsgGroupName :: Text
      }
  | -- | A peer has whispered to this node.
    Whisper
      { _zmsgFromNode :: Text,
        _zmsgName :: Text,
        -- | The message content, coded as 'ZFrame's. A message may hold 0 or more frames.
        -- Note: a ZMsg with 0 frames supplied to 'Network.Zyre2.shout' or 'Network.Zyre2.whisper' will be ignored and not sent.
        _zmsgMessage :: [ZFrame]
      }
  | -- | A peer has shouted in a group.
    Shout
      { _zmsgFromNode :: Text,
        _zmsgName :: Text,
        _zmsgGroupName :: Text,
        _zmsgMessage :: [ZFrame]
      }
  | -- | The zyre context which is being listened to has been stopped.
    Stop
  deriving (Show)

-- | A frame of binary data. See 'Network.Zyre2.ZMsg.mkFrame' and 'Network.Zyre2.ZMsg.frameData'.
newtype ZFrame = ZFrame ByteString

instance Show ZFrame where
  show (ZFrame _) = "ZFrame[Bytes]"

{- Utility functions -}

pushFrame :: ZMsg -> ZFrame -> ZMsg
pushFrame zmsg@Whisper {} zframe = zmsg {_zmsgMessage = zframe : _zmsgMessage zmsg}
pushFrame zmsg@Shout {} zframe = zmsg {_zmsgMessage = zframe : _zmsgMessage zmsg}
pushFrame _ _ = throw ZyreMsgDontSupportFramesException

addFrame :: ZMsg -> ZFrame -> ZMsg
addFrame zmsg@Whisper {} zframe = zmsg {_zmsgMessage = _zmsgMessage zmsg <> [zframe]}
addFrame zmsg@Shout {} zframe = zmsg {_zmsgMessage = _zmsgMessage zmsg <> [zframe]}
addFrame _ _ = throw ZyreMsgDontSupportFramesException

-- {- Interface -}

append :: ZMsg -> ZFrame -> ZMsg
append = addFrame

prepend :: ZMsg -> ZFrame -> ZMsg
prepend = pushFrame

-- | Remove the first frame of a ZMsg, or return 'Nothing'.
pop :: ZMsg -> (Maybe ZFrame, ZMsg)
pop zmsg@Whisper {} = _popMsg zmsg
pop zmsg@Shout {} = _popMsg zmsg
pop _ = throw ZyreMsgDontSupportFramesException

_popMsg :: ZMsg -> (Maybe ZFrame, ZMsg)
_popMsg m = case _zmsgMessage m of
  [] -> (Nothing, m)
  [x] -> (Just x, m {_zmsgMessage = []})
  (x : _) -> (Just x, m {_zmsgMessage = List.tail (_zmsgMessage m)})

-- | Push a text frame to the front of a 'ZMsg'.
pushText :: Text -> ZMsg -> ZMsg
pushText str zmsg = zmsg `pushFrame` ZFrame (fromString (T.unpack str))

-- | Add a text frame to the end of a 'ZMsg'.
addText :: Text -> ZMsg -> ZMsg
addText str zmsg = zmsg `addFrame` ZFrame (fromString (T.unpack str))

-- | Push binary data to the front of a 'ZMsg' as a frame.
pushMem :: ByteString -> ZMsg -> ZMsg
pushMem bs zmsg = zmsg `pushFrame` ZFrame bs

-- | Add binary data to the end of a 'ZMsg' as a frame.
addMem :: ByteString -> ZMsg -> ZMsg
addMem bs zmsg = zmsg `addFrame` ZFrame bs

-- | Remove the first frame of the 'ZMsg', and interpret it as 'Text'.
popText :: ZMsg -> (Maybe Text, ZMsg)
popText zmsg =
  Data.Bifunctor.first
    (fmap (\(ZFrame bs) -> T.pack (BSC.unpack bs)))
    (pop zmsg)

-- | Remove the first frame of the 'ZMsg' as a 'ByteString'.
popMem :: ZMsg -> (Maybe ByteString, ZMsg)
popMem zmsg =
  Data.Bifunctor.first
    (fmap (\(ZFrame bs) -> bs))
    (pop zmsg)

-- | Create a frame from a ByteString.
mkFrame :: ByteString -> ZFrame
mkFrame = ZFrame

-- | Retrieve the data stored in a 'ZFrame'.
frameData :: ZFrame -> ByteString
frameData (ZFrame bs) = bs

-- | Empty 'Whisper' message. Combine with 'addText', 'pushText',
-- 'addMem', and 'pushMem' to construct a whisper to send.
msgWhisper :: ZMsg
msgWhisper =
  Whisper
    { _zmsgFromNode = "Not set",
      _zmsgName = "Not set",
      _zmsgMessage = []
    }

-- | Empty 'Shout' message. Combine with 'addText', 'pushText',
-- 'addMem', and 'pushMem' to construct a shout to send.
msgShout :: ZMsg
msgShout =
  Shout
    { _zmsgFromNode = "Not set",
      _zmsgName = "Not set",
      _zmsgGroupName = "Not set",
      _zmsgMessage = []
    }