packages feed

jsonrpc-conduit 0.2.7 → 0.3.0

raw patch · 8 files changed

+289/−283 lines, 8 filesdep +hspecdep +jsonrpc-conduitPVP ok

version bump matches the API change (PVP)

Dependencies added: hspec, jsonrpc-conduit

API changes (from Hackage documentation)

- Data.Conduit.JsonRpc.Server: instance Data.Aeson.Types.Class.FromJSON (Data.Conduit.JsonRpc.Server.Request Data.Aeson.Types.Internal.Value)
- Data.Conduit.JsonRpc.Server: instance Data.Aeson.Types.Class.ToJSON (Data.Conduit.JsonRpc.Server.Response Data.Aeson.Types.Internal.Value)
- Data.Conduit.JsonRpc.Server: instance GHC.Show.Show a => GHC.Show.Show (Data.Conduit.JsonRpc.Server.Response a)
+ Data.Conduit.JsonRpc.Internal.Types: Error :: Int -> Text -> Maybe Value -> Response a
+ Data.Conduit.JsonRpc.Internal.Types: Request :: Text -> a -> Value -> Request a
+ Data.Conduit.JsonRpc.Internal.Types: Result :: a -> Value -> Response a
+ Data.Conduit.JsonRpc.Internal.Types: [errCode] :: Response a -> Int
+ Data.Conduit.JsonRpc.Internal.Types: [errMsg] :: Response a -> Text
+ Data.Conduit.JsonRpc.Internal.Types: [errRefId] :: Response a -> Maybe Value
+ Data.Conduit.JsonRpc.Internal.Types: [reqId] :: Request a -> Value
+ Data.Conduit.JsonRpc.Internal.Types: [reqMethod] :: Request a -> Text
+ Data.Conduit.JsonRpc.Internal.Types: [reqParams] :: Request a -> a
+ Data.Conduit.JsonRpc.Internal.Types: [resultId] :: Response a -> Value
+ Data.Conduit.JsonRpc.Internal.Types: [result] :: Response a -> a
+ Data.Conduit.JsonRpc.Internal.Types: data Request a
+ Data.Conduit.JsonRpc.Internal.Types: data Response a
+ Data.Conduit.JsonRpc.Internal.Types: instance Data.Aeson.Types.Class.FromJSON (Data.Conduit.JsonRpc.Internal.Types.Request Data.Aeson.Types.Internal.Value)
+ Data.Conduit.JsonRpc.Internal.Types: instance Data.Aeson.Types.Class.FromJSON a => Data.Aeson.Types.Class.FromJSON (Data.Conduit.JsonRpc.Internal.Types.Response a)
+ Data.Conduit.JsonRpc.Internal.Types: instance Data.Aeson.Types.Class.ToJSON (Data.Conduit.JsonRpc.Internal.Types.Response Data.Aeson.Types.Internal.Value)
+ Data.Conduit.JsonRpc.Internal.Types: instance Data.Aeson.Types.Class.ToJSON a => Data.Aeson.Types.Class.ToJSON (Data.Conduit.JsonRpc.Internal.Types.Request a)
+ Data.Conduit.JsonRpc.Internal.Types: instance GHC.Show.Show a => GHC.Show.Show (Data.Conduit.JsonRpc.Internal.Types.Response a)

Files

