remote-json-0.2: Control/Remote/Monad/JSON.hs
{-# LANGUAGE DeriveDataTypeable #-}
{-# LANGUAGE GADTs #-}
{-# LANGUAGE TypeOperators #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE KindSignatures #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE RankNTypes #-}
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE RecursiveDo #-}
{-|
Module: Control.Remote.Monad.JSON where
Copyright: (C) 2015, The University of Kansas
License: BSD-style (see the file LICENSE)
Maintainer: Justin Dawson
Stability: Alpha
Portability: GHC
-}
module Control.Remote.Monad.JSON (
-- * JSON-RPC DSL
RPC, -- abstract
method,
notification,
-- * Invoke the JSON RPC Remote Monad
send,
Session,
weakSession,
strongSession,
applicativeSession,
SendAPI(..),
-- * Types
Args(..)
) where
import Control.Monad.Fail()
import Control.Remote.Monad.JSON.Types
import Control.Monad.Catch()
import Control.Natural
import Data.Aeson
import Data.Text(Text)
import Control.Remote.Monad
import qualified Control.Remote.Monad.Packet.Weak as WP
import qualified Control.Remote.Monad.Packet.Strong as SP
import qualified Control.Remote.Monad.Packet.Applicative as AP
import qualified Data.HashMap.Strict as HM
-- | Sets up a JSON-RPC method call with the function name and arguments
method :: FromJSON a => Text -> Args -> RPC a
method nm args = RPC $ procedure $ Method nm args
-- | Sets up a JSON-RPC notification call with the function name and arguments
notification :: Text -> Args -> RPC ()
notification nm args = RPC $ command $ Notification nm args
runWeakRPC :: (SendAPI ~> IO) -> WP.WeakPacket Notification Method a -> IO a
runWeakRPC f (WP.Command n) = f (Async (toJSON $ NotificationCall $ n))
runWeakRPC f (WP.Procedure m) = do
let tid = 1
v <- f (Sync (toJSON $ mkMethodCall m tid))
res <- parseReply v
parseMethodResult m tid res
runStrongRPC :: (SendAPI ~> IO) -> SP.StrongPacket Notification Method a -> IO a
runStrongRPC f packet = go packet ([]++)
where
go :: forall a . SP.StrongPacket Notification Method a -> ([Notification]->[Notification]) -> IO a
go (SP.Command n cs) ls = go cs (ls . ([n] ++))
go (SP.Done) ls = do
let toSend = (map(toJSON . NotificationCall) (ls []))
() <- sendBatchAsync f toSend
return ()
go (SP.Procedure m) ls = do
let tid = 1
let toSend = (map (toJSON . NotificationCall) (ls []) ) ++ [toJSON $ mkMethodCall m tid]
res <- sendBatchSync f toSend
parseMethodResult m tid res
sendBatchAsync :: (SendAPI ~> IO) -> [Value] -> IO ()
sendBatchAsync _ [] = return () -- never send empty packet
sendBatchAsync f [x] = f (Async x) -- send singleton packet
sendBatchAsync f xs = f (Async (toJSON xs)) -- send batch packet
-- There must be at least one command in the list
sendBatchSync :: (SendAPI ~> IO) -> [Value] -> IO (HM.HashMap IDTag Value)
sendBatchSync f xs = f (Sync (toJSON xs)) >>= parseReply -- send batch packet
runApplicativeRPC :: (SendAPI ~> IO) -> AP.ApplicativePacket Notification Method a -> IO a
runApplicativeRPC f packet = do
case AP.superCommand packet of
Just a -> do () <- sendBatchAsync f (map toJSON $ ls0 [])
return a
Nothing -> do
rs <- sendBatchSync f (map toJSON $ ls0 [])
ff0 rs
where
(ls0,ff0) = go packet 1
go :: forall a . AP.ApplicativePacket Notification Method a -> IDTag
-> ([JSONCall]->[JSONCall], (HM.HashMap IDTag Value -> IO a))
go (AP.Pure a ) _tid = (id, \ _ -> return a)
go (AP.Command aps n) tid = (ls . ([(NotificationCall n)] ++), ff)
where (ls,ff) = go aps tid
go (AP.Procedure aps m ) tid = ( ls . ([mkMethodCall m tid]++)
, \ mp -> ff mp <*> parseMethodResult m tid mp
)
where (ls, ff) = go aps (tid + 1)
-- | Takes a function that handles the sending of Async and Sync messages,
-- and sends each Notification and Method one at a time
weakSession :: (SendAPI :~> IO) -> Session
weakSession f = Session $ runMonad (nat $ runWeakRPC (run f))
-- | Takes a function that handles the sending of Async and Sync messages,
-- and bundles Notifications together terminated by an optional Method
strongSession :: (SendAPI :~> IO) -> Session
strongSession f = Session $ runMonad (nat $ runStrongRPC (run f))
-- | Takes a function that handles the sending of Async and Sync messages,
-- and bundles together Notifications and Procedures that are used in
-- Applicative calls
applicativeSession :: (SendAPI :~> IO) -> Session
applicativeSession f = Session $ runMonad (nat $ runApplicativeRPC (run f))
-- | Send RPC Notifications and Methods by using the given session
send :: Session -> RPC a -> IO a
send (Session f) (RPC m) = f # m