packages feed

json-rpc 0.5.0.0 → 0.6.0.0

raw patch · 6 files changed

+417/−431 lines, 6 filesPVP ok

version bump matches the API change (PVP)

API changes (from Hackage documentation)

- Network.JsonRpc: Err :: !Ver -> !ErrorObj -> !Id -> Err
- Network.JsonRpc: IdNull :: Id
- Network.JsonRpc: MsgError :: !Err -> Message
- Network.JsonRpc: MsgNotif :: !Notif -> Message
- Network.JsonRpc: buildNotif :: (ToJSON n, ToNotif n) => Ver -> n -> Notif
- Network.JsonRpc: class FromNotif n
- Network.JsonRpc: class ToNotif n
- Network.JsonRpc: data Err
- Network.JsonRpc: data Notif
- Network.JsonRpc: dummyRespond :: MonadLoggerIO m => Respond () m ()
- Network.JsonRpc: dummySrv :: MonadLoggerIO m => JsonRpcT m ()
- Network.JsonRpc: fromNotif :: FromNotif n => Notif -> Maybe n
- Network.JsonRpc: getErrId :: Err -> !Id
- Network.JsonRpc: getErrObj :: Err -> !ErrorObj
- Network.JsonRpc: getErrVer :: Err -> !Ver
- Network.JsonRpc: getMsgError :: Message -> !Err
- Network.JsonRpc: getMsgNotif :: Message -> !Notif
- Network.JsonRpc: getNotifMethod :: Notif -> !Method
- Network.JsonRpc: getNotifParams :: Notif -> !Value
- Network.JsonRpc: getNotifVer :: Notif -> !Ver
- Network.JsonRpc: notifMethod :: ToNotif n => n -> Method
- Network.JsonRpc: parseNotif :: FromNotif n => Method -> Maybe (Value -> Parser n)
- Network.JsonRpc: receiveNotif :: (MonadLoggerIO m, FromNotif n) => JsonRpcT m (Maybe (Either ErrorObj (Maybe n)))
- Network.JsonRpc: sendNotif :: (ToJSON no, ToNotif no, MonadLoggerIO m) => no -> JsonRpcT m ()
- Network.JsonRpc.Arbitrary: ReqRes :: !Request -> !Response -> ReqRes
- Network.JsonRpc.Arbitrary: data ReqRes
- Network.JsonRpc.Arbitrary: instance Arbitrary Err
- Network.JsonRpc.Arbitrary: instance Arbitrary Notif
- Network.JsonRpc.Arbitrary: instance Arbitrary ReqRes
- Network.JsonRpc.Arbitrary: instance Eq ReqRes
- Network.JsonRpc.Arbitrary: instance Show ReqRes
+ Network.JsonRpc: OrphanError :: !Ver -> !ErrorObj -> Response
+ Network.JsonRpc: ResponseError :: !Ver -> !ErrorObj -> !Id -> Response
+ Network.JsonRpc: Session :: TBMChan (Either Response Message) -> TBMChan Message -> Maybe (TBMChan Request) -> TVar Id -> TVar SentRequests -> Ver -> Session
+ Network.JsonRpc: data Session
+ Network.JsonRpc: getError :: Response -> !ErrorObj
+ Network.JsonRpc: inCh :: Session -> TBMChan (Either Response Message)
+ Network.JsonRpc: initSession :: Ver -> Bool -> STM Session
+ Network.JsonRpc: lastId :: Session -> TVar Id
+ Network.JsonRpc: outCh :: Session -> TBMChan Message
+ Network.JsonRpc: processIncoming :: (Functor m, MonadLoggerIO m) => JsonRpcT m ()
+ Network.JsonRpc: receiveRequest :: MonadLoggerIO m => JsonRpcT m (Maybe Request)
+ Network.JsonRpc: reqCh :: Session -> Maybe (TBMChan Request)
+ Network.JsonRpc: requestIsNotif :: ToRequest q => q -> Bool
+ Network.JsonRpc: rpcVer :: Session -> Ver
+ Network.JsonRpc: sendResponse :: MonadLoggerIO m => Response -> JsonRpcT m ()
+ Network.JsonRpc: sentReqs :: Session -> TVar SentRequests
+ Network.JsonRpc: type SentRequests = HashMap Id (TMVar (Maybe Response))
- Network.JsonRpc: Notif :: !Ver -> !Method -> !Value -> Notif
+ Network.JsonRpc: Notif :: !Ver -> !Method -> !Value -> Request
- Network.JsonRpc: buildResponse :: (Monad m, FromRequest q, ToJSON r) => Respond q m r -> Request -> m (Either Err Response)
+ Network.JsonRpc: buildResponse :: (Monad m, FromRequest q, ToJSON r) => Respond q m r -> Request -> m (Maybe Response)
- Network.JsonRpc: decodeConduit :: MonadLogger m => Ver -> Conduit ByteString m (Either Err Message)
+ Network.JsonRpc: decodeConduit :: MonadLogger m => Ver -> Conduit ByteString m (Either Response Message)
- Network.JsonRpc: errorParse :: Value -> ErrorObj
+ Network.JsonRpc: errorParse :: ByteString -> ErrorObj
- Network.JsonRpc: fromRequest :: FromRequest q => Request -> Maybe q
+ Network.JsonRpc: fromRequest :: FromRequest q => Request -> Either ErrorObj q
- Network.JsonRpc: jsonRpcTcpClient :: (MonadLoggerIO m, MonadBaseControl IO m, FromRequest q, ToJSON r) => Ver -> ClientSettings -> Respond q m r -> JsonRpcT m a -> m a
+ Network.JsonRpc: jsonRpcTcpClient :: (MonadLoggerIO m, MonadBaseControl IO m) => Ver -> Bool -> ClientSettings -> JsonRpcT m a -> m a
- Network.JsonRpc: jsonRpcTcpServer :: (MonadLoggerIO m, MonadBaseControl IO m, FromRequest q, ToJSON r) => Ver -> ServerSettings -> Respond q m r -> JsonRpcT m () -> m a
+ Network.JsonRpc: jsonRpcTcpServer :: (MonadLoggerIO m, MonadBaseControl IO m) => Ver -> Bool -> ServerSettings -> JsonRpcT m () -> m a
- Network.JsonRpc: runJsonRpcT :: (MonadLoggerIO m, MonadBaseControl IO m, FromRequest q, ToJSON r) => Ver -> Respond q m r -> Sink Message m () -> Source m (Either Err Message) -> JsonRpcT m a -> m a
+ Network.JsonRpc: runJsonRpcT :: (MonadLoggerIO m, MonadBaseControl IO m) => Ver -> Bool -> Sink Message m () -> Source m (Either Response Message) -> JsonRpcT m a -> m a
- Network.JsonRpc: sendRequest :: (MonadLoggerIO m, ToJSON q, ToRequest q, FromResponse r) => q -> JsonRpcT m (Either ErrorObj (Maybe r))
+ Network.JsonRpc: sendRequest :: (MonadLoggerIO m, ToJSON q, ToRequest q, FromResponse r) => q -> JsonRpcT m (Maybe (Either ErrorObj r))

