Statistics
| Branch: | Tag: | Revision:

root / src / Ganeti / THH / RPC.hs @ a85aef5c

History | View | Annotate | Download (3.4 kB)

1
{-# LANGUAGE TemplateHaskell, ExistentialQuantification #-}
2

    
3
{-| Implements Template Haskell generation of RPC server components from Haskell
4
functions.
5

    
6
-}
7

    
8
{-
9

    
10
Copyright (C) 2013 Google Inc.
11

    
12
This program is free software; you can redistribute it and/or modify
13
it under the terms of the GNU General Public License as published by
14
the Free Software Foundation; either version 2 of the License, or
15
(at your option) any later version.
16

    
17
This program is distributed in the hope that it will be useful, but
18
WITHOUT ANY WARRANTY; without even the implied warranty of
19
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
20
General Public License for more details.
21

    
22
You should have received a copy of the GNU General Public License
23
along with this program; if not, write to the Free Software
24
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
25
02110-1301, USA.
26

    
27
-}
28

    
29
module Ganeti.THH.RPC
30
  ( Request(..)
31
  , RpcServer
32
  , dispatch
33
  , mkRpcM
34
  ) where
35

    
36
import Control.Applicative
37
import Control.Arrow ((&&&))
38
import Control.Monad
39
import Control.Monad.Error.Class
40
import Data.Map (Map)
41
import qualified Data.Map as Map
42
import Language.Haskell.TH
43
import qualified Text.JSON as J
44

    
45
import Ganeti.BasicTypes
46
import Ganeti.Errors
47
import Ganeti.JSON
48
import Ganeti.THH.Types
49
import qualified Ganeti.UDSServer as US
50

    
51
data RpcFn m = forall i o . (J.JSON i, J.JSON o) => RpcFn (i -> m o)
52

    
53
type RpcServer m = US.Handler Request m J.JSValue
54

    
55
-- | A RPC request consiting of a method and its argument(s).
56
data Request = Request { rMethod :: String, rArgs :: J.JSValue }
57
  deriving (Eq, Ord, Show)
58

    
59
decodeRequest :: J.JSValue -> J.JSValue -> Result Request
60
decodeRequest method args = Request <$> fromJVal method <*> pure args
61

    
62

    
63
dispatch :: (Monad m)
64
         => Map String (RpcFn (ResultT GanetiException m)) -> RpcServer m
65
dispatch fs =
66
  US.Handler { US.hParse         = decodeRequest
67
             , US.hInputLogShort = rMethod
68
             , US.hInputLogLong  = show
69
             , US.hExec          = liftToHandler . exec
70
             }
71
  where
72
    orError :: (MonadError e m, Error e) => Maybe a -> e -> m a
73
    orError m e = maybe (throwError e) return m
74

    
75
    exec (Request m as) = do
76
      (RpcFn f) <- orError (Map.lookup m fs)
77
                           (strMsg $ "No such method: " ++ m)
78
      i <- fromJResultE "RPC input" . J.readJSON $ as
79
      o <- f i -- lift $ f i
80
      return $ J.showJSON o
81

    
82
    liftToHandler :: (Monad m)
83
                  => ResultT GanetiException m J.JSValue
84
                  -> US.HandlerResult m J.JSValue
85
    liftToHandler = liftM ((,) True) . runResultT
86

    
87
-- | Converts a function into the appropriate @RpcFn m@ expression.
88
-- The function's result must be monadic.
89
toRpcFn :: Name -> Q Exp
90
toRpcFn name = [| RpcFn $( uncurryVar name ) |]
91

    
92
-- | Convert a list of named expressions into an expression containing a list
93
-- of name/expression pairs.
94
rpcFnsList :: [(String, Q Exp)] -> Q Exp
95
rpcFnsList = listE . map (\(name, expr) -> tupE [stringE name, expr])
96

    
97
-- | Takes a list of function names and creates a RPC handler that delegates
98
-- calls to them.
99
--
100
-- The functions must conform to
101
-- @(J.JSON i, J.JSON o) => i -> ResultT GanetiException m o@. The @m@
102
-- monads types of all the functions must unify.
103
--
104
-- The result expression is of type @RpcServer m@.
105
mkRpcM
106
    :: [Name]     -- ^ the names of functions to include
107
    -> Q Exp
108
mkRpcM names = [| dispatch . Map.fromList $
109
                        $( rpcFnsList . map (nameBase &&& toRpcFn) $ names ) |]