Safe Haskell | None |
---|
- type AppConduits qo no ro qi ni ri m = (Source m (IncomingMsg qo qi ni ri), Sink (Message qo no ro) m ())
- data IncomingMsg qo qi ni ri
- = IncomingMsg {
- incomingMsg :: !(Message qi ni ri)
- matchingReq :: !(Maybe (Request qo))
- | IncomingError { }
- = IncomingMsg {
- 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
- 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
- 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 ()
- query :: (ToJSON qo, ToRequest qo, FromResponse ri) => Ver -> [qo] -> AppConduits qo () () () () ri IO -> IO [IncomingMsg qo () () ri]
- data Session qo = Session {
- lastId :: TVar Id
- sentRequests :: TVar (SentRequests qo)
- isLast :: TQueue Bool
- type SentRequests qo = HashMap Id (Request qo)
- initSession :: STM (Session qo)
- encodeConduit :: (ToJSON a, Monad m) => Conduit a m ByteString
- msgConduit :: MonadIO m => Bool -> Session qo -> Conduit (Message qo no ro) m (Message qo no ro)
- decodeConduit :: (FromRequest qi, FromNotif ni, FromResponse ri, MonadIO m) => Ver -> Bool -> Session qo -> Conduit ByteString m (IncomingMsg qo qi ni ri)
- data Request q = Request {
- getReqVer :: !Ver
- getReqMethod :: !Method
- getReqParams :: !q
- getReqId :: !Id
- class FromRequest q where
- paramsParser :: Method -> Maybe (Value -> Parser q)
- parseRequest :: FromRequest q => Value -> Parser (Either ErrorObj (Request q))
- class ToRequest q where
- requestMethod :: q -> Method
- buildRequest :: ToRequest q => Ver -> q -> Request q
- data Response r = Response {}
- class FromResponse r where
- parseResult :: Method -> Value -> Parser r
- parseResponse :: FromResponse r => Request q -> Value -> Parser (Either ErrorObj (Response r))
- data Notif n = Notif {
- getNotifVer :: !Ver
- getNotifMethod :: !Method
- getNotifParams :: !n
- class FromNotif n where
- notifParamsParser :: Method -> Maybe (Value -> Parser n)
- parseNotif :: FromNotif n => Value -> Parser (Either ErrorObj (Notif n))
- class ToNotif n where
- notifMethod :: n -> Method
- buildNotif :: ToNotif n => Ver -> n -> Notif n
- data ErrorObj = ErrorObj {
- getErrVer :: !Ver
- getErrMsg :: !String
- getErrCode :: !Int
- getErrData :: !Value
- getErrId :: !Id
- errorParse :: Ver -> Value -> ErrorObj
- errorInvalid :: Ver -> Value -> ErrorObj
- errorParams :: Ver -> Value -> Id -> ErrorObj
- errorMethod :: Ver -> Method -> Id -> ErrorObj
- errorId :: Ver -> Id -> ErrorObj
- data Message q n r
- = MsgRequest {
- getMsgRequest :: !(Request q)
- | MsgNotif {
- getMsgNotif :: !(Notif n)
- | MsgResponse {
- getMsgResponse :: !(Response r)
- | MsgError {
- getMsgError :: !ErrorObj
- = MsgRequest {
- type Method = Text
- data Id
- data Ver
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.
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 Example
This server returns the current time.
{-# LANGUAGE OverloadedStrings #-} import Data.Aeson.Types import Data.Conduit import qualified Data.Conduit.List as CL import Data.Conduit.Network import Data.Time.Clock import Data.Time.Format import Network.JsonRpc import System.Locale data TimeReq = TimeReq data TimeRes = TimeRes UTCTime instance FromRequest TimeReq where paramsParser "time" = Just $ const $ return TimeReq paramsParser _ = 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 main :: IO () main = tcpServer V2 (serverSettings 31337 "127.0.0.1") srv
Client Example
Corresponding TCP client to get time from server.
{-# LANGUAGE OverloadedStrings #-} 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 import Data.Time.Format import Network.JsonRpc import System.Locale data TimeReq = TimeReq data TimeRes = TimeRes UTCTime instance ToRequest TimeReq where requestMethod TimeReq = "time" instance ToJSON TimeReq where toJSON TimeReq = emptyArray instance FromResponse TimeRes where parseResult "time" = withText "time" $ \t -> case f t of Nothing -> fail "Could not parse time" Just t' -> return $ TimeRes t' where f t = parseTime defaultTimeLocale "%c" (T.unpack t) 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 main :: IO () main = tcpClient V2 True (clientSettings 31337 "127.0.0.1") cli >>= print
Conduits
High-Level
type AppConduits qo no ro qi ni ri m = (Source m (IncomingMsg qo qi ni ri), Sink (Message qo no ro) m ())Source
Conduits of sending and receiving JSON-RPC messages.
data IncomingMsg qo qi ni ri Source
Incoming messages. Responses and corresponding requests go together.
IncomingError
is for problems decoding incoming messages. These should be
sent to the remote party.
IncomingMsg | |
| |
IncomingError | |
:: (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 |
:: (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 |
:: (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 () |
:: (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 |
Send requests and get responses (or errors).
Example:
tcpClient V2 True (clientSettings 31337 "127.0.0.1") (query V2 [TimeReq])
Low-Level
JSON-RPC session mutable data.
Session | |
|
type SentRequests qo = HashMap Id (Request qo)Source
Map of ids to sent requests.
initSession :: STM (Session qo)Source
Initialize JSON-RPC session.
encodeConduit :: (ToJSON a, Monad m) => Conduit a m ByteStringSource
Conduit that serializes JSON documents for sending to the network.
:: 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) |
Conduit for outgoing JSON-RPC messages.
Adds an id to requests whose id is IdNull
.
Tracks all sent requests to match corresponding responses.
:: (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 |
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.
Requests
Request | |
|
Parsing
class FromRequest q whereSource
Class for data that can be received in JSON-RPC requests.
parseRequest :: FromRequest q => Value -> Parser (Either ErrorObj (Request q))Source
Parse JSON-RPC request.
Encoding
Class for data that can be sent as JSON-RPC requests. Define a method name for each request.
requestMethod :: q -> MethodSource
Responses
JSON-RPC response data type
Parsing
class FromResponse r whereSource
Class for data that can be received inside JSON-RPC responses.
parseResult :: Method -> Value -> Parser rSource
Parse result field from JSON-RPC response.
parseResponse :: FromResponse r => Request q -> Value -> Parser (Either ErrorObj (Response r))Source
Parse JSON-RPC response.
Notifications
Class for JSON-RPC notifications.
Notif | |
|
Parsing
Class for data that can be received in JSON-RPC notifications.
Encoding
Errors
JSON-RPC errors.
Error Messages
errorParse :: Ver -> Value -> ErrorObjSource
Parse error.
errorInvalid :: Ver -> Value -> ErrorObjSource
Invalid request.
Others
Class for any JSON-RPC message.
MsgRequest | |
| |
MsgNotif | |
| |
MsgResponse | |
| |
MsgError | |
|
JSON-RPC message id.