-
Notifications
You must be signed in to change notification settings - Fork 3
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #3 from nepeckman/v0.2.0
v0.2.0 release
- Loading branch information
Showing
20 changed files
with
624 additions
and
318 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,19 +1,68 @@ | ||
import macros | ||
import nerve/service, nerve/types, nerve/common, nerve/web, nerve/drivers | ||
|
||
when not defined(js): | ||
import json | ||
import nerve/server, nerve/serverRuntime | ||
macro service*(name: untyped, uri: untyped = nil, body: untyped = nil): untyped = | ||
## Macro to create a RpcService. The name param is the identifier used to reference | ||
## the RpcService. The service can then be used in the other macros, including | ||
## the server and client constructors. The uri is optional. | ||
if body.kind != nnkNilLit: | ||
result = rpcService(name, uri.strVal(), body) | ||
else: | ||
result = rpcService(name, "", uri) | ||
|
||
macro rpc*(name, uri, body: untyped): untyped = | ||
result = rpcServer(name, uri.strVal(), body) | ||
macro newServer*(rpc: static[RpcService], injections: varargs[untyped]): untyped = | ||
## Macro to construct a new server for a RpcService. Injections can be provided | ||
## for the server, if defined by an ``inject`` statement in the service. | ||
let rpcName = $rpc | ||
let serverFactoryProc = rpcServerFactoryProc(rpcName) | ||
result = quote do: | ||
`serverFactoryProc`() | ||
for injection in injections: | ||
result.add(injection) | ||
|
||
export json, serverRuntime | ||
else: | ||
import jsffi | ||
import nerve/client, nerve/clientRuntime | ||
macro newClient*(rpc: static[RpcService], driver: NerveDriver): untyped = | ||
## Macro for constructing a new client for a RpcService. A driver can be | ||
## found in the ``drivers`` module or user defined. | ||
let clientFactoryProc = rpcClientFactoryProc($rpc) | ||
result = quote do: | ||
`clientFactoryProc`(`driver`) | ||
|
||
macro rpc*(name, uri, body: untyped): untyped = | ||
result = rpcClient(name, uri.strVal(), body) | ||
macro newHttpClient*(rpc: static[RpcService], host: static[string] = ""): untyped = | ||
## Macro to create a new client loaded with the http driver. The macro uses | ||
## the provided service uri, prefixed with an optional host. | ||
let clientFactoryProc = rpcClientFactoryProc($rpc) | ||
let serviceName = ident($rpc) | ||
result = quote do: | ||
`clientFactoryProc`(newHttpDriver(`host` & `serviceName`.rpcUri)) | ||
|
||
export jsffi, clientRuntime | ||
macro rpcUri*(rpc: static[RpcService]): untyped = | ||
## Macro that provides a compile time reference to the | ||
## provided service uri. Useful in ``case`` statements. | ||
let rpcName = $rpc | ||
let uriConst = rpcName.rpcUriConstName | ||
result = quote do: | ||
`uriConst` | ||
|
||
macro rpcType*(rpc: static[RpcService]): untyped = | ||
## Macro to provide reference to the generated | ||
## RpcServiceInst subtype. This type describes the objects | ||
## returned by ``newClient`` and ``newServer`` | ||
let typeName = rpcServiceName($rpc) | ||
result = quote do: | ||
`typeName` | ||
|
||
macro routeRpc*(rpc: static[RpcService], server: RpcServiceInst, req: JsObject): untyped = | ||
## Macro to do the server side dispatch of the RPC request | ||
let rpcName = $rpc | ||
let routerProc = rpcName.rpcRouterProcName | ||
result = quote do: | ||
`routerProc`(`server`, `req`) | ||
|
||
macro routeRpc*(rpc: static[RpcService], server: RpcServiceInst, req: string): untyped = | ||
## Macro to do the server side dispatch of the RPC request | ||
let rpcName = $rpc | ||
let routerProc = rpcName.rpcRouterProcName | ||
result = quote do: | ||
`routerProc`(`server`, `req`) | ||
|
||
export types, drivers |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,67 +1,44 @@ | ||
import macros, tables | ||
import common | ||
|
||
proc procDefs(node: NimNode): seq[NimNode] = | ||
# Gets all the proc definitions from the statement list | ||
for child in node: | ||
if child.kind == nnkProcDef: | ||
result.add(child) | ||
|
||
proc getParams(formalParams: NimNode): seq[Table[string, NimNode]] = | ||
# Find all the parameters and build a table with needed information | ||
assert(formalParams[0].len > 1, "RPC procs need to return a future") | ||
assert(formalParams[0][0].strVal == "Future", "RPC procs need to return a future") | ||
for param in formalParams: | ||
if param.kind == nnkIdentDefs: | ||
let defaultIdx = param.len - 1 | ||
let typeIdx = param.len - 2 | ||
for i in 0 ..< typeIdx: | ||
result.add( | ||
{ | ||
"name": param[i], | ||
"nameStr": newStrLitNode(param[i].strVal), | ||
}.toTable | ||
) | ||
|
||
|
||
proc procBody(p: NimNode, uri = "/rpc"): NimNode = | ||
let nameStr = newStrLitNode(p.name.strVal) | ||
proc networkProcBody(p: NimNode, methodName: string): NimNode = | ||
let formalParams = p.findChild(it.kind == nnkFormalParams) | ||
let retType = formalParams[0][1] | ||
let params = formalParams.getParams() | ||
let req = genSym() | ||
let driver = ident("nerveDriver") | ||
|
||
var paramJson = nnkStmtList.newTree() | ||
for param in params: | ||
let nameStr = param["nameStr"] | ||
let name = param["name"] | ||
paramJson.add( | ||
quote do: | ||
`req`["body"]["params"][`nameStr`] = `name`.toJs() | ||
`req`["params"][`nameStr`] = toJs `name` | ||
) | ||
|
||
result = quote do: | ||
let `req` = newJsObject() | ||
`req`["method"] = cstring"POST" | ||
`req`["body"] = newJsObject() | ||
`req`["body"]["jsonrpc"] = cstring("2.0") | ||
`req`["body"]["id"] = 0 | ||
`req`["body"]["method"] = cstring`nameStr` | ||
`req`["body"]["params"] = newJsObject() | ||
`req`["jsonrpc"] = toJs "2.0" | ||
`req`["id"] = toJs 0 | ||
`req`["method"] = toJs `methodName` | ||
`req`["params"] = newJsObject() | ||
`paramJson` | ||
`req`["body"] = JSON.stringify(`req`["body"]) | ||
result = fetch(cstring(`uri`), `req`) | ||
.then(respToJson) | ||
result = `driver`(`req`) | ||
.then(handleRpcResponse[`retType`]) | ||
|
||
proc rpcClient*(name: NimNode, uri: string, body: NimNode): NimNode = | ||
proc networkProcs*(procs: seq[NimNode]): NimNode = | ||
result = newStmtList() | ||
let procs = procDefs(body) | ||
for p in procs: | ||
let newBody = procBody(p, uri) | ||
p[p.len - 1] = newBody | ||
result.add(p) | ||
result.add(rpcServiceType(name, procs)) | ||
result.add(rpcServiceObject(name, procs, uri)) | ||
if defined(nerveRpcDebug): | ||
echo repr result | ||
let networkProc = copy(p) | ||
let methodName = p[0].basename.strVal() | ||
networkProc[0] = networkProcName(methodName) | ||
networkProc[networkProc.len - 1] = networkProcBody(networkProc, methodName) | ||
networkProc.findChild(it.kind == nnkFormalParams).add( | ||
nnkIdentDefs.newTree( | ||
ident("nerveDriver"), | ||
ident("NerveDriver"), | ||
newEmptyNode() | ||
) | ||
) | ||
result.add(networkProc) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,24 +1,30 @@ | ||
import jsffi | ||
import web | ||
|
||
type | ||
InvalidResponseError* = ref object of CatchableError | ||
RpcError* = ref object of CatchableError | ||
|
||
proc Boolean(o: JsObject): bool {. importc .} | ||
|
||
proc respJson*(data: JsObject): JsObject {. importcpp: "#.json()" .} | ||
|
||
proc respToJson*(resp: JsObject): JsObject = | ||
if Boolean(resp.ok): | ||
return respJson(resp) | ||
let msg = "Invalid Response: Server responsed with code " & $to(resp.status, int) | ||
raise InvalidResponseError(msg: msg) | ||
|
||
proc handleRpcResponse*[T](rpcResponse: JsObject): T = | ||
let error = rpcResponse["error"] | ||
if Boolean(error): | ||
let msg = $error.message.to(cstring) & ": " & $error.data.msg.to(cstring) & "\n" & $error.data.stackTrace.to(cstring) & "\n" | ||
if hasKey(rpcResponse, "error"): | ||
let error = rpcResponse["error"] | ||
let msg = $error["message"].to(wstring) & ": " & $error["data"]["msg"].to(wstring) & "\n" & $error["data"]["stackTrace"].to(wstring) & "\n" | ||
raise RpcError(msg: msg) | ||
rpcResponse["result"].to(T) | ||
|
||
var JSON* {. importc, nodecl .}: JsObject | ||
when not defined(js): | ||
proc respToJson*(resp: string): JsObject = | ||
try: | ||
result = parseJson(resp) | ||
except: | ||
let msg = "Invalid Response: Unable to parse JSON" | ||
raise InvalidResponseError(msg: msg) | ||
else: | ||
proc Boolean(o: JsObject): bool {. importc .} | ||
|
||
proc respJson*(data: JsObject): JsObject {. importcpp: "#.json()" .} | ||
|
||
proc respToJson*(resp: JsObject): JsObject = | ||
if Boolean(resp.ok): | ||
return respJson(resp) | ||
let msg = "Invalid Response: Server responsed with code " & $to(resp.status, int) | ||
raise InvalidResponseError(msg: msg) |
Oops, something went wrong.