packages feed

json-rpc 0.2.1.6 → 0.3.0.0

raw patch · 8 files changed

+655/−1017 lines, 8 filesdep ~json-rpcPVP ok

version bump matches the API change (PVP)

Dependency ranges changed: json-rpc

API changes (from Hackage documentation)

- Network.JsonRpc: IncomingError :: !ErrorObj -> IncomingMsg qo qi ni ri
- Network.JsonRpc: IncomingMsg :: !(Message qi ni ri) -> !(Maybe (Request qo)) -> IncomingMsg qo qi ni ri
- Network.JsonRpc: Session :: TVar Id -> TVar (SentRequests qo) -> TQueue Bool -> Session qo
- Network.JsonRpc: data IncomingMsg qo qi ni ri
- Network.JsonRpc: data Session qo
- Network.JsonRpc: decodeConduit :: (FromRequest qi, FromNotif ni, FromResponse ri, MonadIO m) => Ver -> Bool -> Session qo -> Conduit ByteString m (IncomingMsg qo qi ni ri)
- Network.JsonRpc: encodeConduit :: (ToJSON a, Monad m) => Conduit a m ByteString
- Network.JsonRpc: incomingError :: IncomingMsg qo qi ni ri -> !ErrorObj
- Network.JsonRpc: incomingMsg :: IncomingMsg qo qi ni ri -> !(Message qi ni ri)
- Network.JsonRpc: initSession :: STM (Session qo)
- Network.JsonRpc: isLast :: Session qo -> TQueue Bool
- Network.JsonRpc: lastId :: Session qo -> TVar Id
- Network.JsonRpc: matchingReq :: IncomingMsg qo qi ni ri -> !(Maybe (Request qo))
- Network.JsonRpc: msgConduit :: MonadIO m => Bool -> Session qo -> Conduit (Message qo no ro) m (Message qo no ro)
- Network.JsonRpc: notifParamsParser :: FromNotif n => Method -> Maybe (Value -> Parser n)
- Network.JsonRpc: paramsParser :: FromRequest q => Method -> Maybe (Value -> Parser q)
- Network.JsonRpc: parseRequest :: FromRequest q => Value -> Parser (Either ErrorObj (Request q))
- Network.JsonRpc: parseResponse :: FromResponse r => Request q -> Value -> Parser (Either ErrorObj (Response r))
- Network.JsonRpc: query :: (ToJSON qo, ToRequest qo, FromResponse ri) => Ver -> [qo] -> AppConduits qo () () () () ri IO -> IO [IncomingMsg qo () () ri]
- Network.JsonRpc: runConduits :: (FromRequest qi, FromNotif ni, FromResponse ri, ToJSON qo, ToJSON no, ToJSON ro) => Ver -> Bool -> Sink ByteString IO () -> Source IO ByteString -> (AppConduits qo no ro qi ni ri IO -> IO a) -> IO a
- Network.JsonRpc: sentRequests :: Session qo -> TVar (SentRequests qo)
- Network.JsonRpc: tcpClient :: (FromRequest qi, FromNotif ni, FromResponse ri, ToJSON qo, ToJSON no, ToJSON ro) => Ver -> Bool -> ClientSettings -> (AppConduits qo no ro qi ni ri IO -> IO a) -> IO a
- Network.JsonRpc: tcpServer :: (FromRequest qi, FromNotif ni, FromResponse ri, ToJSON qo, ToJSON no, ToJSON ro) => Ver -> ServerSettings -> (AppConduits qo no ro qi ni ri IO -> IO ()) -> IO ()
- Network.JsonRpc: type AppConduits qo no ro qi ni ri m = (Source m (IncomingMsg qo qi ni ri), Sink (Message qo no ro) m ())
- Network.JsonRpc: type SentRequests qo = HashMap Id (Request qo)
+ Network.JsonRpc: ErrorVal :: !Value -> ErrorObj
+ Network.JsonRpc: RpcError :: !Ver -> !ErrorObj -> !Id -> RpcError
+ Network.JsonRpc: buildResponse :: (Monad m, FromRequest q, ToJSON r) => Respond q m r -> Request -> m (Either RpcError Response)
+ Network.JsonRpc: data RpcError
+ Network.JsonRpc: dummyRespond :: Monad m => Respond () m ()
+ Network.JsonRpc: dummySrv :: MonadIO m => JsonRpcT m ()
+ Network.JsonRpc: fromError :: ErrorObj -> String
+ Network.JsonRpc: fromNotif :: FromNotif n => Notif -> Maybe n
+ Network.JsonRpc: fromRequest :: FromRequest q => Request -> Maybe q
+ Network.JsonRpc: fromResponse :: FromResponse r => Method -> Response -> Maybe r
+ Network.JsonRpc: getErrObj :: RpcError -> !ErrorObj
+ Network.JsonRpc: jsonRpcTcpClient :: (FromRequest q, ToJSON r) => Ver -> ClientSettings -> Respond q IO r -> JsonRpcT IO a -> IO a
+ Network.JsonRpc: jsonRpcTcpServer :: (FromRequest q, ToJSON r) => Ver -> ServerSettings -> Respond q IO r -> JsonRpcT IO () -> IO ()
+ Network.JsonRpc: parseParams :: FromRequest q => Method -> Maybe (Value -> Parser q)
+ Network.JsonRpc: receiveNotif :: (Monad m, FromNotif n) => JsonRpcT m (STM (Maybe (Either ErrorObj (Maybe n))))
+ Network.JsonRpc: runJsonRpcT :: (FromRequest q, ToJSON r) => Ver -> Respond q IO r -> Sink ByteString IO () -> Source IO ByteString -> JsonRpcT IO a -> IO a
+ Network.JsonRpc: sendNotif :: (ToJSON no, ToNotif no, Monad m) => no -> JsonRpcT m (STM ())
+ Network.JsonRpc: sendRequest :: (ToJSON q, ToRequest q, FromResponse r, MonadIO m) => q -> JsonRpcT m (STM (Either ErrorObj (Maybe r)))
+ Network.JsonRpc: type JsonRpcT = ReaderT Session
+ Network.JsonRpc: type Respond q m r = q -> m (Either ErrorObj r)
- Network.JsonRpc: ErrorObj :: !Ver -> !String -> !Int -> !Value -> !Id -> ErrorObj
+ Network.JsonRpc: ErrorObj :: !String -> !Int -> !Value -> ErrorObj
- Network.JsonRpc: MsgError :: !ErrorObj -> Message q n r
+ Network.JsonRpc: MsgError :: !RpcError -> Message
- Network.JsonRpc: MsgNotif :: !(Notif n) -> Message q n r
+ Network.JsonRpc: MsgNotif :: !Notif -> Message
- Network.JsonRpc: MsgRequest :: !(Request q) -> Message q n r
+ Network.JsonRpc: MsgRequest :: !Request -> Message
- Network.JsonRpc: MsgResponse :: !(Response r) -> Message q n r
+ Network.JsonRpc: MsgResponse :: !Response -> Message
- Network.JsonRpc: Notif :: !Ver -> !Method -> !n -> Notif n
+ Network.JsonRpc: Notif :: !Ver -> !Method -> !Value -> Notif
- Network.JsonRpc: Request :: !Ver -> !Method -> !q -> !Id -> Request q
+ Network.JsonRpc: Request :: !Ver -> !Method -> !Value -> !Id -> Request
- Network.JsonRpc: Response :: !Ver -> !r -> !Id -> Response r
+ Network.JsonRpc: Response :: !Ver -> !Value -> !Id -> Response
- Network.JsonRpc: buildNotif :: ToNotif n => Ver -> n -> Notif n
+ Network.JsonRpc: buildNotif :: (ToJSON n, ToNotif n) => Ver -> n -> Notif
- Network.JsonRpc: buildRequest :: ToRequest q => Ver -> q -> Request q
+ Network.JsonRpc: buildRequest :: (ToJSON q, ToRequest q) => Ver -> q -> Id -> Request
- Network.JsonRpc: data Message q n r
+ Network.JsonRpc: data Message
- Network.JsonRpc: data Notif n
+ Network.JsonRpc: data Notif
- Network.JsonRpc: data Request q
+ Network.JsonRpc: data Request
- Network.JsonRpc: data Response r
+ Network.JsonRpc: data Response
- Network.JsonRpc: errorId :: Ver -> Id -> ErrorObj
+ Network.JsonRpc: errorId :: Id -> ErrorObj
- Network.JsonRpc: errorInvalid :: Ver -> Value -> ErrorObj
+ Network.JsonRpc: errorInvalid :: Value -> ErrorObj
- Network.JsonRpc: errorMethod :: Ver -> Method -> Id -> ErrorObj
+ Network.JsonRpc: errorMethod :: Method -> ErrorObj
- Network.JsonRpc: errorParams :: Ver -> Value -> Id -> ErrorObj
+ Network.JsonRpc: errorParams :: Value -> ErrorObj
- Network.JsonRpc: errorParse :: Ver -> Value -> ErrorObj
+ Network.JsonRpc: errorParse :: Value -> ErrorObj
- Network.JsonRpc: getErrId :: ErrorObj -> !Id
+ Network.JsonRpc: getErrId :: RpcError -> !Id
- Network.JsonRpc: getErrVer :: ErrorObj -> !Ver
+ Network.JsonRpc: getErrVer :: RpcError -> !Ver
- Network.JsonRpc: getMsgError :: Message q n r -> !ErrorObj
+ Network.JsonRpc: getMsgError :: Message -> !RpcError
- Network.JsonRpc: getMsgNotif :: Message q n r -> !(Notif n)
+ Network.JsonRpc: getMsgNotif :: Message -> !Notif
- Network.JsonRpc: getMsgRequest :: Message q n r -> !(Request q)
+ Network.JsonRpc: getMsgRequest :: Message -> !Request
- Network.JsonRpc: getMsgResponse :: Message q n r -> !(Response r)
+ Network.JsonRpc: getMsgResponse :: Message -> !Response
- Network.JsonRpc: getNotifMethod :: Notif n -> !Method
+ Network.JsonRpc: getNotifMethod :: Notif -> !Method
- Network.JsonRpc: getNotifParams :: Notif n -> !n
+ Network.JsonRpc: getNotifParams :: Notif -> !Value
- Network.JsonRpc: getNotifVer :: Notif n -> !Ver
+ Network.JsonRpc: getNotifVer :: Notif -> !Ver
- Network.JsonRpc: getReqId :: Request q -> !Id
+ Network.JsonRpc: getReqId :: Request -> !Id
- Network.JsonRpc: getReqMethod :: Request q -> !Method
+ Network.JsonRpc: getReqMethod :: Request -> !Method
- Network.JsonRpc: getReqParams :: Request q -> !q
+ Network.JsonRpc: getReqParams :: Request -> !Value
- Network.JsonRpc: getReqVer :: Request q -> !Ver
+ Network.JsonRpc: getReqVer :: Request -> !Ver
- Network.JsonRpc: getResId :: Response r -> !Id
+ Network.JsonRpc: getResId :: Response -> !Id
- Network.JsonRpc: getResVer :: Response r -> !Ver
+ Network.JsonRpc: getResVer :: Response -> !Ver
- Network.JsonRpc: getResult :: Response r -> !r
+ Network.JsonRpc: getResult :: Response -> !Value
- Network.JsonRpc: parseNotif :: FromNotif n => Value -> Parser (Either ErrorObj (Notif n))
+ Network.JsonRpc: parseNotif :: FromNotif n => Method -> Maybe (Value -> Parser n)
- Network.JsonRpc: parseResult :: FromResponse r => Method -> Value -> Parser r
+ Network.JsonRpc: parseResult :: FromResponse r => Method -> Maybe (Value -> Parser r)

