{-# LANGUAGE DataKinds #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE TypeOperators #-}
{-# LANGUAGE UndecidableInstances #-}
module Servant.JsonRpc
(
RawJsonRpc
, JsonRpc
, JsonRpcNotification
, Request (..)
, JsonRpcResponse (..)
, JsonRpcErr (..)
, parseErrorCode
, invalidRequestCode
, methodNotFoundCode
, invalidParamsCode
, internalErrorCode
, JsonRpcEndpoint
) where
import Control.Applicative (liftA3)
import Data.Aeson (FromJSON (..), ToJSON (..), Value (Null),
object, withObject, (.:), (.:?), (.=))
import Data.Aeson.Types (Parser)
import Data.Maybe (isNothing)
import Data.Word (Word64)
import GHC.TypeLits (Symbol)
import Servant.API ((:>), JSON, NoContent, Post, ReqBody)
data Request p
= Request
{ method :: String
, params :: p
, requestId :: Maybe Word64
} deriving (Eq, Show)
instance ToJSON p => ToJSON (Request p) where
toJSON (Request m p ix) =
object
. maybe id (onValue "id") ix
$ [ "jsonrpc" .= ("2.0" :: String)
, "method" .= m
, "params" .= p
]
where
onValue n v = ((n .= v) :)
instance FromJSON p => FromJSON (Request p) where
parseJSON = withObject "JsonRpc Request" $ \obj -> do
ix <- obj .:? "id"
m <- obj .: "method"
p <- obj .: "params"
v <- obj .: "jsonrpc"
versionGuard v . pure $ Request m p ix
versionGuard :: Maybe String -> Parser a -> Parser a
versionGuard v x
| v == Just "2.0" = x
| isNothing v = x
| otherwise = fail "unknown version"
data JsonRpcResponse e r
= Result Word64 r
| Ack Word64
| Errors (Maybe Word64) (JsonRpcErr e)
deriving (Eq, Show)
data JsonRpcErr e = JsonRpcErr
{ errorCode :: Int
, errorMessage :: String
, errorData :: Maybe e
} deriving (Eq, Show)
parseErrorCode :: Int
parseErrorCode = -32700
invalidRequestCode :: Int
invalidRequestCode = -32600
methodNotFoundCode :: Int
methodNotFoundCode = -32601
invalidParamsCode :: Int
invalidParamsCode = -32602
internalErrorCode :: Int
internalErrorCode = -32603
instance (FromJSON e, FromJSON r) => FromJSON (JsonRpcResponse e r) where
parseJSON = withObject "Response" $ \obj -> do
ix <- obj .: "id"
version <- obj .:? "jsonrpc"
result <- obj .:? "result"
err <- obj .:? "error"
versionGuard version $ pack ix result err
where
pack (Just ix) (Just r) Nothing = pure $ Result ix r
pack ix Nothing (Just e) = Errors ix <$> parseErr e
pack (Just ix) Nothing Nothing = pure $ Ack ix
pack _ _ _ = fail "invalid response"
parseErr = withObject "Error" $
liftA3 JsonRpcErr <$> (.: "code") <*> (.: "message") <*> (.:? "data")
instance (ToJSON e, ToJSON r) => ToJSON (JsonRpcResponse e r) where
toJSON (Result ix r) =
object [ "jsonrpc" .= ("2.0" :: String)
, "result" .= r
, "id" .= ix
]
toJSON (Ack ix) =
object [ "jsonrpc" .= ("2.0" :: String)
, "id" .= ix
, "result" .= Null
, "error" .= Null
]
toJSON (Errors ix (JsonRpcErr c msg err)) =
object [ "jsonrpc" .= ("2.0" :: String)
, "id" .= ix
, "error" .= detail
]
where
detail = object [ "code" .= c
, "message" .= msg
, "data" .= err
]
data RawJsonRpc api
data JsonRpc (method :: Symbol) p e r
data JsonRpcNotification (method :: Symbol) p
type family JsonRpcEndpoint a where
JsonRpcEndpoint (JsonRpc m p e r)
= ReqBody '[JSON] (Request p) :> Post '[JSON] (JsonRpcResponse e r)
JsonRpcEndpoint (JsonRpcNotification m p)
= ReqBody '[JSON] (Request p) :> Post '[JSON] NoContent