module Network.JsonRpc.Types ( RpcResult
, Method (..)
, Parameter(..)
, (:+:) (..)
, MethodParams (..)
, Request (..)
, Response (..)
, Id (..)
, RpcError (..)
, rpcError
, rpcErrorWithData) where
import Data.Maybe (catMaybes)
import Data.Text (Text, append, unpack)
import qualified Data.Aeson as A
import Data.Aeson ((.=), (.:), (.:?), (.!=))
import Data.Aeson.Types (emptyObject)
import qualified Data.Vector as V
import qualified Data.HashMap.Strict as H
import Control.DeepSeq (NFData, rnf)
import Control.Monad (when)
import Control.Monad.Except (ExceptT (..), throwError)
import Prelude hiding (length)
import Control.Applicative ((<|>), empty)
#if !MIN_VERSION_base(4,8,0)
import Control.Applicative ((<$>), (<*>), (*>))
#endif
type RpcResult m r = ExceptT RpcError m r
data Parameter a
= Required Text
| Optional Text a
data a :+: ps = (Parameter a) :+: ps
infixr :+:
class (Monad m, A.ToJSON r) => MethodParams f p m r | f -> p m r, p m r -> f where
_apply :: f -> p -> Args -> RpcResult m r
instance (Monad m, A.ToJSON r) => MethodParams (RpcResult m r) () m r where
_apply _ _ (Right ar) | not $ V.null ar =
throwError $ rpcError (32602) "Too many unnamed arguments"
_apply res _ _ = res
instance (A.FromJSON a, MethodParams f p m r) => MethodParams (a -> f) (a :+: p) m r where
_apply f (param :+: ps) args =
ExceptT (return arg) >>= \a -> _apply (f a) ps nextArgs
where
arg = maybe (paramDefault param) (parseArg name) lookupValue
lookupValue = either (H.lookup name) (V.!? 0) args
nextArgs = V.drop 1 <$> args
name = paramName param
parseArg :: A.FromJSON r => Text -> A.Value -> Either RpcError r
parseArg name val = case A.fromJSON val of
A.Error msg -> throwError $ argTypeError msg
A.Success x -> return x
where argTypeError = rpcErrorWithData (32602) $ "Wrong type for argument: " `append` name
paramDefault :: Parameter a -> Either RpcError a
paramDefault (Optional _ d) = Right d
paramDefault (Required name) = Left $ missingArgError name
missingArgError :: Text -> RpcError
missingArgError name = rpcError (32602) $ "Cannot find required argument: " `append` name
paramName :: Parameter a -> Text
paramName (Optional n _) = n
paramName (Required n) = n
data Method m = Method Text (Args -> RpcResult m A.Value)
type Args = Either A.Object A.Array
data Request = Request Text Args (Maybe Id)
instance A.FromJSON Request where
parseJSON (A.Object x) = (checkVersion =<< x .:? versionKey .!= jsonRpcVersion) *>
(Request <$>
x .: "method" <*>
(parseParams =<< x .:? "params" .!= emptyObject) <*>
parseId)
where parseParams (A.Object obj) = return $ Left obj
parseParams (A.Array ar) = return $ Right ar
parseParams _ = empty
checkVersion ver = when (ver /= jsonRpcVersion) $
fail $ "Wrong JSON-RPC version: " ++ unpack ver
parseId = x .:? idKey >>= \optional ->
case optional of
Nothing -> Just <$> (x .: idKey) <|> return Nothing
_ -> return optional
parseJSON _ = empty
data Response = Response Id (Either RpcError A.Value)
instance NFData Response where
rnf (Response i e) = rnf i `seq` rnf e
instance A.ToJSON Response where
toJSON (Response i result) = A.object pairs
where pairs = [ versionKey .= jsonRpcVersion
, either ("error" .=) ("result" .=) result
, idKey .= i]
data Id = IdString A.Value | IdNumber A.Value | IdNull
instance NFData Id where
rnf (IdString s) = rnf s
rnf (IdNumber n) = rnf n
rnf IdNull = ()
instance A.FromJSON Id where
parseJSON x@(A.String _) = return $ IdString x
parseJSON x@(A.Number _) = return $ IdNumber x
parseJSON A.Null = return IdNull
parseJSON _ = empty
instance A.ToJSON Id where
toJSON (IdString x) = x
toJSON (IdNumber x) = x
toJSON IdNull = A.Null
data RpcError = RpcError { errCode :: Int
, errMsg :: Text
, errData :: Maybe A.Value }
deriving (Show, Eq)
instance NFData RpcError where
rnf (RpcError e m d) = rnf e `seq` rnf m `seq` rnf d
instance A.ToJSON RpcError where
toJSON (RpcError code msg data') = A.object pairs
where pairs = catMaybes [ Just $ "code" .= code
, Just $ "message" .= msg
, ("data" .=) <$> data' ]
instance A.FromJSON RpcError where
parseJSON (A.Object v) = RpcError <$>
v .: "code" <*>
v .: "message" <*>
v .:? "data"
parseJSON _ = empty
rpcError :: Int -> Text -> RpcError
rpcError code msg = RpcError code msg Nothing
rpcErrorWithData :: A.ToJSON a => Int -> Text -> a -> RpcError
rpcErrorWithData code msg errorData = RpcError code msg $ Just $ A.toJSON errorData
jsonRpcVersion, versionKey, idKey :: Text
jsonRpcVersion = "2.0"
versionKey = "jsonrpc"
idKey = "id"