Files

Network/JsonRpc/Arbitrary.hs view
@@ -1,10 +1,7 @@ {-# LANGUAGE FlexibleInstances #-} {-# OPTIONS_GHC -fno-warn-orphans #-} -- | Arbitrary instances and data types for use in test suites.-module Network.JsonRpc.Arbitrary-( -- * Arbitrary Data-  ReqRes(..)-) where+module Network.JsonRpc.Arbitrary where  import Control.Applicative import Data.Aeson.Types@@ -15,18 +12,6 @@ import Test.QuickCheck.Arbitrary import Test.QuickCheck.Gen --- | A pair of a request and its corresponding response.--- Id and version should match.-data ReqRes = ReqRes !Request !Response-    deriving (Show, Eq)--instance Arbitrary ReqRes where-    arbitrary = do-        rq <- arbitrary-        rs <- arbitrary-        let rs' = rs { getResId = getReqId rq, getResVer = getReqVer rq }-        return $ ReqRes rq rs'- instance Arbitrary Text where     arbitrary = T.pack <$> arbitrary @@ -34,29 +19,29 @@     arbitrary = elements [V1, V2]  instance Arbitrary Request where-    arbitrary = Request <$> arbitrary <*> arbitrary <*> arbitrary <*> arbitrary--instance Arbitrary Notif where-    arbitrary = Notif <$> arbitrary <*> arbitrary <*> arbitrary+    arbitrary = oneof+        [ Request <$> arbitrary <*> arbitrary <*> arbitrary <*> arbitrary+        , Notif <$> arbitrary <*> arbitrary <*> arbitrary+        ]  instance Arbitrary Response where-    arbitrary = Response <$> arbitrary <*> arbitrary <*> arbitrary+    arbitrary = oneof+        [ Response <$> arbitrary <*> arbitrary <*> arbitrary+        , ResponseError <$> arbitrary <*> arbitrary <*> arbitrary+        , OrphanError <$> arbitrary <*> arbitrary+        ] + instance Arbitrary ErrorObj where     arbitrary = oneof         [ ErrorObj <$> arbitrary <*> arbitrary <*> arbitrary         , ErrorVal <$> arbitrary         ] -instance Arbitrary Err where-    arbitrary = Err <$> arbitrary <*> arbitrary <*> arbitrary- instance Arbitrary Message where     arbitrary = oneof         [ MsgRequest  <$> arbitrary-        , MsgNotif    <$> arbitrary         , MsgResponse <$> arbitrary-        , MsgError    <$> arbitrary         ]  instance Arbitrary Id where
Network/JsonRpc/Data.hs view
@@ -19,21 +19,10 @@   -- ** Encoding , Respond , buildResponse--  -- * Notifications-, Notif(..)-  -- ** Parsing-, FromNotif(..)-, fromNotif-  -- ** Encoding-, ToNotif(..)-, buildNotif--  -- * Errors-, Err(..)+  -- ** Errors , ErrorObj(..) , fromError-  -- ** Error Messages+  -- ** Error messages , errorParse , errorInvalid , errorParams@@ -50,19 +39,20 @@ ) where  import Control.Applicative+import Data.ByteString (ByteString) import qualified Data.ByteString.Lazy as L import Control.DeepSeq import Control.Monad import Data.Aeson (encode) import Data.Aeson.Types import Data.Hashable (Hashable)+import Data.Maybe import Data.Text (Text)-import Data.Text.Encoding import qualified Data.Text as T+import Data.Text.Encoding import GHC.Generics (Generic)  - -- -- Requests --@@ -71,10 +61,16 @@                        , getReqMethod   :: !Method                        , getReqParams   :: !Value                        , getReqId       :: !Id-                       } deriving (Eq, Show)+                       }+             | Notif   { getReqVer      :: !Ver+                       , getReqMethod   :: !Method+                       , getReqParams   :: !Value+                       }+             deriving (Eq, Show)  instance NFData Request where     rnf (Request v m p i) = rnf v `seq` rnf m `seq` rnf p `seq` rnf i+    rnf (Notif v m p) = rnf v `seq` rnf m `seq` rnf p  instance ToJSON Request where     toJSON (Request V2 m p i) = object $ case p of@@ -83,13 +79,29 @@     toJSON (Request V1 m p i) = object $ case p of         Null -> ["method" .= m, "params" .= emptyArray, "id" .= i]         _    -> ["method" .= m, "params" .= p, "id" .= i]+    toJSON (Notif V2 m p) = object $ case p of+        Null -> [jr2, "method" .= m]+        _    -> [jr2, "method" .= m, "params" .= p]+    toJSON (Notif V1 m p) = object $ case p of+        Null -> ["method" .= m, "params" .= emptyArray, "id" .= Null]+        _    -> ["method" .= m, "params" .= p, "id" .= Null]  class FromRequest q where     -- | Parser for params Value in JSON-RPC request.     parseParams :: Method -> Maybe (Value -> Parser q) -fromRequest :: FromRequest q => Request -> Maybe q-fromRequest (Request _ m p _) = parseParams m >>= flip parseMaybe p+fromRequest :: FromRequest q => Request -> Either ErrorObj q+fromRequest req =+    case parserM of+        Nothing -> Left $ errorMethod m+        Just parser ->+            case parseMaybe parser p of+                Nothing -> Left $ errorParams p+                Just  q -> Right q+  where+    m = getReqMethod req+    p = getReqParams req+    parserM = parseParams m  instance FromRequest Value where     parseParams = const $ Just return@@ -99,46 +111,77 @@  instance FromJSON Request where     parseJSON = withObject "request" $ \o -> do-        (v, i, m, p) <- parseVerIdMethParams o-        guard $ i /= IdNull-        return $ Request v m p i+        (v, n, m, p) <- parseVerIdMethParams o+        case n of Nothing -> return $ Notif   v m p+                  Just i  -> return $ Request v m p i +parseVerIdMethParams :: Object -> Parser (Ver, Maybe Id, Method, Value)+parseVerIdMethParams o = do+    v <- parseVer o+    i <- parseId o+    m <- o .: "method"+    p <- o .:? "params" .!= Null+    return (v, i, m, p) class ToRequest q where     -- | Method associated with request data to build a request object.     requestMethod :: q -> Method +    -- | Is this request to be sent as a notification (no id, no response)?+    requestIsNotif :: q -> Bool+ instance ToRequest Value where     requestMethod = const "json"+    requestIsNotif = const False  instance ToRequest () where     requestMethod = const "json"+    requestIsNotif = const False  buildRequest :: (ToJSON q, ToRequest q)              => Ver             -- ^ JSON-RPC version              -> q               -- ^ Request data              -> Id              -> Request-buildRequest ver q = Request ver (requestMethod q) (toJSON q)--+buildRequest ver q = if requestIsNotif q+                         then const $ Notif ver (requestMethod q) (toJSON q)+                         else Request ver (requestMethod q) (toJSON q)  -- -- Responses -- -data Response = Response { getResVer :: !Ver-                         , getResult :: !Value-                         , getResId  :: !Id-                         } deriving (Eq, Show)+data Response = Response      { getResVer :: !Ver+                              , getResult :: !Value+                              , getResId  :: !Id+                              }+              | ResponseError { getResVer :: !Ver+                              , getError  :: !ErrorObj+                              , getResId  :: !Id+                              }+              | OrphanError   { getResVer :: !Ver+                              , getError  :: !ErrorObj+                              }+              deriving (Eq, Show)+               instance NFData Response where     rnf (Response v r i) = rnf v `seq` rnf r `seq` rnf i+    rnf (ResponseError v o i) = rnf v `seq` rnf o `seq` rnf i+    rnf (OrphanError v o) = rnf v `seq` rnf o  instance ToJSON Response where     toJSON (Response V1 r i) = object         ["id" .= i, "result" .= r, "error" .= Null]     toJSON (Response V2 r i) = object         [jr2, "id" .= i, "result" .= r]+    toJSON (ResponseError V1 e i) = object+        ["id" .= i, "error" .= e, "result" .= Null]+    toJSON (ResponseError V2 e i) = object+        [jr2, "id" .= i, "error" .= e]+    toJSON (OrphanError V1 e) = object+        ["id" .= Null, "error" .= e, "result" .= Null]+    toJSON (OrphanError V2 e) = object+        [jr2, "id" .= Null, "error" .= e]  class FromResponse r where     -- | Parser for result Value in JSON-RPC response.@@ -147,96 +190,51 @@  fromResponse :: FromResponse r => Method -> Response -> Maybe r fromResponse m (Response _ r _) = parseResult m >>= flip parseMaybe r+fromResponse _ _ = Nothing  instance FromResponse Value where     parseResult = const $ Just return  instance FromResponse () where-    parseResult = const . Just . const $ return ()+    parseResult = const Nothing  instance FromJSON Response where     parseJSON = withObject "response" $ \o -> do-        i <- o .: "id"-        guard $ i /= IdNull-        r <- o .: "result"-        guard $ r /= Null-        v <- parseVer o-        return $ Response v r i+        (v, d, s) <- parseVerIdResultError o+        case s of+            Right r -> do+                guard $ isJust d+                return $ Response v r (fromJust d)+            Left e ->+                case d of+                    Just  i -> return $ ResponseError v e i+                    Nothing -> return $ OrphanError v e +parseVerIdResultError :: Object+                      -> Parser (Ver, Maybe Id, Either ErrorObj Value)+parseVerIdResultError o = do+    v <- parseVer o+    i <- parseId o+    r <- o .:? "result" .!= Null+    p <- if r == Null then Left <$> o .: "error" else return $ Right r+    return (v, i, p)+ buildResponse :: (Monad m, FromRequest q, ToJSON r)               => Respond q m r               -> Request-              -> m (Either Err Response)-buildResponse f req@(Request v _ p i) = case fromRequest req of-    Nothing -> return . Left $ Err v (errorInvalid p) i-    Just q -> do-        rE <- f q-        return $ either (\e -> Left $ Err v e i)-                        (\r -> Right $ Response v (toJSON r) i) rE+              -> m (Maybe Response)+buildResponse f req@(Request v _ _ i) =+    case fromRequest req of+        Left e -> return . Just $ ResponseError v e i+        Right q -> do+            rE <- f q+            case rE of+                Left  e -> return . Just $ ResponseError v e i+                Right r -> return . Just $ Response v (toJSON r) i+buildResponse _ _ = return Nothing  type Respond q m r = q -> m (Either ErrorObj r) ------- Notifications-----data Notif = Notif  { getNotifVer    :: !Ver-                    , getNotifMethod :: !Method-                    , getNotifParams :: !Value-                    } deriving (Eq, Show)--instance NFData Notif where-    rnf (Notif v m n) = rnf v `seq` rnf m `seq` rnf n--instance ToJSON Notif where-    toJSON (Notif V2 m p) = object $ case p of-        Null -> [jr2, "method" .= m]-        _    -> [jr2, "method" .= m, "params" .= p]-    toJSON (Notif V1 m p) = object $ case p of-        Null -> ["method" .= m, "params" .= emptyArray, "id" .= Null]-        _    -> ["method" .= m, "params" .= p, "id" .= Null]--class FromNotif n where-    -- | Parser for notification params Value.-    parseNotif :: Method -> Maybe (Value -> Parser n)--fromNotif :: FromNotif n => Notif -> Maybe n-fromNotif (Notif _ m n) = parseNotif m >>= flip parseMaybe n--instance FromNotif Value where-    parseNotif = const $ Just return--instance FromNotif () where-    parseNotif = const . Just . const $ return ()--instance FromJSON Notif where-    parseJSON = withObject "notification" $ \o -> do-        (v, i, m, p) <- parseVerIdMethParams o-        guard $ i == IdNull-        return $ Notif v m p--class ToNotif n where-    notifMethod :: n -> Method--instance ToNotif Value where-    notifMethod = const "json"--instance ToNotif () where-    notifMethod = const "json"--buildNotif :: (ToJSON n, ToNotif n)-           => Ver-           -> n-           -> Notif-buildNotif ver n = Notif ver (notifMethod n) (toJSON n)--------- Errors---- -- Error object from JSON-RPC 2.0. ErrorVal for backwards compatibility. data ErrorObj = ErrorObj  { getErrMsg  :: !String                           , getErrCode :: !Int@@ -266,33 +264,16 @@     toJSON (ErrorVal v) = v  fromError :: ErrorObj -> String-fromError (ErrorObj m _ _) = m-fromError (ErrorVal v) = T.unpack $ decodeUtf8 $ L.toStrict $ encode v--data Err = Err { getErrVer  :: !Ver-                         , getErrObj  :: !ErrorObj-                         , getErrId   :: !Id-                         } deriving (Eq, Show)--instance NFData Err where-    rnf (Err v o i) = rnf v `seq` rnf o `seq` rnf i--instance FromJSON Err where-    parseJSON = withObject "error" $ \o -> do-        v <- parseVer o-        e <- o .: "error"-        i <- o .:? "id" .!= IdNull-        return $ Err v e i+fromError (ErrorObj m c v) = show c ++ ": " ++ m ++ ": " ++ valueAsString v+fromError (ErrorVal (String t)) = T.unpack t+fromError (ErrorVal v) = valueAsString v -instance ToJSON Err where-    toJSON (Err V1 o i) =-        object ["id" .= i, "result" .= Null, "error" .= o]-    toJSON (Err V2 o i) =-        object ["id" .= i, "error" .= o, jr2]+valueAsString :: Value -> String+valueAsString = T.unpack . decodeUtf8 . L.toStrict . encode  -- | Parse error.-errorParse :: Value -> ErrorObj-errorParse = ErrorObj "Parse error" (-32700)+errorParse :: ByteString -> ErrorObj+errorParse = ErrorObj "Parse error" (-32700) . String . decodeUtf8  -- | Invalid request. errorInvalid :: Value -> ErrorObj@@ -318,27 +299,19 @@ data Message     = MsgRequest   { getMsgRequest  :: !Request  }     | MsgResponse  { getMsgResponse :: !Response }-    | MsgNotif     { getMsgNotif    :: !Notif    }-    | MsgError     { getMsgError    :: !Err }     deriving (Eq, Show)  instance NFData Message where     rnf (MsgRequest  q) = rnf q     rnf (MsgResponse r) = rnf r-    rnf (MsgNotif    n) = rnf n-    rnf (MsgError    e) = rnf e  instance ToJSON Message where     toJSON (MsgRequest  q) = toJSON q     toJSON (MsgResponse r) = toJSON r-    toJSON (MsgNotif    n) = toJSON n-    toJSON (MsgError    e) = toJSON e  instance FromJSON Message where     parseJSON v = (MsgRequest  <$> parseJSON v)               <|> (MsgResponse <$> parseJSON v)-              <|> (MsgNotif    <$> parseJSON v)-              <|> (MsgError    <$> parseJSON v)  -- -- Types@@ -348,7 +321,6 @@  data Id = IdInt { getIdInt :: !Int  }         | IdTxt { getIdTxt :: !Text }-        | IdNull     deriving (Eq, Show, Read, Generic)  instance Hashable Id@@ -356,7 +328,6 @@ instance NFData Id where     rnf (IdInt i) = rnf i     rnf (IdTxt t) = rnf t-    rnf _ = ()  instance Enum Id where     toEnum = IdInt@@ -366,18 +337,20 @@ instance FromJSON Id where     parseJSON s@(String _) = IdTxt <$> parseJSON s     parseJSON n@(Number _) = IdInt <$> parseJSON n-    parseJSON Null = return IdNull     parseJSON _ = mzero  instance ToJSON Id where     toJSON (IdTxt s) = toJSON s     toJSON (IdInt n) = toJSON n-    toJSON IdNull = Null +parseId :: Object -> Parser (Maybe Id)+parseId o = do+    d <- o .:? "id" .!= Null+    if d == Null then return Nothing else parseJSON d+ fromId :: Id -> String fromId (IdInt i) = show i fromId (IdTxt t) = T.unpack t-fromId IdNull = "null"  data Ver = V1 -- ^ JSON-RPC 1.0          | V2 -- ^ JSON-RPC 2.0@@ -386,12 +359,6 @@ instance NFData Ver where     rnf v = v `seq` () -------- Helpers---- jr2 :: Pair jr2 = "jsonrpc" .= ("2.0" :: Text) @@ -399,11 +366,3 @@ parseVer o = do     j <- o .:? "jsonrpc"     return $ if j == Just ("2.0" :: Text) then V2 else V1--parseVerIdMethParams :: Object -> Parser (Ver, Id, Method, Value)-parseVerIdMethParams o = do-    v <- parseVer o-    i <- o .:? "id" .!= IdNull-    m <- o .: "method"-    p <- o .:? "params" .!= Null-    return (v, i, m, p)
Network/JsonRpc/Interface.hs view
@@ -12,18 +12,21 @@ , encodeConduit    -- * Communicate with remote party+, receiveRequest+, sendResponse , sendRequest-, sendNotif-, receiveNotif-  -- ** Dummies-, dummyRespond-, dummySrv    -- * Transports   -- ** Client , jsonRpcTcpClient   -- ** Server , jsonRpcTcpServer++  -- * Internal data and functions+, SentRequests+, Session(..)+, initSession+, processIncoming ) where  import Control.Applicative@@ -50,11 +53,11 @@ import qualified Data.Text as T import Network.JsonRpc.Data -type SentRequests = HashMap Id (TMVar (Either Err Response))+type SentRequests = HashMap Id (TMVar (Maybe Response)) -data Session = Session { inCh     :: TBMChan (Either Err Message)+data Session = Session { inCh     :: TBMChan (Either Response Message)                        , outCh    :: TBMChan Message-                       , notifCh  :: TBMChan (Either Err Notif)+                       , reqCh    :: Maybe (TBMChan Request)                        , lastId   :: TVar Id                        , sentReqs :: TVar SentRequests                        , rpcVer   :: Ver@@ -64,234 +67,242 @@ -- as context is maintaned. type JsonRpcT = ReaderT Session -initSession :: Ver -> STM Session-initSession v = Session <$> newTBMChan 16-                        <*> newTBMChan 16-                        <*> newTBMChan 16-                        <*> newTVar (IdInt 0)-                        <*> newTVar M.empty-                        <*> return v+initSession :: Ver -> Bool -> STM Session+initSession v ignore =+    Session <$> newTBMChan 128+            <*> newTBMChan 128+            <*> (if ignore then return Nothing else Just <$> newTBMChan 128)+            <*> newTVar (IdInt 0)+            <*> newTVar M.empty+            <*> return v  encodeConduit :: MonadLogger m => Conduit Message m ByteString encodeConduit = CL.mapM $ \m -> do     $(logDebug) $ T.pack $ unwords $ case m of-        MsgError e -> [ "Sending error id:", fromId (getErrId e) ]-        MsgRequest q -> [ "Sending request id:", fromId (getReqId q) ]-        MsgNotif _ -> [ "Sending notification" ]-        MsgResponse r -> [ "Sending response id:", fromId (getResId r) ]+        MsgRequest Request{getReqId = i} ->+            [ "encoding request id:", fromId i ]+        MsgRequest Notif{} ->+            [ "encoding notification" ]+        MsgResponse Response{getResId = i} ->+            [ "encoding response id:", fromId i ]+        MsgResponse ResponseError{getResId = i} ->+            [ "encoding error id:", fromId i ]+        MsgResponse OrphanError{} ->+            [ "encoding error without id" ]     return . L8.toStrict $ encode m  decodeConduit :: MonadLogger m-              => Ver -> Conduit ByteString m (Either Err Message)+              => Ver -> Conduit ByteString m (Either Response Message) decodeConduit ver = evalStateT loop Nothing where     loop = lift await >>= maybe flush process--    flush = get >>= \kM -> case kM of Nothing -> return ()-                                      Just k  -> handle (k B8.empty)-    process = runParser >=> handle-+    flush = get >>= maybe (return ()) (handle True . ($ B8.empty))+    process = runParser >=> handle False     runParser ck = maybe (parse json' ck) ($ ck) <$> get <* put Nothing -    handle (Fail {}) = do-        $(logWarn) "Error parsing incoming message"-        lift . yield . Left $ Err ver (errorParse Null) IdNull+    handle True (Fail "" _ _) =+        $(logDebug) "ignoring null string at end of incoming data"+    handle _ (Fail i _ _) = do+        $(logError) "error parsing incoming message"+        lift . yield . Left $ OrphanError ver (errorParse i)         loop-    handle (Partial k) = put (Just k) >> loop-    handle (Done rest v) = do+    handle _ (Partial k) = put (Just k) >> loop+    handle _ (Done rest v) = do         let msg = decod v-        when (isLeft msg) $ $(logWarn) "Received invalid message"+        when (isLeft msg) $ $(logError) "received invalid message"         lift $ yield msg         if B8.null rest then loop else process rest      decod v = case parseMaybe parseJSON v of         Just msg -> Right msg-        Nothing -> Left $ Err ver (errorInvalid v) IdNull+        Nothing -> Left $ OrphanError ver (errorInvalid v) -processIncoming :: (Functor m, MonadLoggerIO m, FromRequest q, ToJSON r)-                => Respond q m r -> JsonRpcT m ()-processIncoming r = do-    i <- reader inCh-    o <- reader outCh-    n <- reader notifCh-    s <- reader sentReqs-    v <- reader rpcVer-    join . liftIO . atomically $ readTBMChan i >>= \inc -> case inc of-        Nothing -> return $ do-            $(logDebug) "Closed incoming channel"-            return ()-        Just (Left e) -> do-            writeTBMChan o (MsgError e)-            return $ processIncoming r-        Just (Right (MsgNotif t)) -> do-            writeTBMChan n (Right t)-            return $ do-                $(logDebug) "Received notification"-                processIncoming r-        Just (Right (MsgRequest q)) -> return $ do-            $(logDebug) $ T.pack $ unwords-                [ "Received request id:", fromId (getReqId q) ]-            msg <- lift $ either MsgError MsgResponse <$> buildResponse r q-            liftIO . atomically $ writeTBMChan o msg-            processIncoming r-        Just (Right (MsgResponse res@(Response _ _ x))) -> do-            m <- readTVar s-            let pM = x `M.lookup` m-            case pM of-                Nothing ->-                    writeTBMChan o . MsgError $ Err v (errorId x) IdNull-                Just p ->-                    writeTVar s (x `M.delete` m) >> putTMVar p (Right res)-            return $ do-                case pM of-                    Nothing -> $(logWarn) $ T.pack $ unwords-                        [ "Got response with unkwnown id:", fromId x ]-                    _ -> $(logDebug) $ T.pack $ unwords-                        [ "Received response id:", fromId x ]-                processIncoming r-        Just (Right (MsgError err@(Err _ _ IdNull))) -> do-            writeTBMChan n $ Left err-            return $ do-                $(logWarn) "Got standalone error message"-                processIncoming r-        Just (Right (MsgError err@(Err _ _ x))) -> do-            m <- readTVar s-            let pM = x `M.lookup` m-            case pM of-                Nothing ->-                    writeTBMChan o . MsgError $ Err v (errorId x) IdNull-                Just p ->-                    writeTVar s (x `M.delete` m) >> putTMVar p (Left err)-            return $ do-                case pM of-                    Nothing -> $(logWarn) $ T.pack $ unwords-                        [ "Got error with unknown id:", fromId x ]-                    _ -> $(logWarn) $ T.pack $ unwords-                        [ "Received error id:", fromId x ]-                processIncoming r+processIncoming :: (Functor m, MonadLoggerIO m) => JsonRpcT m ()+processIncoming = do+    i  <- reader inCh+    o  <- reader outCh+    qM <- reader reqCh+    s  <- reader sentReqs+    join . liftIO . atomically $ readTBMChan i >>= \inc ->+        case inc of+            Nothing -> do+                m <- readTVar s+                mapM_ ((`putTMVar` Nothing) . snd) $ M.toList m+                return $ do+                    $(logDebug) "closed incoming channel"+                    unless (M.null m) $+                        $(logError) "some requests did not get responses"+                    return ()+            Just (Left e) -> do+                writeTBMChan o (MsgResponse e)+                return $ do+                    $(logError) "replied to sender with error"+                    processIncoming+            Just (Right (MsgRequest req)) ->+                case qM of+                    Just q -> do+                        writeTBMChan q req+                        return $ do+                            $(logDebug) "received request"+                            processIncoming+                    Nothing ->+                        case req of+                            Request v m _ d -> do+                                let e = ResponseError v (errorMethod m) d+                                writeTBMChan o (MsgResponse e)+                                return $ do+                                    $(logError) $ T.pack $ unwords+                                        [ "rejected incoming request id:"+                                        , fromId d+                                        ]+                                    processIncoming+                            Notif{} -> return $ do+                                $(logError) $ "rejected incoming notification"+                                processIncoming+            Just (Right (MsgResponse res)) -> do+                let hasId = case res of+                                Response{}      -> True+                                ResponseError{} -> True+                                OrphanError{}   -> False+                if hasId+                    then do+                        let x = getResId res+                        m <- readTVar s+                        let pM = x `M.lookup` m+                        case pM of+                            Nothing -> do+                                let v = getResVer res+                                    e = errorId x+                                    err = OrphanError v e+                                writeTBMChan o $ MsgResponse err+                                return $ do+                                    $(logError) $ T.pack $ unwords+                                        [ "got response with unknown id:"+                                        , fromId x+                                        ]+                                    processIncoming+                            Just p -> do+                                writeTVar s $ M.delete x m+                                putTMVar p $ Just res+                                return $ do+                                    $(logDebug) $ T.pack $ unwords+                                        [ "received response id:"+                                        , fromId x+                                        ]+                                    processIncoming+                    else return $ do+                        $(logError) $ T.pack $ unwords+                            [ "ignoring orhpan error:"+                            , fromError (getError res)+                            ]+                        processIncoming --- | Returns Right Nothing if could not parse response.+-- | Returns Nothing if did not receive response, could not parse it, or+-- request was a notification. Just Left contains the error object returned+-- by server if any. Just Right means response was received just right. sendRequest :: (MonadLoggerIO m, ToJSON q, ToRequest q, FromResponse r)-            => q -> JsonRpcT m (Either ErrorObj (Maybe r))+            => q -> JsonRpcT m (Maybe (Either ErrorObj r)) sendRequest q = do     v <- reader rpcVer     l <- reader lastId     s <- reader sentReqs     o <- reader outCh-    p <- liftIO . atomically $ do-        p <- newEmptyTMVar -        i <- succ <$> readTVar l-        m <- readTVar s-        let req = buildRequest v q i-        writeTVar s $ M.insert i p m-        writeTBMChan o $ MsgRequest req -        writeTVar l i-        return p-    liftIO . atomically $ takeTMVar p >>= \pE -> case pE of-        Left e -> return . Left $ getErrObj e-        Right y -> case fromResponse (requestMethod q) y of-            Nothing -> return $ Right Nothing-            Just x -> return . Right $ Just x+    if requestIsNotif q+        then do+            $(logDebug) "sending notification"+            liftIO . atomically $ do+                let req = buildRequest v q undefined+                writeTBMChan o $ MsgRequest req+            $(logDebug) "notification sent"+            return Nothing+        else do+            $(logDebug) "sending request"+            p <- liftIO . atomically $ do+                p <- newEmptyTMVar +                i <- succ <$> readTVar l+                m <- readTVar s+                let req = buildRequest v q i+                writeTVar s $ M.insert i p m+                writeTBMChan o $ MsgRequest req +                writeTVar l i+                return p+            $(logDebug) "request sent, awaiting for response"+            liftIO . atomically $ takeTMVar p >>= \rM -> case rM of+                Nothing -> return Nothing+                Just y@Response{} ->+                    case fromResponse (requestMethod q) y of+                        Nothing -> return Nothing+                        Just x -> return . Just $ Right x+                Just e@ResponseError{} ->+                    return . Just $ Left $ getError e+                _ -> undefined --- | Send notification. Will not block.-sendNotif :: (ToJSON no, ToNotif no, MonadLoggerIO m) => no -> JsonRpcT m ()-sendNotif n = do-    o <- reader outCh-    v <- reader rpcVer-    let notif = buildNotif v n-    liftIO . atomically $ writeTBMChan o (MsgNotif notif)+-- | Receive requests from remote endpoint. Returns Nothing if incoming+-- channel is closed or has never been opened.+receiveRequest :: MonadLoggerIO m => JsonRpcT m (Maybe Request)+receiveRequest = do+    chM <- reader reqCh+    case chM of+        Just ch -> do+            $(logDebug) "listening for a new request"+            liftIO . atomically $ readTBMChan ch+        Nothing -> do+            $(logError) "ignoring requests from remote endpoint"+            return Nothing --- | Receive notifications from peer. Will not block.--- Returns Nothing if incoming channel is closed and empty.--- Result is Right Nothing if it failed to parse notification.-receiveNotif :: (MonadLoggerIO m, FromNotif n)-             => JsonRpcT m (Maybe (Either ErrorObj (Maybe n)))-receiveNotif = do-    c <- reader notifCh-    liftIO . atomically $ readTBMChan c >>= \nM -> case nM of-        Nothing -> return Nothing-        Just (Left e) -> return . Just . Left $ getErrObj e-        Just (Right n) -> case fromNotif n of-            Nothing -> return . Just $ Right Nothing-            Just x -> return . Just . Right $ Just x+sendResponse :: MonadLoggerIO m => Response -> JsonRpcT m ()+sendResponse r = do+    o <- reader outCh+    liftIO . atomically . writeTBMChan o $ MsgResponse r  -- | Create JSON-RPC session around conduits from transport -- layer. When context exits session disappears.-runJsonRpcT :: ( MonadLoggerIO m, MonadBaseControl IO m-               , FromRequest q, ToJSON r-               )-            => Ver                    -- ^ JSON-RPC version-            -> Respond q m r          -- ^ Respond to incoming requests-            -> Sink Message m ()      -- ^ Sink to send messages-            -> Source m (Either Err Message)-            -- ^ Source of incoming messages-            -> JsonRpcT m a           -- ^ JSON-RPC action-            -> m a                    -- ^ Output of action-runJsonRpcT ver r snk src f = do-    qs <- liftIO . atomically $ initSession ver+runJsonRpcT :: (MonadLoggerIO m, MonadBaseControl IO m)+            => Ver               -- ^ JSON-RPC version+            -> Bool              -- ^ Ignore incoming requests or notifications+            -> Sink Message m () -- ^ Sink to send messages+            -> Source m (Either Response Message)+            -- ^ Incoming messages or error responses to be returned to sender+            -> JsonRpcT m a      -- ^ JSON-RPC action+            -> m a               -- ^ Output of action+runJsonRpcT ver ignore snk src f = do+    qs <- liftIO . atomically $ initSession ver ignore     let inSnk  = sinkTBMChan (inCh qs) True         outSrc = sourceTBMChan (outCh qs)     withAsync (src $$ inSnk) $ const $         withAsync (outSrc $$ snk) $ const $-            withAsync (runReaderT (processIncoming r) qs) $ const $+            withAsync (runReaderT processIncoming qs) $ const $                 runReaderT f qs   cr :: Monad m => Conduit ByteString m ByteString cr = CL.map (`B8.snoc` '\n') -ln :: Monad m => Conduit ByteString m ByteString-ln = await >>= \bsM -> case bsM of-    Nothing -> return ()-    Just bs -> let (l, ls) = B8.break (=='\n') bs in case ls of-        "" -> await >>= \bsM' -> case bsM' of-            Nothing  -> unless (B8.null l) $ yield l-            Just bs' -> leftover (bs `B8.append` bs') >> ln-        _  -> case l of-            "" -> leftover (B8.tail ls) >> ln-            _  -> leftover (B8.tail ls) >> yield l >> ln----- | Dummy action for servers not expecting clients to send notifications,--- which is true in most cases.-dummySrv :: MonadLoggerIO m => JsonRpcT m ()-dummySrv = receiveNotif >>= \nM -> case nM of-        Just n -> (n :: Either ErrorObj (Maybe ())) `seq` dummySrv-        Nothing -> return ()---- | Respond function for systems that do not reply to requests, as usual--- in clients.-dummyRespond :: MonadLoggerIO m => Respond () m ()-dummyRespond = const . return $ Right () - -- -- Transports --  -- | TCP client transport for JSON-RPC. jsonRpcTcpClient-    :: ( MonadLoggerIO m, MonadBaseControl IO m-       , FromRequest q, ToJSON r-       )+    :: (MonadLoggerIO m, MonadBaseControl IO m)     => Ver            -- ^ JSON-RPC version+    -> Bool           -- ^ Ignore incoming requests or notifications     -> ClientSettings -- ^ Connection settings-    -> Respond q m r  -- ^ Respond to incoming requests     -> JsonRpcT m a   -- ^ JSON-RPC action     -> m a            -- ^ Output of action-jsonRpcTcpClient ver cs r f = runGeneralTCPClient cs $ \ad ->-    runJsonRpcT ver r+jsonRpcTcpClient ver ignore cs f = runGeneralTCPClient cs $ \ad ->+    runJsonRpcT ver ignore         (encodeConduit =$ cr =$ appSink ad)-        (appSource ad $= ln $= decodeConduit ver) f+        (appSource ad $= decodeConduit ver) f  -- | TCP server transport for JSON-RPC. jsonRpcTcpServer-    :: ( MonadLoggerIO m, MonadBaseControl IO m-       , FromRequest q, ToJSON r)+    :: (MonadLoggerIO m, MonadBaseControl IO m)     => Ver             -- ^ JSON-RPC version+    -> Bool            -- ^ Ignore incoming requests or notifications     -> ServerSettings  -- ^ Connection settings-    -> Respond q m r   -- ^ Respond to incoming requests     -> JsonRpcT m ()   -- ^ Action to perform on connecting client thread     -> m a-jsonRpcTcpServer ver ss r f = runGeneralTCPServer ss $ \cl ->-    runJsonRpcT ver r+jsonRpcTcpServer ver ignore ss f = runGeneralTCPServer ss $ \cl ->+    runJsonRpcT ver ignore         (encodeConduit =$ cr =$ appSink cl)-        (appSource cl $= ln $= decodeConduit ver) f+        (appSource cl $= decodeConduit ver) f
README.md view
@@ -23,7 +23,7 @@  ``` haskell {-# LANGUAGE OverloadedStrings #-}-import Control.Applicative+{-# LANGUAGE TemplateHaskell #-} import Control.Monad.Trans import Control.Monad.Logger import Data.Aeson.Types hiding (Error)@@ -38,17 +38,39 @@  instance FromRequest TimeReq where     parseParams "time" = Just $ const $ return TimeReq -    parseParams _ = Nothing+    parseParams _      = Nothing  instance ToJSON TimeRes where     toJSON (TimeRes t) = toJSON $ formatTime defaultTimeLocale "%c" t -respond :: (Functor m, MonadLoggerIO m) => Respond TimeReq m TimeRes-respond TimeReq = Right . TimeRes <$> liftIO getCurrentTime+respond :: MonadLoggerIO m => Respond TimeReq m TimeRes+respond TimeReq = do+    t <- liftIO getCurrentTime+    return . Right $ TimeRes t  main :: IO ()-main = runStderrLoggingT $-    jsonRpcTcpServer V2 (serverSettings 31337 "::1") respond dummySrv+main = runStderrLoggingT $ do+    let ss = serverSettings 31337 "::1"+    jsonRpcTcpServer V2 False ss srv++srv :: MonadLoggerIO m => JsonRpcT m ()+srv = do+    $(logDebug) "listening for new request"+    qM <- receiveRequest+    case qM of+        Nothing -> do+            $(logDebug) "closed request channel, exting"+            return ()+        Just q -> do+            $(logDebug) "got request"+            rM <- buildResponse respond q+            case rM of+                Nothing -> do+                    $(logDebug) "no response for this request"+                    srv+                Just r -> do+                    $(logDebug) "sending response"+                    sendResponse r >> srv ```  Client Example@@ -57,6 +79,7 @@ Corresponding TCP client to get time from server.  ``` haskell+{-# LANGUAGE TemplateHaskell #-} {-# LANGUAGE OverloadedStrings #-} import Control.Concurrent import Control.Monad@@ -72,10 +95,11 @@ import System.Locale  data TimeReq = TimeReq-data TimeRes = TimeRes { timeRes :: UTCTime }+data TimeRes = TimeRes { timeRes :: UTCTime } deriving Show  instance ToRequest TimeReq where     requestMethod TimeReq = "time"+    requestIsNotif = const False  instance ToJSON TimeReq where     toJSON TimeReq = emptyArray@@ -88,14 +112,21 @@         f t = parseTime defaultTimeLocale "%c" (T.unpack t)     parseResult _ = Nothing -req :: MonadLoggerIO m => JsonRpcT m UTCTime-req = sendRequest TimeReq >>= \ts -> case ts of-    Left e -> error $ fromError e-    Right (Just (TimeRes r)) -> return r-    _ -> error "Could not parse response"+req :: MonadLoggerIO m => JsonRpcT m (Either String UTCTime)+req = do+    $(logDebug) "sending time request"+    ts <- sendRequest TimeReq+    $(logDebug) "received response"+    case ts of+        Nothing -> return $ Left "could not parse response"+        Just (Left e) -> return . Left $ fromError e+        Just (Right (TimeRes r)) -> return $ Right r  main :: IO () main = runStderrLoggingT $-    jsonRpcTcpClient V2 (clientSettings 31337 "::1") dummyRespond .-        replicateM_ 4 $ req >>= liftIO . print >> liftIO (threadDelay 1000000)+    jsonRpcTcpClient V2 True (clientSettings 31337 "::1") $ do+        $(logDebug) "sending four time requests one second apart"+        replicateM_ 4 $ do+            req >>= liftIO . print+            liftIO (threadDelay 1000000) ```
json-rpc.cabal view
@@ -1,5 +1,5 @@ name:                   json-rpc-version:                0.5.0.0+version:                0.6.0.0 synopsis:               Fully-featured JSON-RPC 2.0 library description:   This JSON-RPC library is fully-compatible with JSON-RPC 2.0 and 1.0. It@@ -24,7 +24,7 @@ source-repository this   type:                 git   location:             https://github.com/xenog/json-rpc.git-  tag:                  0.5.0.0+  tag:                  0.6.0.0  library   exposed-modules:      Network.JsonRpc,
test/Network/JsonRpc/Tests.hs view
@@ -15,7 +15,6 @@ import Control.Monad.Trans import Data.Aeson import Data.Aeson.Types-import Data.Either import Data.Maybe import Network.JsonRpc import Network.JsonRpc.Arbitrary()@@ -32,38 +31,27 @@         , testProperty "Encode/decode"             (testEncodeDecode :: Request -> Bool)         ]-    , testGroup "JSON-RPC Notifications"-        [ testProperty "Check fields"-            (notifFields :: Notif -> Bool)-        , testProperty "Encode/decode"-            (testEncodeDecode :: Notif -> Bool)-        ]     , testGroup "JSON-RPC Responses"         [ testProperty "Check fields"             (resFields :: Response -> Bool)         , testProperty "Encode/decode"             (testEncodeDecode :: Response -> Bool)         ]-    , testGroup "JSON-RPC Errors"-        [ testProperty "Check fields"-            (errFields :: Err -> Bool)-        , testProperty "Encode/decode"-            (testEncodeDecode :: Err -> Bool)-        ]     , testGroup "Network"         [ testProperty "Test server" serverTest         , testProperty "Test client" clientTest         ]     ] -checkVerId :: Ver -> Id -> Object -> Parser Bool+checkVerId :: Ver -> Maybe Id -> Object -> Parser Bool checkVerId ver i o = do     j <- o .:? "jsonrpc"     guard $ if ver == V2 then j == Just (String "2.0") else isNothing j-    o .:? "id" .!= IdNull >>= guard . (==i)+    o .:? "id" >>= guard . (==i)     return True -checkFieldsReqNotif :: Ver -> Method -> Value -> Id -> Object -> Parser Bool+checkFieldsReqNotif+    :: Ver -> Method -> Value -> Maybe Id -> Object -> Parser Bool checkFieldsReqNotif ver m v i o = do     checkVerId ver i o >>= guard     o .: "method" >>= guard . (==m)@@ -71,22 +59,22 @@     return True  checkFieldsReq :: Request -> Object -> Parser Bool-checkFieldsReq (Request ver m v i) = checkFieldsReqNotif ver m v i--checkFieldsNotif :: Notif -> Object -> Parser Bool-checkFieldsNotif (Notif ver m v) = checkFieldsReqNotif ver m v IdNull+checkFieldsReq (Request ver m v i) = checkFieldsReqNotif ver m v (Just i)+checkFieldsReq (Notif   ver m v)   = checkFieldsReqNotif ver m v Nothing  checkFieldsRes :: Response -> Object -> Parser Bool checkFieldsRes (Response ver v i) o = do-    checkVerId ver i o >>= guard+    checkVerId ver (Just i) o >>= guard     o .: "result" >>= guard . (==v)     return True--checkFieldsErr :: Err -> Object -> Parser Bool-checkFieldsErr (Err ver e i) o = do-    checkVerId ver i o >>= guard+checkFieldsRes (ResponseError ver e i) o = do+    checkVerId ver (Just i) o >>= guard     o .: "error" >>= guard . (==e)     return True+checkFieldsRes (OrphanError ver e) o = do+    checkVerId ver Nothing o >>= guard+    o .: "error" >>= guard . (==e)+    return True  testFields :: ToJSON r => (Object -> Parser Bool) -> r -> Bool testFields ck r = fromMaybe False . parseMaybe f $ toJSON r where@@ -98,63 +86,75 @@ reqFields :: Request -> Bool reqFields rq = testFields (checkFieldsReq rq) rq -notifFields :: Notif -> Bool-notifFields nt = testFields (checkFieldsNotif nt) nt- resFields :: Response -> Bool resFields rs = testFields (checkFieldsRes rs) rs -errFields :: Err -> Bool-errFields er = testFields (checkFieldsErr er) er- serverTest :: ([Request], Ver) -> Property serverTest (reqs, ver) = monadicIO $ do     rt <- run $ runNoLoggingT $ do-        (bso, bsi) <- liftIO . atomically $ (,) <$> newTBMChan 16-                                                <*> newTBMChan 16+        (bso, bsi) <- liftIO . atomically $+            (,) <$> newTBMChan 16 <*> newTBMChan 16         let snk = sinkTBMChan bso False             src = sourceTBMChan bsi-        withAsync (srv snk src) $ const $-            withAsync (sender bsi) $ const $-                receiver bso []-    assert $ length rt == length reqs+        withAsync (server snk src) $ const $ withAsync (sender bsi) $ const $+            receiver bso []+    assert $ length rt == length nonotif     assert $ null rt || all isJust rt     assert $ params == reverse (results rt)   where-    r q = return $ Right (q :: Value)-    srv snk src = runJsonRpcT ver r-        (encodeConduit =$ snk) (src =$ decodeConduit ver) dummySrv+    respond q = return $ Right (q :: Value)+    server snk src = runJsonRpcT ver False+        (encodeConduit =$ snk) (src =$ decodeConduit ver) (srv respond)     sender bsi = forM_ reqs $ liftIO . atomically .         writeTBMChan bsi . L.toStrict . encode . MsgRequest-    receiver bso xs = if length xs == length reqs-        then return xs-        else liftIO (atomically $ readTBMChan bso) >>= \b -> case b of-            Just x -> do-                let res = decodeStrict' x :: Maybe Response-                receiver bso (res:xs)-            Nothing -> undefined-    params = map getReqParams reqs+    receiver bso xs =+        if length xs == length nonotif+            then return xs+            else liftIO (atomically $ readTBMChan bso) >>= \b -> case b of+                Just x -> do+                    let res = decodeStrict' x :: Maybe Response+                    receiver bso (res:xs)+                Nothing -> undefined+    params = map getReqParams nonotif     results = map $ getResult . fromJust+    nonotif = flip filter reqs $ \q -> case q of Request{} -> True+                                                 Notif{} -> False  clientTest :: ([Value], Ver) -> Property clientTest (qs, ver) = monadicIO $ do     rt <- run $ runNoLoggingT $ do-        (bso, bsi) <- liftIO . atomically $ (,) <$> newTBMChan 16-                                                <*> newTBMChan 16+        (bso, bsi) <- liftIO . atomically $+            (,) <$> newTBMChan 16 <*> newTBMChan 16         let snk = sinkTBMChan bso False             src = sourceTBMChan bsi             csnk = sinkTBMChan bsi False             csrc = sourceTBMChan bso-        withAsync (srv snk src) $ const $ cli+        withAsync (server snk src) $ const $ cli             (CL.map Right =$ csnk)             (csrc $= CL.map Right)     assert $ length rt == length qs     assert $ null rt || all correct rt     assert $ qs == results rt   where-    r q = return $ Right (q :: Value)-    srv snk src = runJsonRpcT ver r snk src dummySrv-    cli snk src = runJsonRpcT ver r snk src . forM qs $ sendRequest-    results = map fromJust . rights-    correct (Right (Just _)) = True+    respond q = return $ Right (q :: Value)+    server snk src = runJsonRpcT ver False snk src (srv respond)+    cli snk src = runJsonRpcT ver True snk src . forM qs $ sendRequest+    results = map $ fromRight . fromJust+    correct (Just (Right _)) = True     correct _ = False++srv :: (MonadLoggerIO m, FromRequest q, ToJSON r)+    => Respond q (JsonRpcT m) r -> JsonRpcT m ()+srv respond = do+    qM <- receiveRequest+    case qM of+        Nothing -> return ()+        Just q -> do+            rM <- buildResponse respond q+            case rM of+                Nothing -> srv respond+                Just r -> sendResponse r >> srv respond++fromRight :: Either a b -> b+fromRight (Right x) = x+fromRight _ = undefined