Skip to content

Commit 561bbf8

Browse files
committed
Simplified API by removing Methods type.
1 parent a5b458b commit 561bbf8

File tree

7 files changed

+65
-58
lines changed

7 files changed

+65
-58
lines changed

changelog.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
0.2.0.0
2+
3+
* Updated the error handling type from ErrorT to ExceptT.
4+
5+
* Simplified the call function, so Methods and toMethods are no longer necessary.

demo/Demo.hs

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -23,10 +23,8 @@ main = do
2323

2424
type Server = ReaderT (MVar Integer) IO
2525

26-
methods :: Methods Server
27-
methods = toMethods [add, printSequence, increment]
28-
29-
add, printSequence, increment :: Method Server
26+
methods :: [Method Server]
27+
methods = [add, printSequence, increment]
3028

3129
add = toMethod "add" f (Required "x" :+: Required "y" :+: ())
3230
where f :: Double -> Double -> RpcResult Server Double

json-rpc-server.cabal

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,9 @@ category: Network, JSON
99
maintainer: Kristen Kozak <[email protected]>
1010
synopsis: JSON-RPC 2.0 on the server side.
1111
build-type: Simple
12+
extra-source-files: changelog.md
1213
cabal-version: >=1.8
13-
tested-with: GHC == 7.0.1, GHC == 7.4.1, GHC == 7.6.2,
14-
GHC == 7.6.3, GHC == 7.8.3, GHC == 7.10.1
14+
tested-with: GHC == 7.0.1, GHC == 7.6.2, GHC == 7.8.3, GHC == 7.10.1
1515
description: An implementation of the server side of JSON-RPC 2.0.
1616
See <http://www.jsonrpc.org/specification>. This
1717
library uses 'ByteString' for input and output,

src/Network/JsonRpc/Server.hs

Lines changed: 47 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,6 @@ module Network.JsonRpc.Server (
2020
RpcResult
2121
, Method
2222
, toMethod
23-
, Methods
24-
, toMethods
2523
, call
2624
, callWithBatchStrategy
2725
, Parameter(..)
@@ -30,7 +28,10 @@ module Network.JsonRpc.Server (
3028
-- ** Errors
3129
, RpcError (..)
3230
, rpcError
33-
, rpcErrorWithData) where
31+
, rpcErrorWithData
32+
-- ** Deprecated
33+
, Methods
34+
, toMethods) where
3435

3536
import Network.JsonRpc.Types
3637
import Data.Text (Text, append, pack)
@@ -40,7 +41,7 @@ import qualified Data.Aeson as A
4041
import qualified Data.Vector as V
4142
import qualified Data.HashMap.Strict as H
4243
import Control.DeepSeq (NFData)
43-
import Control.Monad (liftM)
44+
import Control.Monad (liftM, (<=<))
4445
import Control.Monad.Identity (runIdentity)
4546
import Control.Monad.Except (runExceptT, throwError)
4647

@@ -52,10 +53,8 @@ import Control.Applicative ((<$>))
5253
-- * Create methods by calling 'toMethod' and providing the method
5354
-- names, lists of parameters, and functions to be called.
5455
--
55-
-- * Create a set of methods by calling 'toMethods'.
56-
--
5756
-- * Process a request by calling 'call' or 'callWithBatchStrategy'
58-
-- on the 'Methods' and input 'B.ByteString'.
57+
-- on the 'Method's and input 'B.ByteString'.
5958

6059
-- $requests
6160
-- This library handles by-name and by-position arguments, batch and
@@ -78,54 +77,65 @@ import Control.Applicative ((<$>))
7877
-- | Creates a method from a name, function, and parameter descriptions.
7978
-- The parameter names must be unique.
8079
toMethod :: (MethodParams f p m r, A.ToJSON r, Monad m) => Text -> f -> p -> Method m
81-
toMethod name f params = let f' args = A.toJSON <$> _apply f params args
80+
toMethod name f params = let f' args = A.toJSON `liftM` _apply f params args
8281
in Method name f'
8382

