{-# LANGUAGE CPP #-}
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE RankNTypes #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TypeApplications #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE TypeOperators #-}
#if MIN_VERSION_servant_server(0,18,0)
{-# LANGUAGE UndecidableInstances #-}
#endif
{-# OPTIONS_GHC -fno-warn-orphans #-}
module Servant.Server.JsonRpc
( serveJsonRpc
, RouteJsonRpc (..)
, module Servant.JsonRpc
, PossibleContent
, PossibleJsonRpcResponse
) where
import Data.Aeson (FromJSON (..), ToJSON (..), Value)
import Data.Aeson.Types (parseEither)
import Data.Bifunctor (bimap)
import Data.Map.Strict (Map)
import qualified Data.Map.Strict as Map
import Data.Proxy (Proxy (..))
import GHC.TypeLits (KnownSymbol, symbolVal)
import Servant.API ((:<|>) (..), (:>), JSON,
NoContent (..), Post, ReqBody)
import Servant.API.ContentTypes (AllCTRender (..))
#if MIN_VERSION_servant_server(0,18,0)
import Servant.Server (Handler, HasServer (..), HasContextEntry, type (.++), DefaultErrorFormatters, ErrorFormatters)
#elif MIN_VERSION_servant_server(0,14,0)
import Servant.Server (Handler, HasServer (..))
#endif
import Servant.JsonRpc
data PossibleContent a = SomeContent a | EmptyContent
instance ToJSON a => AllCTRender '[JSON] (PossibleContent a) where
handleAcceptH px h = \case
SomeContent x -> handleAcceptH px h x
EmptyContent -> handleAcceptH px h NoContent
type PossibleJsonRpcResponse = PossibleContent (JsonRpcResponse Value Value)
type RawJsonRpcEndpoint
= ReqBody '[JSON] (Request Value)
:> Post '[JSON] PossibleJsonRpcResponse
#if MIN_VERSION_servant_server(0,18,0)
instance (RouteJsonRpc api, HasContextEntry (context .++ DefaultErrorFormatters) ErrorFormatters) => HasServer (RawJsonRpc api) context where
#elif MIN_VERSION_servant_server(0,14,0)
instance RouteJsonRpc api => HasServer (RawJsonRpc api) context where
#endif
type ServerT (RawJsonRpc api) m = RpcHandler api m
route _ cx = route endpoint cx . fmap (serveJsonRpc pxa pxh)
where
endpoint = Proxy @RawJsonRpcEndpoint
pxa = Proxy @api
pxh = Proxy @Handler
hoistServerWithContext _ _ f x = hoistRpcRouter (Proxy @api) f x
class RouteJsonRpc a where
type RpcHandler a (m :: * -> *)
jsonRpcRouter
:: Monad m => Proxy a -> Proxy m -> RpcHandler a m
-> Map String (Value -> m (PossibleContent (Either (JsonRpcErr Value) Value)))
hoistRpcRouter :: Proxy a -> (forall x . m x -> n x) -> RpcHandler a m -> RpcHandler a n
generalizeResponse
:: (ToJSON e, ToJSON r)
=> Either (JsonRpcErr e) r
-> Either (JsonRpcErr Value) Value
generalizeResponse = bimap repack toJSON
where
repack e = e { errorData = toJSON <$> errorData e }
onDecodeFail :: String -> JsonRpcErr e
onDecodeFail msg = JsonRpcErr invalidParamsCode msg Nothing
instance (KnownSymbol method, FromJSON p, ToJSON e, ToJSON r) => RouteJsonRpc (JsonRpc method p e r) where
type RpcHandler (JsonRpc method p e r) m = p -> m (Either (JsonRpcErr e) r)
jsonRpcRouter _ _ h = Map.fromList [ (methodName, h') ]
where
methodName = symbolVal $ Proxy @method
onDecode = fmap generalizeResponse . h
h' = fmap SomeContent
. either (return . Left . onDecodeFail) onDecode
. parseEither parseJSON
hoistRpcRouter _ f x = f . x
instance (KnownSymbol method, FromJSON p) => RouteJsonRpc (JsonRpcNotification method p) where
type RpcHandler (JsonRpcNotification method p) m = p -> m NoContent
jsonRpcRouter _ _ h = Map.fromList [ (methodName, h') ]
where
methodName = symbolVal $ Proxy @method
onDecode x = EmptyContent <$ h x
h' = either (return . SomeContent . Left . onDecodeFail) onDecode
. parseEither parseJSON
hoistRpcRouter _ f x = f . x
instance (RouteJsonRpc a, RouteJsonRpc b) => RouteJsonRpc (a :<|> b) where
type RpcHandler (a :<|> b) m = RpcHandler a m :<|> RpcHandler b m
jsonRpcRouter _ pxm (ha :<|> hb) = jsonRpcRouter pxa pxm ha <> jsonRpcRouter pxb pxm hb
where
pxa = Proxy @a
pxb = Proxy @b
hoistRpcRouter _ f (x :<|> y) = hoistRpcRouter (Proxy @a) f x :<|> hoistRpcRouter (Proxy @b) f y
serveJsonRpc
:: (Monad m, RouteJsonRpc a)
=> Proxy a
-> Proxy m
-> RpcHandler a m
-> Request Value
-> m PossibleJsonRpcResponse
serveJsonRpc px pxm hs (Request m v ix')
| Just h <- Map.lookup m hmap
= h v >>= \case
SomeContent (Right x) | Just ix <- ix' -> return . SomeContent $ Result ix x
| otherwise -> return . SomeContent $ Errors ix' invalidRequest
SomeContent (Left e) -> return . SomeContent $ Errors ix' e
EmptyContent -> return EmptyContent
| otherwise = return . SomeContent $ Errors ix' missingMethod
where
missingMethod = JsonRpcErr methodNotFoundCode ("Unknown method: " <> m) Nothing
hmap = jsonRpcRouter px pxm hs
invalidRequest = JsonRpcErr invalidRequestCode "Missing id" Nothing