json-rpc 0.6.2.1 → 0.7.0.0
raw patch · 6 files changed
+507/−356 lines, 6 filesdep +vectorPVP ok
version bump matches the API change (PVP)
Dependencies added: vector
API changes (from Hackage documentation)
+ Network.JsonRpc: BatchRequest :: ![Request] -> BatchRequest
+ Network.JsonRpc: BatchResponse :: ![Response] -> BatchResponse
+ Network.JsonRpc: MsgBatch :: ![Message] -> Message
+ Network.JsonRpc: SingleRequest :: !Request -> BatchRequest
+ Network.JsonRpc: SingleResponse :: !Response -> BatchResponse
+ Network.JsonRpc: data BatchRequest
+ Network.JsonRpc: data BatchResponse
+ Network.JsonRpc: dead :: Session -> TVar Bool
+ Network.JsonRpc: getBatch :: Message -> ![Message]
+ Network.JsonRpc: getBatchRequest :: BatchRequest -> ![Request]
+ Network.JsonRpc: getBatchResponse :: BatchResponse -> ![Response]
+ Network.JsonRpc: getSingleRequest :: BatchRequest -> !Request
+ Network.JsonRpc: getSingleResponse :: BatchResponse -> !Response
+ Network.JsonRpc: receiveBatchRequest :: MonadLoggerIO m => JsonRpcT m (Maybe BatchRequest)
+ Network.JsonRpc: sendBatchRequest :: (MonadLoggerIO m, ToJSON q, ToRequest q, FromResponse r) => [q] -> JsonRpcT m [Maybe (Either ErrorObj r)]
+ Network.JsonRpc: sendBatchResponse :: MonadLoggerIO m => BatchResponse -> JsonRpcT m ()
- Network.JsonRpc: Session :: TBMChan (Either Response Message) -> TBMChan Message -> Maybe (TBMChan Request) -> TVar Id -> TVar SentRequests -> Ver -> Session
+ Network.JsonRpc: Session :: TBMChan (Either Response Value) -> TBMChan Message -> Maybe (TBMChan BatchRequest) -> TVar Id -> TVar SentRequests -> Ver -> TVar Bool -> Session
- Network.JsonRpc: decodeConduit :: MonadLogger m => Ver -> Conduit ByteString m (Either Response Message)
+ Network.JsonRpc: decodeConduit :: MonadLogger m => Ver -> Conduit ByteString m (Either Response Value)
- Network.JsonRpc: encodeConduit :: MonadLogger m => Conduit Message m ByteString
+ Network.JsonRpc: encodeConduit :: (ToJSON j, MonadLogger m) => Conduit j m ByteString
- Network.JsonRpc: inCh :: Session -> TBMChan (Either Response Message)
+ Network.JsonRpc: inCh :: Session -> TBMChan (Either Response Value)
- Network.JsonRpc: reqCh :: Session -> Maybe (TBMChan Request)
+ Network.JsonRpc: reqCh :: Session -> Maybe (TBMChan BatchRequest)
- Network.JsonRpc: runJsonRpcT :: (MonadLoggerIO m, MonadBaseControl IO m) => Ver -> Bool -> Sink Message m () -> Source m (Either Response Message) -> JsonRpcT m a -> m a
+ Network.JsonRpc: runJsonRpcT :: (MonadLoggerIO m, MonadBaseControl IO m) => Ver -> Bool -> Sink ByteString m () -> Source m ByteString -> JsonRpcT m a -> m a
Files
- Network/JsonRpc/Arbitrary.hs +17/−0
- Network/JsonRpc/Data.hs +60/−16
- Network/JsonRpc/Interface.hs +198/−153
- README.md +30/−15
- json-rpc.cabal +3/−2
- test/Network/JsonRpc/Tests.hs +199/−170
Network/JsonRpc/Arbitrary.hs view
@@ -37,11 +37,28 @@ , ErrorVal <$> arbitrary ] +instance Arbitrary BatchRequest where+ arbitrary = oneof+ [ BatchRequest <$> arbitrary+ , SingleRequest <$> arbitrary+ ]++instance Arbitrary BatchResponse where+ arbitrary = oneof+ [ BatchResponse <$> arbitrary+ , SingleResponse <$> arbitrary+ ]+ instance Arbitrary Message where arbitrary = oneof [ MsgRequest <$> arbitrary , MsgResponse <$> arbitrary+ , MsgBatch <$> batch ]+ where+ batch = listOf $ oneof [ MsgRequest <$> arbitrary+ , MsgResponse <$> arbitrary+ ] instance Arbitrary Id where arbitrary = oneof [IdInt <$> arbitrary, IdTxt <$> arbitrary]
Network/JsonRpc/Data.hs view
@@ -4,6 +4,7 @@ module Network.JsonRpc.Data ( -- * Requests Request(..)+, BatchRequest(..) -- ** Parsing , FromRequest(..) , fromRequest@@ -13,6 +14,7 @@ -- * Responses , Response(..)+, BatchResponse(..) -- ** Parsing , FromResponse(..) , fromResponse@@ -66,7 +68,7 @@ , getReqMethod :: !Method , getReqParams :: !Value }- deriving (Eq, Show)+ deriving (Eq, Show, Generic) instance NFData Request where rnf (Request v m p i) = rnf v `seq` rnf m `seq` rnf p `seq` rnf i@@ -118,7 +120,7 @@ parseVerIdMethParams :: Object -> Parser (Ver, Maybe Id, Method, Value) parseVerIdMethParams o = do v <- parseVer o- i <- parseId o+ i <- o .:? "id" m <- o .: "method" p <- o .:? "params" .!= Null return (v, i, m, p)@@ -162,7 +164,7 @@ | OrphanError { getResVer :: !Ver , getError :: !ErrorObj }- deriving (Eq, Show)+ deriving (Eq, Show, Generic) instance NFData Response where@@ -189,6 +191,7 @@ -- Method corresponds to request to which this response answers. parseResult :: Method -> Maybe (Value -> Parser r) +-- | Parse a response knowing the method of the corresponding request. fromResponse :: FromResponse r => Method -> Response -> Maybe r fromResponse m (Response _ r _) = parseResult m >>= flip parseMaybe r fromResponse _ _ = Nothing@@ -215,11 +218,12 @@ -> Parser (Ver, Maybe Id, Either ErrorObj Value) parseVerIdResultError o = do v <- parseVer o- i <- parseId o+ i <- o .:? "id" r <- o .:? "result" .!= Null p <- if r == Null then Left <$> o .: "error" else return $ Right r return (v, i, p) +-- | Create a response from a request. Use in servers. buildResponse :: (Monad m, FromRequest q, ToJSON r) => Respond q m r -> Request@@ -234,15 +238,17 @@ Right r -> return . Just $ Response v (toJSON r) i buildResponse _ _ = return Nothing +-- | Type of function to make it easy to create a response from a request.+-- Meant to be used in servers. type Respond q m r = q -> m (Either ErrorObj r) --- Error object from JSON-RPC 2.0. ErrorVal for backwards compatibility.+-- | 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)+ deriving (Show, Eq, Generic) instance NFData ErrorObj where rnf (ErrorObj m c d) = rnf m `seq` rnf c `seq` rnf d@@ -264,6 +270,7 @@ ++ if d == Null then [] else ["data" .= d] toJSON (ErrorVal v) = v +-- | Get a user-friendly string with the error information. fromError :: ErrorObj -> String fromError (ErrorObj m c v) = show c ++ ": " ++ m ++ ": " ++ valueAsString v fromError (ErrorVal (String t)) = T.unpack t@@ -297,22 +304,62 @@ -- Messages -- +data BatchRequest+ = BatchRequest { getBatchRequest :: ![Request] }+ | SingleRequest { getSingleRequest :: !Request }+ deriving (Eq, Show, Generic)++instance NFData BatchRequest where+ rnf (BatchRequest qs) = rnf qs+ rnf (SingleRequest q) = rnf q++instance FromJSON BatchRequest where+ parseJSON qs@Array{} = BatchRequest <$> parseJSON qs+ parseJSON q@Object{} = SingleRequest <$> parseJSON q+ parseJSON _ = mzero++instance ToJSON BatchRequest where+ toJSON (BatchRequest qs) = toJSON qs+ toJSON (SingleRequest q) = toJSON q++data BatchResponse+ = BatchResponse { getBatchResponse :: ![Response] }+ | SingleResponse { getSingleResponse :: !Response }+ deriving (Eq, Show, Generic)++instance NFData BatchResponse where+ rnf (BatchResponse qs) = rnf qs+ rnf (SingleResponse q) = rnf q++instance FromJSON BatchResponse where+ parseJSON qs@Array{} = BatchResponse <$> parseJSON qs+ parseJSON q@Object{} = SingleResponse <$> parseJSON q+ parseJSON _ = mzero++instance ToJSON BatchResponse where+ toJSON (BatchResponse qs) = toJSON qs+ toJSON (SingleResponse q) = toJSON q+ data Message- = MsgRequest { getMsgRequest :: !Request }- | MsgResponse { getMsgResponse :: !Response }- deriving (Eq, Show)+ = MsgRequest { getMsgRequest :: !Request }+ | MsgResponse { getMsgResponse :: !Response }+ | MsgBatch { getBatch :: ![Message] }+ deriving (Eq, Show, Generic) instance NFData Message where rnf (MsgRequest q) = rnf q rnf (MsgResponse r) = rnf r+ rnf (MsgBatch b) = rnf b instance ToJSON Message where toJSON (MsgRequest q) = toJSON q toJSON (MsgResponse r) = toJSON r+ toJSON (MsgBatch b) = toJSON b instance FromJSON Message where- parseJSON v = (MsgRequest <$> parseJSON v)- <|> (MsgResponse <$> parseJSON v)+ parseJSON v = (MsgRequest <$> parseJSON v)+ <|> (MsgResponse <$> parseJSON v)+ <|> (MsgBatch <$> parseJSON v) -- -- Types@@ -344,15 +391,12 @@ toJSON (IdTxt s) = toJSON s toJSON (IdInt n) = toJSON n -parseId :: Object -> Parser (Maybe Id)-parseId o = do- d <- o .:? "id" .!= Null- if d == Null then return Nothing else parseJSON d-+-- | Pretty display a message id. Meant for logs. fromId :: Id -> String fromId (IdInt i) = show i fromId (IdTxt t) = T.unpack t +-- | JSON-RPC version. data Ver = V1 -- ^ JSON-RPC 1.0 | V2 -- ^ JSON-RPC 2.0 deriving (Eq, Show, Read, Generic)
Network/JsonRpc/Interface.hs view
@@ -13,8 +13,11 @@ -- * Communicate with remote party , receiveRequest+, receiveBatchRequest , sendResponse+, sendBatchResponse , sendRequest+, sendBatchRequest -- * Transports -- ** Client@@ -49,22 +52,25 @@ import qualified Data.HashMap.Strict as M import Data.Conduit import qualified Data.Conduit.List as CL+import Data.Maybe import Data.Conduit.Network import Data.Conduit.TMChan-import qualified Data.Text as T+import qualified Data.Foldable as F+import qualified Data.Vector as V import Network.JsonRpc.Data type SentRequests = HashMap Id (TMVar (Maybe Response)) -data Session = Session { inCh :: TBMChan (Either Response Message)+data Session = Session { inCh :: TBMChan (Either Response Value) , outCh :: TBMChan Message- , reqCh :: Maybe (TBMChan Request)+ , reqCh :: Maybe (TBMChan BatchRequest) , lastId :: TVar Id , sentReqs :: TVar SentRequests , rpcVer :: Ver+ , dead :: TVar Bool } --- Context for JSON-RPC connection. Connection will remain active as long+-- Context for JSON-RPC connection. Connection will remain active as long -- as context is maintaned. type JsonRpcT = ReaderT Session @@ -76,24 +82,16 @@ <*> newTVar (IdInt 0) <*> newTVar M.empty <*> return v+ <*> newTVar False -encodeConduit :: MonadLogger m => Conduit Message m ByteString-encodeConduit = CL.mapM $ \m -> do- $(logDebug) $ T.pack $ unwords $ case m of- MsgRequest Request{getReqId = i} ->- [ "encoding request id:", fromId i ]- MsgRequest Notif{} ->- [ "encoding notification" ]- MsgResponse Response{getResId = i} ->- [ "encoding response id:", fromId i ]- MsgResponse ResponseError{getResId = i} ->- [ "encoding error id:", fromId i ]- MsgResponse OrphanError{} ->- [ "encoding error without id" ]- return . L8.toStrict $ encode m+-- Conduit to encode JSON to ByteString.+encodeConduit :: (ToJSON j, MonadLogger m) => Conduit j m ByteString+encodeConduit = CL.mapM $ \m -> return . L8.toStrict $ encode m +-- | Conduit to decode incoming messages. Left Response indicates+-- a response to send back to sender if parsing JSON fails. decodeConduit :: MonadLogger m- => Ver -> Conduit ByteString m (Either Response Message)+ => Ver -> Conduit ByteString m (Either Response Value) decodeConduit ver = evalStateT loop Nothing where loop = lift await >>= maybe flush process flush = get >>= maybe (return ()) (handle True . ($ B8.empty))@@ -108,139 +106,178 @@ loop handle _ (Partial k) = put (Just k) >> loop handle _ (Done rest v) = do- let msg = decod v- when (isLeft msg) $ $(logError) "received invalid message"- lift $ yield msg+ lift $ yield $ Right v if B8.null rest then loop else process rest - decod v = case parseMaybe parseJSON v of- Just msg -> Right msg- Nothing -> Left $ OrphanError ver (errorInvalid v)-+-- | Process incoming messages. Do not use this directly unless you know+-- what you are doing. This is an internal function. processIncoming :: (Functor m, MonadLoggerIO m) => JsonRpcT m ()-processIncoming = do- i <- reader inCh- o <- reader outCh- qM <- reader reqCh- s <- reader sentReqs- join . liftIO . atomically $ readTBMChan i >>= \inc ->- case inc of- Nothing -> do- m <- readTVar s- mapM_ ((`putTMVar` Nothing) . snd) $ M.toList m- return $ do- $(logDebug) "closed incoming channel"- unless (M.null m) $- $(logError) "some requests did not get responses"- return ()- Just (Left e) -> do- writeTBMChan o (MsgResponse e)- return $ do- $(logError) "replied to sender with error"- processIncoming- Just (Right (MsgRequest req)) ->- case qM of- Just q -> do- writeTBMChan q req- return $ do- $(logDebug) "received request"- processIncoming- Nothing ->- case req of- Request v m _ d -> do- let e = ResponseError v (errorMethod m) d- writeTBMChan o (MsgResponse e)- return $ do- $(logError) $ T.pack $ unwords- [ "rejected incoming request id:"- , fromId d- ]- processIncoming- Notif{} -> return $ do- $(logError) $ "ignored incoming notification"- processIncoming- Just (Right (MsgResponse res)) -> do- let hasId = case res of- Response{} -> True- ResponseError{} -> True- OrphanError{} -> False- if hasId- then do- let x = getResId res- m <- readTVar s- let pM = x `M.lookup` m- case pM of- Nothing -> do- let v = getResVer res- e = errorId x- err = OrphanError v e- writeTBMChan o $ MsgResponse err- return $ do- $(logError) $ T.pack $ unwords- [ "got response with unknown id:"- , fromId x- ]- processIncoming- Just p -> do- writeTVar s $ M.delete x m- putTMVar p $ Just res- return $ do- $(logDebug) $ T.pack $ unwords- [ "received response id:"- , fromId x- ]- processIncoming- else return $ do- $(logError) $ T.pack $ unwords- [ "ignoring orhpan error:"- , fromError (getError res)- ]+processIncoming = ask >>= \qs -> join . liftIO . atomically $ do+ vEM <- readTBMChan $ inCh qs+ case vEM of+ Nothing -> flush qs+ Just vE ->+ case vE of+ Right v@Object{} -> do+ single qs v+ return $ do+ $(logDebug) "received message" processIncoming+ Right v@(Array a) -> do+ if V.null a+ then do+ let e = OrphanError (rpcVer qs) (errorInvalid v)+ writeTBMChan (outCh qs) $ MsgResponse e+ else batch qs (V.toList a)+ return $ do+ $(logDebug) "received batch"+ processIncoming+ Right v -> do+ let e = OrphanError (rpcVer qs) (errorInvalid v)+ writeTBMChan (outCh qs) $ MsgResponse e+ return $ do+ $(logWarn) "got invalid message"+ processIncoming+ Left e -> do+ writeTBMChan (outCh qs) $ MsgResponse e+ return $ do+ $(logWarn) "error parsing JSON"+ processIncoming+ where+ flush qs = do+ m <- readTVar $ sentReqs qs+ F.forM_ (reqCh qs) closeTBMChan+ closeTBMChan $ outCh qs+ writeTVar (dead qs) True+ mapM_ ((`putTMVar` Nothing) . snd) $ M.toList m+ return $ do+ $(logDebug) "session is now dead"+ unless (M.null m) $ $(logError) "requests remained unfulfilled"+ batch qs vs = do+ ts <- catMaybes <$> forM vs (process qs)+ unless (null ts) $+ if any isRight ts+ then do+ let ch = fromJust $ reqCh qs+ writeTBMChan ch $ BatchRequest $ rights ts+ else writeTBMChan (outCh qs) $ MsgBatch $ lefts ts+ single qs v = do+ tM <- process qs v+ case tM of+ Nothing -> return ()+ Just (Right t) -> do+ let ch = fromJust $ reqCh qs+ writeTBMChan ch $ SingleRequest t+ Just (Left e) -> writeTBMChan (outCh qs) e+ process qs v = do+ let qM = parseMaybe parseJSON v+ case qM of+ Just q -> request qs q+ Nothing -> do+ let rM = parseMaybe parseJSON v+ case rM of+ Just r -> response qs r >> return Nothing+ Nothing -> do+ let e = OrphanError (rpcVer qs) (errorInvalid v)+ m = MsgResponse e+ return $ Just $ Left m+ request qs t =+ case reqCh qs of+ Just _ -> return $ Just $ Right t+ Nothing ->+ case t of+ Notif{} -> return Nothing+ Request{} -> do+ let e = errorMethod (getReqMethod t)+ v = getReqVer t+ i = getReqId t+ m = MsgResponse $ ResponseError v e i+ return $ Just $ Left m+ response qs r = do+ let hasid = case r of+ Response{} -> True+ ResponseError{} -> True+ OrphanError{} -> False -- Ignore orphan errors+ when hasid $ do+ let x = getResId r+ m <- readTVar (sentReqs qs)+ case x `M.lookup` m of+ Nothing -> return () -- Ignore orphan responses+ Just p -> do+ writeTVar (sentReqs qs) $ M.delete x m+ putTMVar p $ Just r -- | Returns Nothing if did not receive response, could not parse it, or--- request was a notification. Just Left contains the error object returned+-- request is a notification. Just Left contains the error object returned -- by server if any. Just Right means response was received just right.-sendRequest :: (MonadLoggerIO m, ToJSON q, ToRequest q, FromResponse r)+sendRequest :: (MonadLoggerIO m , ToJSON q, ToRequest q, FromResponse r) => q -> JsonRpcT m (Maybe (Either ErrorObj r))-sendRequest q = do+sendRequest q = head `liftM` sendBatchRequest [q]++-- | Send multiple requests in a batch. If only a single request, do not+-- put it in a batch.+sendBatchRequest :: (MonadLoggerIO m, ToJSON q, ToRequest q, FromResponse r)+ => [q] -> JsonRpcT m [Maybe (Either ErrorObj r)]+sendBatchRequest qs = do v <- reader rpcVer l <- reader lastId s <- reader sentReqs o <- reader outCh- if requestIsNotif q- then do- $(logDebug) "sending notification"- liftIO . atomically $ do- let req = buildRequest v q undefined- writeTBMChan o $ MsgRequest req- $(logDebug) "notification sent"- return Nothing- else do- $(logDebug) "sending request"- p <- liftIO . atomically $ do- p <- newEmptyTMVar - i <- succ <$> readTVar l- m <- readTVar s- let req = buildRequest v q i- writeTVar s $ M.insert i p m- writeTBMChan o $ MsgRequest req - writeTVar l i- return p- $(logDebug) "request sent, awaiting for response"- liftIO . atomically $ takeTMVar p >>= \rM -> case rM of- Nothing -> return Nothing- Just y@Response{} ->- case fromResponse (requestMethod q) y of- Nothing -> return Nothing- Just x -> return . Just $ Right x- Just e@ResponseError{} ->- return . Just $ Left $ getError e- _ -> undefined+ k <- reader dead+ aps <- liftIO . atomically $ do+ d <- readTVar k+ aps <- forM qs $ \q ->+ if requestIsNotif q+ then return (buildRequest v q undefined, Nothing)+ else do+ p <- newEmptyTMVar + i <- succ <$> readTVar l+ m <- readTVar s+ unless d $ writeTVar s $ M.insert i p m+ unless d $ writeTVar l i+ if d+ then return (buildRequest v q i, Nothing)+ else return (buildRequest v q i, Just p)+ case map fst aps of+ [] -> return ()+ [a] -> unless d $ writeTBMChan o $ MsgRequest a+ as -> unless d $ writeTBMChan o $ MsgBatch $ map MsgRequest as+ return aps+ if null aps+ then $(logDebug) "no responses pending"+ else $(logDebug) "listening for responses if pending"+ liftIO . atomically $ forM aps $ \(a, pM) ->+ case pM of+ Nothing -> return Nothing+ Just p -> do+ rM <- takeTMVar p+ case rM of+ Nothing -> return Nothing+ Just r@Response{} ->+ case fromResponse (getReqMethod a) r of+ Nothing -> return Nothing+ Just x -> return $ Just $ Right x+ Just e -> return $ Just $ Left $ getError e -- | Receive requests from remote endpoint. Returns Nothing if incoming--- channel is closed or has never been opened.+-- channel is closed or has never been opened. Will reject incoming request+-- if sent in a batch. receiveRequest :: MonadLoggerIO m => JsonRpcT m (Maybe Request) receiveRequest = do+ bt <- receiveBatchRequest+ case bt of+ Nothing -> return Nothing+ Just (SingleRequest q) -> return $ Just q+ Just BatchRequest{} -> do+ v <- reader rpcVer+ let e = errorInvalid $ String "not accepting batches"+ m = OrphanError v e+ sendResponse m+ return Nothing++-- | Receive batch of requests. Will also accept single requests.+receiveBatchRequest :: MonadLoggerIO m => JsonRpcT m (Maybe BatchRequest)+receiveBatchRequest = do chM <- reader reqCh case chM of Just ch -> do@@ -250,30 +287,42 @@ $(logError) "ignoring requests from remote endpoint" return Nothing +-- | Send response message. Do not use to respond to a batch of requests. sendResponse :: MonadLoggerIO m => Response -> JsonRpcT m () sendResponse r = do o <- reader outCh liftIO . atomically . writeTBMChan o $ MsgResponse r +-- | Send batch of responses. Use to respond to a batch of requests.+sendBatchResponse :: MonadLoggerIO m => BatchResponse -> JsonRpcT m ()+sendBatchResponse (BatchResponse rs) = do+ o <- reader outCh+ liftIO . atomically . writeTBMChan o $ MsgBatch $ map MsgResponse rs+sendBatchResponse (SingleResponse r) = do+ o <- reader outCh+ liftIO . atomically . writeTBMChan o $ MsgResponse r++-- | Send any message. Do not use this. Use the other high-level functions+-- instead. Will not track request ids. Incoming responses to requests sent+-- using this method will be ignored. sendMessage :: MonadLoggerIO m => Message -> JsonRpcT m () sendMessage msg = reader outCh >>= liftIO . atomically . (`writeTBMChan` msg) --- | Create JSON-RPC session around conduits from transport--- layer. When context exits session disappears.+-- | Create JSON-RPC session around conduits from transport layer. When+-- context exits session disappears. runJsonRpcT :: (MonadLoggerIO m, MonadBaseControl IO m)- => Ver -- ^ JSON-RPC version- -> Bool -- ^ Ignore incoming requests or notifications- -> Sink Message m () -- ^ Sink to send messages- -> Source m (Either Response Message)- -- ^ Incoming messages or error responses to be returned to sender- -> JsonRpcT m a -- ^ JSON-RPC action- -> m a -- ^ Output of action+ => Ver -- ^ JSON-RPC version+ -> Bool -- ^ Ignore incoming requests/notifs+ -> Sink ByteString m () -- ^ Sink to send messages+ -> Source m ByteString -- ^ Source to receive messages from+ -> JsonRpcT m a -- ^ JSON-RPC action+ -> m a -- ^ Output of action runJsonRpcT ver ignore snk src f = do qs <- liftIO . atomically $ initSession ver ignore let inSnk = sinkTBMChan (inCh qs) True outSrc = sourceTBMChan (outCh qs)- withAsync (src $$ inSnk) $ const $- withAsync (outSrc $$ snk) $ \o ->+ withAsync (src $$ decodeConduit ver $= inSnk) $ const $+ withAsync (outSrc =$ encodeConduit $$ snk) $ \o -> withAsync (runReaderT processIncoming qs) $ const $ do a <- runReaderT f qs liftIO $ do@@ -298,9 +347,7 @@ -> JsonRpcT m a -- ^ JSON-RPC action -> m a -- ^ Output of action jsonRpcTcpClient ver ignore cs f = runGeneralTCPClient cs $ \ad ->- runJsonRpcT ver ignore- (encodeConduit =$ cr =$ appSink ad)- (appSource ad $= decodeConduit ver) f+ runJsonRpcT ver ignore (cr =$ appSink ad) (appSource ad) f -- | TCP server transport for JSON-RPC. jsonRpcTcpServer@@ -311,6 +358,4 @@ -> JsonRpcT m () -- ^ Action to perform on connecting client thread -> m a jsonRpcTcpServer ver ignore ss f = runGeneralTCPServer ss $ \cl ->- runJsonRpcT ver ignore- (encodeConduit =$ cr =$ appSink cl)- (appSource cl $= decodeConduit ver) f+ runJsonRpcT ver ignore (cr =$ appSink cl) (appSource cl) f
README.md view
@@ -24,10 +24,13 @@ ``` haskell {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE TemplateHaskell #-}+import Control.Monad import Control.Monad.Trans import Control.Monad.Logger import Data.Aeson.Types hiding (Error) import Data.Conduit.Network+import qualified Data.Foldable as F+import Data.Maybe import Data.Time.Clock import Data.Time.Format import Network.JsonRpc@@ -56,21 +59,21 @@ srv :: MonadLoggerIO m => JsonRpcT m () srv = do $(logDebug) "listening for new request"- qM <- receiveRequest+ qM <- receiveBatchRequest case qM of Nothing -> do $(logDebug) "closed request channel, exting" return ()- Just q -> do+ Just (SingleRequest q) -> do $(logDebug) "got request" rM <- buildResponse respond q- case rM of- Nothing -> do- $(logDebug) "no response for this request"- srv- Just r -> do- $(logDebug) "sending response"- sendResponse r >> srv+ F.forM_ rM sendResponse+ srv+ Just (BatchRequest qs) -> do+ $(logDebug) "got request batch"+ rs <- catMaybes `liftM` forM qs (buildResponse respond)+ sendBatchResponse $ BatchResponse rs+ srv ``` Client Example@@ -112,21 +115,33 @@ f t = parseTime defaultTimeLocale "%c" (T.unpack t) parseResult _ = Nothing -req :: MonadLoggerIO m => JsonRpcT m (Either String UTCTime)+req :: MonadLoggerIO m => JsonRpcT m UTCTime req = do $(logDebug) "sending time request" ts <- sendRequest TimeReq- $(logDebug) "received response" case ts of- Nothing -> return $ Left "could not parse response"- Just (Left e) -> return . Left $ fromError e- Just (Right (TimeRes r)) -> return $ Right r+ Nothing -> error "could not parse response"+ Just (Left e) -> error $ fromError e+ Just (Right (TimeRes r)) -> return r +reqBatch :: MonadLoggerIO m => JsonRpcT m [UTCTime]+reqBatch = do+ $(logDebug) "sending time requests"+ ts <- sendBatchRequest $ replicate 4 TimeReq+ forM ts $ \t ->+ case t of+ Nothing -> error "could not parse response"+ Just (Left e) -> error $ fromError e+ Just (Right (TimeRes r)) -> return r++ main :: IO () main = runStderrLoggingT $ jsonRpcTcpClient V2 True (clientSettings 31337 "::1") $ do $(logDebug) "sending four time requests one second apart" replicateM_ 4 $ do- req >>= liftIO . print+ req >>= $(logDebug) . T.pack . ("response: "++) . show liftIO (threadDelay 1000000)+ $(logDebug) "sending four time requests in a batch"+ reqBatch >>= $(logDebug) . T.pack . ("response: "++) . show ```
json-rpc.cabal view
@@ -1,5 +1,5 @@ name: json-rpc-version: 0.6.2.1+version: 0.7.0.0 synopsis: Fully-featured JSON-RPC 2.0 library description: This JSON-RPC library is fully-compatible with JSON-RPC 2.0 and 1.0. It@@ -24,7 +24,7 @@ source-repository this type: git location: https://github.com/xenog/json-rpc.git- tag: 0.6.2.1+ tag: 0.7.0.0 library exposed-modules: Network.JsonRpc@@ -48,6 +48,7 @@ text >= 0.11 && < 1.3, transformers >= 0.3, unordered-containers >= 0.2 && < 0.3,+ vector >= 0.10 && < 0.11, QuickCheck >= 2.6 && < 2.9 default-language: Haskell2010 ghc-options: -Wall
test/Network/JsonRpc/Tests.hs view
@@ -4,6 +4,7 @@ module Network.JsonRpc.Tests (tests) where import Control.Applicative+import Control.Arrow import Control.Concurrent.Async.Lifted import Control.Concurrent.STM import Control.Monad@@ -32,38 +33,44 @@ tests :: [Test] tests =- [ testGroup "JSON-RPC Requests"- [ testProperty "Check fields"+ [ testGroup "Messages"+ [ testProperty "Check request fields" (reqFields :: Request -> Bool)- , testProperty "Encode/decode"+ , testProperty "Encode/decode request" (testEncodeDecode :: Request -> Bool)- ]- , testGroup "JSON-RPC Responses"- [ testProperty "Check fields"+ , testProperty "Check response fields" (resFields :: Response -> Bool)- , testProperty "Encode/decode"+ , testProperty "Encode/decode response" (testEncodeDecode :: Response -> Bool)+ , testProperty "Encode/decode message"+ (testEncodeDecode :: Message -> Bool) ] , testGroup "Conduits" [ testProperty "Encoding" testEncodeConduit , testProperty "Decoding" testDecodeConduit , testProperty "Decode Garbage" testDecodeGarbageConduit- , testProperty "Decode Invalid" testDecodeInvalidConduit ]- , testGroup "JSON-RPC monad"+ , testGroup "Functions" [ testProperty "Processing incoming messages" testProcessIncoming , testProperty "Processing incoming responses" testIncomingResponse , testProperty "Receiving incoming requests" testReceiveRequest+ , testProperty "Receiving incoming batch requests"+ testReceiveBatchRequest , testProperty "Sending responses" testSendResponse+ , testProperty "Sending batch responses" testSendBatchResponse+ , testProperty "Sending request batch" testSendBatchRequest ] , testGroup "Network"- [ testProperty "Test server" serverTest- , testProperty "Test client" clientTest- , testProperty "Test notifications" notifTest- , testProperty "Test send request" testSendRequest+ [ testProperty "Client/server talk" clientTest+ , testProperty "Notifications" notifTest ] ] +data ReqList = ReqList { getReqList :: [Req] } deriving (Show, Eq)++instance Arbitrary ReqList where+ arbitrary = resize 3 $ ReqList <$> arbitrary+ data Req = Req { getReq :: Request } deriving (Show, Eq) instance Arbitrary Req where@@ -87,7 +94,7 @@ requestIsNotif ReqNotReq{} = False requestIsNotif ReqNotNot{} = True -data Struct = Struct { getValue :: Value } deriving (Show, Eq)+data Struct = Struct Value deriving (Show, Eq) instance Arbitrary Struct where arbitrary = resize 10 $ Struct <$> oneof [lsn, objn] where@@ -105,7 +112,16 @@ objn = toJSON . M.fromList <$> listOf psn psn = (,) <$> (arbitrary :: Gen String) <*> oneof [val, ls, obj] +reqFields :: Request -> Bool+reqFields rq = testFields (checkFieldsReq rq) rq +resFields :: Response -> Bool+resFields rs = testFields (checkFieldsRes rs) rs++fromRight :: Either a b -> b+fromRight (Right x) = x+fromRight _ = undefined+ checkVerId :: Ver -> Maybe Id -> Object -> Parser Bool checkVerId ver i o = do j <- o .:? "jsonrpc"@@ -157,7 +173,7 @@ testDecodeConduit (msgs, ver) = monadicIO $ do rt <- run $ runNoLoggingT $ CL.sourceList (buffer encoded) =$ decodeConduit ver $$ CL.consume- assert $ rt == map Right msgs+ assert $ rt == map (Right . toJSON) msgs where encoded = B.concat $ map (L.toStrict . encode) msgs buffer bs | B.null bs = []@@ -169,27 +185,15 @@ CL.sourceList (map B.pack ss) =$ decodeConduit ver $$ CL.consume assert $ all isLeft rt -testDecodeInvalidConduit :: ([Struct], Ver) -> Property-testDecodeInvalidConduit (structs, ver) = monadicIO $ do- rt <- run $ runNoLoggingT $- CL.sourceList (buffer encoded) =$ decodeConduit ver $$ CL.consume- assert $ all invalid rt- where- encoded = B.concat $ map (L.toStrict . encode) msgs- buffer bs | B.null bs = []- | otherwise = B.take 16 bs : buffer (B.drop 16 bs)- msgs = map getValue structs- invalid (Left OrphanError{getError = ErrorObj{getErrCode = (-32600)}}) =- True- invalid _ = False--testProcessIncoming :: ([Either Response Message], Ver, Bool) -> Property+testProcessIncoming :: ([Either Response Message], Ver, Bool)+ -> Property testProcessIncoming (msgs, ver, ignore) = monadicIO $ do rt <- run $ runNoLoggingT $ do expect <- liftIO . atomically $ newTMChan qs <- liftIO . atomically $ initSession ver ignore- withAsync (cli qs expect msgs) $ const $+ withAsync (cli qs expect msgs) $ \c -> do runReaderT processIncoming qs+ wait c validate expect True assert rt where@@ -202,56 +206,81 @@ closeTMChan expect closeTBMChan $ inCh qs cli qs expect (x:xs) = do- liftIO . atomically $ writeTBMChan (inCh qs) x+ liftIO . atomically $ writeTBMChan (inCh qs) (toJSON <$> x) liftIO . atomically $ case x of- Left e -> do- m <- readTBMChan $ outCh qs- case m of- Just (MsgResponse err) ->- if err == e- then writeTMChan expect True- else writeTMChan expect False- _ -> writeTMChan expect False+ Left e ->+ caseLeftResponse qs e >>= writeTMChan expect Right (MsgRequest req@Request{}) ->+ caseSingleRequest req qs >>= writeTMChan expect+ Right (MsgRequest nt@Notif{}) ->+ caseSingleNotif nt qs >>= writeTMChan expect+ Right MsgResponse{} ->+ writeTMChan expect True+ Right (MsgBatch []) ->+ caseEmptyBatch qs >>= writeTMChan expect+ Right (MsgBatch ms) ->+ caseBatch qs ms >>= writeTMChan expect+ cli qs expect xs+ caseLeftResponse qs e = do+ m <- readTBMChan $ outCh qs+ case m of+ Just (MsgResponse err) -> return $ err == e+ _ -> return False+ caseEmptyBatch qs = do+ m <- readTBMChan $ outCh qs+ case m of+ Just (MsgResponse (OrphanError v (ErrorObj _ i _))) ->+ return $ v == rpcVer qs && i == (-32600)+ _ -> return False+ caseBatch qs ms = do+ let isrq (MsgRequest Request{}) = True+ isrq _ = False+ isnotif (MsgRequest Notif{}) = True+ isnotif _ = False+ if any isrq ms+ then if ignore then do m <- readTBMChan $ outCh qs case m of- Just (MsgResponse (ResponseError _ e _)) ->- case e of- ErrorObj{} ->- if getErrCode e == (-32601)- then writeTMChan expect True- else writeTMChan expect False- _ -> writeTMChan expect False- _ -> writeTMChan expect False- else do- m <- readTBMChan (fromJust $ reqCh qs)- if m == Just req- then writeTMChan expect True- else writeTMChan expect False- Right (MsgRequest nt@Notif{}) ->- if ignore- then writeTMChan expect True+ Just MsgBatch{} -> return True+ _ -> return False else do- m <- readTBMChan (fromJust $ reqCh qs)- if m == Just nt- then writeTMChan expect True- else writeTMChan expect False- Right (MsgResponse OrphanError{}) ->- writeTMChan expect True- Right MsgResponse{} -> do+ m <- readTBMChan $ fromJust $ reqCh qs+ case m of+ Just BatchRequest{} -> return True+ _ -> return False+ else+ if any isnotif ms+ then+ if ignore+ then return True+ else do+ m <- readTBMChan $ fromJust $ reqCh qs+ case m of+ Just BatchRequest{} -> return True+ _ -> return False+ else return True+ caseSingleRequest req qs =+ if ignore+ then do m <- readTBMChan $ outCh qs case m of- Just (MsgResponse (OrphanError _ e@ErrorObj{})) ->- if getErrCode e == (-32000)- then writeTMChan expect True- else writeTMChan expect False- _ -> writeTMChan expect False- cli qs expect xs+ Just (MsgResponse (ResponseError _ e@ErrorObj{} _)) ->+ return $ getErrCode e == (-32601)+ _ -> return False+ else do+ m <- readTBMChan (fromJust $ reqCh qs)+ return $ m == Just (SingleRequest req)+ caseSingleNotif nt qs =+ if ignore+ then return True+ else do+ m <- readTBMChan (fromJust $ reqCh qs)+ return $ m == Just (SingleRequest nt) -testIncomingResponse :: ([Req], Ver, Bool) -> Property-testIncomingResponse (reqss, ver, ignore) = nodup ==> monadicIO $ do+testIncomingResponse :: ([ReqList], Ver, Bool) -> Property+testIncomingResponse (reqss', ver, ignore) = nodup ==> monadicIO $ do rt <- run $ runNoLoggingT $ do expect <- liftIO . atomically $ newTMChan qs <- liftIO . atomically $ do@@ -267,14 +296,20 @@ return $ valid && mapempty assert rt where- reqs = map getReq reqss- nodup = length (nubBy ((==) `on` getReqId) reqs) == length reqs+ reqss = map getReqList reqss'+ reqs = let f [q] = SingleRequest (getReq q)+ f qs = BatchRequest (map getReq qs)+ in map f reqss+ flatreqs = let f (BatchRequest bt) = bt+ f (SingleRequest rt) = [rt]+ in concatMap f reqs+ nodup = length (nubBy ((==) `on` getReqId) flatreqs) == length flatreqs respond (Request v _ p i) = Response v p i respond _ = undefined- responses = map respond reqs+ responses = map respond flatreqs msgs = map MsgResponse responses sent = do- promises <- forM reqs $+ promises <- forM flatreqs $ \Request{getReqId = i} -> (,) i <$> newEmptyTMVar return $ M.fromList promises validate _ [] state = return state@@ -286,72 +321,93 @@ cli qs expect [] = liftIO . atomically $ do closeTMChan expect closeTBMChan $ inCh qs- cli qs expect (x:xs) = do- let i = getResId $ getMsgResponse x- p <- liftIO . atomically $ do+ cli qs expect (x:xs) =+ case x of+ MsgResponse res -> do+ p <- getpromise qs res+ liftIO . atomically $ writeTBMChan (inCh qs) (Right $ toJSON x)+ liftIO . atomically $ do+ rE <- readTMVar p+ F.forM_ rE $ writeTMChan expect+ cli qs expect xs+ MsgBatch resps -> do+ ps <- mapM (getpromise qs . getMsgResponse) resps+ liftIO . atomically $ writeTBMChan (inCh qs) (Right $ toJSON x)+ forM_ ps $ \p -> liftIO . atomically $ do+ rE <- readTMVar p+ F.forM_ rE $ writeTMChan expect+ _ -> undefined+ getpromise qs res = do+ let i = getResId res+ liftIO . atomically $ do snt <- readTVar (sentReqs qs) return . fromJust $ M.lookup i snt- liftIO . atomically $ writeTBMChan (inCh qs) (Right x)- liftIO . atomically $ do- rE <- readTMVar p- F.forM_ rE $ writeTMChan expect- cli qs expect xs -testSendRequest :: ([(ReqNot, Either ErrorObj Value)], Ver, Bool) -> Property-testSendRequest (reqnotres, ver, ignore) = monadicIO $ do- (ex, rs) <- run $ runNoLoggingT $ do- expect <- liftIO . atomically $ newTMChan+testSendBatchRequest :: ([(ReqNot, Either ErrorObj Value)], Ver, Bool)+ -> Property+testSendBatchRequest (reqnotres, ver, ignore) = nonull ==> monadicIO $ do+ rs <- run $ runNoLoggingT $ do qs <- liftIO . atomically $ initSession ver ignore- rs <- withAsync (cli qs expect reqnotres) $ \c -> do- rs <- runReaderT (mapM (sendRequest . fst) reqnotres) qs+ withAsync (cli qs) $ \c -> do+ rs <- runReaderT (sendBatchRequest $ map fst reqnotres) qs wait c return (rs :: [Maybe (Either ErrorObj Value)])- ex <- liftIO . atomically $ getexpect expect []- return (ex, rs) assert $ length rs == length reqnotres- assert $ and $ zipWith matchresult reqnotres rs- assert $ and $ zipWith matchnotif (map fst reqnotres) ex where- matchnotif q r =- case q of- ReqNotReq v ->- case r of- Just (MsgRequest Request{getReqParams = v'}) -> v == v'- _ -> False- ReqNotNot v ->- case r of- Just (MsgRequest Notif{getReqParams = v'}) -> v == v'- _ -> False- matchresult (q, e) r =- case q of- ReqNotReq{} -> Just e == r- _ -> isNothing r- getexpect expect acc = do- valM <- readTMChan expect- case valM of- Nothing -> return $ reverse acc- Just val -> getexpect expect (val:acc)+ nonull = not $ null reqnotres+ resmap = M.fromList $ map (first toJSON) reqnotres respond (Request v _ _ i) (Left y) = Just $ ResponseError v y i respond (Request v _ _ i) (Right y) = Just $ Response v y i respond _ _ = undefined- cli _ expect [] = liftIO . atomically $ closeTMChan expect- cli qs expect ((x,y):xs) = join . liftIO . atomically $ do+ fulfill req sent = F.forM_ (getReqId req `M.lookup` sent) $ \p ->+ putTMVar p $ respond req $ fromJust $ getReqParams req `M.lookup` resmap+ cli qs = liftIO . atomically $ do msg <- readTBMChan (outCh qs)- writeTMChan expect msg- unless (requestIsNotif x) $- case msg of- Just (MsgRequest req@Request{}) -> do- sent <- readTVar (sentReqs qs)- F.forM_ (getReqId req `M.lookup` sent) $ \p ->- putTMVar p $ respond req y- _ -> return ()- return $ cli qs expect xs+ sent <- readTVar (sentReqs qs)+ case msg of+ Just (MsgRequest req@Request{}) -> fulfill req sent+ Just (MsgBatch bt) ->+ forM_ bt $ \m ->+ case m of+ MsgRequest req@Request{} -> fulfill req sent+ _ -> return ()+ _ -> return () -testReceiveRequest :: ([Request], Ver, Bool) -> Property+testReceiveRequest :: ([BatchRequest], Ver, Bool) -> Property testReceiveRequest (reqs, ver, ignore) = monadicIO $ do rt <- run $ runNoLoggingT $ do qs <- liftIO . atomically $ initSession ver ignore withAsync (send qs) $ \c -> do+ res <- runReaderT receive qs+ wait c+ return res+ assert $ and rt+ where+ send qs = F.forM_ (reqCh qs) $ \qch -> do+ forM_ reqs $ liftIO . atomically . writeTBMChan qch+ liftIO . atomically $ closeTBMChan qch+ receive = forM reqs $ \q -> do+ t <- receiveRequest+ if ignore+ then return $ isNothing t+ else+ case q of+ SingleRequest{} ->+ return $ Just q == fmap SingleRequest t+ BatchRequest{} -> do+ ch <- reader outCh+ m <- liftIO . atomically $ readTBMChan ch+ case m of+ Just (MsgResponse OrphanError{}) -> + return $ isNothing t+ _ -> return $ isNothing t+++testReceiveBatchRequest :: ([BatchRequest], Ver, Bool) -> Property+testReceiveBatchRequest (reqs, ver, ignore) = monadicIO $ do+ rt <- run $ runNoLoggingT $ do+ qs <- liftIO . atomically $ initSession ver ignore+ withAsync (send qs) $ \c -> do rt <- runReaderT receive qs wait c return rt@@ -362,7 +418,7 @@ send qs = F.forM_ (reqCh qs) $ \qch -> do forM_ reqs $ liftIO . atomically . writeTBMChan qch liftIO . atomically $ closeTBMChan qch- receive = forM reqs $ const receiveRequest+ receive = forM reqs $ const receiveBatchRequest testSendResponse :: ([Response], Ver, Bool) -> Property testSendResponse (responses, ver, ignore) = monadicIO $ do@@ -378,11 +434,21 @@ receive qs = forM responses $ const $ liftIO . atomically $ readTBMChan (outCh qs) -reqFields :: Request -> Bool-reqFields rq = testFields (checkFieldsReq rq) rq--resFields :: Response -> Bool-resFields rs = testFields (checkFieldsRes rs) rs+testSendBatchResponse :: ([BatchResponse], Ver, Bool) -> Property+testSendBatchResponse (responses, ver, ignore) = monadicIO $ do+ ex <- run $ runNoLoggingT $ do+ qs <- liftIO . atomically $ initSession ver ignore+ withAsync (receive qs) $ \c -> do+ runReaderT send qs+ wait c+ assert $ length ex == length responses+ assert $ all isJust ex && map fromJust ex == map msg responses+ where+ msg (BatchResponse bt) = MsgBatch $ map MsgResponse bt+ msg (SingleResponse r) = MsgResponse r+ send = forM_ responses sendBatchResponse+ receive qs = forM responses $ const $ liftIO . atomically $+ readTBMChan (outCh qs) createChans :: MonadIO m => m ((TBMChan a, TBMChan b), (Sink a m (), Source m b))@@ -391,43 +457,13 @@ let (snk, src) = (sinkTBMChan bso False, sourceTBMChan bsi) return ((bso, bsi), (snk, src)) -serverTest :: ([Request], Ver) -> Property-serverTest (reqs, ver) = monadicIO $ do- rt <- run $ runNoLoggingT $ do- ((bso, bsi), (snk, src)) <- createChans- withAsync (server snk src) $ const $- withAsync (sender bsi) $ const $ receiver bso []- assert $ length rt == length nonotif- assert $ null rt || all isJust rt- assert $ params == reverse (results rt)- where- respond q = return $ Right (q :: Value)- server snk src = runJsonRpcT ver False- (encodeConduit =$ snk) (src =$ decodeConduit ver) (srv respond)- sender bsi = forM_ reqs $ liftIO . atomically .- writeTBMChan bsi . L.toStrict . encode . MsgRequest- receiver bso xs =- if length xs == length nonotif- then return xs- else liftIO (atomically $ readTBMChan bso) >>= \b -> case b of- Just x -> do- let res = decodeStrict' x :: Maybe Response- receiver bso (res:xs)- Nothing -> undefined- params = map getReqParams nonotif- results = map $ getResult . fromJust- nonotif = flip filter reqs $ \q -> case q of Request{} -> True- Notif{} -> False- clientTest :: ([Value], Ver) -> Property clientTest (qs, ver) = monadicIO $ do rt <- run $ runNoLoggingT $ do ((bso, bsi), (snk, src)) <- createChans let csnk = sinkTBMChan bsi False csrc = sourceTBMChan bso- withAsync (server snk src) $ const $ cli- (CL.map Right =$ csnk)- (csrc $= CL.map Right)+ withAsync (server snk src) $ const $ cli csnk csrc assert $ length rt == length qs assert $ null rt || all correct rt assert $ qs == results rt@@ -447,16 +483,14 @@ csrc = sourceTBMChan bso (sig, notifs) <- liftIO . atomically $ (,) <$> newEmptyTMVar <*> newTVar []- withAsync (server snk src sig notifs) $ const $ cli sig- (CL.map Right =$ csnk)- (csrc $= CL.map Right)+ withAsync (server snk src sig notifs) $ const $ cli sig csnk csrc liftIO . atomically $ readTVar notifs assert $ length nt == length ntfs assert $ reverse nt == ntfs where respond q = return $ Right (q :: Value) server snk src sig notifs =- runJsonRpcT ver False snk src $ process sig notifs+ runJsonRpcT ver False snk src (process sig notifs) process sig notifs = do qM <- receiveRequest case qM of@@ -491,8 +525,3 @@ case rM of Nothing -> srv respond Just r -> sendResponse r >> srv respond---fromRight :: Either a b -> b-fromRight (Right x) = x-fromRight _ = undefined