msgpack-rpc-conduit-0.0.7: src/Network/MessagePack/Client/Internal.hs
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE StrictData #-}
{-# LANGUAGE TypeFamilies #-}
{-# OPTIONS_GHC -fno-warn-orphans #-}
module Network.MessagePack.Client.Internal where
import Control.Applicative (Applicative)
import Control.Monad (when)
import Control.Monad.Catch (MonadCatch, MonadThrow,
throwM)
import qualified Control.Monad.State.Strict as CMS
import Control.Monad.Validate (runValidate)
import qualified Data.Binary as Binary
import qualified Data.ByteString as S
import Data.Conduit (ConduitT, SealedConduitT,
Void, runConduit, ($$++),
(.|))
import qualified Data.Conduit.Binary as CB
import Data.Conduit.Serialization.Binary (sinkGet)
import Data.MessagePack (MessagePack, Object,
defaultConfig, fromObject,
fromObjectWith)
import Data.Monoid ((<>))
import Data.Text (Text)
import qualified Data.Text as T
import qualified Network.MessagePack.Types.Result as R
import Network.MessagePack.Interface (IsClientType (..), Returns,
ReturnsM)
import Network.MessagePack.Types.Client
import Network.MessagePack.Types.Error
import Network.MessagePack.Types.Spec
-- | RPC connection type
data Connection m = Connection
{ connSource :: !(SealedConduitT () S.ByteString m ())
, connSink :: !(ConduitT S.ByteString Void m ())
, connMsgId :: !Int
, connMths :: ![Text]
}
newtype ClientT m a
= ClientT { runClientT :: CMS.StateT (Connection m) m a }
deriving (Functor, Applicative, Monad, CMS.MonadIO, MonadThrow, MonadCatch)
type Client a = ClientT IO a
instance IsClientType m (Returns r) where
type ClientType m (Returns r) = ClientT m r
instance IsClientType m (ReturnsM io r) where
type ClientType m (ReturnsM io r) = ClientT m r
instance (CMS.MonadIO m, MonadThrow m, MessagePack o)
=> RpcType (ClientT m o) where
rpcc name args = do
res <- rpcCall name (reverse args)
case runValidate $ fromObjectWith defaultConfig res of
Right ok ->
return ok
Left err ->
throwM $ ResultTypeError (T.pack $ show err) res
rpcCall :: (MonadThrow m, CMS.MonadIO m) => Text -> [Object] -> ClientT m Object
rpcCall methodName args = ClientT $ do
conn <- CMS.get
let msgid = connMsgId conn
(rsrc', res) <- CMS.lift $ do
let req = packRequest (connMths conn) (0, msgid, methodName, args)
runConduit $ CB.sourceLbs req .| connSink conn
connSource conn $$++ sinkGet Binary.get
CMS.put conn
{ connSource = rsrc'
, connMsgId = msgid + 1
}
case unpackResponse res of
Left err -> throwM $ ProtocolError $ "invalid response data: " <> T.pack (show err)
Right (rtype, rmsgid, rerror, rresult) -> do
when (rtype /= 1) $
throwM $ ProtocolError $
"invalid response type (expect 1, but got " <> T.pack (show rtype) <> "): " <> T.pack (show res)
when (rmsgid /= msgid) $
throwM $ ProtocolError $
"message id mismatch: expect " <> T.pack (show msgid) <> ", but got " <> T.pack (show rmsgid)
case fromObject rerror of
Nothing -> throwM $ RemoteError rerror
Just () -> return rresult
setMethodList :: Monad m => [Text] -> ClientT m ()
setMethodList mths = ClientT $ do
conn <- CMS.get
CMS.put conn { connMths = mths }