{-# LANGUAGE QuasiQuotes, MultiParamTypeClasses, FunctionalDependencies, UndecidableInstances #-}{- |
Module : Language.Javascript.JMacro.Rpc
Copyright : (c) Gershom Bazerman, 2010
License : BSD 3 Clause
Maintainer : gershomb@gmail.com
Stability : experimental
Allows for the creation of rpc server/client pairs from monomorphic functions.
The server portion
is a function from a json-encoded list of parameters to a json
response. A list of server functions are expected to be wrapped by a
dispatch function in the server framework of your choice.
The client
portion generated from a function of arity n is a function from a
string identifying a server or a subdirectory on a server to an arity
n function from javascript expressions (of type
'Language.Javascript.JMacro.Base.JExpr') to a single javascript
expression. This expression, when evaluated on the client side, will
call back to the provided server with json-serialized arguments and yield
the result (deserialized from json). This client function is expected to be embedded
via antiquotation into a larger block of jmacro code.
Client portions must unfortunately be given explicit type signatures.
The following example is a server/client pair providing an ajax call to add integers.
> testRPCCall :: String -> JExpr -> JExpr -> JExpr
> (testRPC, testRPCCall) = mkWebRPC "test" $ \x y -> asIO $ return (x + (y::Int))
This code uses a simple request/response type based on strings to be as agnostic as possible about choice of web service stack. It can be used as is, or used as a model for code which targets a particular web stack (Happstack, Snap, FastCGI, etc.).
The jQuery Javascript library is used to handle ajax requests, and hence pages which embed RPC calls must have the jQuery javascript library loaded.
-}moduleLanguage.Javascript.JMacro.Rpc(-- * APImkWebRPC,asIO,Request,Response(..),WebRPCDesc,-- * Helper ClassesCallWebRPC(..),ToWebRPC(..))whereimportPreludehiding(tail,init,head,last,minimum,maximum,foldr1,foldl1,(!!),read)importLanguage.Javascript.JMacro.BaseimportLanguage.Javascript.JMacro.QQimportText.JSONimportText.JSON.StringtypeWebRPCDesc=(String,Request->IOResponse)-- | A String containing a json representation of function arguments encoded as a list of parameters. Generally would be passed as part of an HTTP request.typeRequest=String-- | Either a success or failure (with code). Generally would be turned back into a proper HTTP response.dataResponse=GoodResponseString|BadResponseIntStringreturnResp::JSONa=>a->IOResponsereturnRespr=return$GoodResponse(encoder)respCodece=BadResponsecebadDatae=return$respCode400("Bad Data format: "++e)classToWebRPCawheretoWebRPC_::a->([JSValue]->IOResponse)instance(JSONb)=>ToWebRPC(IOb)wheretoWebRPC_f_=returnResp=<<finstance(JSONa,ToWebRPCb)=>ToWebRPC(a->b)wheretoWebRPC_f(x:xs)=casereadJSONxofOkv->toWebRPC_(fv)xsErrors->badDatastoWebRPC___=badData"missing parameter"toWebRPC::ToWebRPCa=>a->Request->IOResponsetoWebRPCf=\req->caserunGetJSONreadJSArrayreqof(Right(JSArrayxs))->f'xs(Lefte)->badDatae_->badData"toWebRPC error"wheref'=toWebRPC_fclassCallWebRPCab|a->bwherecallWebRPC_::[JExpr]->String->a->binstanceCallWebRPC(IOb)JExprwherecallWebRPC_xsserverLoc_=[$jmacroE|(\(){varres;//$.post(`(serverLoc)`,{args:JSON.stringify`(reversexs)`},\(d){res=d},"json");$.ajax({type:"POST",url:`(serverLoc)`,data:{args:JSON.stringify`(reversexs)`},success:\d{res=d},dataType:"json",async:false});returnres;}())|]instance(CallWebRPCbc,ToJExprd)=>CallWebRPC(a->b)(d->c)wherecallWebRPC_xsserverLocf=\x->callWebRPC_(toJExprx:xs)serverLoc(fundefined)callWebRPC::(CallWebRPCab)=>String->a->bcallWebRPCsf=callWebRPC_[]sf-- | Produce a pair of (ServerFunction, ClientFunction) from a function in IOmkWebRPC::(ToWebRPCa,CallWebRPCab)=>String->a->(WebRPCDesc,String->b)mkWebRPCnamerpcFun=((name,toWebRPCrpcFun),\server->callWebRPC(server++"/"++name)rpcFun)testRPCCall::String->JExpr->JExpr->JExpr(testRPC,testRPCCall)=mkWebRPC"test"$\xy->asIO$return(x+(y::Int))-- | id with a helpful type.asIO::IOa->IOaasIO=id