{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE NamedFieldPuns #-}
{-# LANGUAGE RecordWildCards #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TypeFamilies #-}
module Capnp.Rpc.Server
( ServerOps(..)
, CallInfo(..)
, runServer
, MethodHandler
, pureHandler
, rawHandler
, rawAsyncHandler
, methodThrow
, methodUnimplemented
, untypedHandler
, toUntypedHandler
, fromUntypedHandler
, invokeIO
) where
import Control.Concurrent.STM
import Data.Word
import Control.Exception.Safe (MonadCatch, finally, try)
import Control.Monad.IO.Class (MonadIO, liftIO)
import Control.Monad.Primitive (PrimMonad, PrimState)
import Capnp.Classes
( Cerialize
, Decerialize(Cerial, decerialize)
, FromPtr(fromPtr)
, ToStruct(toStruct)
)
import Capnp.Convert (valueToMsg)
import Capnp.Message (ConstMsg, MutMsg)
import Capnp.Rpc.Errors (eMethodUnimplemented, wrapException)
import Capnp.Rpc.Promise
( Fulfiller
, breakPromise
, breakPromiseSTM
, fulfill
, fulfillSTM
, newCallback
)
import Capnp.TraversalLimit (defaultLimit, evalLimitT)
import Capnp.Untyped (Ptr)
import Data.Mutable (freeze)
import qualified Capnp.Gen.Capnp.Rpc.Pure as RpcGen
import qualified Capnp.Message as Message
import qualified Capnp.Untyped as Untyped
import qualified Internal.TCloseQ as TCloseQ
newtype MethodHandler m p r = MethodHandler
{ handleMethod
:: Maybe (Ptr ConstMsg)
-> Fulfiller (Maybe (Ptr ConstMsg))
-> m ()
}
invokeIO
:: MonadIO m
=> MethodHandler m (Maybe (Ptr ConstMsg)) (Maybe (Ptr ConstMsg))
-> Maybe (Ptr ConstMsg)
-> Fulfiller (Maybe (Ptr ConstMsg))
-> m ()
invokeIO = handleMethod
pureHandler ::
( MonadCatch m
, MonadIO m
, PrimMonad m
, s ~ PrimState m
, Decerialize p
, FromPtr ConstMsg (Cerial ConstMsg p)
, Cerialize r
, ToStruct (MutMsg s) (Cerial (MutMsg s) r)
) =>
(cap -> p -> m r)
-> cap
-> MethodHandler m p r
pureHandler f cap = MethodHandler
{ handleMethod = \ptr reply -> do
param <- evalLimitT defaultLimit $
fromPtr Message.empty ptr >>= decerialize
result <- try $ f cap param
case result of
Right val -> do
struct <- evalLimitT defaultLimit $
valueToMsg val >>= freeze >>= Untyped.rootPtr
fulfill reply (Just (Untyped.PtrStruct struct))
Left e ->
breakPromise reply (wrapException False e)
}
rawHandler ::
( MonadCatch m
, MonadIO m
, PrimMonad m
, s ~ PrimState m
, Decerialize p
, FromPtr ConstMsg (Cerial ConstMsg p)
, Decerialize r
, ToStruct ConstMsg (Cerial ConstMsg r)
) =>
(cap -> Cerial ConstMsg p -> m (Cerial ConstMsg r))
-> cap
-> MethodHandler m p r
rawHandler f cap = MethodHandler
{ handleMethod = \ptr reply -> do
cerial <- evalLimitT defaultLimit $ fromPtr Message.empty ptr
result <- try $ f cap cerial
case result of
Right val -> fulfill reply (Just (Untyped.PtrStruct (toStruct val)))
Left e -> breakPromise reply (wrapException False e)
}
rawAsyncHandler ::
( MonadCatch m
, MonadIO m
, PrimMonad m
, s ~ PrimState m
, Decerialize p
, FromPtr ConstMsg (Cerial ConstMsg p)
, Decerialize r
, ToStruct ConstMsg (Cerial ConstMsg r)
) =>
(cap -> Cerial ConstMsg p -> Fulfiller (Cerial ConstMsg r) -> m ())
-> cap
-> MethodHandler m p r
rawAsyncHandler f cap = MethodHandler
{ handleMethod = \ptr reply -> do
fulfiller <- newCallback $ \case
Left e -> breakPromiseSTM reply e
Right v -> fulfillSTM reply $ Just (Untyped.PtrStruct (toStruct v))
cerial <- evalLimitT defaultLimit $ fromPtr Message.empty ptr
f cap cerial fulfiller
}
toUntypedHandler
:: MethodHandler m p r
-> MethodHandler m (Maybe (Ptr ConstMsg)) (Maybe (Ptr ConstMsg))
toUntypedHandler MethodHandler{..} = MethodHandler{..}
fromUntypedHandler
:: MethodHandler m (Maybe (Ptr ConstMsg)) (Maybe (Ptr ConstMsg))
-> MethodHandler m p r
fromUntypedHandler MethodHandler{..} = MethodHandler{..}
untypedHandler
:: (Maybe (Ptr ConstMsg) -> Fulfiller (Maybe (Ptr ConstMsg)) -> m ())
-> MethodHandler m (Maybe (Ptr ConstMsg)) (Maybe (Ptr ConstMsg))
untypedHandler = MethodHandler
methodThrow :: MonadIO m => RpcGen.Exception -> MethodHandler m p r
methodThrow exn = MethodHandler
{ handleMethod = \_ fulfiller -> liftIO $ breakPromise fulfiller exn
}
methodUnimplemented :: MonadIO m => MethodHandler m p r
methodUnimplemented = methodThrow eMethodUnimplemented
data ServerOps m = ServerOps
{ handleCall
:: Word64
-> Word16
-> MethodHandler m (Maybe (Ptr ConstMsg)) (Maybe (Ptr ConstMsg))
, handleStop :: m ()
}
data CallInfo = CallInfo
{ interfaceId :: !Word64
, methodId :: !Word16
, arguments :: Maybe (Ptr ConstMsg)
, response :: Fulfiller (Maybe (Ptr ConstMsg))
}
runServer :: TCloseQ.Q CallInfo -> ServerOps IO -> IO ()
runServer q ops = go `finally` handleStop ops
where
go = atomically (TCloseQ.read q) >>= \case
Nothing ->
pure ()
Just CallInfo{interfaceId, methodId, arguments, response} -> do
handleMethod
(handleCall ops interfaceId methodId)
arguments
response
go