packages feed

ideas-1.9.1: src/Ideas/Text/JSON/RPC.hs

-----------------------------------------------------------------------------
-- Copyright 2019, Ideas project team. This file is distributed under the
-- terms of the Apache License 2.0. For more information, see the files
-- "LICENSE.txt" and "NOTICE.txt", which are included in the distribution.
-----------------------------------------------------------------------------
-- |
-- Maintainer  :  bastiaan.heeren@ou.nl
-- Stability   :  provisional
-- Portability :  portable (depends on ghc)
--
-----------------------------------------------------------------------------

module Ideas.Text.JSON.RPC
   ( jsonRPC, RPCHandler, RPCRequest(..), RPCResponse(..)
   ) where

import Control.Exception
import Ideas.Text.JSON.Data
import Ideas.Text.JSON.Class
import Ideas.Text.JSON.Decoder
import System.IO.Error

--------------------------------------------------------
-- JSON-RPC

data RPCRequest = Request
   { requestMethod :: String
   , requestParams :: JSON
   , requestId     :: JSON
   }

data RPCResponse = Response
   { responseResult :: JSON
   , responseError  :: JSON
   , responseId     :: JSON
   }

instance InJSON RPCRequest where
   toJSON req = Object
      [ ("method", String $ requestMethod req)
      , ("params", requestParams req)
      , ("id"    , requestId req)
      ]
   jsonDecoder = jObject $
      Request <$> jKey "method" jString 
              <*> (jKey "params" (jNext Right) <|> pure Null)
              <*> (jKey "id"     (jNext Right) <|> pure Null)

instance InJSON RPCResponse where
   toJSON resp = Object
      [ ("result", responseResult resp)
      , ("error" , responseError resp)
      , ("id"    , responseId resp)
      ]
   jsonDecoder = jObject $ 
      Response <$> jKey "result" (jNext Right)
               <*> jKey "error"  (jNext Right)
               <*> jKey "id"     (jNext Right)

instance Show RPCRequest where
   show = show . toJSON

instance Show RPCResponse where
   show = show . toJSON

okResponse :: JSON -> JSON -> RPCResponse
okResponse x y = Response
   { responseResult = x
   , responseError  = Null
   , responseId     = y
   }

errorResponse :: JSON -> JSON -> RPCResponse
errorResponse x y = Response
   { responseResult = Null
   , responseError  = x
   , responseId     = y
   }

--------------------------------------------------------
-- JSON-RPC over HTTP

type RPCHandler = String -> JSON -> IO JSON

jsonRPC :: JSON -> RPCHandler -> IO RPCResponse
jsonRPC input rpc =
   case fromJSON input of
      Nothing  -> return (errorResponse (String "Invalid request") Null)
      Just req -> do
         json <- rpc (requestMethod req) (requestParams req)
         return (okResponse json (requestId req))
       `catch` handler req
 where
   handler :: RPCRequest -> SomeException -> IO RPCResponse
   handler req e =
      let msg = maybe (show e) ioeGetErrorString (fromException e)
      in return $ errorResponse (toJSON msg) (requestId req)