help-esb-0.1.1: HelpEsbClient.hs
{-# LANGUAGE OverloadedStrings, DeriveGeneric #-}
{-|
Module : HelpEsbClient
Description: Haskell version of the Help.com ESB Client
Copyright: (c) Help.com, LLC, 2014
License: MIT
Maintainer: alex.martin@help.com
Stability: Stable
Portability: UNIX
-}
module HelpEsbClient (
-- * Classes
EsbSend
, EsbRecieve
-- * Raw Exported Functions
, getSocket
, sendSocketData
, readSocketDataRaw
, readSocketData
-- * ESB Functions
, esbSend
, esbRecieve
, esbInit
, esbListen
-- * Utility Functions
, encode
, decode
, eitherDecode
, logger
-- * Utility Types
, Socket
, module Data.UUID
, module Data.UUID.V4
, module System.Environment
) where
-- Base Modules
import System.IO
import System.Environment
import Network.Socket
import Control.Exception
import GHC.Generics
import Data.Text hiding (replace)
import Data.List.Utils
import Data.UUID
import Data.UUID.V4
import Data.Aeson
import qualified Data.ByteString.Lazy.Char8 as C
-- JSON Modules
import qualified JSON.Basic.Response as Basic.Response
import qualified JSON.Login.Request as Login.Request
import qualified JSON.Login.Response as Login.Response
import qualified JSON.API.EventGroup.Post.Request as EventGroup.Post.Request
import qualified JSON.API.Event.Post.Request as Event.Post.Request
-- Classes
-- | The 'EsbSend' class determines how a message should be sent to the ESB.
class EsbSend a where
-- | The 'esbSend' method takes a socket and writes somekind of payload.
esbSend :: Socket -- ^ The socket connection.
-> a -- ^ The payload.
-> IO () -- ^ Any IO output.
-- | The 'EsbRecieve' class determines how a message from the ESB should be
-- recieved.
class EsbRecieve a where
-- | The 'esbRecieve' method takes a socket and reads somekind of payload.
esbRecieve :: Socket -- ^ The socket connection.
-> a -- ^ The payload.
-> IO () -- ^ Any IO output.
-- | The 'logger' function simply logs out in a consistent way. Will be
-- updated to be more robust.
logger :: String -- ^ Messaged to be logged.
-> IO () -- ^ IO output.
logger out = do
let logPrefix = "# "
putStrLn $ logPrefix ++ out
-- Socket Specific Functions
-- | The 'getSocket' function takes a host and port and connects to and
-- returns the socket.
getSocket :: String -- ^ Host address.
-> Int -- ^ Host port.
-> IO Socket -- ^ The socket connection.
getSocket host port = withSocketsDo $ do
(serveraddr:_) <- getAddrInfo Nothing (Just host) (Just (show port))
sock <- socket (addrFamily serveraddr) Stream defaultProtocol
connect sock (addrAddress serveraddr) >> return sock
-- | The 'sendSocketData' function accepts a socket and bytes, converts the
-- bytes to cleaned up JSON, and writes the JSON to the socket.
sendSocketData :: Socket -- ^ The socket connection.
-> C.ByteString -- ^ The JSON bytestring payload.
-> IO () -- ^ Any IO output.
sendSocketData sock bytes = do
let json = replace "h_" "" (C.unpack bytes)
send sock (json ++ "\n")
logger ("+ Raw Send: " ++ json)
-- | The 'readSocketDataRaw' function accepts a socket and grabs whatever
-- data might be in the latest message.
readSocketDataRaw :: Socket -- ^ The socket connection.
-> IO [Char] -- ^ Any IO output.
readSocketDataRaw sock = do
message <- recv sock 1024
logger ("+ Raw Read: " ++ message)
return message
-- | The 'readSocketData' function accepts a socket, reads and cleans up
-- any JSON for parsing, and returns the bytes of JSON.
readSocketData :: Socket -- ^ The socket connection.
-> IO (C.ByteString) -- ^ The JSON bytestring payload.
readSocketData sock = do
raw <- readSocketDataRaw sock
let fixed = replace ",\"" ",\"h_" (replace "{\"" "{\"h_" raw)
return $ C.pack fixed
-- Send Socket Instances
-- | The 'EsbSend' instance for a login request.
instance EsbSend Login.Request.Data where
esbSend sock payload = do
uuid <- nextRandom
let meta = Login.Request.Meta {
Login.Request.h_type = "login"
, Login.Request.h_id = toString uuid
}
let message = Login.Request.Message {
Login.Request.h_meta = meta
, Login.Request.h_data = payload
}
sendSocketData sock (encode message)
logger ("Login Request: " ++ show message)
-- | The 'EsbSend' instance for an Event Group post request.
instance EsbSend EventGroup.Post.Request.Data where
esbSend sock payload = do
uuid <- nextRandom
let meta = EventGroup.Post.Request.Meta {
EventGroup.Post.Request.h_type = "sendMessage"
, EventGroup.Post.Request.h_id = toString uuid
, EventGroup.Post.Request.h_group = "api-messages"
}
let message = EventGroup.Post.Request.Message {
EventGroup.Post.Request.h_meta = meta
, EventGroup.Post.Request.h_data = payload
}
sendSocketData sock (encode message)
logger ("EventGroup API Request: " ++ show message)
-- | The 'EsbSend' instance for an Event post request.
instance EsbSend Event.Post.Request.Data where
esbSend sock payload = do
uuid <- nextRandom
let meta = Event.Post.Request.Meta {
Event.Post.Request.h_type = "sendMessage"
, Event.Post.Request.h_id = toString uuid
, Event.Post.Request.h_group = "api-messages"
}
let message = Event.Post.Request.Message {
Event.Post.Request.h_meta = meta
, Event.Post.Request.h_data = payload
}
sendSocketData sock (encode message)
logger ("Event API Request: " ++ show message)
-- Recieve Socket Instances
-- | The 'EsbRecieve' instance for a Login response.
instance EsbRecieve Login.Response.Message where
esbRecieve sock message = do
let payload = Login.Response.h_meta message
case Login.Response.h_result payload of
"SUCCESS" -> logger ("Successfully logged in.")
_ -> error "! Failed to login!"
-- Initialization Instances
-- | The 'esbInit' function initializes the socket connection and logs
-- into the ESB.
esbInit :: Text -- ^ Group name.
-> [Text] -- ^ Subscriptions.
-> String -- ^ Host address.
-> Int -- ^ Host port.
-> IO Socket -- ^ The socket connection.
esbInit name subscriptions host port = do
sock <- getSocket host port
let loginData = Login.Request.Data { Login.Request.h_name = name, Login.Request.h_subscriptions = subscriptions }
esbSend sock loginData
return sock
-- Essential Listening Logic
-- | The 'esbListen' function performs all essential listening logic
-- for any ESB client.
esbListen :: Socket -- ^ The socket connection.
-> IO (C.ByteString) -- ^ The JSON bytestring payload.
esbListen sock = do
bytes <- readSocketData sock
case eitherDecode bytes :: (Either String Login.Response.Message) of
Left error -> return ()
Right response -> do
logger ("Response: " ++ show response)
esbRecieve sock response
return bytes