− Data/Conduit/JsonRpc/Demo.hs
@@ -1,35 +0,0 @@--- |--- Copyright   : (c) 2012-2013 Gabriele Sales <gbrsales@gmail.com>------ An example JSON-RPC server implementing the "echo" and "sum" methods.--{-# LANGUAGE OverloadedStrings #-}--module Main-  ( main )-where--import           Data.Aeson-import           Data.ByteString              (ByteString)-import           Data.Conduit-import           Data.Conduit.Binary-import           Data.Conduit.JsonRpc.Methods-import           Data.Conduit.JsonRpc.Server-import           System.IO---main :: IO ()-main = do-  hSetBuffering stdin NoBuffering-  hSetBuffering stdout NoBuffering-  sourceHandle stdin $= server $$ sinkHandle stdout--server :: Conduit ByteString IO ByteString-server = serve (fromList [ method "echo" echo-                         , method "sum"  sum' ])--echo :: Value -> IO (Either MethodError Value)-echo = return . Right . id--sum' :: [Int] -> IO (Either MethodError Int)-sum' = return . Right . sum
− Data/Conduit/JsonRpc/Methods.hs
@@ -1,66 +0,0 @@--- |--- Module      : Data.Conduit.JsonRpc.Methods--- Copyright   : (c) 2012-2013 Gabriele Sales <gbrsales@gmail.com>------ JSON-RPC methods.--{-# LANGUAGE GADTs      #-}-{-# LANGUAGE RankNTypes #-}--module Data.Conduit.JsonRpc.Methods-  ( Method(..)-  , MethodError(..)--  , NamedMethod-  , method--  , Methods-  , fromList-  , lookup )-where--import           Data.Aeson-import qualified Data.HashMap.Strict as M-import           Data.Text           (Text)-import           Prelude             hiding (lookup)---{-|-A wrapper over a monadic function that can either succeed or fail with a-'MethodError'.--Hides the input and output types.--}-data Method m where-  Method :: forall i m o. (FromJSON i, ToJSON o)-         => (i -> m (Either MethodError o)) -> Method m---- | Represents an error with an integer code and a textual message.-data MethodError = MethodError !Int !Text-  deriving (Eq, Show)----- | A 'Method' with a name.-newtype NamedMethod m = NamedMethod { unWrap :: (Text, Method m) }--{-|-Builds a 'NamedMethod' given its name and function.--Useful in conjuction with 'fromList'.--}-method :: (FromJSON i, ToJSON o)-       => Text-       -> (i -> m (Either MethodError o))-       -> NamedMethod m-method name f = NamedMethod (name, Method f)---- | Collection of 'NamedMethod's.-newtype Methods m = Methods (M.HashMap Text (Method m))---- | Builds a collection from a list of 'NamedMethod's.-fromList :: [NamedMethod m] -> Methods m-fromList = Methods . M.fromList . map unWrap---- | Looks up the method corresponding to the given name.-lookup :: Methods m -> Text -> Maybe (Method m)-lookup (Methods m) name = M.lookup name m
− Data/Conduit/JsonRpc/Server.hs
@@ -1,162 +0,0 @@--- |--- Module      : Data.Conduit.JsonRpc.Server--- Copyright   : (c) 2012-2013 Gabriele Sales <gbrsales@gmail.com>------ JSON-RPC 2.0 server 'Conduit'.--{-# LANGUAGE FlexibleInstances #-}-{-# LANGUAGE OverloadedStrings #-}--module Data.Conduit.JsonRpc.Server-  ( serve )-where--import           Control.Applicative-import           Control.Monad                (guard, (>=>))-import           Control.Monad.Trans          (lift)-import           Control.Monad.Trans.State-import           Data.Aeson                   hiding (Error)-import           Data.Aeson.Types             (emptyArray, parseMaybe)-import           Data.Attoparsec.ByteString-import           Data.ByteString              (ByteString)-import qualified Data.ByteString              as B-import qualified Data.ByteString.Lazy         as L-import           Data.Conduit-import           Data.Conduit.JsonRpc.Methods hiding (method)-import qualified Data.Conduit.List            as C-import           Data.Monoid-import           Data.Text                    (Text)-import           Prelude                      hiding (lookup)---data Request a = Request { method    :: Text-                         , params    :: a-                         , requestId :: Value }--instance FromJSON (Request Value) where-    parseJSON (Object v) = do-      version <- v .: "jsonrpc"-      guard (version == ("2.0" :: Text))-      Request <$> v .:  "method" <*>-                  (v .:? "params") .!= emptyArray <*>-                  v .:  "id"--    parseJSON _ = mempty---data Processed a = Correct !a-                 | InvalidRequest-                 | ParseError---data Response a = Result { result   :: a-                         , resultId :: Value }-                | Error  { code    :: Int-                         , message :: Text-                         , refId   :: Maybe Value }-  deriving (Show)--instance ToJSON (Response Value) where-    toJSON (Result x id) = object [ "jsonrpc" .= ("2.0" :: Text)-                                  , "result"  .= x-                                  , "id"      .= id ]-    toJSON (Error code msg id) =-        let err = object [ "code"    .= code-                         , "message" .= msg ]-        in object [ "jsonrpc" .= ("2.0" :: Text)-                  , "error"   .= err-                  , "id"      .= id ]--{- |-A 'Conduit' that consumes a stream of JSON-RPC requests, tries to process them-with the provided 'Methods' and writes back the results.--Current limitations:--  * does not support batch requests--  * it is not possible to set the @data@ attribute of error objects--}-serve :: (Applicative m, Monad m) => Methods m -> Conduit ByteString m ByteString-serve methods = parseRequests =$=-                C.concatMapM (\r -> encodeResponse <$> handleRequest methods r)---parseRequests :: (Monad m)-              => ConduitM ByteString (Processed (Request Value)) m ()-parseRequests = evalStateT loop Nothing-  where-    loop = lift await >>= maybe flush process--    process = runParser >=> handle--    flush = do-      p <- get-      case p of-        Nothing -> return ()-        Just k  -> handle (k B.empty)--    runParser chunk = do-      p <- getPartialParser-      return $ case p of-                 Nothing -> parse json' chunk-                 Just k  -> k chunk--    getPartialParser = get <* put Nothing--    handle (Fail {})     = lift (yield ParseError)-    handle (Partial k)   = put (Just k) >> loop-    handle (Done rest r) = do-      lift (yieldResponse r)-      if B.null rest-         then loop-         else process rest--    yieldResponse r = yield $ case parseMaybe parseJSON r of-                                Nothing -> InvalidRequest-                                Just r' -> Correct r'--handleRequest :: (Applicative m, Monad m)-              => Methods m-              -> Processed (Request Value)-              -> m (Response Value)-handleRequest _       InvalidRequest    = invalidRequest-handleRequest _       ParseError        = parseError-handleRequest methods (Correct request) =-  case lookup methods (method request) of-    Nothing -> methodNotFound (requestId request)-    Just m  -> runMethod m request--runMethod :: (Applicative m, Monad m)-          => Method m-          -> Request Value-          -> m (Response Value)-runMethod (Method f) request = do-  let reqId = requestId request-  case parseMaybe parseJSON (params request) of-    Nothing -> invalidParams reqId-    Just ps -> processResult reqId <$> f ps--processResult :: (ToJSON a) => Value -> Either MethodError a -> Response Value-processResult reqId (Left (MethodError code msg)) = Error code msg (Just reqId)-processResult reqId (Right res)                   = Result (toJSON res) reqId---invalidRequest :: (Monad m) => m (Response Value)-invalidRequest = mkError (-32600) "Invalid request" Nothing--methodNotFound :: (Monad m) => Value -> m (Response Value)-methodNotFound = mkError (-32601) "Method not found" . Just--invalidParams :: (Monad m) => Value -> m (Response Value)-invalidParams = mkError (-32602) "Invalid params" . Just--parseError :: (Monad m) => m (Response Value)-parseError = mkError (-32700) "Parse error" Nothing--mkError :: (Monad m) => Int -> Text -> Maybe Value -> m (Response Value)-mkError code msg id = return (Error code msg id)---encodeResponse :: Response Value -> [ByteString]-encodeResponse = L.toChunks . encode
jsonrpc-conduit.cabal view
@@ -1,5 +1,5 @@ name:                jsonrpc-conduit-version:             0.2.7+version:             0.3.0 synopsis:            JSON-RPC 2.0 server over a Conduit. description:   @jsonrpc-conduit@ implements the basic building block of a JSON-RPC 2.0 server.@@ -25,38 +25,33 @@   type:     git   location: https://github.com/gbrsales/jsonrpc-conduit.git -flag demo-  description: Build the demo executable.-  default:     False- library-  exposed-modules:     Data.Conduit.JsonRpc.Methods,+  hs-source-dirs:      src+  exposed-modules:     Data.Conduit.JsonRpc.Internal.Types,+                       Data.Conduit.JsonRpc.Methods,                        Data.Conduit.JsonRpc.Server   build-depends:       base >=4 && <5,                        aeson >=0.7 && <0.11,                        attoparsec >=0.11 && <0.14,-                       bytestring ==0.10.*,+                       bytestring >=0.10 && <0.11,                        conduit >=1.1 && <1.3,                        conduit-extra >=1.1 && <1.3,                        mtl >=2.1 && <2.3,                        text >=1.1 && <1.3,                        transformers >=0.3 && <0.5,                        unordered-containers ==0.2.*--executable jsonrpc-conduit-demo-  if !flag(demo)-    buildable: False+  ghc-options:         -Wall -fno-warn-name-shadowing -  main-is:             Data/Conduit/JsonRpc/Demo.hs-  other-modules:       Data.Conduit.JsonRpc.Methods,-                       Data.Conduit.JsonRpc.Server+test-suite spec+  type:                exitcode-stdio-1.0+  hs-source-dirs:      test+  main-is:             Spec.hs   build-depends:       base >=4 && <5,                        aeson >=0.7 && <0.11,-                       attoparsec >=0.11 && <0.14,-                       bytestring ==0.10.*,+                       bytestring >=0.10 && <0.11,                        conduit >=1.1 && <1.3,                        conduit-extra >=1.1 && <1.3,-                       mtl >=2.1 && <2.3,-                       text >=1.1 && <1.3,-                       transformers >=0.3 && <0.5,-                       unordered-containers ==0.2.*+                       hspec >=2.1 && <2.3,+                       jsonrpc-conduit,+                       text >=1.1 && <1.3+  ghc-options:         -Wall -fno-warn-name-shadowing
+ src/Data/Conduit/JsonRpc/Internal/Types.hs view
@@ -0,0 +1,79 @@+{- |+Module      :  Data.Conduit.JsonRpc.Internal.Types+Description :  Types used internally to encode requests and responses.+Copyright   :  (c) 2015 Gabriele Sales+-}++{-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE OverloadedStrings #-}++module Data.Conduit.JsonRpc.Internal.Types+  ( Request(..)+  , Response(..) )+where++import           Control.Applicative+import           Control.Monad+import           Data.Aeson          hiding (Error)+import           Data.Aeson.Types    (emptyArray)+import           Data.Monoid         as M (mempty)+import           Data.Text           (Text)+++data Request a = Request { reqMethod :: Text+                         , reqParams :: a+                         , reqId     :: Value }++instance FromJSON (Request Value) where+  parseJSON (Object v) = do+    version <- v .: "jsonrpc"+    guard (version == ("2.0" :: Text))+    Request <$> v .:  "method" <*>+                (v .:? "params") .!= emptyArray <*>+                v .:  "id"++  parseJSON _ = M.mempty++instance ToJSON a => ToJSON (Request a) where+  toJSON (Request m ps id) =+    object [ "jsonrpc" .= ("2.0" :: Text)+           , "method"  .= m+           , "params"  .= toJSON ps+           , "id"      .= id ]+++data Response a = Result { result   :: a+                         , resultId :: Value }+                | Error  { errCode  :: Int+                         , errMsg   :: Text+                         , errRefId :: Maybe Value }+  deriving (Show)++instance FromJSON a => FromJSON (Response a) where+  parseJSON (Object v) = do+    version <- v .: "jsonrpc"+    guard (version == ("2.0" :: Text))+    fromResult <|> fromError++    where+      fromResult = Result <$> (v .: "result" >>= parseJSON)+                          <*> v .: "id"++      fromError = do+        err <- v .: "error"+        Error <$> err .: "code"+              <*> err .: "message"+              <*> v   .: "id"++  parseJSON _ = mempty++instance ToJSON (Response Value) where+  toJSON (Result x id) = object [ "jsonrpc" .= ("2.0" :: Text)+                                , "result"  .= x+                                , "id"      .= id ]+  toJSON (Error code msg id) =+    let err = object [ "code"    .= code+                     , "message" .= msg ]+    in object [ "jsonrpc" .= ("2.0" :: Text)+              , "error"   .= err+              , "id"      .= id ]
+ src/Data/Conduit/JsonRpc/Methods.hs view
@@ -0,0 +1,66 @@+-- |+-- Module      : Data.Conduit.JsonRpc.Methods+-- Copyright   : (c) 2012-2013 Gabriele Sales <gbrsales@gmail.com>+--+-- JSON-RPC methods.++{-# LANGUAGE GADTs      #-}+{-# LANGUAGE RankNTypes #-}++module Data.Conduit.JsonRpc.Methods+  ( Method(..)+  , MethodError(..)++  , NamedMethod+  , method++  , Methods+  , fromList+  , lookup )+where++import           Data.Aeson+import qualified Data.HashMap.Strict as M+import           Data.Text           (Text)+import           Prelude             hiding (lookup)+++{-|+A wrapper over a monadic function that can either succeed or fail with a+'MethodError'.++Hides the input and output types.+-}+data Method m where+  Method :: forall i m o. (FromJSON i, ToJSON o)+         => (i -> m (Either MethodError o)) -> Method m++-- | Represents an error with an integer code and a textual message.+data MethodError = MethodError !Int !Text+  deriving (Eq, Show)+++-- | A 'Method' with a name.+newtype NamedMethod m = NamedMethod { unWrap :: (Text, Method m) }++{-|+Builds a 'NamedMethod' given its name and function.++Useful in conjuction with 'fromList'.+-}+method :: (FromJSON i, ToJSON o)+       => Text+       -> (i -> m (Either MethodError o))+       -> NamedMethod m+method name f = NamedMethod (name, Method f)++-- | Collection of 'NamedMethod's.+newtype Methods m = Methods (M.HashMap Text (Method m))++-- | Builds a collection from a list of 'NamedMethod's.+fromList :: [NamedMethod m] -> Methods m+fromList = Methods . M.fromList . map unWrap++-- | Looks up the method corresponding to the given name.+lookup :: Methods m -> Text -> Maybe (Method m)+lookup (Methods m) name = M.lookup name m
+ src/Data/Conduit/JsonRpc/Server.hs view
@@ -0,0 +1,128 @@+-- |+-- Module      : Data.Conduit.JsonRpc.Server+-- Copyright   : (c) 2012-2013,2015 Gabriele Sales <gbrsales@gmail.com>+--+-- JSON-RPC 2.0 server 'Conduit'.++{-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE OverloadedStrings #-}++module Data.Conduit.JsonRpc.Server+  ( serve )+where++import           Control.Applicative+import           Control.Monad                       ((>=>))+import           Control.Monad.Trans                 (lift)+import           Control.Monad.Trans.State+import           Data.Aeson                          hiding (Error)+import           Data.Aeson.Types                    (parseMaybe)+import           Data.Attoparsec.ByteString+import           Data.ByteString                     (ByteString)+import qualified Data.ByteString                     as B+import qualified Data.ByteString.Lazy                as L+import           Data.Conduit+import           Data.Conduit.JsonRpc.Internal.Types+import           Data.Conduit.JsonRpc.Methods        hiding (method)+import qualified Data.Conduit.List                   as C+import           Data.Text                           (Text)+import           Prelude                             hiding (lookup)+++data Processed a = Correct !a+                 | InvalidRequest+                 | ParseError++{- |+A 'Conduit' that consumes a stream of JSON-RPC requests, tries to process them+with the provided 'Methods' and writes back the results.++Current limitations:++  * does not support batch requests++  * it is not possible to set the @data@ attribute of error objects+-}+serve :: (Applicative m, Monad m) => Methods m -> Conduit ByteString m ByteString+serve methods = parseRequests =$=+                C.concatMapM (\r -> encodeResponse <$> handleRequest methods r)+++parseRequests :: (Monad m)+              => ConduitM ByteString (Processed (Request Value)) m ()+parseRequests = evalStateT loop Nothing+  where+    loop = lift await >>= maybe flush process++    process = runParser >=> handle++    flush = do+      p <- get+      case p of+        Nothing -> return ()+        Just k  -> handle (k B.empty)++    runParser chunk = do+      p <- getPartialParser+      return $ case p of+                 Nothing -> parse json' chunk+                 Just k  -> k chunk++    getPartialParser = get <* put Nothing++    handle Fail{}        = lift (yield ParseError)+    handle (Partial k)   = put (Just k) >> loop+    handle (Done rest r) = do+      lift (yieldResponse r)+      if B.null rest+         then loop+         else process rest++    yieldResponse r = yield $ case parseMaybe parseJSON r of+                                Nothing -> InvalidRequest+                                Just r' -> Correct r'++handleRequest :: (Applicative m, Monad m)+              => Methods m+              -> Processed (Request Value)+              -> m (Response Value)+handleRequest _       InvalidRequest    = invalidRequest+handleRequest _       ParseError        = parseError+handleRequest methods (Correct request) =+  case lookup methods (reqMethod request) of+    Nothing -> methodNotFound (reqId request)+    Just m  -> runMethod m request++runMethod :: (Applicative m, Monad m)+          => Method m+          -> Request Value+          -> m (Response Value)+runMethod (Method f) request = do+  let ri = reqId request+  case parseMaybe parseJSON (reqParams request) of+    Nothing -> invalidParams ri+    Just ps -> processResult ri <$> f ps++processResult :: (ToJSON a) => Value -> Either MethodError a -> Response Value+processResult reqId (Left (MethodError code msg)) = Error code msg (Just reqId)+processResult reqId (Right res)                   = Result (toJSON res) reqId+++invalidRequest :: (Monad m) => m (Response Value)+invalidRequest = mkError (-32600) "Invalid request" Nothing++methodNotFound :: (Monad m) => Value -> m (Response Value)+methodNotFound = mkError (-32601) "Method not found" . Just++invalidParams :: (Monad m) => Value -> m (Response Value)+invalidParams = mkError (-32602) "Invalid params" . Just++parseError :: (Monad m) => m (Response Value)+parseError = mkError (-32700) "Parse error" Nothing++mkError :: (Monad m) => Int -> Text -> Maybe Value -> m (Response Value)+mkError code msg id = return (Error code msg id)+++encodeResponse :: Response Value -> [ByteString]+encodeResponse = L.toChunks . encode
+ test/Spec.hs view
@@ -0,0 +1,1 @@+{-# OPTIONS_GHC -F -pgmF hspec-discover #-}