84-
-- | Creates a set of methods to be called by name. The names must be unique.
83+
type Methods m = [Method m]
84+
{-# DEPRECATED Methods "Use ['Method' m]." #-}
85+
8586
toMethods :: [Method m] -> Methods m
86-
toMethods fs = Methods $ H.fromList $ map pair fs
87-
where pair mth@(Method name _) = (name, mth)
87+
toMethods = id
88+
{-# DEPRECATED toMethods "Use 'call' directly." #-}
89+
90+
type MethodMap m = H.HashMap Text (Method m)
8891

8992
-- | Handles one JSON-RPC request. It is the same as
9093
-- @callWithBatchStrategy sequence@.
91-
call :: Monad m => Methods m -- ^ Choice of methods to call.
94+
call :: Monad m => [Method m] -- ^ Choice of methods to call.
9295
-> B.ByteString -- ^ JSON-RPC request.
9396
-> m (Maybe B.ByteString) -- ^ The response wrapped in 'Just', or
9497
-- 'Nothing' in the case of a notification,
9598
-- all wrapped in the given monad.
9699
call = callWithBatchStrategy sequence
97100

98-
-- | Handles one JSON-RPC request.
101+
-- | Handles one JSON-RPC request. The method names must be unique.
99102
callWithBatchStrategy :: Monad m =>
100103
(forall a . NFData a => [m a] -> m [a]) -- ^ Function specifying the
101104
-- evaluation strategy.
102-
-> Methods m -- ^ Choice of methods to call.
105+
-> [Method m] -- ^ Choice of methods to call.
103106
-> B.ByteString -- ^ JSON-RPC request.
104107
-> m (Maybe B.ByteString) -- ^ The response wrapped in 'Just', or
105108
-- 'Nothing' in the case of a notification,
106109
-- all wrapped in the given monad.
107-
callWithBatchStrategy strategy fs input = either returnErr callMethod request
108-
where request :: Either RpcError (Either A.Value [A.Value])
109-
request = runIdentity $ runExceptT $ parseVal =<< parseJson input
110-
parseJson = maybe invalidJson return . A.decode
111-
parseVal val = case val of
112-
obj@(A.Object _) -> return $ Left obj
113-
A.Array vec | V.null vec -> throwInvalidRpc "Empty batch request"
114-
| otherwise -> return $ Right $ V.toList vec
115-
_ -> throwInvalidRpc "Not a JSON object or array"
116-
callMethod rq = case rq of
117-
Left val -> encodeJust `liftM` singleCall fs val
118-
Right vals -> encodeJust `liftM` batchCall strategy fs vals
119-
where encodeJust r = A.encode <$> r
120-
returnErr = return . Just . A.encode . nullIdResponse
121-
invalidJson = throwError $ rpcError (-32700) "Invalid JSON"
122-
123-
singleCall :: Monad m => Methods m -> A.Value -> m (Maybe Response)
124-
singleCall (Methods fs) val = case parsed of
110+
callWithBatchStrategy strategy methods =
111+
mthMap `seq` either returnErr callMethod . parse
112+
where
113+
mthMap = H.fromList $
114+
map (\mth@(Method name _) -> (name, mth)) methods
115+
parse :: B.ByteString -> Either RpcError (Either A.Value [A.Value])
116+
parse = runIdentity . runExceptT . parseVal <=< parseJson
117+
parseJson = maybe invalidJson return . A.decode
118+
parseVal val =
119+
case val of
120+
obj@(A.Object _) -> return $ Left obj
121+
A.Array vec | V.null vec -> throwInvalidRpc "Empty batch request"
122+
| otherwise -> return $ Right $ V.toList vec
123+
_ -> throwInvalidRpc "Not a JSON object or array"
124+
callMethod rq =
125+
case rq of
126+
Left val -> encodeJust `liftM` singleCall mthMap val
127+
Right vals -> encodeJust `liftM` batchCall strategy mthMap vals
128+
where
129+
encodeJust r = A.encode <$> r
130+
returnErr = return . Just . A.encode . nullIdResponse
131+
invalidJson = throwError $ rpcError (-32700) "Invalid JSON"
132+
133+
singleCall :: Monad m => MethodMap m -> A.Value -> m (Maybe Response)
134+
singleCall methods val = case parsed of
125135
Left err -> return $ nullIdResponse err
126136
Right (Request name args i) ->
127137
toResponse i `liftM` runExceptT (applyMethodTo args =<< method)
128-
where method = lookupMethod name fs
138+
where method = lookupMethod name methods
129139
where parsed = runIdentity $ runExceptT $ parseValue val
130140
applyMethodTo args (Method _ f) = f args
131141

@@ -137,19 +147,19 @@ parseValue val = case A.fromJSON val of
137147
A.Error msg -> throwInvalidRpc $ pack msg
138148
A.Success x -> return x
139149

140-
lookupMethod :: Monad m => Text -> H.HashMap Text (Method m) -> RpcResult m (Method m)
150+
lookupMethod :: Monad m => Text -> MethodMap m -> RpcResult m (Method m)
141151
lookupMethod name = maybe notFound return . H.lookup name
142152
where notFound = throwError $ rpcError (-32601) $ "Method not found: " `append` name
143153

144154
throwInvalidRpc :: Monad m => Text -> RpcResult m a
145155
throwInvalidRpc = throwError . rpcErrorWithData (-32600) "Invalid JSON-RPC 2.0 request"
146156

147157
batchCall :: Monad m => (forall a. NFData a => [m a] -> m [a])
148-
-> Methods m
158+
-> MethodMap m
149159
-> [A.Value]
150160
-> m (Maybe [Response])
151-
batchCall strategy mths vals = (noNull . catMaybes) `liftM` results
152-
where results = strategy $ map (singleCall mths) vals
161+
batchCall strategy methods vals = (noNull . catMaybes) `liftM` results
162+
where results = strategy $ map (singleCall methods) vals
153163
noNull rs = if null rs then Nothing else Just rs
154164

155165
toResponse :: A.ToJSON a => Maybe Id -> Either RpcError a -> Maybe Response

src/Network/JsonRpc/Types.hs

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@
99

1010
module Network.JsonRpc.Types ( RpcResult
1111
, Method (..)
12-
, Methods (..)
1312
, Parameter(..)
1413
, (:+:) (..)
1514
, MethodParams (..)
@@ -56,10 +55,10 @@ infixr :+:
5655
-- monad ('m'), and return type ('r'). 'p' has one 'Parameter' for
5756
-- every argument of 'f' and is terminated by @()@. The return type
5857
-- of 'f' is @RpcResult m r@. This class is treated as closed.
59-
class (Monad m, Functor m, A.ToJSON r) => MethodParams f p m r | f -> p m r, p m r -> f where
58+
class (Monad m, A.ToJSON r) => MethodParams f p m r | f -> p m r, p m r -> f where
6059
_apply :: f -> p -> Args -> RpcResult m r
6160

62-
instance (Monad m, Functor m, A.ToJSON r) => MethodParams (RpcResult m r) () m r where
61+
instance (Monad m, A.ToJSON r) => MethodParams (RpcResult m r) () m r where
6362
_apply _ _ (Right ar) | not $ V.null ar =
6463
throwError $ rpcError (-32602) "Too many unnamed arguments"
6564
_apply res _ _ = res
@@ -90,12 +89,9 @@ paramName :: Parameter a -> Text
9089
paramName (Optional n _) = n
9190
paramName (Required n) = n
9291

93-
-- | Single method.
92+
-- | A JSON-RPC method.
9493
data Method m = Method Text (Args -> RpcResult m A.Value)
9594

96-
-- | Multiple methods.
97-
newtype Methods m = Methods (H.HashMap Text (Method m))
98-
9995
type Args = Either A.Object A.Array
10096

10197
data Request = Request Text Args (Maybe Id)

tests/TestParallelism.hs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ testParallelizingTasks = do
3737
, unlockRequest 'B'
3838
, lockRequest 2
3939
, unlockRequest 'A']
40-
createMethods lock = S.toMethods [lockMethod lock, unlockMethod lock]
40+
createMethods lock = [lockMethod lock, unlockMethod lock]
4141

4242
possibleResponses :: [[A.Value]]
4343
possibleResponses = (rsp <$>) <$> perms

tests/TestSuite.hs

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ main = defaultMain $ errorHandlingTests ++ otherTests
3535

3636
errorHandlingTests :: [Test]
3737
errorHandlingTests = [ testCase "invalid JSON" $
38-
let rsp = runIdentity $ S.call (S.toMethods []) $ LB.pack "{"
38+
let rsp = runIdentity $ S.call [] $ LB.pack "{"
3939
in removeErrMsg <$> (A.decode =<< rsp) @?= Just (nullIdErrRsp (-32700))
4040

4141
, testCase "invalid JSON-RPC" $
@@ -140,7 +140,7 @@ testBatch = sortBy (compare `on` rspToIdString) <$> response @?= Just expected
140140

141141
testBatchNotifications :: Assertion
142142
testBatchNotifications = runState response 0 @?= (Nothing, 10)
143-
where response = S.call (S.toMethods [incrementStateMethod]) $ A.encode rq
143+
where response = S.call [incrementStateMethod] $ A.encode rq
144144
rq = replicate 10 $ request Nothing "increment" Nothing
145145

146146
testAllowMissingVersion :: Assertion
@@ -160,15 +160,13 @@ assertGetTimeResponse args = passed @? "unexpected RPC response"
160160
rsp = callGetTimeMethod req
161161

162162
callSubtractMethods :: A.Value -> Maybe A.Value
163-
callSubtractMethods req = let methods :: S.Methods Identity
164-
methods = S.toMethods [subtractMethod, flippedSubtractMethod]
163+
callSubtractMethods req = let methods :: [S.Method Identity]
164+
methods = [subtractMethod, flippedSubtractMethod]
165165
rsp = S.call methods $ A.encode req
166166
in A.decode =<< runIdentity rsp
167167

168168
callGetTimeMethod :: A.Value -> IO (Maybe A.Value)
169-
callGetTimeMethod req = let methods :: S.Methods IO
170-
methods = S.toMethods [getTimeMethod]
171-
rsp = S.call methods $ A.encode req
169+
callGetTimeMethod req = let rsp = S.call [getTimeMethod] $ A.encode req
172170
in (A.decode =<<) <$> rsp
173171

174172
subtractMethod :: S.Method Identity

0 commit comments

Comments
 (0)