-- | -- This module defines the client monad and the type signatures for -- transports and prototocols. -- ----------------------------------------------------------------------------- {-# LANGUAGE Rank2Types #-} module Network.Colchis ( -- * Client JSONClient (..) , JSONClientError (..) , call -- * Protocol , Protocol -- * Transport , Transport -- * Running clients , runJSONClient -- * Utils -- $utils , umap , umapM -- * Re-exported -- $reexports , hoist ) where import Data.Text import Data.Aeson import Control.Monad import Control.Monad.Trans.Except import Pipes import Pipes.Core import Pipes.Lift import Network.Colchis.Protocol import Network.Colchis.Transport {-| (request associated with the error, error message, response that caused the error) -} type JSONClientError = (Value,Text,Value) {-| Emits requests consisting in `Value`s paired with some metadata. The metadata is usually the method name. Receives `Value` responses. -} type JSONClient s m r = Client (s,Value) Value (ExceptT JSONClientError m) r call :: (ToJSON a, FromJSON r, Monad m) => s -> a -> JSONClient s m r call s a = do let jreq = toJSON a rj <- request (s,jreq) case fromJSON rj of Error msg -> lift $ throwE (jreq,pack msg,rj) Success r -> return r {- $utils These functions can be used to manipulate requests flowing upstream. -} {-| Apply a function to all requests flowing upstream in a bidirectional pipe. Returns a function that can be composed with `+>>` or `>+>`. -} umap :: Monad m => (b' -> a') -> b' -> Proxy a' x b' x m r umap f = go where go b = request (f b) >>= respond >>= go {-| Apply a monadic function to all requests flowing upstream in a bidirectional pipe. Returns a function that can be composed with `+>>` or `>+>`. -} umapM :: Monad m => (b' -> m a') -> b' -> Proxy a' x b' x m r umapM f = go where go b = lift (f b) >>= request >>= respond >>= go {-| The return value lives inside the monad associated to the transport layer. The run function that peels off that layer depends on the transport. See for example `Network.Colchis.Transport.TCP.runTcp` for the `Network.Colchis.Transport.TCP.tcp` transport. -} runJSONClient :: (MonadTrans t, MFunctor t, MonadIO m, Monad (t m)) => Transport t m -> Protocol s m e -> JSONClient s m r -> t m (Either e (Either JSONClientError r)) runJSONClient server adapter client = runExceptT $ runExceptT $ runEffect $ hoist (lift.lift) . server +>> hoist (lift.hoist lift) . adapter +>> hoist (hoist (lift.lift)) client {- $reexports When the function that runs the transport layer requires the underlying monad to be whittled down to `IO`, `hoist` (along with a suitable monad morphism) can come in handy. -}