Files

Network/JsonRpc.hs view
@@ -8,54 +8,33 @@   -- ** Client Example   -- $client -  module Network.JsonRpc.Conduit+  module Network.JsonRpc.Interface , module Network.JsonRpc.Data ) where -import Network.JsonRpc.Conduit+import Network.JsonRpc.Interface import Network.JsonRpc.Data  -- $introduction ----- This JSON-RPC library is fully-compatible with JSON-RPC 2.0 and--- partially-compatible with JSON-RPC 1.0. It provides an interface that--- combines a JSON-RPC client and server. It can set and keep track of request--- ids to parse responses.  There is support for sending and receiving--- notifications. You may use any underlying transport.  Basic TCP client and--- server provided.------ The recommended interface to this library is provided as conduits that--- encode outgoing messages, and decode incoming messages.  Incoming messages--- are delivered as an 'IncomingMsg' data structure, while outgoing messages--- are sent in a 'Message' data structure.  The former packs responses and--- errors with their corresponding request, and has a separate constructor for--- decoding errors.+-- This JSON-RPC library is fully-compatible with JSON-RPC 2.0 and 1.0. It+-- provides an interface that combines a JSON-RPC client and server. It can+-- set and keep track of request ids to parse responses.  There is support+-- for sending and receiving notifications. You may use any underlying+-- transport.  Basic TCP client and server provided. -- -- A JSON-RPC application using this interface is considered to be -- peer-to-peer, as it can send and receive all types of JSON-RPC message -- independent of whether it originated the connection.------ Type classes 'ToRequest', 'ToNotif' are for data that can be converted into--- JSON-RPC requests and notifications respectively. An instance of aeson's--- 'ToJSON' class is also required to serialize these data structures.  Make--- sure that they serialize as a structured JSON value (array or object) that--- can go into the params field of the JSON-RPC object.  Type classes--- 'FromRequest', 'FromNotif' and 'FromResult' are for deserializing JSON-RPC--- messages.------ Errors are deserialized to the 'ErrorObj' data type. Only a string is--- supported as contents inside a JSON-RPC 1.0 error. JSON-RPC 2.0 errors also--- have a code, and possibly additional data as an aeson 'Value'.   -- $server ----- This server returns the current time.+-- This JSON-RPC server returns the current time. -- -- >{-# LANGUAGE OverloadedStrings #-}--- >import Data.Aeson.Types--- >import Data.Conduit--- >import qualified Data.Conduit.List as CL+-- >import Control.Applicative+-- >import Data.Aeson.Types hiding (Error) -- >import Data.Conduit.Network -- >import Data.Time.Clock -- >import Data.Time.Format@@ -63,40 +42,34 @@ -- >import System.Locale -- > -- >data TimeReq = TimeReq--- >data TimeRes = TimeRes UTCTime+-- >data TimeRes = TimeRes { timeRes :: UTCTime } -- > -- >instance FromRequest TimeReq where--- >    paramsParser "time" = Just $ const $ return TimeReq --- >    paramsParser _ = Nothing+-- >    parseParams "time" = Just $ const $ return TimeReq +-- >    parseParams _ = Nothing -- > -- >instance ToJSON TimeRes where -- >    toJSON (TimeRes t) = toJSON $ formatTime defaultTimeLocale "%c" t -- >--- >srv :: AppConduits () () TimeRes TimeReq () () IO -> IO ()--- >srv (src, snk) = src $= CL.mapM respond $$ snk--- >--- >respond :: IncomingMsg () TimeReq () ()--- >        -> IO (Message () () TimeRes)--- >respond (IncomingMsg (MsgRequest (Request ver _ TimeReq i)) Nothing) = do    --- >    t <- getCurrentTime--- >    return $ MsgResponse (Response ver (TimeRes t) i)--- >--- >respond (IncomingError e) = return $ MsgError e--- >respond (IncomingMsg (MsgError e) _) = return $ MsgError $ e--- >respond _ = undefined+-- >respond :: Respond TimeReq IO TimeRes+-- >respond TimeReq = Right . TimeRes <$> getCurrentTime -- > -- >main :: IO ()--- >main = tcpServer V2 (serverSettings 31337 "127.0.0.1") srv+-- >main = jsonRpcTcpServer V2 (serverSettings 31337 "::1") respond dummySrv  + -- $client -- -- Corresponding TCP client to get time from server. -- -- >{-# LANGUAGE OverloadedStrings #-}+-- >import Control.Concurrent+-- >import Control.Concurrent.STM+-- >import Control.Monad+-- >import Control.Monad.Trans+-- >import Data.Aeson -- >import Data.Aeson.Types hiding (Error)--- >import Data.Conduit--- >import qualified Data.Conduit.List as CL -- >import Data.Conduit.Network -- >import qualified Data.Text as T -- >import Data.Time.Clock@@ -105,7 +78,7 @@ -- >import System.Locale -- > -- >data TimeReq = TimeReq--- >data TimeRes = TimeRes UTCTime+-- >data TimeRes = TimeRes { timeRes :: UTCTime } -- > -- >instance ToRequest TimeReq where -- >    requestMethod TimeReq = "time"@@ -114,23 +87,19 @@ -- >    toJSON TimeReq = emptyArray -- > -- >instance FromResponse TimeRes where--- >    parseResult "time" = withText "time" $ \t -> case f t of--- >        Nothing -> fail "Could not parse time"+-- >    parseResult "time" = Just $ withText "time" $ \t -> case f t of -- >        Just t' -> return $ TimeRes t'+-- >        Nothing -> mzero -- >      where -- >        f t = parseTime defaultTimeLocale "%c" (T.unpack t)+-- >    parseResult _ = Nothing -- >--- >cli :: AppConduits TimeReq () () () () TimeRes IO--- >    -> IO UTCTime--- >cli (src, snk) = do--- >    CL.sourceList [MsgRequest $ buildRequest V2 TimeReq] $$ snk--- >    ts <- src $$ CL.consume--- >    case ts of--- >        [] -> error "No response received"--- >        [IncomingError (ErrorObj _ m _ _ _)] -> error $ "Unknown: " ++ m--- >        [IncomingMsg (MsgError (ErrorObj _ m _ _ _)) _] -> error m--- >        [IncomingMsg (MsgResponse (Response _ (TimeRes t) _)) _] -> return t--- >        _ -> undefined+-- >req :: JsonRpcT IO UTCTime+-- >req = sendRequest TimeReq >>= liftIO . atomically >>= \ts -> case ts of+-- >    Left e -> error $ fromError e+-- >    Right (Just (TimeRes r)) -> return r+-- >    _ -> error "Could not parse response" -- > -- >main :: IO ()--- >main = tcpClient V2 True (clientSettings 31337 "127.0.0.1") cli >>= print+-- >main = jsonRpcTcpClient V2 (clientSettings 31337 "::1") dummyRespond .+-- >    replicateM_ 4 $ req >>= liftIO . print >> liftIO (threadDelay 1000000)
− Network/JsonRpc/Conduit.hs
@@ -1,304 +0,0 @@-{-# LANGUAGE OverloadedStrings #-}--- | Conduit Interface for JSON-RPC.-module Network.JsonRpc.Conduit-( -- * Conduits-  -- ** High-Level-  AppConduits-, IncomingMsg(..)-, runConduits-, tcpClient-, tcpServer-, query-  -- ** Low-Level-, Session(..)-, SentRequests-, initSession-, encodeConduit-, msgConduit-, decodeConduit-) where--import Control.Applicative-import Control.Concurrent.Async-import Control.Concurrent.STM-import Control.DeepSeq-import Control.Monad-import Control.Monad.Trans-import Control.Monad.Trans.State-import Data.Aeson-import Data.Aeson.Types (parseEither)-import Data.Attoparsec.ByteString-import Data.ByteString (ByteString)-import qualified Data.ByteString as B-import qualified Data.ByteString.Char8 as B8-import qualified Data.ByteString.Lazy.Char8 as L8-import Data.HashMap.Strict (HashMap)-import qualified Data.HashMap.Strict as M-import Data.Conduit-import qualified Data.Conduit.List as CL-import Data.Conduit.Network-import Data.Conduit.TMChan-import Data.Maybe-import Data.Text (Text)-import Network.JsonRpc.Data---- | Conduits for sending and receiving JSON-RPC messages.-type AppConduits qo no ro qi ni ri m =-    (Source m (IncomingMsg qo qi ni ri), Sink (Message qo no ro) m ())---- | Map of ids to sent requests.-type SentRequests qo = HashMap Id (Request qo)---- | Incoming messages. Responses and corresponding requests go together.--- 'IncomingError' is for problems decoding incoming messages. These should be--- sent to the remote party.-data IncomingMsg qo qi ni ri-    = IncomingMsg   { incomingMsg :: !(Message qi ni ri)-                    , matchingReq :: !(Maybe (Request qo))-                    }-    | IncomingError { incomingError :: !ErrorObj-                    }-    deriving (Eq, Show)--instance (NFData qo, NFData qi, NFData ni, NFData ri)-    => NFData (IncomingMsg qo qi ni ri)-  where-    rnf (IncomingMsg msg qr) = rnf msg `seq` rnf qr-    rnf (IncomingError e) = rnf e---- | JSON-RPC session mutable data.-data Session qo = Session-    { lastId        :: TVar Id                  -- ^ Last generated id-    , sentRequests  :: TVar (SentRequests qo)   -- ^ Map of ids to requests-    , isLast        :: TQueue Bool-    -- ^ For each sent request, write a False, when sink closes, write a True-    }---- | Initialize JSON-RPC session.-initSession :: STM (Session qo)-initSession = Session <$> newTVar (IdInt 0)-                      <*> newTVar M.empty-                      <*> newTQueue---- | Conduit that serializes JSON documents for sending to the network.-encodeConduit :: (ToJSON a, Monad m) => Conduit a m ByteString-encodeConduit = CL.map (L8.toStrict . encode)---- | Conduit for outgoing JSON-RPC messages.--- Adds an id to requests whose id is 'IdNull'.--- Tracks all sent requests to match corresponding responses.-msgConduit :: MonadIO m-           => Bool-           -- ^ Set to true if decodeConduit must disconnect on last response-           -> Session qo-           -> Conduit (Message qo no ro) m (Message qo no ro)-msgConduit c qs = await >>= \nqM -> case nqM of-    Nothing ->-        when c . liftIO . atomically $ writeTQueue (isLast qs) True-    Just (MsgRequest rq) -> do-        msg <- MsgRequest <$> liftIO (atomically $ addId rq)-        yield msg >> msgConduit c qs-    Just msg ->-        yield msg >> msgConduit c qs-  where-    addId rq = case getReqId rq of-        IdNull -> do-            i <- readTVar (lastId qs)-            h <- readTVar (sentRequests qs)-            let i'  = succ i-                rq' = rq { getReqId = i' }-                h'  = M.insert i' rq' h-            writeTVar (lastId qs) i'-            writeTVar (sentRequests qs) h'-            when c $ writeTQueue (isLast qs) False-            return rq'-        i -> do-            h <- readTVar (sentRequests qs)-            writeTVar (sentRequests qs) (M.insert i rq h)-            when c $ writeTQueue (isLast qs) False-            return rq---- | Conduit to decode incoming JSON-RPC messages.  An error in the decoding--- operation will output an 'IncomingError', which should be relayed to the--- remote party.-decodeConduit-    :: (FromRequest qi, FromNotif ni, FromResponse ri, MonadIO m)-    => Ver                          -- ^ JSON-RPC version-    -> Bool                         -- ^ Close on last response-    -> Session qo                   -- ^ JSON-RPC session-    -> Conduit ByteString m (IncomingMsg qo qi ni ri)-                                    -- ^ Decoded incoming messages-decodeConduit ver c qs = evalStateT (f True) Nothing where-    f re = if c && re-      then do-        l <- liftIO . atomically $ readTQueue (isLast qs)-        unless l loop-      else loop--    loop = lift await >>= maybe flush process--    process = runParser >=> handle--    flush = do-      p <- get-      case p of-        Nothing -> return ()-        Just k -> handle (k B.empty)--    runParser chunk = do-      p <- getPartialParser-      return $ case p of-        Nothing -> parse json' chunk-        Just k  -> k chunk--    getPartialParser = get <* put Nothing--    handle (Fail {})     = lift (yield . IncomingError $ errorParse ver Null)-    handle (Partial k)   = put (Just k) >> loop-    handle (Done rest v) = do-        msg <- liftIO . atomically $ decodeSTM v-        lift $ yield msg-        if B.null rest-          then f (re msg)-          else process rest-      where-        re (IncomingMsg _ (Just _)) = True-        re _ = False--    decodeSTM v = do-        h <- readTVar (sentRequests qs)-        case parseEither (topParse h) v of-          Left _ -> return . IncomingError $ errorParse ver Null-          Right x -> case x of-            Right m@(MsgResponse rs) -> do-                rq <- requestSTM h $ getResId rs-                return $ IncomingMsg m (Just rq)-            Right m@(MsgError re) -> case getErrId re of-                IdNull ->-                    return $ IncomingMsg m Nothing-                i -> do-                    rq <- requestSTM h i-                    return $ IncomingMsg m (Just rq)-            Right m -> return $ IncomingMsg m Nothing-            Left  e -> return $ IncomingError e--    requestSTM h i = do-       let rq = fromJust $ i `M.lookup` h-           h' = M.delete i h-       writeTVar (sentRequests qs) h'-       return rq--    topParse h v = parseReq v-               <|> parseNot v-               <|> parseRes h v-               <|> return (Left $ errorInvalid ver v)--    parseRes h = withObject "response" $ \o -> do-        r <- o .:? "result" .!= Null-        e <- o .:? "error"  .!= Null-        when (r == Null && e == Null) mzero-        i <- o .:? "id" .!= IdNull-        j <- o .:? "jsonrpc"-        let ver' = if j == Just ("2.0" :: Text) then V2 else V1-        case i of-            IdNull ->-                Right . MsgError <$> parseJSON (Object o)-            _ -> case M.lookup i h of-                Nothing -> return . Left $ errorId ver' i-                Just rq -> do-                    rsE <- parseResponse rq (Object o)-                    return $ case rsE of-                        Left  er -> Right $ MsgError er-                        Right rs -> Right $ MsgResponse rs--    parseReq v = flip fmap (parseRequest v) $ \rqE -> case rqE of-        Left   e -> Left e-        Right rq -> Right $ MsgRequest rq--    parseNot v = flip fmap (parseNotif v) $ \rnE -> case rnE of-        Left   e -> Left e-        Right rn -> Right $ MsgNotif rn---- | Send requests and get responses (or errors).------ Example:------ >tcpClient V2 True (clientSettings 31337 "127.0.0.1") (query V2 [TimeReq])-query :: (ToJSON qo, ToRequest qo, FromResponse ri)-      => Ver                              -- ^ JSON-RPC version-      -> [qo]                             -- ^ List of requests-      -> AppConduits qo () () () () ri IO -- ^ Message conduits-      -> IO [IncomingMsg qo () () ri]     -- ^ Incoming messages-query ver qs (src, snk) = withAsync (src $$ CL.consume) $ \a -> do-    link a-    CL.sourceList qs $= CL.map (MsgRequest . buildRequest ver) $$ snk-    wait a---- Will create JSON-RPC conduits around 'ByteString' conduits from a transport--- layer.-runConduits :: ( FromRequest qi, FromNotif ni, FromResponse ri-               , ToJSON qo, ToJSON no, ToJSON ro )-            => Ver                      -- ^ JSON-RPC version-            -> Bool                     -- ^ Disconnect on last response-            -> Sink ByteString IO ()    -- ^ Sink to send messages-            -> Source IO ByteString     -- ^ Source of incoming messages-            -> (AppConduits qo no ro qi ni ri IO -> IO a)-                                        -- ^ JSON-RPC action-            -> IO a                     -- ^ Output of action-runConduits ver d rpcSnk rpcSrc f = do-    (reqChan, msgChan) <- atomically $ (,) <$> newTBMChan 128-                                           <*> newTBMChan 128-    let inbSrc = sourceTBMChan msgChan-        inbSnk = sinkTBMChan   msgChan True-        outSrc = sourceTBMChan reqChan-        outSnk = sinkTBMChan   reqChan True-    withAsync (rpcThread outSrc inbSnk) (g inbSrc outSnk)-  where-    rpcThread outSrc inbSnk = do-        qs <- atomically initSession-        withAsync (outThread qs outSrc) $ \a -> do-            link a-            _ <- rpcSrc $= decodeConduit ver d qs $$ inbSnk-            wait a-    outThread qs outSrc = outSrc $= msgConduit d qs $= encodeConduit $$ rpcSnk-    g inbSrc outSnk a = do-        link a-        x <- f (inbSrc, outSnk)-        _ <- wait a-        return x---- JSON-RPC-over-TCP client.-tcpClient :: ( FromRequest qi, FromNotif ni, FromResponse ri-             , ToJSON qo, ToJSON no, ToJSON ro )-          => Ver                      -- ^ JSON-RPC version-          -> Bool                     -- ^ Disconnect on last response-          -> ClientSettings           -- ^ Connection settings-          -> (AppConduits qo no ro qi ni ri IO -> IO a)-                                      -- ^ JSON-RPC action-          -> IO a                     -- ^ Output of action-tcpClient ver d cs f = runTCPClient cs $ \ad ->-    runConduits ver d (cr =$ appSink ad) (appSource ad $= ln) f-  where-    cr = CL.map (`B8.snoc` '\n')-    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  -> if B8.null l then return () else 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---- JSON-RPC-over-TCP server.-tcpServer :: ( FromRequest qi, FromNotif ni, FromResponse ri-             , ToJSON qo, ToJSON no, ToJSON ro )-          => Ver                        -- ^ JSON-RPC version-          -> ServerSettings             -- ^ Connection settings-          -> (AppConduits qo no ro qi ni ri IO -> IO ())-          -- ^ JSON-RPC action to perform on connecting client thread-          -> IO ()-tcpServer ver ss f = runTCPServer ss $ \cl ->-    runConduits ver False (cr =$ appSink cl) (appSource cl) f-  where-    cr = CL.map (`B8.snoc` '\n')
Network/JsonRpc/Data.hs view
@@ -6,7 +6,7 @@   Request(..)   -- ** Parsing , FromRequest(..)-, parseRequest+, fromRequest   -- ** Encoding , ToRequest(..) , buildRequest@@ -15,19 +15,24 @@ , Response(..)   -- ** Parsing , FromResponse(..)-, parseResponse+, fromResponse+  -- ** Encoding+, Respond+, buildResponse    -- * Notifications , Notif(..)   -- ** Parsing , FromNotif(..)-, parseNotif+, fromNotif   -- ** Encoding , ToNotif(..) , buildNotif    -- * Errors+, RpcError(..) , ErrorObj(..)+, fromError   -- ** Error Messages , errorParse , errorInvalid@@ -43,12 +48,16 @@  ) where -import Control.Applicative ((<$>), (<*>), (<|>))-import Control.DeepSeq (NFData, rnf)-import Control.Monad (when, guard, mzero)+import Control.Applicative+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.Text (Text)+import Data.Text.Encoding+import qualified Data.Text as T import GHC.Generics (Generic)  @@ -57,67 +66,58 @@ -- Requests -- --- Request data type.-data Request q = Request { getReqVer      :: !Ver       -- Version-                         , getReqMethod   :: !Method    -- Method-                         , getReqParams   :: !q         -- Params-                         , getReqId       :: !Id        -- Id-                         } deriving (Eq, Show, Read)+data Request = Request { getReqVer      :: !Ver+                       , getReqMethod   :: !Method+                       , getReqParams   :: !Value+                       , getReqId       :: !Id+                       } deriving (Eq, Show) -instance NFData q => NFData (Request q) where-    rnf (Request v m q i) = rnf v `seq` rnf m `seq` rnf q `seq` rnf i+instance NFData Request where+    rnf (Request v m p i) = rnf v `seq` rnf m `seq` rnf p `seq` rnf i -instance ToJSON q => ToJSON (Request q) where-    toJSON (Request V2 m p i) = object $ case toJSON p of+instance ToJSON Request where+    toJSON (Request V2 m p i) = object $ case p of         Null -> [jr2, "method" .= m, "id" .= i]-        v    -> [jr2, "method" .= m, "id" .= i, "params" .= v]-    toJSON (Request V1 m p i) = object $ case toJSON p of+        _    -> [jr2, "method" .= m, "id" .= i, "params" .= p]+    toJSON (Request V1 m p i) = object $ case p of         Null -> ["method" .= m, "params" .= emptyArray, "id" .= i]-        v    -> ["method" .= m, "params" .= v, "id" .= i]+        _    -> ["method" .= m, "params" .= p, "id" .= i] --- | Class for data that can be received in JSON-RPC requests. class FromRequest q where-    -- | Parser for params field.-    paramsParser :: Method -> Maybe (Value -> Parser q)+    -- | 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+ instance FromRequest Value where-    paramsParser _ = Just return+    parseParams = const $ Just return  instance FromRequest () where-    paramsParser _ = Nothing+    parseParams = const . Just . const $ return () --- | Parse JSON-RPC request.-parseRequest :: FromRequest q => Value -> Parser (Either ErrorObj (Request q))-parseRequest = withObject "request" $ \o -> do-    j <- o .:? "jsonrpc"-    i <- o .:  "id"-    when (i == IdNull) $ fail "Request must have non-null id"-    m <- o .:  "method"-    p <- o .:? "params" .!= Null-    let ver = if j == Just ("2.0" :: Text) then V2 else V1-    case paramsParser m of-        Nothing -> return (Left (errorMethod ver m i))-        Just x -> parseIt ver m i x p <|> return (Left (errorParams ver p i))-  where-    parseIt ver m i x p = (\y -> Right (Request ver m y i)) <$> x p+instance FromJSON Request where+    parseJSON = withObject "request" $ \o -> do+        (v, i, m, p) <- parseVerIdMethParams o+        guard $ i /= IdNull+        return $ Request v m p i --- | Class for data that can be sent as JSON-RPC requests.--- Define a method name for each request. class ToRequest q where+    -- | Method associated with request data to build a request object.     requestMethod :: q -> Method  instance ToRequest Value where-    requestMethod _ = ""+    requestMethod = const "json"  instance ToRequest () where-    requestMethod _ = undefined+    requestMethod = const "json" --- Build JSON-RPC request.-buildRequest :: ToRequest q+buildRequest :: (ToJSON q, ToRequest q)              => Ver             -- ^ JSON-RPC version              -> q               -- ^ Request data-             -> Request q-buildRequest ver q = Request ver (requestMethod q) q IdNull+             -> Id+             -> Request+buildRequest ver q = Request ver (requestMethod q) (toJSON q)   @@ -125,115 +125,110 @@ -- Responses -- --- | JSON-RPC response data type-data Response r = Response  { getResVer :: !Ver             -- ^ Version-                            , getResult :: !r               -- ^ Result-                            , getResId  :: !Id              -- ^ Id-                            } deriving (Eq, Show, Read)+data Response = Response { getResVer :: !Ver+                         , getResult :: !Value+                         , getResId  :: !Id+                         } deriving (Eq, Show) -instance NFData r => NFData (Response r) where+instance NFData Response where     rnf (Response v r i) = rnf v `seq` rnf r `seq` rnf i -instance ToJSON r => ToJSON (Response r) where+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] --- | Class for data that can be received inside JSON-RPC responses. class FromResponse r where-    -- | Parse result field from JSON-RPC response.-    parseResult :: Method -> Value -> Parser r+    -- | Parser for result Value in JSON-RPC response.+    -- Method corresponds to request to which this response answers.+    parseResult :: Method -> Maybe (Value -> Parser r) +fromResponse :: FromResponse r => Method -> Response -> Maybe r+fromResponse m (Response _ r _) = parseResult m >>= flip parseMaybe r+ instance FromResponse Value where-    parseResult _ = return+    parseResult = const $ Just return  instance FromResponse () where-    parseResult _ _ = return ()+    parseResult = const . Just . const $ return () --- | Parse JSON-RPC response.-parseResponse :: FromResponse r-              => Request q -> Value -> Parser (Either ErrorObj (Response r))-parseResponse rq = withObject "response" $ \o -> do-    let m  = getReqMethod rq-        qi = getReqId rq-    j <- o .:? "jsonrpc"-    i <- o .: "id"-    when (i == IdNull) $ fail "Response must have non-null id"-    when (qi /= i) $ fail "Response id mismatch"-    let ver = if j == Just ("2.0" :: Text) then V2 else V1-    (Right <$> parseRes ver i m o) <|> (Left <$> parseJSON (Object o))-  where-    parseRes ver i m o = do-        v <- o .: "result"-        guard $ v /= Null-        r <- parseResult m v-        return $ Response ver r i+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 +buildResponse :: (Monad m, FromRequest q, ToJSON r)+              => Respond q m r+              -> Request+              -> m (Either RpcError Response)+buildResponse f req@(Request v _ p i) = case fromRequest req of+    Nothing -> return . Left $ RpcError v (errorInvalid p) i+    Just q -> do+        rE <- f q+        return $ either (\e -> Left $ RpcError v e i)+                        (\r -> Right $ Response v (toJSON r) i) rE +type Respond q m r = q -> m (Either ErrorObj r) + -- -- Notifications -- --- | Class for JSON-RPC notifications.-data Notif n = Notif  { getNotifVer    :: !Ver          -- ^ Version-                      , getNotifMethod :: !Method       -- ^ Method-                      , getNotifParams :: !n            -- ^ Params-                      } deriving (Eq, Show, Read)+data Notif = Notif  { getNotifVer    :: !Ver+                    , getNotifMethod :: !Method+                    , getNotifParams :: !Value+                    } deriving (Eq, Show) -instance NFData n => NFData (Notif n) where+instance NFData Notif where     rnf (Notif v m n) = rnf v `seq` rnf m `seq` rnf n -instance ToJSON n => ToJSON (Notif n) where-    toJSON (Notif V2 m p) = object $ case toJSON p of+instance ToJSON Notif where+    toJSON (Notif V2 m p) = object $ case p of         Null -> [jr2, "method" .= m]-        v    -> [jr2, "method" .= m, "params" .= v]-    toJSON (Notif V1 m p) = object $ case toJSON p of+        _    -> [jr2, "method" .= m, "params" .= p]+    toJSON (Notif V1 m p) = object $ case p of         Null -> ["method" .= m, "params" .= emptyArray, "id" .= Null]-        v    -> ["method" .= m, "params" .=          v, "id" .= Null]+        _    -> ["method" .= m, "params" .= p, "id" .= Null] --- | Class for data that can be received in JSON-RPC notifications. class FromNotif n where-    -- | Parser for notification params field-    notifParamsParser :: Method -> Maybe (Value -> Parser n)+    -- | 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-    notifParamsParser _ = Just return+    parseNotif = const $ Just return  instance FromNotif () where-    notifParamsParser _ = Nothing+    parseNotif = const . Just . const $ return () --- | Parse notifications.-parseNotif :: FromNotif n => Value -> Parser (Either ErrorObj (Notif n))-parseNotif = withObject "notification" $ \o -> do-    j <- o .:? "jsonrpc"-    i <- o .:? "id" .!= IdNull-    m <- o .:  "method"-    p <- o .:? "params" .!= Null-    guard $ i == IdNull-    let ver = if j == Just ("2.0" :: Text) then V2 else V1-    case notifParamsParser m of-        Nothing -> return (Left $ errorMethod ver m IdNull)-        Just x -> f ver m x p <|> return (Left (errorParams ver p i))-  where-    f ver m x p = (Right . Notif ver m) <$> x p+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 _ = ""+    notifMethod = const "json"  instance ToNotif () where-    notifMethod _ = undefined+    notifMethod = const "json" --- | Build notifications.-buildNotif :: ToNotif n-           => Ver           -- ^ Version-           -> n             -- ^ Notification data-           -> Notif n-buildNotif ver n = Notif ver (notifMethod n) n+buildNotif :: (ToJSON n, ToNotif n)+           => Ver+           -> n+           -> Notif+buildNotif ver n = Notif ver (notifMethod n) (toJSON n)   @@ -241,96 +236,115 @@ -- Errors -- --- | JSON-RPC errors.-data ErrorObj = ErrorObj { getErrVer  :: !Ver           -- ^ Version-                         , getErrMsg  :: !String        -- ^ Message-                         , getErrCode :: !Int           -- ^ Error code (2.0)-                         , getErrData :: !Value         -- ^ Error data (2.0)-                         , getErrId   :: !Id            -- ^ Error id-                         } deriving (Eq, Show)+-- Error object from JSON-RPC 2.0. ErrorVal for backwards compatibility.+data ErrorObj = ErrorObj  { getErrMsg  :: !String+                          , getErrCode :: !Int+                          , getErrData :: !Value+                          }+              | ErrorVal  { getErrData :: !Value }+              deriving (Show, Eq)  instance NFData ErrorObj where-    rnf (ErrorObj v m c d i) =-        rnf v `seq` rnf m `seq` rnf c `seq` rnf d `seq` rnf i+    rnf (ErrorObj m c d) = rnf m `seq` rnf c `seq` rnf d+    rnf (ErrorVal v) = rnf v  instance FromJSON ErrorObj where+    parseJSON Null = mzero+    parseJSON v@(Object o) = p1 <|> p2 where+        p1 = do+            m <- o .: "message"+            c <- o .: "code"+            d <- o .:? "data" .!= Null+            return $ ErrorObj m c d+        p2 = return $ ErrorVal v+    parseJSON v = return $ ErrorVal v++instance ToJSON ErrorObj where+    toJSON (ErrorObj s i d) = object $ ["message" .= s, "code" .= i]+        ++ if d == Null then [] else ["data" .= d]+    toJSON (ErrorVal v) = v++fromError :: ErrorObj -> String+fromError (ErrorObj m _ _) = m+fromError (ErrorVal v) = T.unpack $ decodeUtf8 $ L.toStrict $ encode v++data RpcError = RpcError { getErrVer  :: !Ver+                         , getErrObj  :: !ErrorObj+                         , getErrId   :: !Id+                         } deriving (Eq, Show)++instance NFData RpcError where+    rnf (RpcError v o i) = rnf v `seq` rnf o `seq` rnf i++instance FromJSON RpcError where     parseJSON = withObject "error" $ \o -> do+        v <- parseVer o+        e <- o .: "error"         i <- o .:? "id" .!= IdNull-        j <- o .:? "jsonrpc"-        let ver = if j == Just ("2.0" :: Text) then V2 else V1-        case ver of-            V2 -> o .: "error" >>= \e -> case e of-                (Object b) -> ErrorObj V2 <$> b .: "message"-                                          <*> b .: "code"-                                          <*> b .:? "data" .!= Null-                                          <*> return i-                _ -> fail "JSON-RPC 2.0 error must be a JSON object"-            V1 -> (o .: "error"  >>= \e -> return $ ErrorObj V1 e 0 Null i) <|>-                  (o .: "result" >>= \e -> return $ ErrorObj V1 e 0 Null i)-                      -- Buggy servers sometimes put errors in result field+        return $ RpcError v e i -instance ToJSON ErrorObj where-    toJSON (ErrorObj V2 m c d i) = object [jr2, "id" .= i, "error" .= o] where-        o = case d of-            Null -> object ["code" .= c, "message" .= m]-            _    -> object ["code" .= c, "message" .= m, "data" .= d]-    toJSON (ErrorObj V1 m _ _ i) =-        object ["id" .= i, "error" .= m, "result" .= Null]+instance ToJSON RpcError where+    toJSON (RpcError V1 o i) =+        object ["id" .= i, "result" .= Null, "error" .= o]+    toJSON (RpcError V2 o i) =+        object ["id" .= i, "error" .= o, jr2]  -- | Parse error.-errorParse :: Ver -> Value -> ErrorObj-errorParse ver v = ErrorObj ver "Parse error" (-32700) v IdNull+errorParse :: Value -> ErrorObj+errorParse = ErrorObj "Parse error" (-32700)  -- | Invalid request.-errorInvalid :: Ver -> Value -> ErrorObj-errorInvalid ver v = ErrorObj ver "Invalid request" (-32600) v IdNull+errorInvalid :: Value -> ErrorObj+errorInvalid = ErrorObj "Invalid request" (-32600)  -- | Invalid params.-errorParams :: Ver -> Value -> Id -> ErrorObj-errorParams ver v = ErrorObj ver "Invalid params" (-32602) v+errorParams :: Value -> ErrorObj+errorParams = ErrorObj "Invalid params" (-32602)  -- | Method not found.-errorMethod :: Ver -> Method -> Id -> ErrorObj-errorMethod ver m = ErrorObj ver "Method not found" (-32601) (toJSON m)+errorMethod :: Method -> ErrorObj+errorMethod = ErrorObj "Method not found" (-32601) . toJSON  -- | Id not recognized.-errorId :: Ver -> Id -> ErrorObj-errorId ver i = ErrorObj ver "Id not recognized" (-32000) (toJSON i) IdNull-+errorId :: Id -> ErrorObj+errorId = ErrorObj "Id not recognized" (-32000) . toJSON   -- -- Messages -- --- | Class for any JSON-RPC message.-data Message q n r-    = MsgRequest   { getMsgRequest  :: !(Request  q) }-    | MsgNotif     { getMsgNotif    :: !(Notif    n) }-    | MsgResponse  { getMsgResponse :: !(Response r) }-    | MsgError     { getMsgError    :: !ErrorObj     }+data Message+    = MsgRequest   { getMsgRequest  :: !Request  }+    | MsgResponse  { getMsgResponse :: !Response }+    | MsgNotif     { getMsgNotif    :: !Notif    }+    | MsgError     { getMsgError    :: !RpcError }     deriving (Eq, Show) -instance (NFData q, NFData n, NFData r) => NFData (Message q n r) where+instance NFData Message where     rnf (MsgRequest  q) = rnf q-    rnf (MsgNotif    n) = rnf n     rnf (MsgResponse r) = rnf r+    rnf (MsgNotif    n) = rnf n     rnf (MsgError    e) = rnf e -instance (ToJSON q, ToJSON n, ToJSON r) => ToJSON (Message q n r) where-    toJSON (MsgRequest  rq) = toJSON rq-    toJSON (MsgNotif    rn) = toJSON rn-    toJSON (MsgResponse rs) = toJSON rs-    toJSON (MsgError     e) = toJSON 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 -- --- | JSON-RPC methods in requests and notifications. type Method = Text --- | JSON-RPC message id. data Id = IdInt { getIdInt :: !Int  }         | IdTxt { getIdTxt :: !Text }         | IdNull@@ -359,12 +373,12 @@     toJSON (IdInt n) = toJSON n     toJSON IdNull = Null --- | JSON-RPC version data Ver = V1 -- ^ JSON-RPC 1.0          | V2 -- ^ JSON-RPC 2.0          deriving (Eq, Show, Read) -instance NFData Ver+instance NFData Ver where+    rnf v = v `seq` ()   @@ -375,3 +389,15 @@ jr2 :: Pair jr2 = "jsonrpc" .= ("2.0" :: Text) +parseVer :: Object -> Parser Ver+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
@@ -0,0 +1,255 @@+{-# LANGUAGE OverloadedStrings #-}+-- | Interface for JSON-RPC.+module Network.JsonRpc.Interface+( -- * Establish JSON-RPC context+  JsonRpcT+, runJsonRpcT++  -- * Communicate with remote party+, sendRequest+, sendNotif+, receiveNotif++  -- * Transports+  -- ** Client+, jsonRpcTcpClient+, dummyRespond+  -- ** Server+, jsonRpcTcpServer+, dummySrv+) where++import Control.Applicative+import Control.Concurrent.Async+import Control.Concurrent.STM+import Control.Monad+import Control.Monad.Reader+import Control.Monad.Trans.State+import Data.Aeson+import Data.Aeson.Types (parseMaybe)+import Data.Attoparsec.ByteString+import Data.ByteString (ByteString)+import qualified Data.ByteString as B+import qualified Data.ByteString.Char8 as B8+import qualified Data.ByteString.Lazy.Char8 as L8+import Data.HashMap.Strict (HashMap)+import qualified Data.HashMap.Strict as M+import Data.Conduit+import qualified Data.Conduit.List as CL+import Data.Conduit.Network+import Data.Conduit.TMChan+import Network.JsonRpc.Data++type SentRequests = HashMap Id (TMVar (Either RpcError Response))++data Session = Session { inCh     :: TBMChan (Either RpcError Message)+                       , outCh    :: TBMChan Message+                       , notifCh  :: TBMChan (Either RpcError Notif)+                       , lastId   :: TVar Id+                       , sentReqs :: TVar SentRequests+                       , rpcVer   :: Ver+                       }++-- Context for JSON-RPC connection. Connection will remain active as long+-- 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++encodeConduit :: (ToJSON a, Monad m) => Conduit a m ByteString+encodeConduit = CL.map $ L8.toStrict . encode++parseMessages :: Monad m+              => Ver -> Conduit ByteString m (Either RpcError Message)+parseMessages ver = evalStateT loop Nothing where+    loop = lift await >>= maybe flush process++    flush = get >>= \kM -> case kM of Nothing -> return ()+                                      Just k  -> handle (k B.empty)+    process = runParser >=> handle++    runParser ck = maybe (parse json' ck) ($ ck) <$> get <* put Nothing++    handle (Fail {}) = do+        lift . yield . Left $ RpcError ver (errorParse Null) IdNull+        loop+    handle (Partial k) = put (Just k) >> loop+    handle (Done rest v) = do+        let msg = decodeJsonRpc v+        lift $ yield msg+        if B.null rest then loop else process rest++    decodeJsonRpc v = case parseMaybe parseJSON v of+        Just msg -> Right msg+        Nothing -> Left $ RpcError ver (errorInvalid v) IdNull++processIncoming :: (FromRequest q, ToJSON r)+                => Respond q IO r -> JsonRpcT IO ()+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 $ return ()+        Just (Left e) -> do+            writeTBMChan o (MsgError e)+            return $ processIncoming r+        Just (Right (MsgNotif t)) ->+            writeTBMChan n (Right t) >> return (processIncoming r)+        Just (Right (MsgRequest q)) -> return $ do+            msg <- either MsgError MsgResponse <$> liftIO (buildResponse r q)+            liftIO . atomically $ writeTBMChan o msg+            processIncoming r+        Just (Right (MsgResponse res@(Response _ _ x))) -> do+            m <- readTVar s+            case x `M.lookup` m of+                Nothing ->+                    writeTBMChan o . MsgError $ RpcError v (errorId x) IdNull+                Just p ->+                    writeTVar s (x `M.delete` m) >> putTMVar p (Right res)+            return $ processIncoming r+        Just (Right (MsgError err@(RpcError _ _ IdNull))) -> do+            writeTBMChan n $ Left err+            return $ processIncoming r+        Just (Right (MsgError err@(RpcError _ _ x))) -> do+            m <- readTVar s+            case x `M.lookup` m of+                Nothing ->+                    writeTBMChan o . MsgError $ RpcError v (errorId x) IdNull+                Just p ->+                    writeTVar s (x `M.delete` m) >> putTMVar p (Left err)+            return $ processIncoming r++-- | Returns Right Nothing if could not parse response. Run output in STM+-- monad. STM will block until response arrives.+sendRequest :: (ToJSON q, ToRequest q, FromResponse r, MonadIO m)+            => q -> JsonRpcT m (STM (Either ErrorObj (Maybe r)))+sendRequest q = do+    o <- reader outCh+    v <- reader rpcVer+    l <- reader lastId+    s <- reader sentReqs+    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+    return $ takeTMVar p >>= \pE -> case pE of+        Left e -> return . Left $ getErrObj e+        Right y@(Response ver r _) -> +            case fromResponse (requestMethod q) y of+                Nothing -> do+                    let err = MsgError $ RpcError ver (errorInvalid r) IdNull+                    writeTBMChan o err+                    return $ Right Nothing+                Just x -> return . Right $ Just x++-- | Send notification. Run output in STM monad. Will not block.+sendNotif :: (ToJSON no, ToNotif no, Monad m) => no -> JsonRpcT m (STM ())+sendNotif n = do+    o <- reader outCh+    v <- reader rpcVer+    let notif = buildNotif v n+    return $ writeTBMChan o (MsgNotif notif)++-- | Receive notifications from peer.+-- Returns Nothing if incoming channel is closed and empty.+-- Result is Right Nothing if it failed to parse notification.+-- Run output in STM monad. Will not block.+receiveNotif :: (Monad m, FromNotif n)+             => JsonRpcT m (STM (Maybe (Either ErrorObj (Maybe n))))+receiveNotif = do+    c <- reader notifCh+    o <- reader outCh+    return $ readTBMChan c >>= \nM -> case nM of+        Nothing -> return Nothing+        Just (Left e) -> return . Just . Left $ getErrObj e+        Just (Right n@(Notif v _ p)) -> case fromNotif n of+            Nothing -> do+                let err = MsgError $ RpcError v (errorParse p) IdNull+                writeTBMChan o err+                return . Just $ Right Nothing+            Just x -> return . Just . Right $ Just x++-- | Create JSON-RPC session around ByteString conduits from transport+-- layer. When context exits, session stops existing.+runJsonRpcT :: (FromRequest q, ToJSON r)+            => Ver                     -- ^ JSON-RPC version+            -> Respond q IO r          -- ^ Respond to incoming requests+            -> Sink ByteString IO ()   -- ^ Sink to send messages+            -> Source IO ByteString    -- ^ Source of incoming messages+            -> JsonRpcT IO a           -- ^ JSON-RPC action+            -> IO a                    -- ^ Output of action+runJsonRpcT ver r snk src f = do+    qs <- atomically $ initSession ver+    let inSnk  = sinkTBMChan (inCh qs) True+        outSrc = sourceTBMChan (outCh qs)+    withAsync (fromNet inSnk) $ const $+        withAsync (toNet outSrc) $ const $+            withAsync (runReaderT (processIncoming r) qs) $ const $+                runReaderT f qs+  where+    fromNet inSnk = src $= parseMessages ver $$ inSnk+    toNet outSrc = outSrc $= encodeConduit $$ snk+++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+++-- | TCP client transport for JSON-RPC.+jsonRpcTcpClient+    :: (FromRequest q, ToJSON r)+    => Ver             -- ^ JSON-RPC version+    -> ClientSettings  -- ^ Connection settings+    -> Respond q IO r  -- ^ Respond to incoming requests+    -> JsonRpcT IO a   -- ^ JSON-RPC action+    -> IO a            -- ^ Output of action+jsonRpcTcpClient ver cs r f = runTCPClient cs $ \ad ->+    runJsonRpcT ver r (cr =$ appSink ad) (appSource ad $= ln) f++-- | TCP server transport for JSON-RPC.+jsonRpcTcpServer+    :: (FromRequest q, ToJSON r)+    => Ver             -- ^ JSON-RPC version+    -> ServerSettings  -- ^ Connection settings+    -> Respond q IO r  -- ^ Respond to incoming requests+    -> JsonRpcT IO ()  -- ^ Action to perform on connecting client thread+    -> IO ()+jsonRpcTcpServer ver ss r f = runTCPServer ss $ \cl ->+    runJsonRpcT ver r (cr =$ appSink cl) (appSource cl $= ln) f++-- | Dummy server for servers not expecting client to send notifications,+-- that is most cases.+dummySrv :: MonadIO m => JsonRpcT m ()+dummySrv = forever $ do+    n <- receiveNotif+    liftIO (atomically n :: IO (Maybe (Either ErrorObj (Maybe ()))))++-- | Respond function for systems that do not reply to requests, as usual+-- in clients.+dummyRespond :: Monad m => Respond () m ()+dummyRespond = const . return $ Right () 
README.md view
@@ -3,44 +3,26 @@  Fully-featured JSON-RPC 2.0 library for Haskell programs. -This JSON-RPC library is fully-compatible with JSON-RPC 2.0 and-partially-compatible with JSON-RPC 1.0. It provides an interface that combines-a JSON-RPC client and server. It can set and keep track of request ids to parse-responses. There is support for sending and receiving notifications. You may-use any underlying transport. Basic TCP client and server provided.--The recommended interface to this library is provided as conduits that encode-outgoing messages, and decode incoming messages. Incoming messages are-delivered as an IncomingMsg data structure, while outgoing messages are sent in-a Message data structure. The former packs responses and errors with their-corresponding request, and has a separate constructor for decoding errors.--A JSON-RPC application using this interface is considered to be peer-to-peer,-as it can send and receive all types of JSON-RPC message independent of whether-it originated the connection.--Type classes ToRequest, ToNotif are for data that can be converted into-JSON-RPC requests and notifications respectively. An instance of aeson's ToJSON-class is also required to serialize these data structures. Make sure that they-serialize as a structured JSON value (array or object) that can go into the-params field of the JSON-RPC object. Type classes FromRequest, FromNotif and-FromResult are for deserializing JSON-RPC messages.+This JSON-RPC library is fully-compatible with JSON-RPC 2.0 and 1.0. It+provides an interface that combines a JSON-RPC client and server. It can+set and keep track of request ids to parse responses.  There is support+for sending and receiving notifications. You may use any underlying+transport.  Basic TCP client and server provided. -Errors are deserialized to the ErrorObj data type. Only a string is supported-as contents inside a JSON-RPC 1.0 error. JSON-RPC 2.0 errors also have a code,-and possibly additional data as an aeson Value. +A JSON-RPC application using this interface is considered to be+peer-to-peer, as it can send and receive all types of JSON-RPC message+independent of whether it originated the connection.   Server Example -------------- -This server returns the current time.+This JSON-RPC server returns the current time.  ``` haskell {-# LANGUAGE OverloadedStrings #-}-import Data.Aeson.Types-import Data.Conduit-import qualified Data.Conduit.List as CL+import Control.Applicative+import Data.Aeson.Types hiding (Error) import Data.Conduit.Network import Data.Time.Clock import Data.Time.Format@@ -48,30 +30,21 @@ import System.Locale  data TimeReq = TimeReq-data TimeRes = TimeRes UTCTime+data TimeRes = TimeRes { timeRes :: UTCTime }  instance FromRequest TimeReq where-    paramsParser "time" = Just $ const $ return TimeReq -    paramsParser _ = Nothing+    parseParams "time" = Just $ const $ return TimeReq +    parseParams _ = Nothing  instance ToJSON TimeRes where     toJSON (TimeRes t) = toJSON $ formatTime defaultTimeLocale "%c" t -srv :: AppConduits () () TimeRes TimeReq () () IO -> IO ()-srv (src, snk) = src $= CL.mapM respond $$ snk--respond :: IncomingMsg () TimeReq () ()-        -> IO (Message () () TimeRes)-respond (IncomingMsg (MsgRequest (Request ver _ TimeReq i)) Nothing) = do    -    t <- getCurrentTime-    return $ MsgResponse (Response ver (TimeRes t) i)--respond (IncomingError e) = return $ MsgError e-respond (IncomingMsg (MsgError e) _) = return $ MsgError $ e-respond _ = undefined+respond :: Respond TimeReq IO TimeRes+respond TimeReq = Right . TimeRes <$> getCurrentTime  main :: IO ()-main = tcpServer V2 (serverSettings 31337 "127.0.0.1") srv+main = jsonRpcTcpServer V2 (serverSettings 31337 "::1") respond dummySrv+ ```  Client Example@@ -81,9 +54,12 @@  ``` haskell {-# LANGUAGE OverloadedStrings #-}+import Control.Concurrent+import Control.Concurrent.STM+import Control.Monad+import Control.Monad.Trans+import Data.Aeson import Data.Aeson.Types hiding (Error)-import Data.Conduit-import qualified Data.Conduit.List as CL import Data.Conduit.Network import qualified Data.Text as T import Data.Time.Clock@@ -92,7 +68,7 @@ import System.Locale  data TimeReq = TimeReq-data TimeRes = TimeRes UTCTime+data TimeRes = TimeRes { timeRes :: UTCTime }  instance ToRequest TimeReq where     requestMethod TimeReq = "time"@@ -101,24 +77,20 @@     toJSON TimeReq = emptyArray  instance FromResponse TimeRes where-    parseResult "time" = withText "time" $ \t -> case f t of-        Nothing -> fail "Could not parse time"+    parseResult "time" = Just $ withText "time" $ \t -> case f t of         Just t' -> return $ TimeRes t'+        Nothing -> mzero       where         f t = parseTime defaultTimeLocale "%c" (T.unpack t)+    parseResult _ = Nothing -cli :: AppConduits TimeReq () () () () TimeRes IO-    -> IO UTCTime-cli (src, snk) = do-    CL.sourceList [MsgRequest $ buildRequest V2 TimeReq] $$ snk-    ts <- src $$ CL.consume-    case ts of-        [] -> error "No response received"-        [IncomingError (ErrorObj _ m _ _ _)] -> error $ "Unknown: " ++ m-        [IncomingMsg (MsgError (ErrorObj _ m _ _ _)) _] -> error m-        [IncomingMsg (MsgResponse (Response _ (TimeRes t) _)) _] -> return t-        _ -> undefined+req :: JsonRpcT IO UTCTime+req = sendRequest TimeReq >>= liftIO . atomically >>= \ts -> case ts of+    Left e -> error $ fromError e+    Right (Just (TimeRes r)) -> return r+    _ -> error "Could not parse response"  main :: IO ()-main = tcpClient V2 True (clientSettings 31337 "127.0.0.1") cli >>= print+main = jsonRpcTcpClient V2 (clientSettings 31337 "::1") dummyRespond .+    replicateM_ 4 $ req >>= liftIO . print >> liftIO (threadDelay 1000000) ```
json-rpc.cabal view
@@ -1,12 +1,12 @@ name:                   json-rpc-version:                0.2.1.6+version:                0.3.0.0 synopsis:               Fully-featured JSON-RPC 2.0 library description:-  This JSON-RPC library is fully-compatible with JSON-RPC 2.0 and-  partially-compatible with JSON-RPC 1.0. It provides an interface that combines-  a JSON-RPC client and server. It can set and keep track of request ids to-  parse responses. There is support for sending and receiving notifications.-  You may use any underlying transport. Basic TCP client and server provided.+  This JSON-RPC library is fully-compatible with JSON-RPC 2.0 and 1.0. It+  provides an interface that combines a JSON-RPC client and server. It can set+  and keep track of request ids to parse responses. There is support for+  sending and receiving notifications.  You may use any underlying transport.+  Basic TCP client and server provided. homepage:               https://github.com/xenog/json-rpc license:                PublicDomain license-file:           UNLICENSE@@ -24,12 +24,12 @@ source-repository this   type:                 git   location:             https://github.com/xenog/json-rpc.git-  tag:                  0.2.1.5+  tag:                  0.3.0.0  library   exposed-modules:      Network.JsonRpc   other-modules:        Network.JsonRpc.Data,-                        Network.JsonRpc.Conduit+                        Network.JsonRpc.Interface   build-depends:        base                        >= 4.6      && < 5,                         aeson                       >= 0.7      && < 0.9,                         attoparsec                  >= 0.11,@@ -58,16 +58,12 @@                         aeson                       >= 0.7      && < 0.9,                         async                       >= 2.0      && < 2.1,                         bytestring                  >= 0.10     && < 0.11,-                        conduit                     >= 1.2      && < 1.3,-                        conduit-extra               >= 1.1      && < 1.2,-                        deepseq                     >= 1.3      && < 1.4,-                        hashable                    >= 1.1      && < 1.3,-                        json-rpc                    >= 0.2      && < 0.3,+                        json-rpc,                         mtl                         >= 2.1      && < 2.3,-                        stm                         >= 2.4      && < 2.5,-                        stm-conduit                 >= 2.5      && < 2.6,                         text                        >= 0.11     && < 1.3,                         unordered-containers        >= 0.2      && < 0.3,+                        stm                         >= 2.4      && < 2.5,+                        stm-conduit                 >= 2.5      && < 2.6,                         QuickCheck                  >= 2.6      && < 2.8,                         test-framework              >= 0.8      && < 0.9,                         test-framework-quickcheck2  >= 0.3      && < 0.4
test/Network/JsonRpc/Arbitrary.hs view
@@ -7,7 +7,7 @@ ) where  import Control.Applicative-import Data.Aeson.Types hiding (Error)+import Data.Aeson.Types import qualified Data.HashMap.Strict as M import Data.Text (Text) import qualified Data.Text as T@@ -17,10 +17,10 @@  -- | A pair of a request and its corresponding response. -- Id and version should match.-data ReqRes q r = ReqRes !(Request q) !(Response r)+data ReqRes = ReqRes !Request !Response     deriving (Show, Eq) -instance Arbitrary (ReqRes Value Value) where+instance Arbitrary ReqRes where     arbitrary = do         rq <- arbitrary         rs <- arbitrary@@ -33,36 +33,32 @@ instance Arbitrary Ver where     arbitrary = elements [V1, V2] -instance (Arbitrary q, ToRequest q) => Arbitrary (Request q) where-    arbitrary = do-        q <- arbitrary-        v <- arbitrary-        let m = requestMethod q-        Request v m q <$> arbitrary+instance Arbitrary Request where+    arbitrary = Request <$> arbitrary <*> arbitrary <*> arbitrary <*> arbitrary -instance (Arbitrary n, ToNotif n) => Arbitrary (Notif n) where-    arbitrary = do-        n <- arbitrary-        v <- arbitrary-        let m = notifMethod n-        return $ Notif v m n+instance Arbitrary Notif where+    arbitrary = Notif <$> arbitrary <*> arbitrary <*> arbitrary -instance Arbitrary r => Arbitrary (Response r) where+instance Arbitrary Response where     arbitrary = Response <$> arbitrary <*> arbitrary <*> arbitrary  instance Arbitrary ErrorObj where-    arbitrary = ErrorObj <$> arbitrary <*> arbitrary <*> arbitrary-                         <*> arbitrary <*> arbitrary+    arbitrary = oneof+        [ ErrorObj <$> arbitrary <*> arbitrary <*> arbitrary+        , ErrorVal <$> arbitrary+        ] -instance ( Arbitrary q, Arbitrary n, Arbitrary r-         , ToRequest q, ToNotif n, ToJSON r )-    => Arbitrary (Message q n r)-  where-    arbitrary = oneof [ MsgRequest  <$> arbitrary-                      , MsgNotif    <$> arbitrary-                      , MsgResponse <$> arbitrary-                      , MsgError    <$> arbitrary ]+instance Arbitrary RpcError where+    arbitrary = RpcError <$> arbitrary <*> arbitrary <*> arbitrary +instance Arbitrary Message where+    arbitrary = oneof+        [ MsgRequest  <$> arbitrary+        , MsgNotif    <$> arbitrary+        , MsgResponse <$> arbitrary+        , MsgError    <$> arbitrary+        ]+ instance Arbitrary Id where     arbitrary = oneof [IdInt <$> arbitrary, IdTxt <$> arbitrary] @@ -71,7 +67,8 @@         val = oneof [ toJSON <$> (arbitrary :: Gen String)                     , toJSON <$> (arbitrary :: Gen Int)                     , toJSON <$> (arbitrary :: Gen Double)-                    , toJSON <$> (arbitrary :: Gen Bool) ]+                    , toJSON <$> (arbitrary :: Gen Bool)+                    ]         ls   = toJSON <$> listOf val         obj  = toJSON . M.fromList <$> listOf ps         ps   = (,) <$> (arbitrary :: Gen String) <*> oneof [val, ls]
test/Network/JsonRpc/Tests.hs view
@@ -3,22 +3,19 @@ {-# LANGUAGE Rank2Types #-} module Network.JsonRpc.Tests (tests) where -import Control.Concurrent+import Control.Applicative import Control.Concurrent.Async import Control.Concurrent.STM-import Control.Exception hiding (assert)+import qualified Data.ByteString.Lazy as L+import Data.Conduit.TMChan import Control.Monad+import Control.Monad.Trans+import Data.Aeson import Data.Aeson.Types-import Data.Conduit-import qualified Data.Conduit.List as CL-import Data.List-import Data.Conduit.Network-import Data.Conduit.TMChan-import qualified Data.HashMap.Strict as M+import Data.Either import Data.Maybe-import Data.Text (Text) import Network.JsonRpc-import Network.JsonRpc.Arbitrary+import Network.JsonRpc.Arbitrary() import Test.QuickCheck import Test.QuickCheck.Monadic import Test.Framework@@ -28,398 +25,128 @@ tests =     [ testGroup "JSON-RPC Requests"         [ testProperty "Check fields"-            (reqFields :: Request Value -> Bool)+            (reqFields :: Request -> Bool)         , testProperty "Encode/decode"-            (reqDecode :: Request Value -> Bool)+            (testEncodeDecode :: Request -> Bool)         ]     , testGroup "JSON-RPC Notifications"         [ testProperty "Check fields"-            (notifFields :: Notif Value -> Bool)+            (notifFields :: Notif -> Bool)         , testProperty "Encode/decode"-            (notifDecode :: Notif Value -> Bool)+            (testEncodeDecode :: Notif -> Bool)         ]     , testGroup "JSON-RPC Responses"         [ testProperty "Check fields"-            (resFields :: Response Value -> Bool)+            (resFields :: Response -> Bool)         , testProperty "Encode/decode"-            (resDecode :: ReqRes Value Value -> Bool)-        , testProperty "Bad response id"-            (rpcBadResId :: ReqRes Value Value -> Bool)-        , testProperty "Error response"-            (rpcErrRes :: (ReqRes Value Value, ErrorObj) -> Bool)+            (testEncodeDecode :: Response -> Bool)         ]-    , testGroup "JSON-RPC Conduits"-        [ testProperty "Outgoing conduit"-            (newMsgConduit :: [Message Value Value Value] -> Property)-        , testProperty "Decode requests"-            (decodeReqConduit :: ([Request Value], Ver) -> Property)-        , testProperty "Decode responses"-            (decodeResConduit :: ([ReqRes Value Value], Ver) -> Property)-        , testProperty "Bad responses"-            (decodeErrConduit :: ([ReqRes Value Value], Ver) -> Property)-        , testProperty "Sending messages" sendMsgNet-        , testProperty "Two-way communication" twoWayNet-        , testProperty "Real network communication" realNet+    , testGroup "JSON-RPC Errors"+        [ testProperty "Check fields"+            (errFields :: RpcError -> Bool)+        , testProperty "Encode/decode"+            (testEncodeDecode :: RpcError -> Bool)         ]+    , testGroup "Network"+        [ testProperty "Test server" serverTest+        , testProperty "Test client" clientTest+        ]     ] ------ Requests-----reqFields :: (ToRequest a, ToJSON a) => Request a -> Bool-reqFields rq = case rq of-    Request V1 m p i -> r1ks && vals m p i-    Request V2 m p i -> r2ks && vals m p i-  where-    (Object o) = toJSON rq-    r1ks = sort (M.keys o) == ["id", "method", "params"]-    r2ks = sort (M.keys o) == ["id", "jsonrpc", "method", "params"]-        || sort (M.keys o) == ["id", "jsonrpc", "method"]-    vals m p i = fromMaybe False $ parseMaybe (f m p i) o-    f m p i _ = do-        j <- o .:? "jsonrpc"-        guard $ maybe True (== ("2.0" :: Text)) j-        i' <- o .: "id"-        guard $ i == i'-        m' <- o .: "method"-        guard $ m == m'-        p' <- o .:? "params" .!= Null-        guard $ toJSON p == p'-        return True--reqDecode :: (Eq a, ToRequest a, ToJSON a, FromRequest a) => Request a -> Bool-reqDecode rq = case parseMaybe parseRequest (toJSON rq) of-    Nothing  -> False-    Just rqE -> either (const False) (rq ==) rqE------- Notifications-----notifFields :: (ToNotif a, ToJSON a) => Notif a -> Bool-notifFields rn = case rn of-    Notif V1 m p -> n1ks && vals m p-    Notif V2 m p -> n2ks && vals m p-  where-    (Object o) = toJSON rn-    n1ks = sort (M.keys o) == ["id", "method", "params"]-    n2ks = sort (M.keys o) == ["jsonrpc", "method", "params"]-        || sort (M.keys o) == ["jsonrpc", "method"]-    vals m p = fromMaybe False $ parseMaybe (f m p) o-    f m p _ = do-        i <- o .:? "id" .!= Null-        guard $ i == Null-        j <- o .:? "jsonrpc"-        guard $ maybe True (== ("2.0" :: Text)) j-        m' <- o .: "method"-        guard $ m == m'-        p' <- o .:? "params" .!= Null-        guard $ toJSON p == p'-        return True--notifDecode :: (Eq a, ToNotif a, ToJSON a, FromNotif a)-            => Notif a -> Bool-notifDecode rn = case parseMaybe parseNotif (toJSON rn) of-    Nothing  -> False-    Just rnE -> either (const False) (rn ==) rnE------- Responses-----resFields :: (Eq a, ToJSON a, FromJSON a) => Response a -> Bool-resFields rs = case rs of-    Response V1 s i -> s1ks && vals s i-    Response V2 s i -> s2ks && vals s i-  where-    (Object o) = toJSON rs-    s1ks = sort (M.keys o) == ["error", "id", "result"]-    s2ks = sort (M.keys o) == ["id", "jsonrpc", "result"]-    vals s i = fromMaybe False $ parseMaybe (f s i) o-    f s i _ = do-        i' <- o .: "id"-        guard $ i == i'-        j <- o .:? "jsonrpc"-        guard $ maybe True (== ("2.0" :: Text)) j-        s' <- o .: "result"-        guard $ s == s'-        e <- o .:? "error" .!= Null-        guard $ e == Null-        return True--resDecode :: (Eq r, ToJSON r, FromResponse r)-          => ReqRes q r -> Bool-resDecode (ReqRes rq rs) = case parseMaybe (parseResponse rq) (toJSON rs) of-    Nothing -> False-    Just rsE -> either (const False) (rs ==) rsE--rpcBadResId :: forall q r. (ToJSON r, FromResponse r)-            => ReqRes q r -> Bool-rpcBadResId (ReqRes rq rs) = case parseMaybe f (toJSON rs') of-    Nothing -> True-    _ -> False-  where-    f :: FromResponse r => Value -> Parser (Either ErrorObj (Response r))-    f = parseResponse rq-    rs' = rs { getResId = IdNull }--rpcErrRes :: forall q r. FromResponse r => (ReqRes q r, ErrorObj) -> Bool-rpcErrRes (ReqRes rq _, re) = case parseMaybe f (toJSON re') of-    Nothing -> False-    Just (Left _) -> True-    _ -> False-  where-    f :: FromResponse r => Value -> Parser (Either ErrorObj (Response r))-    f = parseResponse rq-    re' = re { getErrId = getReqId rq }------- Conduit-----newMsgConduit :: ( ToRequest q, ToJSON q, ToNotif n, ToJSON n-                 , ToJSON r, FromResponse r )-              => [Message q n r] -> Property-newMsgConduit (snds) = monadicIO $ do-    msgs <- run $ do-        qs <- atomically initSession-        CL.sourceList snds' $= msgConduit False qs $$ CL.consume-    assert $ length msgs == length snds'-    assert $ length (filter rqs msgs) == length (filter rqs snds')-    assert $ map idn (filter rqs msgs) == take (length (filter rqs msgs)) [1..]-  where-    rqs (MsgRequest _) = True-    rqs _ = False-    idn (MsgRequest rq) = getIdInt $ getReqId rq-    idn _ = error "Unexpected request"-    snds' = flip map snds $ \m -> case m of-        (MsgRequest rq) -> MsgRequest $ rq { getReqId = IdNull }-        _ -> m+checkVerId :: Ver -> 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)+    return True -decodeReqConduit :: forall q. (ToRequest q, FromRequest q, Eq q, ToJSON q)-                 => ([Request q], Ver) -> Property-decodeReqConduit (vs, ver) = monadicIO $ do-    inmsgs <- run $ do-        qs  <- atomically initSession-        qs' <- atomically initSession-        CL.sourceList vs-            $= CL.map f-            $= msgConduit False qs-            $= encodeConduit-            $= decodeConduit ver False qs'-            $$ CL.consume-    assert $ not (any unexpected inmsgs)-    assert $ all (uncurry match) (zip vs inmsgs)-  where-    unexpected :: IncomingMsg () q () () -> Bool-    unexpected (IncomingMsg (MsgRequest _) Nothing) = False-    unexpected _ = True-    match rq (IncomingMsg (MsgRequest rq') _) =-        rq { getReqId = getReqId rq' } == rq'-    match _ _ = False-    f rq = MsgRequest $ rq { getReqId = IdNull } :: Message q () ()+checkFieldsReqNotif :: Ver -> Method -> Value -> Id -> Object -> Parser Bool+checkFieldsReqNotif ver m v i o = do+    checkVerId ver i o >>= guard+    o .: "method" >>= guard . (==m)+    o .: "params" >>= guard . (==v)+    return True -decodeResConduit :: forall q r.-                    ( ToRequest q, FromRequest q, Eq q, ToJSON q, ToJSON r-                    , FromResponse r, Eq r )-                 => ([ReqRes q r], Ver) -> Property-decodeResConduit (rr, ver) = monadicIO $ do-    inmsgs <- run $ do-        qs  <- atomically initSession-        qs' <- atomically initSession-        CL.sourceList vs-            $= CL.map f-            $= msgConduit False qs-            $= encodeConduit-            $= decodeConduit ver False qs'-            $= CL.map respond-            $= encodeConduit-            $= decodeConduit ver False qs-            $$ CL.consume-    assert $ not (any unexpected inmsgs)-    assert $ all (uncurry match) (zip vs inmsgs)-  where-    unexpected :: IncomingMsg q () () r -> Bool-    unexpected (IncomingMsg (MsgResponse _) (Just _)) = False-    unexpected _ = True+checkFieldsReq :: Request -> Object -> Parser Bool+checkFieldsReq (Request ver m v i) = checkFieldsReqNotif ver m v i -    match rq (IncomingMsg (MsgResponse rs) (Just rq')) =-        rq { getReqId = getReqId rq' } == rq'-            && rs == g rq'-    match _ _ = False+checkFieldsNotif :: Notif -> Object -> Parser Bool+checkFieldsNotif (Notif ver m v) = checkFieldsReqNotif ver m v IdNull -    respond :: IncomingMsg () q () () -> Response r-    respond (IncomingMsg (MsgRequest rq) Nothing) = g rq-    respond _ = undefined+checkFieldsRes :: Response -> Object -> Parser Bool+checkFieldsRes (Response ver v i) o = do+    checkVerId ver i o >>= guard+    o .: "result" >>= guard . (==v)+    return True -    f rq = MsgRequest $ rq { getReqId = IdNull } :: Message q () ()-    vs = map (\(ReqRes rq _) -> rq) rr+checkFieldsErr :: RpcError -> Object -> Parser Bool+checkFieldsErr (RpcError ver e i) o = do+    checkVerId ver i o >>= guard+    o .: "error" >>= guard . (==e)+    return True -    g rq = let (ReqRes _ rs) = fromJust $ find h rr-               h (ReqRes rq' _) = getReqParams rq == getReqParams rq'-           in  rs { getResId = getReqId rq }+testFields :: ToJSON r => (Object -> Parser Bool) -> r -> Bool+testFields ck r = fromMaybe False . parseMaybe f $ toJSON r where+    f = withObject "json" ck -decodeErrConduit :: forall q r.-                    ( ToRequest q, FromRequest q, Eq q, ToJSON q, ToJSON r-                    , FromResponse r, Eq r, Show r, Show q )-                 => ([ReqRes q r], Ver) -> Property-decodeErrConduit (vs, ver) = monadicIO $ do-    inmsgs <- run $ do-        qs  <- atomically initSession-        qs' <- atomically initSession-        CL.sourceList vs-            $= CL.map f-            $= msgConduit False qs-            $= encodeConduit-            $= decodeConduit ver False qs'-            $= CL.map respond-            $= encodeConduit-            $= decodeConduit ver False qs-            $$ CL.consume-    assert $ not (any unexpected inmsgs)-    assert $ all (uncurry match) (zip vs inmsgs)-  where-    unexpected :: IncomingMsg q () () r -> Bool-    unexpected (IncomingMsg (MsgError _) (Just _)) = False-    -- unexpected _ = True-    unexpected i = error $ show i+testEncodeDecode :: (Eq r, ToJSON r, FromJSON r) => r -> Bool+testEncodeDecode r = maybe False (==r) $ parseMaybe parseJSON (toJSON r) -    match (ReqRes rq _) (IncomingMsg (MsgError _) (Just rq')) =-        rq' { getReqId = getReqId rq } == rq-    match _ _ = False+reqFields :: Request -> Bool+reqFields rq = testFields (checkFieldsReq rq) rq -    respond :: IncomingMsg () q () () -> ErrorObj-    respond (IncomingMsg (MsgRequest (Request ver' _ _ i)) Nothing) =-        ErrorObj ver' "test" (getIdInt i) Null i-    respond _ = undefined+notifFields :: Notif -> Bool+notifFields nt = testFields (checkFieldsNotif nt) nt -    f (ReqRes rq _) = MsgRequest $ rq { getReqId = IdNull } :: Message q () ()+resFields :: Response -> Bool+resFields rs = testFields (checkFieldsRes rs) rs -type ClientAppConduits = AppConduits Value Value Value () () () IO-type ServerAppConduits = AppConduits () () () Value Value Value IO+errFields :: RpcError -> Bool+errFields er = testFields (checkFieldsErr er) er -sendMsgNet :: ([Message Value Value Value], Ver) -> Property-sendMsgNet (rs, ver) = monadicIO $ do+serverTest :: ([Request], Ver) -> Property+serverTest (reqs, ver) = monadicIO $ do     rt <- run $ do-        mv <- newEmptyMVar-        to <- atomically $ newTBMChan 128-        ti <- atomically $ newTBMChan 128-        let tiSink   = sinkTBMChan ti True-            toSource = sourceTBMChan to-            toSink   = sinkTBMChan to True-            tiSource = sourceTBMChan ti-        withAsync (srv tiSink toSource mv) $ \_ ->-            runConduits ver False toSink tiSource (cliApp mv)-    assert $ length rt == length rs-    assert $ all (uncurry match) (zip rs rt)+        (bso, bsi) <- 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+    assert $ null rt || all isJust rt+    assert $ params == reverse (results rt)   where-    srv tiSink toSource mv = runConduits ver False tiSink toSource (srvApp mv)--    srvApp :: MVar [IncomingMsg () Value Value Value]-           -> ServerAppConduits -> IO ()-    srvApp mv (src, snk) =-        (CL.sourceNull $$ snk) >> (src $$ CL.consume) >>= putMVar mv--    cliApp :: MVar [IncomingMsg () Value Value Value]-           -> ClientAppConduits -> IO [IncomingMsg () Value Value Value]-    cliApp mv (src, snk) =-        (CL.sourceList rs $$ snk) >> (src $$ CL.sinkNull) >> readMVar mv--    match (MsgRequest rq) (IncomingMsg (MsgRequest rq') Nothing) =-        rq == rq'-    match (MsgNotif rn) (IncomingMsg (MsgNotif rn') Nothing) =-        rn == rn'-    match (MsgResponse _) (IncomingError e) =-        getErrMsg e == "Id not recognized"-    match (MsgError e) (IncomingMsg (MsgError e') Nothing) =-        getErrMsg e == getErrMsg e'-    match (MsgError _) (IncomingError e) =-        getErrMsg e == "Id not recognized"-    match _ _ = False--type TwoWayAppConduits = AppConduits Value Value Value Value Value Value IO+    r q = return $ Right (q :: Value)+    srv snk src = runJsonRpcT ver r snk src dummySrv+    sender bsi = forM_ reqs $ 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+    results = map $ getResult . fromJust -twoWayNet :: ([Message Value Value Value], Ver) -> Property-twoWayNet (rr, ver) = monadicIO $ do+clientTest :: ([Value], Ver) -> Property+clientTest (qs, ver) = monadicIO $ do     rt <- run $ do-        to <- atomically $ newTBMChan 128-        ti <- atomically $ newTBMChan 128-        let tiSink   = sinkTBMChan ti True-            toSource = sourceTBMChan to-            toSink   = sinkTBMChan to True-            tiSource = sourceTBMChan ti-        withAsync (srv tiSink toSource) $ \_ ->-            runConduits ver False toSink tiSource cliApp-    assert $ length rt == length rs-    assert $ all (uncurry match) (zip rs rt)-  where-    rs = map f rr where-        f (MsgRequest rq) = MsgRequest $ rq { getReqId = IdNull }-        f m = m--    cliApp :: TwoWayAppConduits -> IO [IncomingMsg Value Value Value Value]-    cliApp (src, snk) = (CL.sourceList rs $$ snk) >> (src $$ CL.consume)--    srv tiSink toSource = runConduits ver False tiSink toSource srvApp--    srvApp :: TwoWayAppConduits -> IO ()-    srvApp (src, snk) = src $= CL.map respond $$ snk--    respond (IncomingError e) =-        MsgError e-    respond (IncomingMsg (MsgRequest (Request ver' _ p i)) _) =-        MsgResponse (Response ver' p i)-    respond (IncomingMsg (MsgNotif rn) _) =-        MsgNotif rn-    respond (IncomingMsg (MsgError e) _) =-        MsgNotif (Notif (getErrVer e) "error" (toJSON e))-    respond _ = undefined--    match (MsgRequest (Request ver' m p _))-        ( IncomingMsg (MsgResponse (Response ver'' p' _))-                      (Just (Request ver''' m' p'' _)) ) =-        p == p' && p == p'' && m == m' && ver' == ver'' && ver'' == ver'''-    match (MsgNotif (Notif ver' _ p))-        (IncomingMsg (MsgNotif (Notif ver'' _ p')) Nothing) =-        p == p' && ver' == ver''-    match (MsgResponse (Response ver' _ _))-        (IncomingMsg (MsgError e) Nothing) =-        ver' == getErrVer e && getErrMsg e == "Id not recognized"-    match (MsgError e@(ErrorObj _ _ _ _ IdNull))-        (IncomingMsg (MsgNotif (Notif _ "error" e')) Nothing) =-        toJSON e == e'-    match (MsgError _)-        (IncomingMsg (MsgError e) Nothing) =-        getErrMsg e == "Id not recognized"-    match _ _ = False--realNet :: ([Request Value], Ver) -> Property-realNet (rr, ver) = monadicIO $ do-    rs <- run $ withAsync (tcpServer ver ss srvApp) $ const cli-    assert $ length rs == length rr-    assert $-        map (getReqParams . fromJust . matchingReq) rs == map getReqParams rr+        (bso, bsi) <- 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 csnk csrc+    assert $ length rt == length qs+    assert $ null rt || all correct rt+    assert $ qs == results rt   where-    ss = serverSettings 58493 "127.0.0.1"-    cs = clientSettings 58493 "127.0.0.1"--    cli = do-        cE <- try $ tcpClient ver True cs cliApp-        either (const cli) return-            (cE :: Either SomeException [IncomingMsg Value () () Value])--    srvApp :: AppConduits () () Value Value () () IO -> IO ()-    srvApp (src, snk) = src $= CL.map respond $$ snk--    cliApp :: AppConduits Value () () () () Value IO-           -> IO [IncomingMsg Value () () Value]-    cliApp (src, snk) = do-        CL.sourceList (map f rr) $$ snk-        src $$ CL.consume-      where-        f rq = MsgRequest (rq { getReqId = IdNull })--    respond (IncomingMsg (MsgRequest (Request ver' _ p i)) _) =-        MsgResponse (Response ver' p i)-    respond _ = undefined+    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 >=> liftIO . atomically+    results = map fromJust . rights+    correct (Right (Just _)) = True+    correct _ = False