From ecf329bbd908372671e88da5fa8ebccca1f4e012 Mon Sep 17 00:00:00 2001 From: nepeckman Date: Fri, 28 Jun 2019 18:08:57 -0400 Subject: [PATCH 01/22] Changed macro name --- src/nerve.nim | 4 ++-- src/nerve/service.nim | 0 tests/fileService.nim | 2 +- tests/greetingService.nim | 2 +- tests/personService.nim | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) create mode 100644 src/nerve/service.nim diff --git a/src/nerve.nim b/src/nerve.nim index dc10087..4fe20b4 100644 --- a/src/nerve.nim +++ b/src/nerve.nim @@ -4,7 +4,7 @@ when not defined(js): import json import nerve/server, nerve/serverRuntime - macro rpc*(name, uri, body: untyped): untyped = + macro service*(name, uri, body: untyped): untyped = result = rpcServer(name, uri.strVal(), body) export json, serverRuntime @@ -12,7 +12,7 @@ else: import jsffi import nerve/client, nerve/clientRuntime - macro rpc*(name, uri, body: untyped): untyped = + macro service*(name, uri, body: untyped): untyped = result = rpcClient(name, uri.strVal(), body) export jsffi, clientRuntime diff --git a/src/nerve/service.nim b/src/nerve/service.nim new file mode 100644 index 0000000..e69de29 diff --git a/tests/fileService.nim b/tests/fileService.nim index 4fcf98d..9bb4ebf 100644 --- a/tests/fileService.nim +++ b/tests/fileService.nim @@ -1,6 +1,6 @@ import nerve, nerve/utils -rpc FileService, "/api/file": +service FileService, "/api/file": proc saveFile(filename, data: wstring): Future[wstring] = let file = open(filename) diff --git a/tests/greetingService.nim b/tests/greetingService.nim index 1383092..a77cb9c 100644 --- a/tests/greetingService.nim +++ b/tests/greetingService.nim @@ -1,6 +1,6 @@ import nerve, nerve/utils -rpc GreetingService, "/api/greeting": +service GreetingService, "/api/greeting": proc greet(greeting = wstring("Hello"), name = wstring("World")): Future[wstring] = fwrap(greeting & " " & name) diff --git a/tests/personService.nim b/tests/personService.nim index bf631ee..aabd34c 100644 --- a/tests/personService.nim +++ b/tests/personService.nim @@ -8,7 +8,7 @@ type self*: Person children*: seq[Person] -rpc PersonService, "/api/person": +service PersonService, "/api/person": proc helloWorld(): Future[wstring] = result = newFuture[wstring]() result.complete("Hello world") From 0b672b525027bd8935084dbdfb5690e047e84ac5 Mon Sep 17 00:00:00 2001 From: nepeckman Date: Sun, 30 Jun 2019 18:43:15 -0400 Subject: [PATCH 02/22] Added working new server --- src/nerve.nim | 1 - src/nerve/common.nim | 26 ++--- src/nerve/newserver.nim | 146 ++++++++++++++++++++++++++++ src/nerve/runtime/serverRuntime.nim | 57 +++++++++++ src/nerve/serverRuntime.nim | 2 - src/nerve/service.nim | 34 +++++++ src/nerve/utils.nim | 17 ++-- tests/newTest.nim | 11 +++ 8 files changed, 268 insertions(+), 26 deletions(-) create mode 100644 src/nerve/newserver.nim create mode 100644 src/nerve/runtime/serverRuntime.nim create mode 100644 tests/newTest.nim diff --git a/src/nerve.nim b/src/nerve.nim index 4fe20b4..84874ae 100644 --- a/src/nerve.nim +++ b/src/nerve.nim @@ -16,4 +16,3 @@ else: result = rpcClient(name, uri.strVal(), body) export jsffi, clientRuntime - diff --git a/src/nerve/common.nim b/src/nerve/common.nim index d0743cf..afbd51c 100644 --- a/src/nerve/common.nim +++ b/src/nerve/common.nim @@ -3,27 +3,21 @@ import json const dispatchPrefix* = "NerveRpc" -type RpcServer* = object of RootObj +type RpcService* = object of RootObj uri*: string -proc rpcRouterProcName*(name: NimNode): NimNode = - let nameStr = strVal(name) - result = ident("NerveRpc" & nameStr & "Router") +proc rpcRouterProcName*(name: string): NimNode = ident("NerveRpc" & name & "Router") -proc rpcUriConstName*(name: NimNode): NimNode = - let nameStr = strVal(name) - result = ident("NerveRpc" & nameStr & "Uri") +proc rpcUriConstName*(name: string): NimNode = ident("NerveRpc" & name & "Uri") -proc rpcUriConst*(name: NimNode, uri: string): NimNode = +proc rpcUriConst*(name, uri: string): NimNode = let uriConst = name.rpcUriConstName() result = quote do: const `uriConst`* = `uri` -proc rpcServiceName*(name: NimNode): NimNode = - let nameStr = strVal(name) - result = ident("NerveRpc" & nameStr & "Object") +proc rpcServiceName*(name: string): NimNode = ident("NerveRpc" & name & "Object") -proc rpcServiceType*(name: NimNode, procs: seq[NimNode]): NimNode = +proc rpcServiceType*(name: string, procs: seq[NimNode]): NimNode = let typeName = rpcServiceName(name) var procFields = nnkRecList.newTree() for p in procs: @@ -35,13 +29,13 @@ proc rpcServiceType*(name: NimNode, procs: seq[NimNode]): NimNode = ) result = quote do: - type `typeName`* = object of RpcServer + type `typeName`* = object of RpcService result[0][2][2] = procFields -proc rpcServiceObject*(name: NimNode, procs: seq[NimNode], uri = "rpc"): NimNode = +proc rpcServiceObject*(name: string, procs: seq[NimNode], uri = "rpc"): NimNode = let typeName = rpcServiceName(name) result = quote do: - var `name`* = `typeName`(uri: `uri`) + `typeName`(uri: `uri`) for p in procs: var field = newColonExpr(p[0].basename, p[0].basename) - result[0][2].add(field) + result.add(field) diff --git a/src/nerve/newserver.nim b/src/nerve/newserver.nim new file mode 100644 index 0000000..a68a7ba --- /dev/null +++ b/src/nerve/newserver.nim @@ -0,0 +1,146 @@ +import macros, tables, strutils, json +import common, service, runtime/serverRuntime + +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 dispatchName(node: NimNode): NimNode = ident(dispatchPrefix & node.name.strVal.capitalizeAscii) + +proc enumDeclaration(enumName: NimNode, procs: seq[NimNode]): NimNode = + # The enum used to dispatch methods + var enumTy = nnkEnumTy.newTree(newEmptyNode()) + for p in procs: + enumTy.add(dispatchName(p)) + result = quote do: + type `enumName` = `enumTy` + +proc paramType(param: NimNode): NimNode = + # Either returns the type node of the param + # or creates a node that gets the type of default value + let defaultIdx = param.len - 1 + let typeIdx = param.len - 2 + if param[typeIdx].kind == nnkEmpty: + var defaultParam = param[defaultIdx] + result = quote do: + typeof(`defaultParam`) + else: + result = param[typeIdx] + +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 + let ptype = paramType(param) + for i in 0 ..< typeIdx: + result.add( + { + "name": param[i], + "nameStr": newStrLitNode(param[i].strVal), + "type": ptype, + "defaultVal": param[defaultIdx] + }.toTable + ) + +proc unboxExpression(param: Table[string, NimNode], requestSym: NimNode): NimNode = + # Retrieve the param from the request, convert to Nim type + var nameStr = param["nameStr"] + var ntype = param["type"] + var defaultVal = param["defaultVal"] + + if defaultVal.kind != nnkEmpty: + result = quote do: + nerveUnboxParameter(`requestSym`, `nameStr`, `defaultVal`) + else: + result = quote do: + nerveUnboxParameter[`ntype`](`requestSym`, `nameStr`) + +proc procWrapper(requestSym, p: NimNode): NimNode = + # This wrapper gets the parameters from the request and uses them to invoke the proc + result = nnkStmtList.newTree() + var methodCall = nnkCall.newTree(p.name) + let params = p.findChild(it.kind == nnkFormalParams).getParams() + + for param in params: + var name = param["name"] + let unboxExpr = param.unboxExpression(requestSym) + result.add(quote do: + let `name` = `unboxExpr` + ) + methodCall.add(name) + + # Invoke the method with the params, convert to json, and return response + result.add(quote do: + result["result"] = % await `methodCall` + ) + +proc dispatch(procs: seq[NimNode], methodSym, requestSym: NimNode): NimNode = + # Create the case statement used to dispatch proc + result = nnkCaseStmt.newTree(methodSym) + + for p in procs: + # Add the branch that dispatches the proc + let wrapper = procWrapper(requestSym, p) + result.add( + nnkOfBranch.newTree( + dispatchName(p), + wrapper + )) + +macro server*(service: untyped): untyped = + let name = service.strVal() + let uri = "" + + let + enumSym = genSym(nskType) # Type name the enum used to dispatch procs + methodSym = genSym(nskLet) # Variable that holds the requested method + requestSym = ident("request") # The request parameter + routerSym = rpcRouterProcName(name) + routerName = routerSym.strVal() + let procs = procDefs(getService(name)) + + let dispatchStatement = dispatch(procs, methodSym, requestSym) + let enumDeclaration = enumDeclaration(enumSym, procs) + + result = newStmtList() + result.add(getService(name)) + + result.add(quote do: + `enumDeclaration` + proc `routerSym`*(`requestSym`: JsonNode): Future[JsonNode] {.async.} = + result = %* {"jsonrpc": "2.0"} + if not nerveValidateRequest(`requestSym`): + result["id"] = if `requestSym`.hasKey("id"): `requestSym`["id"] else: newJNull() + result["error"] = newNerveError(-32600, "Invalid Request") + try: + let `methodSym` = nerveGetMethod[`enumSym`](`requestSym`) + `dispatchStatement` + except DispatchError as e: + result["error"] = newNerveError(-32601, "Method not found", e) + except ParameterError as e: + result["error"] = newNerveError(-32602, "Invalid params", e) + except CatchableError as e: + result["error"] = newNerveError(-32000, "Server error", e) + + proc `routerSym`*(`requestSym`: string): Future[JsonNode] = + try: + let requestJson = parseJson(`requestSym`) + result = `routerSym`(requestJson) + except CatchableError as e: + result = newFuture[JsonNode](`routerName`) + var response = %* {"jsonrpc": "2.0", "id": newJNull()} + response["error"] = newNerveError(-32700, "Parse error", e) + result.complete(response) + ) + + result.add(rpcServiceObject(name, procs, uri)) + + echo repr result + +export serverRuntime diff --git a/src/nerve/runtime/serverRuntime.nim b/src/nerve/runtime/serverRuntime.nim new file mode 100644 index 0000000..eb4b476 --- /dev/null +++ b/src/nerve/runtime/serverRuntime.nim @@ -0,0 +1,57 @@ +import json, strutils, sequtils, os +import ../common + +type + RequestFormatError* = ref object of CatchableError + DispatchError* = ref object of CatchableError + ParameterError* = ref object of CatchableError + +proc nerveValidateRequest*(req: JsonNode): bool = + req.hasKey("jsonrpc") and req["jsonrpc"].getStr() == "2.0" and req.hasKey("id") + +proc nerveGetMethod*[T: enum](req: JsonNode): T = + try: + parseEnum[T](dispatchPrefix & req["method"].getStr()) + except: + let msg = if req.hasKey("method"): "No method '" & req["method"].getStr() & "'" + else: "Request missing method field" + raise DispatchError(msg: msg, parent: getCurrentException()) + +proc nerveUnboxParameter*[T](req: JsonNode, param: string, default: T): T = + try: + return if req["params"].hasKey(param): req["params"][param].to(T) + else: default + except: + let msg = "Error in param '" & param & "': " & getCurrentExceptionMsg() + raise ParameterError(msg: msg, parent: getCurrentException()) + +proc nerveUnboxParameter*[T](req: JsonNode, param: string): T = + try: + return req["params"][param].to(T) + except: + let msg = "Error in param '" & param & "': " & getCurrentExceptionMsg() + raise ParameterError(msg: msg, parent: getCurrentException()) + +proc newNerveError*(code: int, message: string): JsonNode = + %* { + "code": code, + "message": message + } + +proc newNerveError*(code: int, message: string, e: ref CatchableError): JsonNode = + let dir = getCurrentDir() + let stackTrace = e.getStackTraceEntries() + .filterIt(`$`(it.filename).find(dir) != -1 and + `$`(it.procname).find(dispatchPrefix) == -1) + .mapIt($it.filename & "(" & $it.line & ")" & " " & $it.procname) + .join("\n") + return %* { + "code": code, + "message": message, + "data": { + "msg": e.msg, + "stackTrace": if defined(release): "" else: stackTrace + } + } + +export json diff --git a/src/nerve/serverRuntime.nim b/src/nerve/serverRuntime.nim index 54b997e..64d0d21 100644 --- a/src/nerve/serverRuntime.nim +++ b/src/nerve/serverRuntime.nim @@ -53,5 +53,3 @@ proc newNerveError*(code: int, message: string, e: ref CatchableError): JsonNode "stackTrace": if defined(release): "" else: stackTrace } } - -export parseJson diff --git a/src/nerve/service.nim b/src/nerve/service.nim index e69de29..7cee723 100644 --- a/src/nerve/service.nim +++ b/src/nerve/service.nim @@ -0,0 +1,34 @@ +import macros, macrocache +import common + +const serviceMap = CacheTable("serviceMap") + +proc checkParams(formalParams: NimNode) = + # Ensure return types are future + 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") + +proc procDefs(node: NimNode): seq[NimNode] = + # Gets all the proc definitions from the statement list + for child in node: + if child.kind == nnkProcDef: + child.findChild(it.kind == nnkFormalParams).checkParams() + result.add(child) + +proc addService(name: string, procs: seq[NimNode]) = + var procList = newStmtList() + for p in procs: + procList.add(p) + serviceMap[name] = procList + +proc getService*(name: string): NimNode = serviceMap[name] + +macro service*(name: untyped, uri: static[string], body: untyped): untyped = + let procs = procDefs(body) + let nameStr = name.strVal() + addService(nameStr, procs) + result = newStmtList() + result.add(rpcServiceType(nameStr, procs)) + result.add(rpcUriConst(nameStr, uri)) + echo repr result + diff --git a/src/nerve/utils.nim b/src/nerve/utils.nim index 3e6a2a0..0852d81 100644 --- a/src/nerve/utils.nim +++ b/src/nerve/utils.nim @@ -26,19 +26,22 @@ else: result = newFuture[T]() result.complete(it) - macro rpcUri*(rpc: RpcServer): untyped = - let uriConst = rpc.rpcUriConstName + macro rpcUri*(rpc: RpcService): untyped = + let rpcName = rpc.strVal() + let uriConst = rpcName.rpcUriConstName result = quote do: `uriConst` - macro routeRpc*(rpc: RpcServer, req: JsonNode): untyped = - let routerProc = rpc.rpcRouterProcName + macro routeRpc*(rpc: RpcService, req: JsonNode): untyped = + let rpcName = rpc.strVal() + let routerProc = rpcName.rpcRouterProcName result = quote do: `routerProc`(`req`) - macro routeRpc*(rpc: RpcServer, req: string): untyped = - let routerProc = rpc.rpcRouterProcName + macro routeRpc*(rpc: RpcService, req: string): untyped = + let rpcName = rpc.strVal() + let routerProc = rpcName.rpcRouterProcName result = quote do: `routerProc`(`req`) - export asyncdispatch, RpcServer + export asyncdispatch, RpcService diff --git a/tests/newTest.nim b/tests/newTest.nim new file mode 100644 index 0000000..0c436fe --- /dev/null +++ b/tests/newTest.nim @@ -0,0 +1,11 @@ +import nerve/service, nerve/newserver, nerve/utils +import macros + +service Hello, "/api/hello": + + proc helloWorld(): Future[wstring] = fwrap("Hello World") + + proc greet(greeting, name: wstring): Future[wstring] = fwrap(greeting & " " & name) + +let helloServer = server(Hello) +discard helloServer.helloWorld() From 7cee393886252ce2e0796e91b4ffc6960c85f599 Mon Sep 17 00:00:00 2001 From: nepeckman Date: Sun, 30 Jun 2019 22:18:22 -0400 Subject: [PATCH 03/22] Started new client --- src/nerve/drivers.nim | 8 +++++ src/nerve/newclient.nim | 66 +++++++++++++++++++++++++++++++++++++++++ tests/newTest.nim | 6 ++-- 3 files changed, 77 insertions(+), 3 deletions(-) create mode 100644 src/nerve/drivers.nim create mode 100644 src/nerve/newclient.nim diff --git a/src/nerve/drivers.nim b/src/nerve/drivers.nim new file mode 100644 index 0000000..8d577c1 --- /dev/null +++ b/src/nerve/drivers.nim @@ -0,0 +1,8 @@ +when not defined(js): + import json, asyncdispatch + + type NerveDriver*[T] = proc (req: JsonNode): Future[T] + + proc echoDriver*[T] (req: JsonNode): Future[T] = + result = newFuture[T]() + result.complete(req.to(T)) diff --git a/src/nerve/newclient.nim b/src/nerve/newclient.nim new file mode 100644 index 0000000..cf1587b --- /dev/null +++ b/src/nerve/newclient.nim @@ -0,0 +1,66 @@ +import macros, tables +import common, service, drivers + +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, driver: NimNode): NimNode = + let nameStr = newStrLitNode(p.name.strVal) + let formalParams = p.findChild(it.kind == nnkFormalParams) + let retType = formalParams[0][1] + let params = formalParams.getParams() + let req = genSym() + let newJsObject = if defined(js): ident("newJsObject") else: ident("newJObject") + + var paramJson = nnkStmtList.newTree() + for param in params: + let nameStr = param["nameStr"] + let name = param["name"] + paramJson.add( + quote do: + `req`["params"][`nameStr`] = % `name` + ) + + result = quote do: + let `req` = `newJsObject`() + `req`["jsonrpc"] = % "2.0" + `req`["id"] = % 0 + `req`["method"] = % `nameStr` + `req`["params"] = `newJsObject`() + `paramJson` + result = `driver`[`retType`](`req`) + +macro client*(service: untyped, driver: NerveDriver): untyped = + result = newStmtList() + let name = service.strVal() + let uri = "" + let body = getService(name) + let procs = procDefs(body) + for p in procs: + let newBody = procBody(p, driver) + p[p.len - 1] = newBody + result.add(p) + result.add(rpcServiceObject(name, procs, uri)) + echo repr result + +import json +export json diff --git a/tests/newTest.nim b/tests/newTest.nim index 0c436fe..347e5bd 100644 --- a/tests/newTest.nim +++ b/tests/newTest.nim @@ -1,4 +1,4 @@ -import nerve/service, nerve/newserver, nerve/utils +import nerve/service, nerve/newclient, nerve/drivers, nerve/utils import macros service Hello, "/api/hello": @@ -7,5 +7,5 @@ service Hello, "/api/hello": proc greet(greeting, name: wstring): Future[wstring] = fwrap(greeting & " " & name) -let helloServer = server(Hello) -discard helloServer.helloWorld() +let helloClient = client(Hello, echoDriver) +discard helloClient.helloWorld() From fc349918e98e7c4feeaaba054acf6ff4e16ec23f Mon Sep 17 00:00:00 2001 From: nepeckman Date: Mon, 1 Jul 2019 22:17:01 -0400 Subject: [PATCH 04/22] Work on drivers --- src/nerve/drivers.nim | 14 +++++++++----- src/nerve/newclient.nim | 10 +++++++--- tests/newTest.nim | 2 +- 3 files changed, 17 insertions(+), 9 deletions(-) diff --git a/src/nerve/drivers.nim b/src/nerve/drivers.nim index 8d577c1..a7e205a 100644 --- a/src/nerve/drivers.nim +++ b/src/nerve/drivers.nim @@ -1,8 +1,12 @@ when not defined(js): - import json, asyncdispatch + import json, asyncdispatch, httpClient - type NerveDriver*[T] = proc (req: JsonNode): Future[T] + type HttpDriver* = ref object + client: AsyncHttpClient + uri: string - proc echoDriver*[T] (req: JsonNode): Future[T] = - result = newFuture[T]() - result.complete(req.to(T)) + proc send*[T](driver: HttpDriver, req: JsonNode): Future[T] {.async.} = + let res = await driver.client.postContent(driver.uri, $ req) + result = res.parseJson().to(T) + + proc newHttpDriver*(uri: string): HttpDriver = HttpDriver(client: newAsyncHttpClient(), uri: uri) diff --git a/src/nerve/newclient.nim b/src/nerve/newclient.nim index cf1587b..e488d82 100644 --- a/src/nerve/newclient.nim +++ b/src/nerve/newclient.nim @@ -47,16 +47,20 @@ proc procBody(p, driver: NimNode): NimNode = `req`["method"] = % `nameStr` `req`["params"] = `newJsObject`() `paramJson` - result = `driver`[`retType`](`req`) + result = send[`retType`](`driver`, `req`) -macro client*(service: untyped, driver: NerveDriver): untyped = +macro client*(service: untyped, driver: untyped): untyped = + let driverSym = genSym() result = newStmtList() + result.add(quote do: + let `driverSym` = `driver` + ) let name = service.strVal() let uri = "" let body = getService(name) let procs = procDefs(body) for p in procs: - let newBody = procBody(p, driver) + let newBody = procBody(p, driverSym) p[p.len - 1] = newBody result.add(p) result.add(rpcServiceObject(name, procs, uri)) diff --git a/tests/newTest.nim b/tests/newTest.nim index 347e5bd..ea66346 100644 --- a/tests/newTest.nim +++ b/tests/newTest.nim @@ -7,5 +7,5 @@ service Hello, "/api/hello": proc greet(greeting, name: wstring): Future[wstring] = fwrap(greeting & " " & name) -let helloClient = client(Hello, echoDriver) +let helloClient = client(Hello, newHttpDriver("http://a.a" & "/api/hello")) discard helloClient.helloWorld() From 4b4cab26212e4de332a0bee62943b2cf30d7d4d4 Mon Sep 17 00:00:00 2001 From: nepeckman Date: Tue, 2 Jul 2019 21:51:42 -0400 Subject: [PATCH 05/22] Started work on factories --- src/nerve/service.nim | 144 +++++++++++++++++++++++++++++++++++++++--- tests/newTest.nim | 4 +- 2 files changed, 136 insertions(+), 12 deletions(-) diff --git a/src/nerve/service.nim b/src/nerve/service.nim index 7cee723..ee60f0e 100644 --- a/src/nerve/service.nim +++ b/src/nerve/service.nim @@ -1,13 +1,16 @@ -import macros, macrocache +import macros, tables import common -const serviceMap = CacheTable("serviceMap") - proc checkParams(formalParams: NimNode) = # Ensure return types are future 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") +proc toStmtList(nodes: seq[NimNode]): NimNode = + result = newStmtList() + for node in nodes: + result.add(node) + proc procDefs(node: NimNode): seq[NimNode] = # Gets all the proc definitions from the statement list for child in node: @@ -15,20 +18,143 @@ proc procDefs(node: NimNode): seq[NimNode] = child.findChild(it.kind == nnkFormalParams).checkParams() result.add(child) -proc addService(name: string, procs: seq[NimNode]) = - var procList = newStmtList() +proc rpcServiceObj*(name: string, procs: Table[string, NimNode], uri = "rpc"): NimNode = + let typeName = rpcServiceName(name) + result = quote do: + `typeName`(uri: `uri`) + for pName in procs.keys: + var field = newColonExpr(procs[pName][0].basename, ident(pName)) + result.add(field) + + +proc paramType(param: NimNode): NimNode = + # Either returns the type node of the param + # or creates a node that gets the type of default value + let defaultIdx = param.len - 1 + let typeIdx = param.len - 2 + if param[typeIdx].kind == nnkEmpty: + var defaultParam = param[defaultIdx] + result = quote do: + typeof(`defaultParam`) + else: + result = param[typeIdx] + +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 + let ptype = paramType(param) + for i in 0 ..< typeIdx: + result.add( + { + "name": param[i], + "nameStr": newStrLitNode(param[i].strVal), + "type": ptype, + "defaultVal": param[defaultIdx] + }.toTable + ) + +proc networkProcName(name: string): NimNode = ident("NerveNetwork" & name) + +proc createServerProc(p: NimNode, name: string): NimNode = + let providedProcName = p[0].basename + let procCall = nnkCall.newTree(providedProcName) + let params = p.findChild(it.kind == nnkFormalParams).getParams() + for param in params: + procCall.add(param["name"]) + result = copy(p) + result[result.len - 1] = procCall + result[0] = ident(name) + +proc createClientProc(p: NimNode, name, networkProc: string): NimNode = + let procCall = nnkCall.newTree(networkProcName(networkProc)) + let params = p.findChild(it.kind == nnkFormalParams).getParams() + for param in params: + procCall.add(param["name"]) + result = copy(p) + result[result.len - 1] = procCall + result[0] = ident(name) + +proc rpcServerFactory(name: string, serviceType: NimNode, procs: seq[NimNode]): NimNode = + let procName = ident("NerveRpc" & name & "ServerFactory") + var serverProcs = newStmtList() + var procTable = initTable[string, NimNode]() + for p in procs: + let serverProcName ="NerveServer" & p[0].basename.strVal() + procTable[serverProcName] = p + serverProcs.add(createServerProc(p, serverProcName)) + let service = rpcServiceObj(name, procTable) + result = quote do: + proc `procName`(): `serviceType` = + `serverProcs` + `service` + +proc rpcClientFactory(name: string, serviceType: NimNode, procs: seq[NimNode]): NimNode = + let procName = ident("NerveRpc" & name & "ClientFactory") + var clientProcs = newStmtList() + var procTable = initTable[string, NimNode]() for p in procs: - procList.add(p) - serviceMap[name] = procList + let pName = p[0].basename.strVal() + let clientProcName ="NerveClient" & pName + procTable[clientProcName] = p + clientProcs.add(createClientProc(p, clientProcName, pName)) + let service = rpcServiceObj(name, procTable) + result = quote do: + proc `procName`(): `serviceType` = + `clientProcs` + `service` -proc getService*(name: string): NimNode = serviceMap[name] +proc networkProcBody(p: NimNode): NimNode = + let nameStr = newStrLitNode(p.name.strVal) + let formalParams = p.findChild(it.kind == nnkFormalParams) + let retType = formalParams[0][1] + let params = formalParams.getParams() + let req = genSym() + let newJsObject = if defined(js): ident("newJsObject") else: ident("newJObject") + + var paramJson = nnkStmtList.newTree() + for param in params: + let nameStr = param["nameStr"] + let name = param["name"] + paramJson.add( + quote do: + `req`["params"][`nameStr`] = % `name` + ) + + result = quote do: + let `req` = `newJsObject`() + `req`["jsonrpc"] = % "2.0" + `req`["id"] = % 0 + `req`["method"] = % `nameStr` + `req`["params"] = `newJsObject`() + `paramJson` + result = newFuture[`retType`]() + result.complete(`req`.to(`retType`)) + +proc rpcNetworkProcs(procs: seq[NimNode]): NimNode = + result = newStmtList() + for p in procs: + let networkProc = copy(p) + networkProc[0] = networkProcName(p[0].basename.strVal()) + networkProc[networkProc.len - 1] = networkProcBody(networkProc) + result.add(networkProc) macro service*(name: untyped, uri: static[string], body: untyped): untyped = let procs = procDefs(body) let nameStr = name.strVal() - addService(nameStr, procs) + let serviceType = rpcServiceName(nameStr) result = newStmtList() + result.add(procs.toStmtList()) + result.add(rpcNetworkProcs(procs)) result.add(rpcServiceType(nameStr, procs)) result.add(rpcUriConst(nameStr, uri)) + result.add(rpcServerFactory(nameStr, serviceType, procs)) + result.add(rpcClientFactory(nameStr, serviceType, procs)) echo repr result +import json +export json diff --git a/tests/newTest.nim b/tests/newTest.nim index ea66346..a33274a 100644 --- a/tests/newTest.nim +++ b/tests/newTest.nim @@ -1,4 +1,4 @@ -import nerve/service, nerve/newclient, nerve/drivers, nerve/utils +import nerve/service, nerve/utils import macros service Hello, "/api/hello": @@ -7,5 +7,3 @@ service Hello, "/api/hello": proc greet(greeting, name: wstring): Future[wstring] = fwrap(greeting & " " & name) -let helloClient = client(Hello, newHttpDriver("http://a.a" & "/api/hello")) -discard helloClient.helloWorld() From a69faf6d38fa819f639fd9843d2183e8e9fee91d Mon Sep 17 00:00:00 2001 From: nepeckman Date: Tue, 2 Jul 2019 22:29:27 -0400 Subject: [PATCH 06/22] Modified server dispatch --- src/nerve/newserver.nim | 2 -- src/nerve/server.nim | 57 +++-------------------------------- src/nerve/service.nim | 67 +++++++++++++++++++++++++++++++++++++---- 3 files changed, 66 insertions(+), 60 deletions(-) diff --git a/src/nerve/newserver.nim b/src/nerve/newserver.nim index a68a7ba..65cd5d2 100644 --- a/src/nerve/newserver.nim +++ b/src/nerve/newserver.nim @@ -139,8 +139,6 @@ macro server*(service: untyped): untyped = result.complete(response) ) - result.add(rpcServiceObject(name, procs, uri)) - echo repr result export serverRuntime diff --git a/src/nerve/server.nim b/src/nerve/server.nim index 14c2281..017891f 100644 --- a/src/nerve/server.nim +++ b/src/nerve/server.nim @@ -7,9 +7,9 @@ proc procDefs(node: NimNode): seq[NimNode] = if child.kind == nnkProcDef: result.add(child) -proc dispatchName(node: NimNode): NimNode = ident(dispatchPrefix & node.name.strVal.capitalizeAscii) +proc dispatchName*(node: NimNode): NimNode = ident(dispatchPrefix & node.name.strVal.capitalizeAscii) -proc enumDeclaration(enumName: NimNode, procs: seq[NimNode]): NimNode = +proc enumDeclaration*(enumName: NimNode, procs: seq[NimNode]): NimNode = # The enum used to dispatch methods var enumTy = nnkEnumTy.newTree(newEmptyNode()) for p in procs: @@ -48,7 +48,7 @@ proc getParams(formalParams: NimNode): seq[Table[string, NimNode]] = }.toTable ) -proc unboxExpression(param: Table[string, NimNode], requestSym: NimNode): NimNode = +proc unboxExpression*(param: Table[string, NimNode], requestSym: NimNode): NimNode = # Retrieve the param from the request, convert to Nim type var nameStr = param["nameStr"] var ntype = param["type"] @@ -61,7 +61,7 @@ proc unboxExpression(param: Table[string, NimNode], requestSym: NimNode): NimNod result = quote do: nerveUnboxParameter[`ntype`](`requestSym`, `nameStr`) -proc procWrapper(requestSym, p: NimNode): NimNode = +proc procWrapper*(requestSym, p: NimNode): NimNode = # This wrapper gets the parameters from the request and uses them to invoke the proc result = nnkStmtList.newTree() var methodCall = nnkCall.newTree(p.name) @@ -80,7 +80,7 @@ proc procWrapper(requestSym, p: NimNode): NimNode = result["result"] = % await `methodCall` ) -proc dispatch(procs: seq[NimNode], methodSym, requestSym: NimNode): NimNode = +proc dispatch*(procs: seq[NimNode], methodSym, requestSym: NimNode): NimNode = # Create the case statement used to dispatch proc result = nnkCaseStmt.newTree(methodSym) @@ -93,50 +93,3 @@ proc dispatch(procs: seq[NimNode], methodSym, requestSym: NimNode): NimNode = wrapper )) -proc rpcServer*(name: NimNode, uri: string, body: NimNode): NimNode = - let - enumSym = genSym(nskType) # Type name the enum used to dispatch procs - methodSym = genSym(nskLet) # Variable that holds the requested method - requestSym = ident("request") # The request parameter - routerSym = rpcRouterProcName(name) - routerName = routerSym.strVal() - let procs = procDefs(body) - - let dispatchStatement = dispatch(procs, methodSym, requestSym) - let enumDeclaration = enumDeclaration(enumSym, procs) - - body.add(rpcServiceType(name, procs)) - body.add(rpcServiceObject(name, procs, uri)) - body.add(rpcUriConst(name, uri)) - - body.add(quote do: - `enumDeclaration` - proc `routerSym`*(`requestSym`: JsonNode): Future[JsonNode] {.async.} = - result = %* {"jsonrpc": "2.0"} - if not nerveValidateRequest(`requestSym`): - result["id"] = if `requestSym`.hasKey("id"): `requestSym`["id"] else: newJNull() - result["error"] = newNerveError(-32600, "Invalid Request") - try: - let `methodSym` = nerveGetMethod[`enumSym`](`requestSym`) - `dispatchStatement` - except DispatchError as e: - result["error"] = newNerveError(-32601, "Method not found", e) - except ParameterError as e: - result["error"] = newNerveError(-32602, "Invalid params", e) - except CatchableError as e: - result["error"] = newNerveError(-32000, "Server error", e) - - proc `routerSym`*(`requestSym`: string): Future[JsonNode] = - try: - let requestJson = parseJson(`requestSym`) - result = `routerSym`(requestJson) - except CatchableError as e: - result = newFuture[JsonNode](`routerName`) - var response = %* {"jsonrpc": "2.0", "id": newJNull()} - response["error"] = newNerveError(-32700, "Parse error", e) - result.complete(response) - ) - result = body - - if defined(nerveRpcDebug): - echo repr result diff --git a/src/nerve/service.nim b/src/nerve/service.nim index ee60f0e..f49c860 100644 --- a/src/nerve/service.nim +++ b/src/nerve/service.nim @@ -1,5 +1,5 @@ import macros, tables -import common +import common, server proc checkParams(formalParams: NimNode) = # Ensure return types are future @@ -70,11 +70,12 @@ proc createServerProc(p: NimNode, name: string): NimNode = result[result.len - 1] = procCall result[0] = ident(name) -proc createClientProc(p: NimNode, name, networkProc: string): NimNode = +proc createClientProc(p: NimNode, name, networkProc: string, driver: NimNode): NimNode = let procCall = nnkCall.newTree(networkProcName(networkProc)) let params = p.findChild(it.kind == nnkFormalParams).getParams() for param in params: procCall.add(param["name"]) + procCall.add(driver) result = copy(p) result[result.len - 1] = procCall result[0] = ident(name) @@ -95,16 +96,17 @@ proc rpcServerFactory(name: string, serviceType: NimNode, procs: seq[NimNode]): proc rpcClientFactory(name: string, serviceType: NimNode, procs: seq[NimNode]): NimNode = let procName = ident("NerveRpc" & name & "ClientFactory") + let driverName = ident("nerveDriver") var clientProcs = newStmtList() var procTable = initTable[string, NimNode]() for p in procs: let pName = p[0].basename.strVal() let clientProcName ="NerveClient" & pName procTable[clientProcName] = p - clientProcs.add(createClientProc(p, clientProcName, pName)) + clientProcs.add(createClientProc(p, clientProcName, pName, driverName)) let service = rpcServiceObj(name, procTable) result = quote do: - proc `procName`(): `serviceType` = + proc `procName`(`driverName`: string): `serviceType` = `clientProcs` `service` @@ -141,8 +143,60 @@ proc rpcNetworkProcs(procs: seq[NimNode]): NimNode = let networkProc = copy(p) networkProc[0] = networkProcName(p[0].basename.strVal()) networkProc[networkProc.len - 1] = networkProcBody(networkProc) + networkProc.findChild(it.kind == nnkFormalParams).add( + nnkIdentDefs.newTree( + ident("nerveDriver"), + ident("string"), + newEmptyNode() + ) + ) result.add(networkProc) +proc serverDispatch*(name: string, procs: seq[NimNode]): NimNode = + let uri = "" + + let + enumSym = genSym(nskType) # Type name the enum used to dispatch procs + methodSym = genSym(nskLet) # Variable that holds the requested method + serverSym = ident("server") + serviceType = rpcServiceName(name) + requestSym = ident("request") # The request parameter + routerSym = rpcRouterProcName(name) + routerName = routerSym.strVal() + + let dispatchStatement = dispatch(procs, methodSym, requestSym) + let enumDeclaration = enumDeclaration(enumSym, procs) + + result = newStmtList() + + result.add(quote do: + `enumDeclaration` + proc `routerSym`*(`serverSym`: `serviceType`,`requestSym`: JsonNode): Future[JsonNode] {.async.} = + result = %* {"jsonrpc": "2.0"} + if not nerveValidateRequest(`requestSym`): + result["id"] = if `requestSym`.hasKey("id"): `requestSym`["id"] else: newJNull() + result["error"] = newNerveError(-32600, "Invalid Request") + try: + let `methodSym` = nerveGetMethod[`enumSym`](`requestSym`) + `dispatchStatement` + except DispatchError as e: + result["error"] = newNerveError(-32601, "Method not found", e) + except ParameterError as e: + result["error"] = newNerveError(-32602, "Invalid params", e) + except CatchableError as e: + result["error"] = newNerveError(-32000, "Server error", e) + + proc `routerSym`*(`serverSym`: `serviceType`,`requestSym`: string): Future[JsonNode] = + try: + let requestJson = parseJson(`requestSym`) + result = `routerSym`(`serverSym`, requestJson) + except CatchableError as e: + result = newFuture[JsonNode](`routerName`) + var response = %* {"jsonrpc": "2.0", "id": newJNull()} + response["error"] = newNerveError(-32700, "Parse error", e) + result.complete(response) + ) + macro service*(name: untyped, uri: static[string], body: untyped): untyped = let procs = procDefs(body) let nameStr = name.strVal() @@ -152,9 +206,10 @@ macro service*(name: untyped, uri: static[string], body: untyped): untyped = result.add(rpcNetworkProcs(procs)) result.add(rpcServiceType(nameStr, procs)) result.add(rpcUriConst(nameStr, uri)) + result.add(serverDispatch(nameStr, procs)) result.add(rpcServerFactory(nameStr, serviceType, procs)) result.add(rpcClientFactory(nameStr, serviceType, procs)) echo repr result -import json -export json +import json, serverRuntime +export json, serverRuntime From 4b999907753a36978d404bc44c94db528a227581 Mon Sep 17 00:00:00 2001 From: nepeckman Date: Wed, 3 Jul 2019 16:50:02 -0400 Subject: [PATCH 07/22] Restructured project --- src/nerve/client.nim | 67 +++++-------- src/nerve/common.nim | 43 ++++++-- src/nerve/factories.nim | 52 ++++++++++ src/nerve/newclient.nim | 70 ------------- src/nerve/newserver.nim | 144 --------------------------- src/nerve/server.nim | 91 +++++++++-------- src/nerve/serverRuntime.nim | 2 + src/nerve/service.nim | 189 ++---------------------------------- src/nerve/types.nim | 4 + src/nerve/utils.nim | 4 +- 10 files changed, 175 insertions(+), 491 deletions(-) create mode 100644 src/nerve/factories.nim delete mode 100644 src/nerve/newclient.nim delete mode 100644 src/nerve/newserver.nim create mode 100644 src/nerve/types.nim diff --git a/src/nerve/client.nim b/src/nerve/client.nim index 05735ae..466b3d5 100644 --- a/src/nerve/client.nim +++ b/src/nerve/client.nim @@ -1,35 +1,14 @@ 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 = +proc networkProcBody(p: NimNode): NimNode = let nameStr = newStrLitNode(p.name.strVal) let formalParams = p.findChild(it.kind == nnkFormalParams) let retType = formalParams[0][1] let params = formalParams.getParams() let req = genSym() + let newJsObject = if defined(js): ident("newJsObject") else: ident("newJObject") var paramJson = nnkStmtList.newTree() for param in params: @@ -37,31 +16,31 @@ proc procBody(p: NimNode, uri = "/rpc"): NimNode = let name = param["name"] paramJson.add( quote do: - `req`["body"]["params"][`nameStr`] = `name`.toJs() + `req`["params"][`nameStr`] = % `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() + let `req` = `newJsObject`() + `req`["jsonrpc"] = % "2.0" + `req`["id"] = % 0 + `req`["method"] = % `nameStr` + `req`["params"] = `newJsObject`() `paramJson` - `req`["body"] = JSON.stringify(`req`["body"]) - result = fetch(cstring(`uri`), `req`) - .then(respToJson) - .then(handleRpcResponse[`retType`]) + result = newFuture[`retType`]() + result.complete(`req`.to(`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) + networkProc[0] = networkProcName(p[0].basename.strVal()) + networkProc[networkProc.len - 1] = networkProcBody(networkProc) + networkProc.findChild(it.kind == nnkFormalParams).add( + nnkIdentDefs.newTree( + ident("nerveDriver"), + ident("string"), + newEmptyNode() + ) + ) + result.add(networkProc) + diff --git a/src/nerve/common.nim b/src/nerve/common.nim index afbd51c..4cb7b0c 100644 --- a/src/nerve/common.nim +++ b/src/nerve/common.nim @@ -1,10 +1,8 @@ -import macros -import json +import macros, tables const dispatchPrefix* = "NerveRpc" -type RpcService* = object of RootObj - uri*: string +proc networkProcName*(name: string): NimNode = ident("NerveNetwork" & name) proc rpcRouterProcName*(name: string): NimNode = ident("NerveRpc" & name & "Router") @@ -32,10 +30,41 @@ proc rpcServiceType*(name: string, procs: seq[NimNode]): NimNode = type `typeName`* = object of RpcService result[0][2][2] = procFields -proc rpcServiceObject*(name: string, procs: seq[NimNode], uri = "rpc"): NimNode = +proc rpcServiceObject*(name: string, procs: Table[string, NimNode], uri = "rpc"): NimNode = let typeName = rpcServiceName(name) result = quote do: `typeName`(uri: `uri`) - for p in procs: - var field = newColonExpr(p[0].basename, p[0].basename) + for pName in procs.keys: + var field = newColonExpr(procs[pName][0].basename, ident(pName)) result.add(field) + +proc paramType(param: NimNode): NimNode = + # Either returns the type node of the param + # or creates a node that gets the type of default value + let defaultIdx = param.len - 1 + let typeIdx = param.len - 2 + if param[typeIdx].kind == nnkEmpty: + var defaultParam = param[defaultIdx] + result = quote do: + typeof(`defaultParam`) + else: + result = param[typeIdx] + +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 + let ptype = paramType(param) + for i in 0 ..< typeIdx: + result.add( + { + "name": param[i], + "nameStr": newStrLitNode(param[i].strVal), + "type": ptype, + "defaultVal": param[defaultIdx] + }.toTable + ) diff --git a/src/nerve/factories.nim b/src/nerve/factories.nim new file mode 100644 index 0000000..78fec33 --- /dev/null +++ b/src/nerve/factories.nim @@ -0,0 +1,52 @@ +import macros, tables +import common + +proc createServerProc(p: NimNode, name: string): NimNode = + let providedProcName = p[0].basename + let procCall = nnkCall.newTree(providedProcName) + let params = p.findChild(it.kind == nnkFormalParams).getParams() + for param in params: + procCall.add(param["name"]) + result = copy(p) + result[result.len - 1] = procCall + result[0] = ident(name) + +proc createClientProc(p: NimNode, name, networkProc: string, driver: NimNode): NimNode = + let procCall = nnkCall.newTree(networkProcName(networkProc)) + let params = p.findChild(it.kind == nnkFormalParams).getParams() + for param in params: + procCall.add(param["name"]) + procCall.add(driver) + result = copy(p) + result[result.len - 1] = procCall + result[0] = ident(name) + +proc rpcServerFactory*(name: string, serviceType: NimNode, procs: seq[NimNode]): NimNode = + let procName = ident("NerveRpc" & name & "ServerFactory") + var serverProcs = newStmtList() + var procTable = initTable[string, NimNode]() + for p in procs: + let serverProcName ="NerveServer" & p[0].basename.strVal() + procTable[serverProcName] = p + serverProcs.add(createServerProc(p, serverProcName)) + let service = rpcServiceObject(name, procTable) + result = quote do: + proc `procName`(): `serviceType` = + `serverProcs` + `service` + +proc rpcClientFactory*(name: string, serviceType: NimNode, procs: seq[NimNode]): NimNode = + let procName = ident("NerveRpc" & name & "ClientFactory") + let driverName = ident("nerveDriver") + var clientProcs = newStmtList() + var procTable = initTable[string, NimNode]() + for p in procs: + let pName = p[0].basename.strVal() + let clientProcName ="NerveClient" & pName + procTable[clientProcName] = p + clientProcs.add(createClientProc(p, clientProcName, pName, driverName)) + let service = rpcServiceObject(name, procTable) + result = quote do: + proc `procName`(`driverName`: string): `serviceType` = + `clientProcs` + `service` diff --git a/src/nerve/newclient.nim b/src/nerve/newclient.nim deleted file mode 100644 index e488d82..0000000 --- a/src/nerve/newclient.nim +++ /dev/null @@ -1,70 +0,0 @@ -import macros, tables -import common, service, drivers - -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, driver: NimNode): NimNode = - let nameStr = newStrLitNode(p.name.strVal) - let formalParams = p.findChild(it.kind == nnkFormalParams) - let retType = formalParams[0][1] - let params = formalParams.getParams() - let req = genSym() - let newJsObject = if defined(js): ident("newJsObject") else: ident("newJObject") - - var paramJson = nnkStmtList.newTree() - for param in params: - let nameStr = param["nameStr"] - let name = param["name"] - paramJson.add( - quote do: - `req`["params"][`nameStr`] = % `name` - ) - - result = quote do: - let `req` = `newJsObject`() - `req`["jsonrpc"] = % "2.0" - `req`["id"] = % 0 - `req`["method"] = % `nameStr` - `req`["params"] = `newJsObject`() - `paramJson` - result = send[`retType`](`driver`, `req`) - -macro client*(service: untyped, driver: untyped): untyped = - let driverSym = genSym() - result = newStmtList() - result.add(quote do: - let `driverSym` = `driver` - ) - let name = service.strVal() - let uri = "" - let body = getService(name) - let procs = procDefs(body) - for p in procs: - let newBody = procBody(p, driverSym) - p[p.len - 1] = newBody - result.add(p) - result.add(rpcServiceObject(name, procs, uri)) - echo repr result - -import json -export json diff --git a/src/nerve/newserver.nim b/src/nerve/newserver.nim deleted file mode 100644 index 65cd5d2..0000000 --- a/src/nerve/newserver.nim +++ /dev/null @@ -1,144 +0,0 @@ -import macros, tables, strutils, json -import common, service, runtime/serverRuntime - -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 dispatchName(node: NimNode): NimNode = ident(dispatchPrefix & node.name.strVal.capitalizeAscii) - -proc enumDeclaration(enumName: NimNode, procs: seq[NimNode]): NimNode = - # The enum used to dispatch methods - var enumTy = nnkEnumTy.newTree(newEmptyNode()) - for p in procs: - enumTy.add(dispatchName(p)) - result = quote do: - type `enumName` = `enumTy` - -proc paramType(param: NimNode): NimNode = - # Either returns the type node of the param - # or creates a node that gets the type of default value - let defaultIdx = param.len - 1 - let typeIdx = param.len - 2 - if param[typeIdx].kind == nnkEmpty: - var defaultParam = param[defaultIdx] - result = quote do: - typeof(`defaultParam`) - else: - result = param[typeIdx] - -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 - let ptype = paramType(param) - for i in 0 ..< typeIdx: - result.add( - { - "name": param[i], - "nameStr": newStrLitNode(param[i].strVal), - "type": ptype, - "defaultVal": param[defaultIdx] - }.toTable - ) - -proc unboxExpression(param: Table[string, NimNode], requestSym: NimNode): NimNode = - # Retrieve the param from the request, convert to Nim type - var nameStr = param["nameStr"] - var ntype = param["type"] - var defaultVal = param["defaultVal"] - - if defaultVal.kind != nnkEmpty: - result = quote do: - nerveUnboxParameter(`requestSym`, `nameStr`, `defaultVal`) - else: - result = quote do: - nerveUnboxParameter[`ntype`](`requestSym`, `nameStr`) - -proc procWrapper(requestSym, p: NimNode): NimNode = - # This wrapper gets the parameters from the request and uses them to invoke the proc - result = nnkStmtList.newTree() - var methodCall = nnkCall.newTree(p.name) - let params = p.findChild(it.kind == nnkFormalParams).getParams() - - for param in params: - var name = param["name"] - let unboxExpr = param.unboxExpression(requestSym) - result.add(quote do: - let `name` = `unboxExpr` - ) - methodCall.add(name) - - # Invoke the method with the params, convert to json, and return response - result.add(quote do: - result["result"] = % await `methodCall` - ) - -proc dispatch(procs: seq[NimNode], methodSym, requestSym: NimNode): NimNode = - # Create the case statement used to dispatch proc - result = nnkCaseStmt.newTree(methodSym) - - for p in procs: - # Add the branch that dispatches the proc - let wrapper = procWrapper(requestSym, p) - result.add( - nnkOfBranch.newTree( - dispatchName(p), - wrapper - )) - -macro server*(service: untyped): untyped = - let name = service.strVal() - let uri = "" - - let - enumSym = genSym(nskType) # Type name the enum used to dispatch procs - methodSym = genSym(nskLet) # Variable that holds the requested method - requestSym = ident("request") # The request parameter - routerSym = rpcRouterProcName(name) - routerName = routerSym.strVal() - let procs = procDefs(getService(name)) - - let dispatchStatement = dispatch(procs, methodSym, requestSym) - let enumDeclaration = enumDeclaration(enumSym, procs) - - result = newStmtList() - result.add(getService(name)) - - result.add(quote do: - `enumDeclaration` - proc `routerSym`*(`requestSym`: JsonNode): Future[JsonNode] {.async.} = - result = %* {"jsonrpc": "2.0"} - if not nerveValidateRequest(`requestSym`): - result["id"] = if `requestSym`.hasKey("id"): `requestSym`["id"] else: newJNull() - result["error"] = newNerveError(-32600, "Invalid Request") - try: - let `methodSym` = nerveGetMethod[`enumSym`](`requestSym`) - `dispatchStatement` - except DispatchError as e: - result["error"] = newNerveError(-32601, "Method not found", e) - except ParameterError as e: - result["error"] = newNerveError(-32602, "Invalid params", e) - except CatchableError as e: - result["error"] = newNerveError(-32000, "Server error", e) - - proc `routerSym`*(`requestSym`: string): Future[JsonNode] = - try: - let requestJson = parseJson(`requestSym`) - result = `routerSym`(requestJson) - except CatchableError as e: - result = newFuture[JsonNode](`routerName`) - var response = %* {"jsonrpc": "2.0", "id": newJNull()} - response["error"] = newNerveError(-32700, "Parse error", e) - result.complete(response) - ) - - echo repr result - -export serverRuntime diff --git a/src/nerve/server.nim b/src/nerve/server.nim index 017891f..cdbe6b4 100644 --- a/src/nerve/server.nim +++ b/src/nerve/server.nim @@ -1,15 +1,9 @@ import macros, tables, strutils 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 dispatchName(node: NimNode): NimNode = ident(dispatchPrefix & node.name.strVal.capitalizeAscii) -proc dispatchName*(node: NimNode): NimNode = ident(dispatchPrefix & node.name.strVal.capitalizeAscii) - -proc enumDeclaration*(enumName: NimNode, procs: seq[NimNode]): NimNode = +proc enumDeclaration(enumName: NimNode, procs: seq[NimNode]): NimNode = # The enum used to dispatch methods var enumTy = nnkEnumTy.newTree(newEmptyNode()) for p in procs: @@ -17,38 +11,7 @@ proc enumDeclaration*(enumName: NimNode, procs: seq[NimNode]): NimNode = result = quote do: type `enumName` = `enumTy` -proc paramType(param: NimNode): NimNode = - # Either returns the type node of the param - # or creates a node that gets the type of default value - let defaultIdx = param.len - 1 - let typeIdx = param.len - 2 - if param[typeIdx].kind == nnkEmpty: - var defaultParam = param[defaultIdx] - result = quote do: - typeof(`defaultParam`) - else: - result = param[typeIdx] - -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 - let ptype = paramType(param) - for i in 0 ..< typeIdx: - result.add( - { - "name": param[i], - "nameStr": newStrLitNode(param[i].strVal), - "type": ptype, - "defaultVal": param[defaultIdx] - }.toTable - ) - -proc unboxExpression*(param: Table[string, NimNode], requestSym: NimNode): NimNode = +proc unboxExpression(param: Table[string, NimNode], requestSym: NimNode): NimNode = # Retrieve the param from the request, convert to Nim type var nameStr = param["nameStr"] var ntype = param["type"] @@ -61,7 +24,7 @@ proc unboxExpression*(param: Table[string, NimNode], requestSym: NimNode): NimNo result = quote do: nerveUnboxParameter[`ntype`](`requestSym`, `nameStr`) -proc procWrapper*(requestSym, p: NimNode): NimNode = +proc procWrapper(requestSym, p: NimNode): NimNode = # This wrapper gets the parameters from the request and uses them to invoke the proc result = nnkStmtList.newTree() var methodCall = nnkCall.newTree(p.name) @@ -80,7 +43,7 @@ proc procWrapper*(requestSym, p: NimNode): NimNode = result["result"] = % await `methodCall` ) -proc dispatch*(procs: seq[NimNode], methodSym, requestSym: NimNode): NimNode = +proc dispatch(procs: seq[NimNode], methodSym, requestSym: NimNode): NimNode = # Create the case statement used to dispatch proc result = nnkCaseStmt.newTree(methodSym) @@ -93,3 +56,47 @@ proc dispatch*(procs: seq[NimNode], methodSym, requestSym: NimNode): NimNode = wrapper )) +proc serverDispatch*(name: string, procs: seq[NimNode]): NimNode = + let uri = "" + + let + enumSym = genSym(nskType) # Type name the enum used to dispatch procs + methodSym = genSym(nskLet) # Variable that holds the requested method + serverSym = ident("server") + serviceType = rpcServiceName(name) + requestSym = ident("request") # The request parameter + routerSym = rpcRouterProcName(name) + routerName = routerSym.strVal() + + let dispatchStatement = dispatch(procs, methodSym, requestSym) + let enumDeclaration = enumDeclaration(enumSym, procs) + + result = newStmtList() + + result.add(quote do: + `enumDeclaration` + proc `routerSym`*(`serverSym`: `serviceType`,`requestSym`: JsonNode): Future[JsonNode] {.async.} = + result = %* {"jsonrpc": "2.0"} + if not nerveValidateRequest(`requestSym`): + result["id"] = if `requestSym`.hasKey("id"): `requestSym`["id"] else: newJNull() + result["error"] = newNerveError(-32600, "Invalid Request") + try: + let `methodSym` = nerveGetMethod[`enumSym`](`requestSym`) + `dispatchStatement` + except DispatchError as e: + result["error"] = newNerveError(-32601, "Method not found", e) + except ParameterError as e: + result["error"] = newNerveError(-32602, "Invalid params", e) + except CatchableError as e: + result["error"] = newNerveError(-32000, "Server error", e) + + proc `routerSym`*(`serverSym`: `serviceType`,`requestSym`: string): Future[JsonNode] = + try: + let requestJson = parseJson(`requestSym`) + result = `routerSym`(`serverSym`, requestJson) + except CatchableError as e: + result = newFuture[JsonNode](`routerName`) + var response = %* {"jsonrpc": "2.0", "id": newJNull()} + response["error"] = newNerveError(-32700, "Parse error", e) + result.complete(response) + ) diff --git a/src/nerve/serverRuntime.nim b/src/nerve/serverRuntime.nim index 64d0d21..aaa7891 100644 --- a/src/nerve/serverRuntime.nim +++ b/src/nerve/serverRuntime.nim @@ -53,3 +53,5 @@ proc newNerveError*(code: int, message: string, e: ref CatchableError): JsonNode "stackTrace": if defined(release): "" else: stackTrace } } + +export json diff --git a/src/nerve/service.nim b/src/nerve/service.nim index f49c860..48bdd3b 100644 --- a/src/nerve/service.nim +++ b/src/nerve/service.nim @@ -1,5 +1,6 @@ import macros, tables -import common, server +import types, common, server, client, factories + proc checkParams(formalParams: NimNode) = # Ensure return types are future @@ -17,193 +18,18 @@ proc procDefs(node: NimNode): seq[NimNode] = if child.kind == nnkProcDef: child.findChild(it.kind == nnkFormalParams).checkParams() result.add(child) - -proc rpcServiceObj*(name: string, procs: Table[string, NimNode], uri = "rpc"): NimNode = - let typeName = rpcServiceName(name) - result = quote do: - `typeName`(uri: `uri`) - for pName in procs.keys: - var field = newColonExpr(procs[pName][0].basename, ident(pName)) - result.add(field) - - -proc paramType(param: NimNode): NimNode = - # Either returns the type node of the param - # or creates a node that gets the type of default value - let defaultIdx = param.len - 1 - let typeIdx = param.len - 2 - if param[typeIdx].kind == nnkEmpty: - var defaultParam = param[defaultIdx] - result = quote do: - typeof(`defaultParam`) - else: - result = param[typeIdx] - -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 - let ptype = paramType(param) - for i in 0 ..< typeIdx: - result.add( - { - "name": param[i], - "nameStr": newStrLitNode(param[i].strVal), - "type": ptype, - "defaultVal": param[defaultIdx] - }.toTable - ) - -proc networkProcName(name: string): NimNode = ident("NerveNetwork" & name) - -proc createServerProc(p: NimNode, name: string): NimNode = - let providedProcName = p[0].basename - let procCall = nnkCall.newTree(providedProcName) - let params = p.findChild(it.kind == nnkFormalParams).getParams() - for param in params: - procCall.add(param["name"]) - result = copy(p) - result[result.len - 1] = procCall - result[0] = ident(name) - -proc createClientProc(p: NimNode, name, networkProc: string, driver: NimNode): NimNode = - let procCall = nnkCall.newTree(networkProcName(networkProc)) - let params = p.findChild(it.kind == nnkFormalParams).getParams() - for param in params: - procCall.add(param["name"]) - procCall.add(driver) - result = copy(p) - result[result.len - 1] = procCall - result[0] = ident(name) - -proc rpcServerFactory(name: string, serviceType: NimNode, procs: seq[NimNode]): NimNode = - let procName = ident("NerveRpc" & name & "ServerFactory") - var serverProcs = newStmtList() - var procTable = initTable[string, NimNode]() - for p in procs: - let serverProcName ="NerveServer" & p[0].basename.strVal() - procTable[serverProcName] = p - serverProcs.add(createServerProc(p, serverProcName)) - let service = rpcServiceObj(name, procTable) - result = quote do: - proc `procName`(): `serviceType` = - `serverProcs` - `service` - -proc rpcClientFactory(name: string, serviceType: NimNode, procs: seq[NimNode]): NimNode = - let procName = ident("NerveRpc" & name & "ClientFactory") - let driverName = ident("nerveDriver") - var clientProcs = newStmtList() - var procTable = initTable[string, NimNode]() - for p in procs: - let pName = p[0].basename.strVal() - let clientProcName ="NerveClient" & pName - procTable[clientProcName] = p - clientProcs.add(createClientProc(p, clientProcName, pName, driverName)) - let service = rpcServiceObj(name, procTable) - result = quote do: - proc `procName`(`driverName`: string): `serviceType` = - `clientProcs` - `service` - -proc networkProcBody(p: NimNode): NimNode = - let nameStr = newStrLitNode(p.name.strVal) - let formalParams = p.findChild(it.kind == nnkFormalParams) - let retType = formalParams[0][1] - let params = formalParams.getParams() - let req = genSym() - let newJsObject = if defined(js): ident("newJsObject") else: ident("newJObject") - - var paramJson = nnkStmtList.newTree() - for param in params: - let nameStr = param["nameStr"] - let name = param["name"] - paramJson.add( - quote do: - `req`["params"][`nameStr`] = % `name` - ) - +proc serviceImports(): NimNode = result = quote do: - let `req` = `newJsObject`() - `req`["jsonrpc"] = % "2.0" - `req`["id"] = % 0 - `req`["method"] = % `nameStr` - `req`["params"] = `newJsObject`() - `paramJson` - result = newFuture[`retType`]() - result.complete(`req`.to(`retType`)) - -proc rpcNetworkProcs(procs: seq[NimNode]): NimNode = - result = newStmtList() - for p in procs: - let networkProc = copy(p) - networkProc[0] = networkProcName(p[0].basename.strVal()) - networkProc[networkProc.len - 1] = networkProcBody(networkProc) - networkProc.findChild(it.kind == nnkFormalParams).add( - nnkIdentDefs.newTree( - ident("nerveDriver"), - ident("string"), - newEmptyNode() - ) - ) - result.add(networkProc) - -proc serverDispatch*(name: string, procs: seq[NimNode]): NimNode = - let uri = "" - - let - enumSym = genSym(nskType) # Type name the enum used to dispatch procs - methodSym = genSym(nskLet) # Variable that holds the requested method - serverSym = ident("server") - serviceType = rpcServiceName(name) - requestSym = ident("request") # The request parameter - routerSym = rpcRouterProcName(name) - routerName = routerSym.strVal() - - let dispatchStatement = dispatch(procs, methodSym, requestSym) - let enumDeclaration = enumDeclaration(enumSym, procs) - - result = newStmtList() - - result.add(quote do: - `enumDeclaration` - proc `routerSym`*(`serverSym`: `serviceType`,`requestSym`: JsonNode): Future[JsonNode] {.async.} = - result = %* {"jsonrpc": "2.0"} - if not nerveValidateRequest(`requestSym`): - result["id"] = if `requestSym`.hasKey("id"): `requestSym`["id"] else: newJNull() - result["error"] = newNerveError(-32600, "Invalid Request") - try: - let `methodSym` = nerveGetMethod[`enumSym`](`requestSym`) - `dispatchStatement` - except DispatchError as e: - result["error"] = newNerveError(-32601, "Method not found", e) - except ParameterError as e: - result["error"] = newNerveError(-32602, "Invalid params", e) - except CatchableError as e: - result["error"] = newNerveError(-32000, "Server error", e) - - proc `routerSym`*(`serverSym`: `serviceType`,`requestSym`: string): Future[JsonNode] = - try: - let requestJson = parseJson(`requestSym`) - result = `routerSym`(`serverSym`, requestJson) - except CatchableError as e: - result = newFuture[JsonNode](`routerName`) - var response = %* {"jsonrpc": "2.0", "id": newJNull()} - response["error"] = newNerveError(-32700, "Parse error", e) - result.complete(response) - ) + import nerve/serverRuntime macro service*(name: untyped, uri: static[string], body: untyped): untyped = let procs = procDefs(body) let nameStr = name.strVal() let serviceType = rpcServiceName(nameStr) result = newStmtList() + result.add(serviceImports()) result.add(procs.toStmtList()) - result.add(rpcNetworkProcs(procs)) + result.add(networkProcs(procs)) result.add(rpcServiceType(nameStr, procs)) result.add(rpcUriConst(nameStr, uri)) result.add(serverDispatch(nameStr, procs)) @@ -211,5 +37,4 @@ macro service*(name: untyped, uri: static[string], body: untyped): untyped = result.add(rpcClientFactory(nameStr, serviceType, procs)) echo repr result -import json, serverRuntime -export json, serverRuntime +export types diff --git a/src/nerve/types.nim b/src/nerve/types.nim new file mode 100644 index 0000000..787dcc6 --- /dev/null +++ b/src/nerve/types.nim @@ -0,0 +1,4 @@ +type RpcService* = object of RootObj + uri*: string + +type RpcServiceName* = distinct string diff --git a/src/nerve/utils.nim b/src/nerve/utils.nim index 0852d81..d9eeb14 100644 --- a/src/nerve/utils.nim +++ b/src/nerve/utils.nim @@ -1,5 +1,5 @@ import macros -import common +import common, types when defined(js): import jsffi, asyncjs @@ -44,4 +44,4 @@ else: result = quote do: `routerProc`(`req`) - export asyncdispatch, RpcService + export asyncdispatch From dc8d8324c11ee4d4c15a039445bad5eb1da807dd Mon Sep 17 00:00:00 2001 From: nepeckman Date: Mon, 8 Jul 2019 21:31:48 -0400 Subject: [PATCH 08/22] More restructuring --- src/nerve.nim | 35 +++++++++++++++++++++++------------ src/nerve/serverRuntime.nim | 4 ++-- src/nerve/service.nim | 4 +--- src/nerve/utils.nim | 23 +---------------------- tests/newTest.nim | 4 +--- 5 files changed, 28 insertions(+), 42 deletions(-) diff --git a/src/nerve.nim b/src/nerve.nim index 84874ae..713c245 100644 --- a/src/nerve.nim +++ b/src/nerve.nim @@ -1,18 +1,29 @@ import macros - -when not defined(js): +import nerve/service, nerve/types, nerve/common +when defined(js): + import jsffi +else: import json - import nerve/server, nerve/serverRuntime - macro service*(name, uri, body: untyped): untyped = - result = rpcServer(name, uri.strVal(), body) +macro service*(name: untyped, uri: static[string], body: untyped): untyped = + result = rpcService(name, uri, body) - export json, serverRuntime -else: - import jsffi - import nerve/client, nerve/clientRuntime +macro rpcUri*(rpc: RpcService): untyped = + let rpcName = rpc.strVal() + let uriConst = rpcName.rpcUriConstName + result = quote do: + `uriConst` + +macro routeRpc*(rpc: RpcService, req: JsonNode): untyped = + let rpcName = rpc.strVal() + let routerProc = rpcName.rpcRouterProcName + result = quote do: + `routerProc`(`req`) - macro service*(name, uri, body: untyped): untyped = - result = rpcClient(name, uri.strVal(), body) +macro routeRpc*(rpc: RpcService, req: string): untyped = + let rpcName = rpc.strVal() + let routerProc = rpcName.rpcRouterProcName + result = quote do: + `routerProc`(`req`) - export jsffi, clientRuntime +export types diff --git a/src/nerve/serverRuntime.nim b/src/nerve/serverRuntime.nim index aaa7891..84efeb6 100644 --- a/src/nerve/serverRuntime.nim +++ b/src/nerve/serverRuntime.nim @@ -1,4 +1,4 @@ -import json, strutils, sequtils, os +import json, asyncdispatch, strutils, sequtils, os import common type @@ -54,4 +54,4 @@ proc newNerveError*(code: int, message: string, e: ref CatchableError): JsonNode } } -export json +export json, asyncdispatch diff --git a/src/nerve/service.nim b/src/nerve/service.nim index 48bdd3b..d1ea104 100644 --- a/src/nerve/service.nim +++ b/src/nerve/service.nim @@ -22,7 +22,7 @@ proc serviceImports(): NimNode = result = quote do: import nerve/serverRuntime -macro service*(name: untyped, uri: static[string], body: untyped): untyped = +proc rpcService*(name: NimNode, uri: string, body: NimNode): NimNode = let procs = procDefs(body) let nameStr = name.strVal() let serviceType = rpcServiceName(nameStr) @@ -36,5 +36,3 @@ macro service*(name: untyped, uri: static[string], body: untyped): untyped = result.add(rpcServerFactory(nameStr, serviceType, procs)) result.add(rpcClientFactory(nameStr, serviceType, procs)) echo repr result - -export types diff --git a/src/nerve/utils.nim b/src/nerve/utils.nim index d9eeb14..8057b99 100644 --- a/src/nerve/utils.nim +++ b/src/nerve/utils.nim @@ -15,33 +15,12 @@ when defined(js): proc catch*[T, R](promise: Future[T], next: proc (data: T): R): Future[R] {. importcpp: "#.catch(@)" .} proc catch*[T](promise: Future[T], next: proc(data: T)): Future[void] {. importcpp: "#.catch(@)" .} - export asyncjs else: - import json, asyncdispatch + import asyncdispatch type wstring* = string proc fwrap*[T](it: T): Future[T] = result = newFuture[T]() result.complete(it) - - macro rpcUri*(rpc: RpcService): untyped = - let rpcName = rpc.strVal() - let uriConst = rpcName.rpcUriConstName - result = quote do: - `uriConst` - - macro routeRpc*(rpc: RpcService, req: JsonNode): untyped = - let rpcName = rpc.strVal() - let routerProc = rpcName.rpcRouterProcName - result = quote do: - `routerProc`(`req`) - - macro routeRpc*(rpc: RpcService, req: string): untyped = - let rpcName = rpc.strVal() - let routerProc = rpcName.rpcRouterProcName - result = quote do: - `routerProc`(`req`) - - export asyncdispatch diff --git a/tests/newTest.nim b/tests/newTest.nim index a33274a..cfe2fd7 100644 --- a/tests/newTest.nim +++ b/tests/newTest.nim @@ -1,9 +1,7 @@ -import nerve/service, nerve/utils -import macros +import nerve, nerve/utils service Hello, "/api/hello": proc helloWorld(): Future[wstring] = fwrap("Hello World") proc greet(greeting, name: wstring): Future[wstring] = fwrap(greeting & " " & name) - From 4aa19db83c266f18bb0cd009b347ae10ee745322 Mon Sep 17 00:00:00 2001 From: nepeckman Date: Mon, 8 Jul 2019 21:32:41 -0400 Subject: [PATCH 09/22] Updated drivers --- src/nerve/client.nim | 2 +- src/nerve/drivers.nim | 17 +++++++---------- src/nerve/factories.nim | 2 +- src/nerve/types.nim | 8 ++++++++ src/nerve/utils.nim | 8 ++++++-- tests/newTest.nim | 2 +- 6 files changed, 24 insertions(+), 15 deletions(-) diff --git a/src/nerve/client.nim b/src/nerve/client.nim index 466b3d5..b197060 100644 --- a/src/nerve/client.nim +++ b/src/nerve/client.nim @@ -38,7 +38,7 @@ proc networkProcs*(procs: seq[NimNode]): NimNode = networkProc.findChild(it.kind == nnkFormalParams).add( nnkIdentDefs.newTree( ident("nerveDriver"), - ident("string"), + ident("NerveDriver"), newEmptyNode() ) ) diff --git a/src/nerve/drivers.nim b/src/nerve/drivers.nim index a7e205a..377edcd 100644 --- a/src/nerve/drivers.nim +++ b/src/nerve/drivers.nim @@ -1,12 +1,9 @@ when not defined(js): - import json, asyncdispatch, httpClient + import asyncdispatch, httpClient + import types, utils - type HttpDriver* = ref object - client: AsyncHttpClient - uri: string - - proc send*[T](driver: HttpDriver, req: JsonNode): Future[T] {.async.} = - let res = await driver.client.postContent(driver.uri, $ req) - result = res.parseJson().to(T) - - proc newHttpDriver*(uri: string): HttpDriver = HttpDriver(client: newAsyncHttpClient(), uri: uri) + proc newHttpDriver*(uri: string): NerveDriver = + let client = newAsyncHttpClient() + result = proc (req: WObject): Future[WObject] {.async.} = + let res = await client.postContent(uri, $ req) + result = res.parseJson() diff --git a/src/nerve/factories.nim b/src/nerve/factories.nim index 78fec33..44078a8 100644 --- a/src/nerve/factories.nim +++ b/src/nerve/factories.nim @@ -47,6 +47,6 @@ proc rpcClientFactory*(name: string, serviceType: NimNode, procs: seq[NimNode]): clientProcs.add(createClientProc(p, clientProcName, pName, driverName)) let service = rpcServiceObject(name, procTable) result = quote do: - proc `procName`(`driverName`: string): `serviceType` = + proc `procName`(`driverName`: NerveDriver): `serviceType` = `clientProcs` `service` diff --git a/src/nerve/types.nim b/src/nerve/types.nim index 787dcc6..46dfa1e 100644 --- a/src/nerve/types.nim +++ b/src/nerve/types.nim @@ -1,4 +1,12 @@ +import utils +when not defined(js): + import asyncdispatch +else: + import asyncjs + type RpcService* = object of RootObj uri*: string type RpcServiceName* = distinct string + +type NerveDriver* = proc (req: WObject): Future[WObject] diff --git a/src/nerve/utils.nim b/src/nerve/utils.nim index 8057b99..f8b8961 100644 --- a/src/nerve/utils.nim +++ b/src/nerve/utils.nim @@ -1,10 +1,10 @@ import macros -import common, types when defined(js): import jsffi, asyncjs type wstring* = cstring + type WObject* = JsObject proc fetch*(uri: cstring): Future[JsObject] {. importc .} proc fetch*(uri: cstring, data: JsObject): Future[JsObject] {. importc .} @@ -15,12 +15,16 @@ when defined(js): proc catch*[T, R](promise: Future[T], next: proc (data: T): R): Future[R] {. importcpp: "#.catch(@)" .} proc catch*[T](promise: Future[T], next: proc(data: T)): Future[void] {. importcpp: "#.catch(@)" .} + export jsffi else: - import asyncdispatch + import json, asyncdispatch type wstring* = string + type WObject* = JsonNode proc fwrap*[T](it: T): Future[T] = result = newFuture[T]() result.complete(it) + + export json diff --git a/tests/newTest.nim b/tests/newTest.nim index cfe2fd7..ca20fd1 100644 --- a/tests/newTest.nim +++ b/tests/newTest.nim @@ -1,4 +1,4 @@ -import nerve, nerve/utils +import nerve, nerve/utils, nerve/drivers service Hello, "/api/hello": From 998c08753776ec6f9c75487fea83c507787f798a Mon Sep 17 00:00:00 2001 From: nepeckman Date: Mon, 8 Jul 2019 21:33:05 -0400 Subject: [PATCH 10/22] Updated client runtime --- src/nerve/client.nim | 5 +++-- src/nerve/clientRuntime.nim | 39 +++++++++++++++++++++++-------------- src/nerve/drivers.nim | 4 ++-- src/nerve/service.nim | 2 ++ src/nerve/utils.nim | 23 ++++++++++++++++++++++ 5 files changed, 54 insertions(+), 19 deletions(-) diff --git a/src/nerve/client.nim b/src/nerve/client.nim index b197060..a45a4a3 100644 --- a/src/nerve/client.nim +++ b/src/nerve/client.nim @@ -8,6 +8,7 @@ proc networkProcBody(p: NimNode): NimNode = let retType = formalParams[0][1] let params = formalParams.getParams() let req = genSym() + let driver = ident("nerveDriver") let newJsObject = if defined(js): ident("newJsObject") else: ident("newJObject") var paramJson = nnkStmtList.newTree() @@ -26,8 +27,8 @@ proc networkProcBody(p: NimNode): NimNode = `req`["method"] = % `nameStr` `req`["params"] = `newJsObject`() `paramJson` - result = newFuture[`retType`]() - result.complete(`req`.to(`retType`)) + result = `driver`(`req`) + .then(handleRpcResponse[`retType`]) proc networkProcs*(procs: seq[NimNode]): NimNode = result = newStmtList() diff --git a/src/nerve/clientRuntime.nim b/src/nerve/clientRuntime.nim index f0041cf..ff587bf 100644 --- a/src/nerve/clientRuntime.nim +++ b/src/nerve/clientRuntime.nim @@ -1,24 +1,33 @@ -import jsffi +import utils 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" +proc handleRpcResponse*[T](rpcResponse: WObject): T = + if rpcResponse.hasKey("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): WObject = + try: + result = parseJson(resp) + except: + let msg = "Invalid Response: Unable to parse JSON" + raise InvalidResponseError(msg: msg) +else: + proc Boolean(o: WObject): bool {. importc .} + + proc respJson*(data: WObject): WObject {. importcpp: "#.json()" .} + + proc respToJson*(resp: WObject): WObject = + if Boolean(resp.ok): + return respJson(resp) + let msg = "Invalid Response: Server responsed with code " & $to(resp.status, int) + raise InvalidResponseError(msg: msg) + + var JSON* {. importc, nodecl .}: JsObject diff --git a/src/nerve/drivers.nim b/src/nerve/drivers.nim index 377edcd..4bc15fe 100644 --- a/src/nerve/drivers.nim +++ b/src/nerve/drivers.nim @@ -1,9 +1,9 @@ when not defined(js): import asyncdispatch, httpClient - import types, utils + import types, utils, clientRuntime proc newHttpDriver*(uri: string): NerveDriver = let client = newAsyncHttpClient() result = proc (req: WObject): Future[WObject] {.async.} = let res = await client.postContent(uri, $ req) - result = res.parseJson() + result = res.respToJson() diff --git a/src/nerve/service.nim b/src/nerve/service.nim index d1ea104..e3c4b6d 100644 --- a/src/nerve/service.nim +++ b/src/nerve/service.nim @@ -20,7 +20,9 @@ proc procDefs(node: NimNode): seq[NimNode] = result.add(child) proc serviceImports(): NimNode = result = quote do: + import nerve/utils import nerve/serverRuntime + import nerve/clientRuntime proc rpcService*(name: NimNode, uri: string, body: NimNode): NimNode = let procs = procDefs(body) diff --git a/src/nerve/utils.nim b/src/nerve/utils.nim index f8b8961..aa82085 100644 --- a/src/nerve/utils.nim +++ b/src/nerve/utils.nim @@ -15,6 +15,8 @@ when defined(js): proc catch*[T, R](promise: Future[T], next: proc (data: T): R): Future[R] {. importcpp: "#.catch(@)" .} proc catch*[T](promise: Future[T], next: proc(data: T)): Future[void] {. importcpp: "#.catch(@)" .} + let hasKey = hasOwnProperty + export jsffi else: @@ -23,6 +25,27 @@ else: type wstring* = string type WObject* = JsonNode + proc then*[T, R](future: Future[T], cb: proc (t: T): R {.gcsafe.}): Future[R] = + let rv = newFuture[R]("then") + future.callback = proc (data: Future[T]) = + rv.complete(cb(data.read)) + result = rv + + proc then*[T, R](future: Future[T], cb: proc (t: T): Future[R] {.gcsafe.}): Future[R] = + let rv = newFuture[R]("then") + future.callback = proc (data: Future[T]) = + let intermediate = cb(data.read) + intermediate.callback = proc (otherData: Future[R]) = + rv.complete(otherData.read) + result = rv + + proc then*[T](future: Future[T], cb: proc (t: T) {.gcsafe.}): Future[void] = + let rv = newFuture[void]("then") + future.callback = proc (data: Future[T]) = + cb(data.read) + rv.complete() + result = rv + proc fwrap*[T](it: T): Future[T] = result = newFuture[T]() result.complete(it) From 1f12b57a2ed8abb8f7112654c9218e448a69beab Mon Sep 17 00:00:00 2001 From: nepeckman Date: Mon, 8 Jul 2019 23:17:37 -0400 Subject: [PATCH 11/22] WIP: js client/server --- src/nerve.nim | 4 +- src/nerve/client.nim | 10 ++--- src/nerve/drivers.nim | 11 ++++++ src/nerve/serverRuntime.nim | 79 ++++++++++++++++++++++++------------- src/nerve/utils.nim | 5 +++ tests/newTest.nim | 2 +- 6 files changed, 75 insertions(+), 36 deletions(-) diff --git a/src/nerve.nim b/src/nerve.nim index 713c245..16daaaf 100644 --- a/src/nerve.nim +++ b/src/nerve.nim @@ -1,5 +1,5 @@ import macros -import nerve/service, nerve/types, nerve/common +import nerve/service, nerve/types, nerve/common, nerve/utils when defined(js): import jsffi else: @@ -14,7 +14,7 @@ macro rpcUri*(rpc: RpcService): untyped = result = quote do: `uriConst` -macro routeRpc*(rpc: RpcService, req: JsonNode): untyped = +macro routeRpc*(rpc: RpcService, req: WObject): untyped = let rpcName = rpc.strVal() let routerProc = rpcName.rpcRouterProcName result = quote do: diff --git a/src/nerve/client.nim b/src/nerve/client.nim index a45a4a3..a69ba0a 100644 --- a/src/nerve/client.nim +++ b/src/nerve/client.nim @@ -1,7 +1,6 @@ import macros, tables import common - proc networkProcBody(p: NimNode): NimNode = let nameStr = newStrLitNode(p.name.strVal) let formalParams = p.findChild(it.kind == nnkFormalParams) @@ -10,6 +9,7 @@ proc networkProcBody(p: NimNode): NimNode = let req = genSym() let driver = ident("nerveDriver") let newJsObject = if defined(js): ident("newJsObject") else: ident("newJObject") + let toJs = if defined(js): ident("toJs") else: ident("%") var paramJson = nnkStmtList.newTree() for param in params: @@ -17,14 +17,14 @@ proc networkProcBody(p: NimNode): NimNode = let name = param["name"] paramJson.add( quote do: - `req`["params"][`nameStr`] = % `name` + `req`["params"][`nameStr`] = `toJs` `name` ) result = quote do: let `req` = `newJsObject`() - `req`["jsonrpc"] = % "2.0" - `req`["id"] = % 0 - `req`["method"] = % `nameStr` + `req`["jsonrpc"] = `toJs` "2.0" + `req`["id"] = `toJs` 0 + `req`["method"] = `toJs` `nameStr` `req`["params"] = `newJsObject`() `paramJson` result = `driver`(`req`) diff --git a/src/nerve/drivers.nim b/src/nerve/drivers.nim index 4bc15fe..dd3a709 100644 --- a/src/nerve/drivers.nim +++ b/src/nerve/drivers.nim @@ -7,3 +7,14 @@ when not defined(js): result = proc (req: WObject): Future[WObject] {.async.} = let res = await client.postContent(uri, $ req) result = res.respToJson() +else: + import asyncjs + import types, utils, clientRuntime + + proc newHttpDriver*(uri: string): NerveDriver = + result = proc (req: WObject): Future[WObject] = + let msg = newJsObject() + msg["method"] = cstring"POST" + msg["body"] = JSON.stringify(req) + result = fetch(cstring(uri), msg) + .then(respToJson) diff --git a/src/nerve/serverRuntime.nim b/src/nerve/serverRuntime.nim index 84efeb6..3fde20f 100644 --- a/src/nerve/serverRuntime.nim +++ b/src/nerve/serverRuntime.nim @@ -1,15 +1,25 @@ -import json, asyncdispatch, strutils, sequtils, os -import common +import strutils, sequtils, os +when not defined(js): + import json, asyncdispatch, os + export json, asyncdispatch +else: + import jsffi, asyncjs + export jsffi, asyncjs +import common, utils type RequestFormatError* = ref object of CatchableError DispatchError* = ref object of CatchableError ParameterError* = ref object of CatchableError -proc nerveValidateRequest*(req: JsonNode): bool = - req.hasKey("jsonrpc") and req["jsonrpc"].getStr() == "2.0" and req.hasKey("id") +when not defined(js): + proc nerveValidateRequest*(req: WObject): bool = + req.hasKey("jsonrpc") and req["jsonrpc"].getStr() == "2.0" and req.hasKey("id") +else: + proc nerveValidateRequest*(req: WObject): bool = + req.hasOwnProperty("jsonrpc") and (req["jsonrpc"].to(cstring) == "2.0") and req.hasOwnProperty("id") -proc nerveGetMethod*[T: enum](req: JsonNode): T = +proc nerveGetMethod*[T: enum](req: WObject): T = try: parseEnum[T](dispatchPrefix & req["method"].getStr()) except: @@ -17,7 +27,7 @@ proc nerveGetMethod*[T: enum](req: JsonNode): T = else: "Request missing method field" raise DispatchError(msg: msg, parent: getCurrentException()) -proc nerveUnboxParameter*[T](req: JsonNode, param: string, default: T): T = +proc nerveUnboxParameter*[T](req: WObject, param: string, default: T): T = try: return if req["params"].hasKey(param): req["params"][param].to(T) else: default @@ -25,33 +35,46 @@ proc nerveUnboxParameter*[T](req: JsonNode, param: string, default: T): T = let msg = "Error in param '" & param & "': " & getCurrentExceptionMsg() raise ParameterError(msg: msg, parent: getCurrentException()) -proc nerveUnboxParameter*[T](req: JsonNode, param: string): T = +proc nerveUnboxParameter*[T](req: WObject, param: string): T = try: return req["params"][param].to(T) except: let msg = "Error in param '" & param & "': " & getCurrentExceptionMsg() raise ParameterError(msg: msg, parent: getCurrentException()) -proc newNerveError*(code: int, message: string): JsonNode = - %* { - "code": code, - "message": message - } - -proc newNerveError*(code: int, message: string, e: ref CatchableError): JsonNode = - let dir = getCurrentDir() - let stackTrace = e.getStackTraceEntries() - .filterIt(`$`(it.filename).find(dir) != -1 and - `$`(it.procname).find(dispatchPrefix) == -1) - .mapIt($it.filename & "(" & $it.line & ")" & " " & $it.procname) - .join("\n") - return %* { - "code": code, - "message": message, - "data": { - "msg": e.msg, - "stackTrace": if defined(release): "" else: stackTrace +when not defined(js): + proc newNerveError*(code: int, message: string): WObject = + %* { + "code": code, + "message": message } - } -export json, asyncdispatch + proc newNerveError*(code: int, message: string, e: ref CatchableError): WObject = + let dir = getCurrentDir() + let stackTrace = e.getStackTraceEntries() + .filterIt(`$`(it.filename).find(dir) != -1 and + `$`(it.procname).find(dispatchPrefix) == -1) + .mapIt($it.filename & "(" & $it.line & ")" & " " & $it.procname) + .join("\n") + return %* { + "code": code, + "message": message, + "data": { + "msg": e.msg, + "stackTrace": if defined(release): "" else: stackTrace + } + } + +else: + proc newNerveError*(code: int, message: string): WObject = + result = newJsObject() + result["code"] = code + result["message"] = message + + proc newNerveError*(code: int, message: string, e: ref CatchableError): WObject = + result = newJsObject() + result["code"] = code + result["message"] = message + result["data"] = newJsObject() + result["data"]["msg"] = e.msg + result["data"]["stackTrace"] = getStackTrace() diff --git a/src/nerve/utils.nim b/src/nerve/utils.nim index aa82085..30d5f7c 100644 --- a/src/nerve/utils.nim +++ b/src/nerve/utils.nim @@ -16,6 +16,11 @@ when defined(js): proc catch*[T](promise: Future[T], next: proc(data: T)): Future[void] {. importcpp: "#.catch(@)" .} let hasKey = hasOwnProperty + type Promise = distinct JsObject + var PromiseObj {. importc: "Promise", nodecl .}: Promise + proc resolve[T](promise: Promise, it: T): Future[T] {.importcpp: "#.resolve(@)".} + proc fwrap*[T](it: T): Future[T] = + PromiseObj.resolve(it) export jsffi diff --git a/tests/newTest.nim b/tests/newTest.nim index ca20fd1..915fa40 100644 --- a/tests/newTest.nim +++ b/tests/newTest.nim @@ -2,6 +2,6 @@ import nerve, nerve/utils, nerve/drivers service Hello, "/api/hello": - proc helloWorld(): Future[wstring] = fwrap("Hello World") + proc helloWorld(): Future[wstring] = fwrap(wstring"Hello World") proc greet(greeting, name: wstring): Future[wstring] = fwrap(greeting & " " & name) From 9fc8d5bb863add3423cc1725ae3b4aa8e5017786 Mon Sep 17 00:00:00 2001 From: nepeckman Date: Tue, 9 Jul 2019 16:18:23 -0400 Subject: [PATCH 12/22] JS client compiles --- src/nerve/clientRuntime.nim | 14 +++++++------- src/nerve/server.nim | 12 ++++++------ src/nerve/serverRuntime.nim | 10 ++++++++++ src/nerve/service.nim | 12 +++++++++--- src/nerve/utils.nim | 3 --- src/nerve/web.nim | 24 ++++++++++++++++++++++++ tests/newTest.nim | 2 +- 7 files changed, 57 insertions(+), 20 deletions(-) create mode 100644 src/nerve/web.nim diff --git a/src/nerve/clientRuntime.nim b/src/nerve/clientRuntime.nim index ff587bf..12fbbb7 100644 --- a/src/nerve/clientRuntime.nim +++ b/src/nerve/clientRuntime.nim @@ -1,30 +1,30 @@ -import utils +import web type InvalidResponseError* = ref object of CatchableError RpcError* = ref object of CatchableError -proc handleRpcResponse*[T](rpcResponse: WObject): T = - if rpcResponse.hasKey("error"): +proc handleRpcResponse*[T](rpcResponse: JsObject): T = + 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) when not defined(js): - proc respToJson*(resp: string): WObject = + 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: WObject): bool {. importc .} + proc Boolean(o: JsObject): bool {. importc .} - proc respJson*(data: WObject): WObject {. importcpp: "#.json()" .} + proc respJson*(data: JsObject): JsObject {. importcpp: "#.json()" .} - proc respToJson*(resp: WObject): WObject = + proc respToJson*(resp: JsObject): JsObject = if Boolean(resp.ok): return respJson(resp) let msg = "Invalid Response: Server responsed with code " & $to(resp.status, int) diff --git a/src/nerve/server.nim b/src/nerve/server.nim index cdbe6b4..6f9a3ed 100644 --- a/src/nerve/server.nim +++ b/src/nerve/server.nim @@ -1,5 +1,5 @@ import macros, tables, strutils -import common +import common, utils proc dispatchName(node: NimNode): NimNode = ident(dispatchPrefix & node.name.strVal.capitalizeAscii) @@ -75,8 +75,8 @@ proc serverDispatch*(name: string, procs: seq[NimNode]): NimNode = result.add(quote do: `enumDeclaration` - proc `routerSym`*(`serverSym`: `serviceType`,`requestSym`: JsonNode): Future[JsonNode] {.async.} = - result = %* {"jsonrpc": "2.0"} + proc `routerSym`*(`serverSym`: `serviceType`,`requestSym`: WObject): Future[WObject] {.async.} = + result = newNerveResponse() if not nerveValidateRequest(`requestSym`): result["id"] = if `requestSym`.hasKey("id"): `requestSym`["id"] else: newJNull() result["error"] = newNerveError(-32600, "Invalid Request") @@ -90,13 +90,13 @@ proc serverDispatch*(name: string, procs: seq[NimNode]): NimNode = except CatchableError as e: result["error"] = newNerveError(-32000, "Server error", e) - proc `routerSym`*(`serverSym`: `serviceType`,`requestSym`: string): Future[JsonNode] = + proc `routerSym`*(`serverSym`: `serviceType`,`requestSym`: string): Future[WObject] = try: let requestJson = parseJson(`requestSym`) result = `routerSym`(`serverSym`, requestJson) except CatchableError as e: - result = newFuture[JsonNode](`routerName`) - var response = %* {"jsonrpc": "2.0", "id": newJNull()} + result = newFuture[WObject](`routerName`) + var response = newNerveResponse() response["error"] = newNerveError(-32700, "Parse error", e) result.complete(response) ) diff --git a/src/nerve/serverRuntime.nim b/src/nerve/serverRuntime.nim index 3fde20f..1bf51ed 100644 --- a/src/nerve/serverRuntime.nim +++ b/src/nerve/serverRuntime.nim @@ -43,6 +43,10 @@ proc nerveUnboxParameter*[T](req: WObject, param: string): T = raise ParameterError(msg: msg, parent: getCurrentException()) when not defined(js): + + proc newNerveResponse*(): WObject = + %* {"jsonrpc": "2.0", "id": newJNull()} + proc newNerveError*(code: int, message: string): WObject = %* { "code": code, @@ -66,6 +70,12 @@ when not defined(js): } else: + + proc newNerveResponse*(): JsObject = + result = newJsObject() + result["jsonrpc"] = cstring"2.0" + result["id"] = jsNull + proc newNerveError*(code: int, message: string): WObject = result = newJsObject() result["code"] = code diff --git a/src/nerve/service.nim b/src/nerve/service.nim index e3c4b6d..7040eaf 100644 --- a/src/nerve/service.nim +++ b/src/nerve/service.nim @@ -21,7 +21,12 @@ proc procDefs(node: NimNode): seq[NimNode] = proc serviceImports(): NimNode = result = quote do: import nerve/utils - import nerve/serverRuntime + import nerve/web + when not defined(js): + import asyncdispatch + import nerve/serverRuntime + else: + import asyncjs import nerve/clientRuntime proc rpcService*(name: NimNode, uri: string, body: NimNode): NimNode = @@ -34,7 +39,8 @@ proc rpcService*(name: NimNode, uri: string, body: NimNode): NimNode = result.add(networkProcs(procs)) result.add(rpcServiceType(nameStr, procs)) result.add(rpcUriConst(nameStr, uri)) - result.add(serverDispatch(nameStr, procs)) - result.add(rpcServerFactory(nameStr, serviceType, procs)) + if not defined(js): + result.add(serverDispatch(nameStr, procs)) + result.add(rpcServerFactory(nameStr, serviceType, procs)) result.add(rpcClientFactory(nameStr, serviceType, procs)) echo repr result diff --git a/src/nerve/utils.nim b/src/nerve/utils.nim index 30d5f7c..0defd6c 100644 --- a/src/nerve/utils.nim +++ b/src/nerve/utils.nim @@ -3,7 +3,6 @@ import macros when defined(js): import jsffi, asyncjs - type wstring* = cstring type WObject* = JsObject proc fetch*(uri: cstring): Future[JsObject] {. importc .} @@ -15,7 +14,6 @@ when defined(js): proc catch*[T, R](promise: Future[T], next: proc (data: T): R): Future[R] {. importcpp: "#.catch(@)" .} proc catch*[T](promise: Future[T], next: proc(data: T)): Future[void] {. importcpp: "#.catch(@)" .} - let hasKey = hasOwnProperty type Promise = distinct JsObject var PromiseObj {. importc: "Promise", nodecl .}: Promise proc resolve[T](promise: Promise, it: T): Future[T] {.importcpp: "#.resolve(@)".} @@ -27,7 +25,6 @@ when defined(js): else: import json, asyncdispatch - type wstring* = string type WObject* = JsonNode proc then*[T, R](future: Future[T], cb: proc (t: T): R {.gcsafe.}): Future[R] = diff --git a/src/nerve/web.nim b/src/nerve/web.nim new file mode 100644 index 0000000..8139614 --- /dev/null +++ b/src/nerve/web.nim @@ -0,0 +1,24 @@ +when not defined(js): + import json + + type wstring* = string + type JsObject* = JsonNode + + template toJs(x: untyped): untyped = % x + const newJsObject* = newJObject + + + export json +else: + import jsffi + + type wstring* = cstring + + proc parseJson* (data: cstring): JsObject {. importcpp: "JSON.parse(#)", nodecl .} + proc `$`* (jsObject: JsObject): string {. importcpp: "JSON.stringify(#)", nodecl .} + + template hasKey* (obj: JsObject, key: untyped): untyped = obj.hasOwnProperty(key) + + template newJNull*(): untyped = jsNull + + export jsffi diff --git a/tests/newTest.nim b/tests/newTest.nim index 915fa40..6d9565c 100644 --- a/tests/newTest.nim +++ b/tests/newTest.nim @@ -1,4 +1,4 @@ -import nerve, nerve/utils, nerve/drivers +import nerve, nerve/utils, nerve/drivers, nerve/web service Hello, "/api/hello": From 4ae1aae54d39f48643f23319e907b8a0c4dd00f5 Mon Sep 17 00:00:00 2001 From: nepeckman Date: Sun, 21 Jul 2019 19:41:39 -0400 Subject: [PATCH 13/22] Client and Server macros --- src/nerve.nim | 27 +++++++++++++++++++-------- src/nerve/common.nim | 4 ++++ src/nerve/factories.nim | 4 ++-- src/nerve/service.nim | 8 ++++++++ src/nerve/types.nim | 1 + tests/newTest.nim | 3 +++ 6 files changed, 37 insertions(+), 10 deletions(-) diff --git a/src/nerve.nim b/src/nerve.nim index 16daaaf..0332537 100644 --- a/src/nerve.nim +++ b/src/nerve.nim @@ -8,22 +8,33 @@ else: macro service*(name: untyped, uri: static[string], body: untyped): untyped = result = rpcService(name, uri, body) -macro rpcUri*(rpc: RpcService): untyped = - let rpcName = rpc.strVal() +macro newServer*(rpc: static[RpcServiceName]): untyped = + let rpcName = $rpc + let serverFactoryProc = rpcServerFactoryProc(rpcName) + result = quote do: + `serverFactoryProc`() + +macro newClient*(rpc: static[RpcServiceName], driver: NerveDriver): untyped = + let clientFactoryProc = rpcClientFactoryProc($rpc) + result = quote do: + `clientFactoryProc`(`driver`) + +macro rpcUri*(rpc: static[RpcServiceName]): untyped = + let rpcName = $rpc let uriConst = rpcName.rpcUriConstName result = quote do: `uriConst` -macro routeRpc*(rpc: RpcService, req: WObject): untyped = - let rpcName = rpc.strVal() +macro routeRpc*(rpc: static[RpcServiceName], server: RpcService, req: WObject): untyped = + let rpcName = $rpc let routerProc = rpcName.rpcRouterProcName result = quote do: - `routerProc`(`req`) + `routerProc`(`server`, `req`) -macro routeRpc*(rpc: RpcService, req: string): untyped = - let rpcName = rpc.strVal() +macro routeRpc*(rpc: static[RpcServiceName], server: RpcService, req: string): untyped = + let rpcName = $rpc let routerProc = rpcName.rpcRouterProcName result = quote do: - `routerProc`(`req`) + `routerProc`(`server`, `req`) export types diff --git a/src/nerve/common.nim b/src/nerve/common.nim index 4cb7b0c..3ba9d06 100644 --- a/src/nerve/common.nim +++ b/src/nerve/common.nim @@ -15,6 +15,10 @@ proc rpcUriConst*(name, uri: string): NimNode = proc rpcServiceName*(name: string): NimNode = ident("NerveRpc" & name & "Object") +proc rpcServerFactoryProc*(name: string): NimNode = ident("NerveRpc" & name & "ServerFactory") + +proc rpcClientFactoryProc*(name: string): NimNode = ident("NerveRpc" & name & "ClientFactory") + proc rpcServiceType*(name: string, procs: seq[NimNode]): NimNode = let typeName = rpcServiceName(name) var procFields = nnkRecList.newTree() diff --git a/src/nerve/factories.nim b/src/nerve/factories.nim index 44078a8..ad36d56 100644 --- a/src/nerve/factories.nim +++ b/src/nerve/factories.nim @@ -22,7 +22,7 @@ proc createClientProc(p: NimNode, name, networkProc: string, driver: NimNode): N result[0] = ident(name) proc rpcServerFactory*(name: string, serviceType: NimNode, procs: seq[NimNode]): NimNode = - let procName = ident("NerveRpc" & name & "ServerFactory") + let procName = rpcServerFactoryProc(name) var serverProcs = newStmtList() var procTable = initTable[string, NimNode]() for p in procs: @@ -36,7 +36,7 @@ proc rpcServerFactory*(name: string, serviceType: NimNode, procs: seq[NimNode]): `service` proc rpcClientFactory*(name: string, serviceType: NimNode, procs: seq[NimNode]): NimNode = - let procName = ident("NerveRpc" & name & "ClientFactory") + let procName = rpcClientFactoryProc(name) let driverName = ident("nerveDriver") var clientProcs = newStmtList() var procTable = initTable[string, NimNode]() diff --git a/src/nerve/service.nim b/src/nerve/service.nim index 7040eaf..452404f 100644 --- a/src/nerve/service.nim +++ b/src/nerve/service.nim @@ -18,10 +18,12 @@ proc procDefs(node: NimNode): seq[NimNode] = if child.kind == nnkProcDef: child.findChild(it.kind == nnkFormalParams).checkParams() result.add(child) + proc serviceImports(): NimNode = result = quote do: import nerve/utils import nerve/web + import nerve/types when not defined(js): import asyncdispatch import nerve/serverRuntime @@ -29,6 +31,11 @@ proc serviceImports(): NimNode = import asyncjs import nerve/clientRuntime +proc compiletimeReference(name: NimNode): NimNode = + let nameStr = name.strVal().newStrLitNode() + result = quote do: + const `name`* = RpcServiceName(`nameStr`) + proc rpcService*(name: NimNode, uri: string, body: NimNode): NimNode = let procs = procDefs(body) let nameStr = name.strVal() @@ -43,4 +50,5 @@ proc rpcService*(name: NimNode, uri: string, body: NimNode): NimNode = result.add(serverDispatch(nameStr, procs)) result.add(rpcServerFactory(nameStr, serviceType, procs)) result.add(rpcClientFactory(nameStr, serviceType, procs)) + result.add(compiletimeReference(name)) echo repr result diff --git a/src/nerve/types.nim b/src/nerve/types.nim index 46dfa1e..409d661 100644 --- a/src/nerve/types.nim +++ b/src/nerve/types.nim @@ -8,5 +8,6 @@ type RpcService* = object of RootObj uri*: string type RpcServiceName* = distinct string +proc `$`*(s: RpcServiceName): string {.borrow.} type NerveDriver* = proc (req: WObject): Future[WObject] diff --git a/tests/newTest.nim b/tests/newTest.nim index 6d9565c..876a831 100644 --- a/tests/newTest.nim +++ b/tests/newTest.nim @@ -5,3 +5,6 @@ service Hello, "/api/hello": proc helloWorld(): Future[wstring] = fwrap(wstring"Hello World") proc greet(greeting, name: wstring): Future[wstring] = fwrap(greeting & " " & name) + +let server = Hello.newClient(newHttpDriver("")) +discard Hello.routeRpc(server, "") From 7b73285d355df7b7ff83c9655c5a3f77ea4cf472 Mon Sep 17 00:00:00 2001 From: nepeckman Date: Sun, 21 Jul 2019 21:29:24 -0400 Subject: [PATCH 14/22] Functional clients --- src/nerve/client.nim | 9 +++++---- src/nerve/factories.nim | 4 ++-- src/nerve/service.nim | 3 ++- tests/newApi.nim | 4 ++++ tests/newClient.nim | 10 ++++++++++ tests/newServer.nim | 21 +++++++++++++++++++++ tests/newTest.nim | 1 - 7 files changed, 44 insertions(+), 8 deletions(-) create mode 100644 tests/newApi.nim create mode 100644 tests/newClient.nim create mode 100644 tests/newServer.nim diff --git a/src/nerve/client.nim b/src/nerve/client.nim index a69ba0a..26dc2c2 100644 --- a/src/nerve/client.nim +++ b/src/nerve/client.nim @@ -1,7 +1,7 @@ import macros, tables import common -proc networkProcBody(p: NimNode): NimNode = +proc networkProcBody(p: NimNode, methodName: string): NimNode = let nameStr = newStrLitNode(p.name.strVal) let formalParams = p.findChild(it.kind == nnkFormalParams) let retType = formalParams[0][1] @@ -24,7 +24,7 @@ proc networkProcBody(p: NimNode): NimNode = let `req` = `newJsObject`() `req`["jsonrpc"] = `toJs` "2.0" `req`["id"] = `toJs` 0 - `req`["method"] = `toJs` `nameStr` + `req`["method"] = `toJs` `methodName` `req`["params"] = `newJsObject`() `paramJson` result = `driver`(`req`) @@ -34,8 +34,9 @@ proc networkProcs*(procs: seq[NimNode]): NimNode = result = newStmtList() for p in procs: let networkProc = copy(p) - networkProc[0] = networkProcName(p[0].basename.strVal()) - networkProc[networkProc.len - 1] = networkProcBody(networkProc) + 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"), diff --git a/src/nerve/factories.nim b/src/nerve/factories.nim index ad36d56..11ffc8e 100644 --- a/src/nerve/factories.nim +++ b/src/nerve/factories.nim @@ -31,7 +31,7 @@ proc rpcServerFactory*(name: string, serviceType: NimNode, procs: seq[NimNode]): serverProcs.add(createServerProc(p, serverProcName)) let service = rpcServiceObject(name, procTable) result = quote do: - proc `procName`(): `serviceType` = + proc `procName`*(): `serviceType` = `serverProcs` `service` @@ -47,6 +47,6 @@ proc rpcClientFactory*(name: string, serviceType: NimNode, procs: seq[NimNode]): clientProcs.add(createClientProc(p, clientProcName, pName, driverName)) let service = rpcServiceObject(name, procTable) result = quote do: - proc `procName`(`driverName`: NerveDriver): `serviceType` = + proc `procName`*(`driverName`: NerveDriver): `serviceType` = `clientProcs` `service` diff --git a/src/nerve/service.nim b/src/nerve/service.nim index 452404f..aab4948 100644 --- a/src/nerve/service.nim +++ b/src/nerve/service.nim @@ -42,7 +42,8 @@ proc rpcService*(name: NimNode, uri: string, body: NimNode): NimNode = let serviceType = rpcServiceName(nameStr) result = newStmtList() result.add(serviceImports()) - result.add(procs.toStmtList()) + if not defined(js): + result.add(procs.toStmtList()) result.add(networkProcs(procs)) result.add(rpcServiceType(nameStr, procs)) result.add(rpcUriConst(nameStr, uri)) diff --git a/tests/newApi.nim b/tests/newApi.nim new file mode 100644 index 0000000..a06b07a --- /dev/null +++ b/tests/newApi.nim @@ -0,0 +1,4 @@ +import nerve, nerve/drivers, nerve/web, nerve/utils + +service Hello, "": + proc greet(): Future[wstring] = fwrap("Hello world") diff --git a/tests/newClient.nim b/tests/newClient.nim new file mode 100644 index 0000000..3cc5e96 --- /dev/null +++ b/tests/newClient.nim @@ -0,0 +1,10 @@ +import asyncdispatch, httpclient +import newApi, nerve, nerve/drivers + +let client = Hello.newClient(newHttpDriver("http://127.0.0.1:1234/rpc")) + +proc main() {.async.} = + let greeting = await client.greet() + echo greeting + +waitFor main() diff --git a/tests/newServer.nim b/tests/newServer.nim new file mode 100644 index 0000000..6d39618 --- /dev/null +++ b/tests/newServer.nim @@ -0,0 +1,21 @@ +import asyncHttpServer, asyncdispatch, json +import newApi, nerve + +let server = newAsyncHttpServer() + +proc cb(req: Request) {.async.} = + let rpcServer = Hello.newServer() + let body = req.body + case req.url.path + of "/rpc": + await req.respond(Http200, $ await Hello.routeRpc(rpcServer, body)) + of "/client.js": + let headers = newHttpHeaders() + headers["Content-Type"] = "application/javascript" + await req.respond(Http200, readFile("tests/nimcache/newClient.js"), headers) + of "/": + await req.respond(Http200, """Testing""") + else: + await req.respond(Http404, "Not Found") + +waitFor server.serve(Port(1234), cb) diff --git a/tests/newTest.nim b/tests/newTest.nim index 876a831..fcc564f 100644 --- a/tests/newTest.nim +++ b/tests/newTest.nim @@ -7,4 +7,3 @@ service Hello, "/api/hello": proc greet(greeting, name: wstring): Future[wstring] = fwrap(greeting & " " & name) let server = Hello.newClient(newHttpDriver("")) -discard Hello.routeRpc(server, "") From 5f1568d5ada17af744c34ca2802657c4ce6a84a1 Mon Sep 17 00:00:00 2001 From: nepeckman Date: Mon, 22 Jul 2019 15:10:21 -0400 Subject: [PATCH 15/22] Improved user facing abstractions --- src/nerve.nim | 17 +++++---- src/nerve/common.nim | 8 ++-- src/nerve/factories.nim | 6 +-- src/nerve/runtime/serverRuntime.nim | 57 ----------------------------- src/nerve/server.nim | 2 + src/nerve/service.nim | 2 +- src/nerve/types.nim | 10 +++-- tests/newApi.nim | 2 +- 8 files changed, 28 insertions(+), 76 deletions(-) delete mode 100644 src/nerve/runtime/serverRuntime.nim diff --git a/src/nerve.nim b/src/nerve.nim index 0332537..2c1f2a6 100644 --- a/src/nerve.nim +++ b/src/nerve.nim @@ -5,33 +5,36 @@ when defined(js): else: import json -macro service*(name: untyped, uri: static[string], body: untyped): untyped = - result = rpcService(name, uri, body) +macro service*(name: untyped, uri: untyped = nil, body: untyped = nil): untyped = + if body.kind != nnkNilLit: + result = rpcService(name, uri.strVal(), body) + else: + result = rpcService(name, "", uri) -macro newServer*(rpc: static[RpcServiceName]): untyped = +macro newServer*(rpc: static[RpcService]): untyped = let rpcName = $rpc let serverFactoryProc = rpcServerFactoryProc(rpcName) result = quote do: `serverFactoryProc`() -macro newClient*(rpc: static[RpcServiceName], driver: NerveDriver): untyped = +macro newClient*(rpc: static[RpcService], driver: NerveDriver): untyped = let clientFactoryProc = rpcClientFactoryProc($rpc) result = quote do: `clientFactoryProc`(`driver`) -macro rpcUri*(rpc: static[RpcServiceName]): untyped = +macro rpcUri*(rpc: static[RpcService]): untyped = let rpcName = $rpc let uriConst = rpcName.rpcUriConstName result = quote do: `uriConst` -macro routeRpc*(rpc: static[RpcServiceName], server: RpcService, req: WObject): untyped = +macro routeRpc*(rpc: static[RpcService], server: RpcServiceInst, req: WObject): untyped = let rpcName = $rpc let routerProc = rpcName.rpcRouterProcName result = quote do: `routerProc`(`server`, `req`) -macro routeRpc*(rpc: static[RpcServiceName], server: RpcService, req: string): untyped = +macro routeRpc*(rpc: static[RpcService], server: RpcServiceInst, req: string): untyped = let rpcName = $rpc let routerProc = rpcName.rpcRouterProcName result = quote do: diff --git a/src/nerve/common.nim b/src/nerve/common.nim index 3ba9d06..a8d4168 100644 --- a/src/nerve/common.nim +++ b/src/nerve/common.nim @@ -1,4 +1,5 @@ import macros, tables +import types const dispatchPrefix* = "NerveRpc" @@ -31,13 +32,14 @@ proc rpcServiceType*(name: string, procs: seq[NimNode]): NimNode = ) result = quote do: - type `typeName`* = object of RpcService + type `typeName`* = object of RpcServiceInst result[0][2][2] = procFields -proc rpcServiceObject*(name: string, procs: Table[string, NimNode], uri = "rpc"): NimNode = +proc rpcServiceObject*(name: string, procs: Table[string, NimNode], kind: RpcServiceKind): NimNode = let typeName = rpcServiceName(name) + let kindName = ident($kind) result = quote do: - `typeName`(uri: `uri`) + `typeName`(kind: `kindName`) for pName in procs.keys: var field = newColonExpr(procs[pName][0].basename, ident(pName)) result.add(field) diff --git a/src/nerve/factories.nim b/src/nerve/factories.nim index 11ffc8e..b73f209 100644 --- a/src/nerve/factories.nim +++ b/src/nerve/factories.nim @@ -1,5 +1,5 @@ import macros, tables -import common +import common, types proc createServerProc(p: NimNode, name: string): NimNode = let providedProcName = p[0].basename @@ -29,7 +29,7 @@ proc rpcServerFactory*(name: string, serviceType: NimNode, procs: seq[NimNode]): let serverProcName ="NerveServer" & p[0].basename.strVal() procTable[serverProcName] = p serverProcs.add(createServerProc(p, serverProcName)) - let service = rpcServiceObject(name, procTable) + let service = rpcServiceObject(name, procTable, rskServer) result = quote do: proc `procName`*(): `serviceType` = `serverProcs` @@ -45,7 +45,7 @@ proc rpcClientFactory*(name: string, serviceType: NimNode, procs: seq[NimNode]): let clientProcName ="NerveClient" & pName procTable[clientProcName] = p clientProcs.add(createClientProc(p, clientProcName, pName, driverName)) - let service = rpcServiceObject(name, procTable) + let service = rpcServiceObject(name, procTable, rskClient) result = quote do: proc `procName`*(`driverName`: NerveDriver): `serviceType` = `clientProcs` diff --git a/src/nerve/runtime/serverRuntime.nim b/src/nerve/runtime/serverRuntime.nim deleted file mode 100644 index eb4b476..0000000 --- a/src/nerve/runtime/serverRuntime.nim +++ /dev/null @@ -1,57 +0,0 @@ -import json, strutils, sequtils, os -import ../common - -type - RequestFormatError* = ref object of CatchableError - DispatchError* = ref object of CatchableError - ParameterError* = ref object of CatchableError - -proc nerveValidateRequest*(req: JsonNode): bool = - req.hasKey("jsonrpc") and req["jsonrpc"].getStr() == "2.0" and req.hasKey("id") - -proc nerveGetMethod*[T: enum](req: JsonNode): T = - try: - parseEnum[T](dispatchPrefix & req["method"].getStr()) - except: - let msg = if req.hasKey("method"): "No method '" & req["method"].getStr() & "'" - else: "Request missing method field" - raise DispatchError(msg: msg, parent: getCurrentException()) - -proc nerveUnboxParameter*[T](req: JsonNode, param: string, default: T): T = - try: - return if req["params"].hasKey(param): req["params"][param].to(T) - else: default - except: - let msg = "Error in param '" & param & "': " & getCurrentExceptionMsg() - raise ParameterError(msg: msg, parent: getCurrentException()) - -proc nerveUnboxParameter*[T](req: JsonNode, param: string): T = - try: - return req["params"][param].to(T) - except: - let msg = "Error in param '" & param & "': " & getCurrentExceptionMsg() - raise ParameterError(msg: msg, parent: getCurrentException()) - -proc newNerveError*(code: int, message: string): JsonNode = - %* { - "code": code, - "message": message - } - -proc newNerveError*(code: int, message: string, e: ref CatchableError): JsonNode = - let dir = getCurrentDir() - let stackTrace = e.getStackTraceEntries() - .filterIt(`$`(it.filename).find(dir) != -1 and - `$`(it.procname).find(dispatchPrefix) == -1) - .mapIt($it.filename & "(" & $it.line & ")" & " " & $it.procname) - .join("\n") - return %* { - "code": code, - "message": message, - "data": { - "msg": e.msg, - "stackTrace": if defined(release): "" else: stackTrace - } - } - -export json diff --git a/src/nerve/server.nim b/src/nerve/server.nim index 6f9a3ed..cee2af6 100644 --- a/src/nerve/server.nim +++ b/src/nerve/server.nim @@ -76,6 +76,7 @@ proc serverDispatch*(name: string, procs: seq[NimNode]): NimNode = result.add(quote do: `enumDeclaration` proc `routerSym`*(`serverSym`: `serviceType`,`requestSym`: WObject): Future[WObject] {.async.} = + assert(`serverSym`.kind == rskServer, "Only Nerve Servers can do routing") result = newNerveResponse() if not nerveValidateRequest(`requestSym`): result["id"] = if `requestSym`.hasKey("id"): `requestSym`["id"] else: newJNull() @@ -91,6 +92,7 @@ proc serverDispatch*(name: string, procs: seq[NimNode]): NimNode = result["error"] = newNerveError(-32000, "Server error", e) proc `routerSym`*(`serverSym`: `serviceType`,`requestSym`: string): Future[WObject] = + assert(`serverSym`.kind == rskServer, "Only Nerve Servers can do routing") try: let requestJson = parseJson(`requestSym`) result = `routerSym`(`serverSym`, requestJson) diff --git a/src/nerve/service.nim b/src/nerve/service.nim index aab4948..25eb481 100644 --- a/src/nerve/service.nim +++ b/src/nerve/service.nim @@ -34,7 +34,7 @@ proc serviceImports(): NimNode = proc compiletimeReference(name: NimNode): NimNode = let nameStr = name.strVal().newStrLitNode() result = quote do: - const `name`* = RpcServiceName(`nameStr`) + const `name`* = RpcService(`nameStr`) proc rpcService*(name: NimNode, uri: string, body: NimNode): NimNode = let procs = procDefs(body) diff --git a/src/nerve/types.nim b/src/nerve/types.nim index 409d661..4a0bd21 100644 --- a/src/nerve/types.nim +++ b/src/nerve/types.nim @@ -4,10 +4,12 @@ when not defined(js): else: import asyncjs -type RpcService* = object of RootObj - uri*: string +type RpcServiceKind* = enum rskClient, rskServer -type RpcServiceName* = distinct string -proc `$`*(s: RpcServiceName): string {.borrow.} +type RpcServiceInst* = object of RootObj + kind*: RpcServiceKind + +type RpcService* = distinct string +proc `$`*(s: RpcService): string {.borrow.} type NerveDriver* = proc (req: WObject): Future[WObject] diff --git a/tests/newApi.nim b/tests/newApi.nim index a06b07a..7de779f 100644 --- a/tests/newApi.nim +++ b/tests/newApi.nim @@ -1,4 +1,4 @@ import nerve, nerve/drivers, nerve/web, nerve/utils -service Hello, "": +service Hello, "/Hello": proc greet(): Future[wstring] = fwrap("Hello world") From 66836b9a8a30bcc6f8cd46daf1f72069c1c28442 Mon Sep 17 00:00:00 2001 From: nepeckman Date: Mon, 22 Jul 2019 15:50:38 -0400 Subject: [PATCH 16/22] Updated tests to 0.2.0 --- src/nerve.nim | 5 ++++ tests/client.nim | 36 +++++++++++++++------------- tests/fileService.nim | 2 +- tests/greetingService.nim | 2 +- tests/newApi.nim | 4 ---- tests/newClient.nim | 10 -------- tests/newServer.nim | 21 ----------------- tests/newTest.nim | 9 ------- tests/personService.nim | 2 +- tests/server.nim | 49 +++++++++++++++++++++++---------------- 10 files changed, 57 insertions(+), 83 deletions(-) delete mode 100644 tests/newApi.nim delete mode 100644 tests/newClient.nim delete mode 100644 tests/newServer.nim delete mode 100644 tests/newTest.nim diff --git a/src/nerve.nim b/src/nerve.nim index 2c1f2a6..450a9eb 100644 --- a/src/nerve.nim +++ b/src/nerve.nim @@ -28,6 +28,11 @@ macro rpcUri*(rpc: static[RpcService]): untyped = result = quote do: `uriConst` +macro rpcType*(rpc: static[RpcService]): untyped = + let typeName = rpcServiceName($rpc) + result = quote do: + `typeName` + macro routeRpc*(rpc: static[RpcService], server: RpcServiceInst, req: WObject): untyped = let rpcName = $rpc let routerProc = rpcName.rpcRouterProcName diff --git a/tests/client.nim b/tests/client.nim index 2246340..15df120 100644 --- a/tests/client.nim +++ b/tests/client.nim @@ -1,52 +1,56 @@ -import asyncjs, unittest, nerve/clientRuntime +import asyncjs, unittest, nerve, nerve/drivers, nerve/clientRuntime import personService, greetingService, fileService proc main() {.async.} = + let personClient = PersonService.newClient(newHttpDriver(PersonService.rpcUri)) + let greetingClient = GreetingService.newClient(newHttpDriver(GreetingService.rpcUri)) + let fileClient = FileService.newClient(newHttpDriver(FileService.rpcUri)) + suite "Sanity": test "Hello": - let msg = await PersonService.hello("Nic") + let msg = await personClient.hello("Nic") check(msg == "Hello Nic") - let helloWorld = await PersonService.hello() + let helloWorld = await personClient.hello() check(helloWorld == "Hello World") test "Add": - let x = await PersonService.add() - let y = await PersonService.add(1) - let z = await PersonService.add(2, 3) + let x = await personClient.add() + let y = await personClient.add(1) + let z = await personClient.add(2, 3) check(x == 0) check(y == 1) check(z == 5) test "Person": - let person = await PersonService.newPerson("Nic", 24) + let person = await personClient.newPerson("Nic", 24) check(person.name == "Nic") test "Parent": - let person = await PersonService.newPerson("Alex", 32) - let child = await PersonService.newPerson("James", 4) - let parent = await PersonService.newParent(person, child) + let person = await personClient.newPerson("Alex", 32) + let child = await personClient.newPerson("James", 4) + let parent = await personClient.newParent(person, child) check(parent.self.name == "Alex") check(parent.children[0].name == "James") test "Error": expect RpcError: - discard await FileService.saveFile("missing.txt", "failure") + discard await fileClient.saveFile("missing.txt", "failure") suite "Proc arguments": test "Multiple defaults": - let g1 = await GreetingService.greet() - let g2 = await GreetingService.greet(name = "Nic") - let g3 = await GreetingService.greet("Yo") - let g4 = await GreetingService.greet("Goodday", "child") + let g1 = await greetingClient.greet() + let g2 = await greetingClient.greet(name = "Nic") + let g3 = await greetingClient.greet("Yo") + let g4 = await greetingClient.greet("Goodday", "child") check(g1 == "Hello World") check(g2 == "Hello Nic") check(g3 == "Yo World") check(g4 == "Goodday child") test "No params": - let msg = await PersonService.helloWorld() + let msg = await personClient.helloWorld() check(msg == "Hello world") diff --git a/tests/fileService.nim b/tests/fileService.nim index 9bb4ebf..560c0cf 100644 --- a/tests/fileService.nim +++ b/tests/fileService.nim @@ -1,4 +1,4 @@ -import nerve, nerve/utils +import nerve, nerve/web service FileService, "/api/file": diff --git a/tests/greetingService.nim b/tests/greetingService.nim index a77cb9c..fbfb67d 100644 --- a/tests/greetingService.nim +++ b/tests/greetingService.nim @@ -1,4 +1,4 @@ -import nerve, nerve/utils +import nerve, nerve/web service GreetingService, "/api/greeting": diff --git a/tests/newApi.nim b/tests/newApi.nim deleted file mode 100644 index 7de779f..0000000 --- a/tests/newApi.nim +++ /dev/null @@ -1,4 +0,0 @@ -import nerve, nerve/drivers, nerve/web, nerve/utils - -service Hello, "/Hello": - proc greet(): Future[wstring] = fwrap("Hello world") diff --git a/tests/newClient.nim b/tests/newClient.nim deleted file mode 100644 index 3cc5e96..0000000 --- a/tests/newClient.nim +++ /dev/null @@ -1,10 +0,0 @@ -import asyncdispatch, httpclient -import newApi, nerve, nerve/drivers - -let client = Hello.newClient(newHttpDriver("http://127.0.0.1:1234/rpc")) - -proc main() {.async.} = - let greeting = await client.greet() - echo greeting - -waitFor main() diff --git a/tests/newServer.nim b/tests/newServer.nim deleted file mode 100644 index 6d39618..0000000 --- a/tests/newServer.nim +++ /dev/null @@ -1,21 +0,0 @@ -import asyncHttpServer, asyncdispatch, json -import newApi, nerve - -let server = newAsyncHttpServer() - -proc cb(req: Request) {.async.} = - let rpcServer = Hello.newServer() - let body = req.body - case req.url.path - of "/rpc": - await req.respond(Http200, $ await Hello.routeRpc(rpcServer, body)) - of "/client.js": - let headers = newHttpHeaders() - headers["Content-Type"] = "application/javascript" - await req.respond(Http200, readFile("tests/nimcache/newClient.js"), headers) - of "/": - await req.respond(Http200, """Testing""") - else: - await req.respond(Http404, "Not Found") - -waitFor server.serve(Port(1234), cb) diff --git a/tests/newTest.nim b/tests/newTest.nim deleted file mode 100644 index fcc564f..0000000 --- a/tests/newTest.nim +++ /dev/null @@ -1,9 +0,0 @@ -import nerve, nerve/utils, nerve/drivers, nerve/web - -service Hello, "/api/hello": - - proc helloWorld(): Future[wstring] = fwrap(wstring"Hello World") - - proc greet(greeting, name: wstring): Future[wstring] = fwrap(greeting & " " & name) - -let server = Hello.newClient(newHttpDriver("")) diff --git a/tests/personService.nim b/tests/personService.nim index aabd34c..6c6fb5b 100644 --- a/tests/personService.nim +++ b/tests/personService.nim @@ -1,4 +1,4 @@ -import nerve, nerve/utils +import nerve, nerve/web type Person* = ref object diff --git a/tests/server.nim b/tests/server.nim index c6f4305..ebc4255 100644 --- a/tests/server.nim +++ b/tests/server.nim @@ -1,23 +1,32 @@ -import httpbeast, asyncdispatch, json, options -import personService, greetingService, fileService, nerve/utils +import asyncHttpServer, asyncdispatch, json +import personService, greetingService, fileService, nerve -template routeRpc(service: RpcServer, req: Request): untyped = - service.routeRpc(if req.body.isSome: req.body.get() else: "") +let server = newAsyncHttpServer() -proc cb(req: Request) {.async.} = - case req.path.get() - of PersonService.rpcUri: - req.send($ await PersonService.routeRpc(req)) - of GreetingService.rpcUri: - req.send($ await GreetingService.routeRpc(req)) - of FileService.rpcUri: - req.send($ await FileService.routeRpc(req)) - of "/client.js": - const headers = "Content-Type: application/javascript" - req.send(Http200, readFile("tests/nimcache/client.js"), headers) - of "/": - req.send("""Testing""") - else: - req.send(Http404) +proc generateCb(): proc (req: Request): Future[void] {.gcsafe.} = -run(cb, Settings(port: Port(1234), bindAddr: "")) + let personServer = PersonService.newServer() + let greetingServer = GreetingService.newServer() + let fileServer = FileService.newServer() + + proc cb(req: Request) {.async, gcsafe.} = + let body = req.body + case req.url.path + of PersonService.rpcUri: + await req.respond(Http200, $ await PersonService.routeRpc(personServer, body)) + of GreetingService.rpcUri: + await req.respond(Http200, $ await GreetingService.routeRpc(greetingServer, body)) + of FileService.rpcUri: + await req.respond(Http200, $ await FileService.routeRpc(fileServer, body)) + of "/client.js": + let headers = newHttpHeaders() + headers["Content-Type"] = "application/javascript" + await req.respond(Http200, readFile("tests/nimcache/client.js"), headers) + of "/": + await req.respond(Http200, """Testing""") + else: + await req.respond(Http404, "Not Found") + + result = cb + +waitFor server.serve(Port(1234), generateCb()) From f900c9abf5ea19c808d4c9c60c8eb48ed4674027 Mon Sep 17 00:00:00 2001 From: nepeckman Date: Mon, 22 Jul 2019 18:08:30 -0400 Subject: [PATCH 17/22] Client tests on native --- src/nerve.nim | 10 ++++++++-- tests/client.nim | 27 +++++++++++++++++++-------- 2 files changed, 27 insertions(+), 10 deletions(-) diff --git a/src/nerve.nim b/src/nerve.nim index 450a9eb..ade8496 100644 --- a/src/nerve.nim +++ b/src/nerve.nim @@ -1,5 +1,5 @@ import macros -import nerve/service, nerve/types, nerve/common, nerve/utils +import nerve/service, nerve/types, nerve/common, nerve/utils, nerve/drivers when defined(js): import jsffi else: @@ -22,6 +22,12 @@ macro newClient*(rpc: static[RpcService], driver: NerveDriver): untyped = result = quote do: `clientFactoryProc`(`driver`) +macro newHttpClient*(rpc: static[RpcService], host: static[string] = ""): untyped = + let clientFactoryProc = rpcClientFactoryProc($rpc) + let serviceName = ident($rpc) + result = quote do: + `clientFactoryProc`(newHttpDriver(`host` & `serviceName`.rpcUri)) + macro rpcUri*(rpc: static[RpcService]): untyped = let rpcName = $rpc let uriConst = rpcName.rpcUriConstName @@ -45,4 +51,4 @@ macro routeRpc*(rpc: static[RpcService], server: RpcServiceInst, req: string): u result = quote do: `routerProc`(`server`, `req`) -export types +export types, drivers diff --git a/tests/client.nim b/tests/client.nim index 15df120..a76d8b4 100644 --- a/tests/client.nim +++ b/tests/client.nim @@ -1,10 +1,17 @@ -import asyncjs, unittest, nerve, nerve/drivers, nerve/clientRuntime +when defined(js): + import asyncjs + const host = "" +else: + import asyncdispatch + const host = "http://127.0.0.1:1234" + +import unittest, nerve, nerve/clientRuntime import personService, greetingService, fileService proc main() {.async.} = - let personClient = PersonService.newClient(newHttpDriver(PersonService.rpcUri)) - let greetingClient = GreetingService.newClient(newHttpDriver(GreetingService.rpcUri)) - let fileClient = FileService.newClient(newHttpDriver(FileService.rpcUri)) + let personClient = PersonService.newHttpClient(host) + let greetingClient = GreetingService.newHttpClient(host) + let fileClient = FileService.newHttpClient(host) suite "Sanity": @@ -33,9 +40,10 @@ proc main() {.async.} = check(parent.self.name == "Alex") check(parent.children[0].name == "James") - test "Error": - expect RpcError: - discard await fileClient.saveFile("missing.txt", "failure") + when defined(js): + test "Error": + expect RpcError: + discard await fileClient.saveFile("missing.txt", "failure") suite "Proc arguments": @@ -54,4 +62,7 @@ proc main() {.async.} = check(msg == "Hello world") -discard main() +when defined(js): + discard main() +else: + waitFor main() From 833e3284c6fc216f9f2abf9887a6d5cf8dc34cb3 Mon Sep 17 00:00:00 2001 From: nepeckman Date: Tue, 23 Jul 2019 08:49:21 -0400 Subject: [PATCH 18/22] Changed utils to promises --- src/nerve.nim | 8 ++----- src/nerve/client.nim | 15 ++++++-------- src/nerve/clientRuntime.nim | 3 --- src/nerve/drivers.nim | 12 +++++------ src/nerve/{utils.nim => promises.nim} | 13 +++++------- src/nerve/server.nim | 8 +++---- src/nerve/serverRuntime.nim | 30 ++++++++++++--------------- src/nerve/service.nim | 8 ++----- src/nerve/types.nim | 8 ++----- src/nerve/web.nim | 5 +++-- tests/client.nim | 3 +-- 11 files changed, 44 insertions(+), 69 deletions(-) rename src/nerve/{utils.nim => promises.nim} (93%) diff --git a/src/nerve.nim b/src/nerve.nim index ade8496..2b5573d 100644 --- a/src/nerve.nim +++ b/src/nerve.nim @@ -1,9 +1,5 @@ import macros -import nerve/service, nerve/types, nerve/common, nerve/utils, nerve/drivers -when defined(js): - import jsffi -else: - import json +import nerve/service, nerve/types, nerve/common, nerve/web, nerve/drivers macro service*(name: untyped, uri: untyped = nil, body: untyped = nil): untyped = if body.kind != nnkNilLit: @@ -39,7 +35,7 @@ macro rpcType*(rpc: static[RpcService]): untyped = result = quote do: `typeName` -macro routeRpc*(rpc: static[RpcService], server: RpcServiceInst, req: WObject): untyped = +macro routeRpc*(rpc: static[RpcService], server: RpcServiceInst, req: JsObject): untyped = let rpcName = $rpc let routerProc = rpcName.rpcRouterProcName result = quote do: diff --git a/src/nerve/client.nim b/src/nerve/client.nim index 26dc2c2..2a3c9c5 100644 --- a/src/nerve/client.nim +++ b/src/nerve/client.nim @@ -8,8 +8,6 @@ proc networkProcBody(p: NimNode, methodName: string): NimNode = let params = formalParams.getParams() let req = genSym() let driver = ident("nerveDriver") - let newJsObject = if defined(js): ident("newJsObject") else: ident("newJObject") - let toJs = if defined(js): ident("toJs") else: ident("%") var paramJson = nnkStmtList.newTree() for param in params: @@ -17,15 +15,15 @@ proc networkProcBody(p: NimNode, methodName: string): NimNode = let name = param["name"] paramJson.add( quote do: - `req`["params"][`nameStr`] = `toJs` `name` + `req`["params"][`nameStr`] = toJs `name` ) result = quote do: - let `req` = `newJsObject`() - `req`["jsonrpc"] = `toJs` "2.0" - `req`["id"] = `toJs` 0 - `req`["method"] = `toJs` `methodName` - `req`["params"] = `newJsObject`() + let `req` = newJsObject() + `req`["jsonrpc"] = toJs "2.0" + `req`["id"] = toJs 0 + `req`["method"] = toJs `methodName` + `req`["params"] = newJsObject() `paramJson` result = `driver`(`req`) .then(handleRpcResponse[`retType`]) @@ -45,4 +43,3 @@ proc networkProcs*(procs: seq[NimNode]): NimNode = ) ) result.add(networkProc) - diff --git a/src/nerve/clientRuntime.nim b/src/nerve/clientRuntime.nim index 12fbbb7..4b0e24b 100644 --- a/src/nerve/clientRuntime.nim +++ b/src/nerve/clientRuntime.nim @@ -4,7 +4,6 @@ type InvalidResponseError* = ref object of CatchableError RpcError* = ref object of CatchableError - proc handleRpcResponse*[T](rpcResponse: JsObject): T = if hasKey(rpcResponse, "error"): let error = rpcResponse["error"] @@ -29,5 +28,3 @@ else: return respJson(resp) let msg = "Invalid Response: Server responsed with code " & $to(resp.status, int) raise InvalidResponseError(msg: msg) - - var JSON* {. importc, nodecl .}: JsObject diff --git a/src/nerve/drivers.nim b/src/nerve/drivers.nim index dd3a709..16a7f0f 100644 --- a/src/nerve/drivers.nim +++ b/src/nerve/drivers.nim @@ -1,18 +1,18 @@ +import types, web, promises, clientRuntime + when not defined(js): - import asyncdispatch, httpClient - import types, utils, clientRuntime + import httpClient proc newHttpDriver*(uri: string): NerveDriver = let client = newAsyncHttpClient() - result = proc (req: WObject): Future[WObject] {.async.} = + result = proc (req: JsObject): Future[JsObject] {.async.} = let res = await client.postContent(uri, $ req) result = res.respToJson() + else: - import asyncjs - import types, utils, clientRuntime proc newHttpDriver*(uri: string): NerveDriver = - result = proc (req: WObject): Future[WObject] = + result = proc (req: JsObject): Future[JsObject] = let msg = newJsObject() msg["method"] = cstring"POST" msg["body"] = JSON.stringify(req) diff --git a/src/nerve/utils.nim b/src/nerve/promises.nim similarity index 93% rename from src/nerve/utils.nim rename to src/nerve/promises.nim index 0defd6c..b38c9d7 100644 --- a/src/nerve/utils.nim +++ b/src/nerve/promises.nim @@ -1,9 +1,8 @@ import macros +import web when defined(js): - import jsffi, asyncjs - - type WObject* = JsObject + import asyncjs proc fetch*(uri: cstring): Future[JsObject] {. importc .} proc fetch*(uri: cstring, data: JsObject): Future[JsObject] {. importc .} @@ -20,12 +19,10 @@ when defined(js): proc fwrap*[T](it: T): Future[T] = PromiseObj.resolve(it) - export jsffi + export asyncjs else: - import json, asyncdispatch - - type WObject* = JsonNode + import asyncdispatch proc then*[T, R](future: Future[T], cb: proc (t: T): R {.gcsafe.}): Future[R] = let rv = newFuture[R]("then") @@ -52,4 +49,4 @@ else: result = newFuture[T]() result.complete(it) - export json + export asyncdispatch diff --git a/src/nerve/server.nim b/src/nerve/server.nim index cee2af6..4b17744 100644 --- a/src/nerve/server.nim +++ b/src/nerve/server.nim @@ -1,5 +1,5 @@ import macros, tables, strutils -import common, utils +import common proc dispatchName(node: NimNode): NimNode = ident(dispatchPrefix & node.name.strVal.capitalizeAscii) @@ -75,7 +75,7 @@ proc serverDispatch*(name: string, procs: seq[NimNode]): NimNode = result.add(quote do: `enumDeclaration` - proc `routerSym`*(`serverSym`: `serviceType`,`requestSym`: WObject): Future[WObject] {.async.} = + proc `routerSym`*(`serverSym`: `serviceType`,`requestSym`: JsObject): Future[JsObject] {.async.} = assert(`serverSym`.kind == rskServer, "Only Nerve Servers can do routing") result = newNerveResponse() if not nerveValidateRequest(`requestSym`): @@ -91,13 +91,13 @@ proc serverDispatch*(name: string, procs: seq[NimNode]): NimNode = except CatchableError as e: result["error"] = newNerveError(-32000, "Server error", e) - proc `routerSym`*(`serverSym`: `serviceType`,`requestSym`: string): Future[WObject] = + proc `routerSym`*(`serverSym`: `serviceType`,`requestSym`: string): Future[JsObject] = assert(`serverSym`.kind == rskServer, "Only Nerve Servers can do routing") try: let requestJson = parseJson(`requestSym`) result = `routerSym`(`serverSym`, requestJson) except CatchableError as e: - result = newFuture[WObject](`routerName`) + result = newFuture[JsObject](`routerName`) var response = newNerveResponse() response["error"] = newNerveError(-32700, "Parse error", e) result.complete(response) diff --git a/src/nerve/serverRuntime.nim b/src/nerve/serverRuntime.nim index 1bf51ed..e1623c8 100644 --- a/src/nerve/serverRuntime.nim +++ b/src/nerve/serverRuntime.nim @@ -1,11 +1,7 @@ -import strutils, sequtils, os +import strutils, sequtils +import web, promises, common when not defined(js): - import json, asyncdispatch, os - export json, asyncdispatch -else: - import jsffi, asyncjs - export jsffi, asyncjs -import common, utils + import os type RequestFormatError* = ref object of CatchableError @@ -13,13 +9,13 @@ type ParameterError* = ref object of CatchableError when not defined(js): - proc nerveValidateRequest*(req: WObject): bool = + proc nerveValidateRequest*(req: JsObject): bool = req.hasKey("jsonrpc") and req["jsonrpc"].getStr() == "2.0" and req.hasKey("id") else: - proc nerveValidateRequest*(req: WObject): bool = + proc nerveValidateRequest*(req: JsObject): bool = req.hasOwnProperty("jsonrpc") and (req["jsonrpc"].to(cstring) == "2.0") and req.hasOwnProperty("id") -proc nerveGetMethod*[T: enum](req: WObject): T = +proc nerveGetMethod*[T: enum](req: JsObject): T = try: parseEnum[T](dispatchPrefix & req["method"].getStr()) except: @@ -27,7 +23,7 @@ proc nerveGetMethod*[T: enum](req: WObject): T = else: "Request missing method field" raise DispatchError(msg: msg, parent: getCurrentException()) -proc nerveUnboxParameter*[T](req: WObject, param: string, default: T): T = +proc nerveUnboxParameter*[T](req: JsObject, param: string, default: T): T = try: return if req["params"].hasKey(param): req["params"][param].to(T) else: default @@ -35,7 +31,7 @@ proc nerveUnboxParameter*[T](req: WObject, param: string, default: T): T = let msg = "Error in param '" & param & "': " & getCurrentExceptionMsg() raise ParameterError(msg: msg, parent: getCurrentException()) -proc nerveUnboxParameter*[T](req: WObject, param: string): T = +proc nerveUnboxParameter*[T](req: JsObject, param: string): T = try: return req["params"][param].to(T) except: @@ -44,16 +40,16 @@ proc nerveUnboxParameter*[T](req: WObject, param: string): T = when not defined(js): - proc newNerveResponse*(): WObject = + proc newNerveResponse*(): JsObject = %* {"jsonrpc": "2.0", "id": newJNull()} - proc newNerveError*(code: int, message: string): WObject = + proc newNerveError*(code: int, message: string): JsObject = %* { "code": code, "message": message } - proc newNerveError*(code: int, message: string, e: ref CatchableError): WObject = + proc newNerveError*(code: int, message: string, e: ref CatchableError): JsObject = let dir = getCurrentDir() let stackTrace = e.getStackTraceEntries() .filterIt(`$`(it.filename).find(dir) != -1 and @@ -76,12 +72,12 @@ else: result["jsonrpc"] = cstring"2.0" result["id"] = jsNull - proc newNerveError*(code: int, message: string): WObject = + proc newNerveError*(code: int, message: string): JsObject = result = newJsObject() result["code"] = code result["message"] = message - proc newNerveError*(code: int, message: string, e: ref CatchableError): WObject = + proc newNerveError*(code: int, message: string, e: ref CatchableError): JsObject = result = newJsObject() result["code"] = code result["message"] = message diff --git a/src/nerve/service.nim b/src/nerve/service.nim index 25eb481..4453a80 100644 --- a/src/nerve/service.nim +++ b/src/nerve/service.nim @@ -21,14 +21,10 @@ proc procDefs(node: NimNode): seq[NimNode] = proc serviceImports(): NimNode = result = quote do: - import nerve/utils + import nerve/promises import nerve/web import nerve/types - when not defined(js): - import asyncdispatch - import nerve/serverRuntime - else: - import asyncjs + import nerve/serverRuntime import nerve/clientRuntime proc compiletimeReference(name: NimNode): NimNode = diff --git a/src/nerve/types.nim b/src/nerve/types.nim index 4a0bd21..e63e555 100644 --- a/src/nerve/types.nim +++ b/src/nerve/types.nim @@ -1,8 +1,4 @@ -import utils -when not defined(js): - import asyncdispatch -else: - import asyncjs +import promises, web type RpcServiceKind* = enum rskClient, rskServer @@ -12,4 +8,4 @@ type RpcServiceInst* = object of RootObj type RpcService* = distinct string proc `$`*(s: RpcService): string {.borrow.} -type NerveDriver* = proc (req: WObject): Future[WObject] +type NerveDriver* = proc (req: JsObject): Future[JsObject] diff --git a/src/nerve/web.nim b/src/nerve/web.nim index 8139614..dcf00e4 100644 --- a/src/nerve/web.nim +++ b/src/nerve/web.nim @@ -4,16 +4,17 @@ when not defined(js): type wstring* = string type JsObject* = JsonNode - template toJs(x: untyped): untyped = % x + template toJs*(x: untyped): untyped = % x const newJsObject* = newJObject - export json + else: import jsffi type wstring* = cstring + var JSON* {. importc, nodecl .}: JsObject proc parseJson* (data: cstring): JsObject {. importcpp: "JSON.parse(#)", nodecl .} proc `$`* (jsObject: JsObject): string {. importcpp: "JSON.stringify(#)", nodecl .} diff --git a/tests/client.nim b/tests/client.nim index a76d8b4..c895960 100644 --- a/tests/client.nim +++ b/tests/client.nim @@ -1,8 +1,7 @@ +import nerve/promises when defined(js): - import asyncjs const host = "" else: - import asyncdispatch const host = "http://127.0.0.1:1234" import unittest, nerve, nerve/clientRuntime From 5e526ac2628a01d704a7f4ecacced62272ed5da6 Mon Sep 17 00:00:00 2001 From: nepeckman Date: Tue, 23 Jul 2019 09:22:32 -0400 Subject: [PATCH 19/22] Cleanup commit --- src/nerve/client.nim | 1 - src/nerve/server.nim | 2 -- src/nerve/service.nim | 3 ++- 3 files changed, 2 insertions(+), 4 deletions(-) diff --git a/src/nerve/client.nim b/src/nerve/client.nim index 2a3c9c5..efb8ebd 100644 --- a/src/nerve/client.nim +++ b/src/nerve/client.nim @@ -2,7 +2,6 @@ import macros, tables import common proc networkProcBody(p: NimNode, methodName: string): NimNode = - let nameStr = newStrLitNode(p.name.strVal) let formalParams = p.findChild(it.kind == nnkFormalParams) let retType = formalParams[0][1] let params = formalParams.getParams() diff --git a/src/nerve/server.nim b/src/nerve/server.nim index 4b17744..1c358a1 100644 --- a/src/nerve/server.nim +++ b/src/nerve/server.nim @@ -57,8 +57,6 @@ proc dispatch(procs: seq[NimNode], methodSym, requestSym: NimNode): NimNode = )) proc serverDispatch*(name: string, procs: seq[NimNode]): NimNode = - let uri = "" - let enumSym = genSym(nskType) # Type name the enum used to dispatch procs methodSym = genSym(nskLet) # Variable that holds the requested method diff --git a/src/nerve/service.nim b/src/nerve/service.nim index 4453a80..247ccd7 100644 --- a/src/nerve/service.nim +++ b/src/nerve/service.nim @@ -48,4 +48,5 @@ proc rpcService*(name: NimNode, uri: string, body: NimNode): NimNode = result.add(rpcServerFactory(nameStr, serviceType, procs)) result.add(rpcClientFactory(nameStr, serviceType, procs)) result.add(compiletimeReference(name)) - echo repr result + if defined(nerveRpcDebug): + echo repr result From dbf5b3246bdae24c9c98cc2a4eb8b29622539ca3 Mon Sep 17 00:00:00 2001 From: nepeckman Date: Thu, 25 Jul 2019 14:15:37 -0400 Subject: [PATCH 20/22] Added server injections --- src/nerve.nim | 4 +++- src/nerve/common.nim | 4 ++-- src/nerve/factories.nim | 16 +++++++++++++--- src/nerve/server.nim | 20 +++++++++++++++----- src/nerve/service.nim | 19 ++++++++++++++++--- src/nerve/types.nim | 2 +- tests/greetingService.nim | 7 +++++++ tests/server.nim | 2 +- 8 files changed, 58 insertions(+), 16 deletions(-) diff --git a/src/nerve.nim b/src/nerve.nim index 2b5573d..29c2f23 100644 --- a/src/nerve.nim +++ b/src/nerve.nim @@ -7,11 +7,13 @@ macro service*(name: untyped, uri: untyped = nil, body: untyped = nil): untyped else: result = rpcService(name, "", uri) -macro newServer*(rpc: static[RpcService]): untyped = +macro newServer*(rpc: static[RpcService], injections: varargs[untyped]): untyped = let rpcName = $rpc let serverFactoryProc = rpcServerFactoryProc(rpcName) result = quote do: `serverFactoryProc`() + for injection in injections: + result.add(injection) macro newClient*(rpc: static[RpcService], driver: NerveDriver): untyped = let clientFactoryProc = rpcClientFactoryProc($rpc) diff --git a/src/nerve/common.nim b/src/nerve/common.nim index a8d4168..9372865 100644 --- a/src/nerve/common.nim +++ b/src/nerve/common.nim @@ -26,7 +26,7 @@ proc rpcServiceType*(name: string, procs: seq[NimNode]): NimNode = for p in procs: var procType = nnkProcTy.newTree() procType.add(p.findChild(it.kind == nnkFormalParams)) - procType.add(newEmptyNode()) + procType.add(nnkPragma.newTree(ident("gcsafe"))) procFields.add( newIdentDefs(postfix(p[0].basename, "*"), procType) ) @@ -44,7 +44,7 @@ proc rpcServiceObject*(name: string, procs: Table[string, NimNode], kind: RpcSer var field = newColonExpr(procs[pName][0].basename, ident(pName)) result.add(field) -proc paramType(param: NimNode): NimNode = +proc paramType*(param: NimNode): NimNode = # Either returns the type node of the param # or creates a node that gets the type of default value let defaultIdx = param.len - 1 diff --git a/src/nerve/factories.nim b/src/nerve/factories.nim index b73f209..bca453a 100644 --- a/src/nerve/factories.nim +++ b/src/nerve/factories.nim @@ -1,12 +1,14 @@ import macros, tables import common, types -proc createServerProc(p: NimNode, name: string): NimNode = +proc createServerProc(p: NimNode, name: string, injections: seq[Table[string, NimNode]]): NimNode = let providedProcName = p[0].basename let procCall = nnkCall.newTree(providedProcName) let params = p.findChild(it.kind == nnkFormalParams).getParams() for param in params: procCall.add(param["name"]) + for injection in injections: + procCall.add(injection["ident"]) result = copy(p) result[result.len - 1] = procCall result[0] = ident(name) @@ -21,19 +23,27 @@ proc createClientProc(p: NimNode, name, networkProc: string, driver: NimNode): N result[result.len - 1] = procCall result[0] = ident(name) -proc rpcServerFactory*(name: string, serviceType: NimNode, procs: seq[NimNode]): NimNode = +proc rpcServerFactory*(name: string, serviceType: NimNode, procs: seq[NimNode], injections: seq[Table[string, NimNode]]): NimNode = let procName = rpcServerFactoryProc(name) var serverProcs = newStmtList() var procTable = initTable[string, NimNode]() for p in procs: let serverProcName ="NerveServer" & p[0].basename.strVal() procTable[serverProcName] = p - serverProcs.add(createServerProc(p, serverProcName)) + serverProcs.add(createServerProc(p, serverProcName, injections)) let service = rpcServiceObject(name, procTable, rskServer) result = quote do: proc `procName`*(): `serviceType` = `serverProcs` `service` + let params = result.findChild(it.kind == nnkFormalParams) + for injection in injections: + params.add(nnkIdentDefs.newTree( + injection["ident"], + injection["type"], + injection["default"] + )) + proc rpcClientFactory*(name: string, serviceType: NimNode, procs: seq[NimNode]): NimNode = let procName = rpcClientFactoryProc(name) diff --git a/src/nerve/server.nim b/src/nerve/server.nim index 1c358a1..7e92879 100644 --- a/src/nerve/server.nim +++ b/src/nerve/server.nim @@ -24,10 +24,10 @@ proc unboxExpression(param: Table[string, NimNode], requestSym: NimNode): NimNod result = quote do: nerveUnboxParameter[`ntype`](`requestSym`, `nameStr`) -proc procWrapper(requestSym, p: NimNode): NimNode = +proc procWrapper(serverSym, requestSym, p: NimNode): NimNode = # This wrapper gets the parameters from the request and uses them to invoke the proc result = nnkStmtList.newTree() - var methodCall = nnkCall.newTree(p.name) + var methodCall = nnkCall.newTree(nnkDotExpr.newTree(serverSym, p.name)) let params = p.findChild(it.kind == nnkFormalParams).getParams() for param in params: @@ -43,19 +43,29 @@ proc procWrapper(requestSym, p: NimNode): NimNode = result["result"] = % await `methodCall` ) -proc dispatch(procs: seq[NimNode], methodSym, requestSym: NimNode): NimNode = +proc dispatch(procs: seq[NimNode], serverSym, methodSym, requestSym: NimNode): NimNode = # Create the case statement used to dispatch proc result = nnkCaseStmt.newTree(methodSym) for p in procs: # Add the branch that dispatches the proc - let wrapper = procWrapper(requestSym, p) + let wrapper = procWrapper(serverSym, requestSym, p) result.add( nnkOfBranch.newTree( dispatchName(p), wrapper )) +proc localProc*(p: NimNode, injections: seq[Table[string, NimNode]]): NimNode = + result = copy(p) + var params = result.findChild(it.kind == nnkFormalParams) + for injection in injections: + params.add(nnkIdentDefs.newTree( + injection["ident"], + injection["type"], + newEmptyNode() + )) + proc serverDispatch*(name: string, procs: seq[NimNode]): NimNode = let enumSym = genSym(nskType) # Type name the enum used to dispatch procs @@ -66,7 +76,7 @@ proc serverDispatch*(name: string, procs: seq[NimNode]): NimNode = routerSym = rpcRouterProcName(name) routerName = routerSym.strVal() - let dispatchStatement = dispatch(procs, methodSym, requestSym) + let dispatchStatement = dispatch(procs, serverSym, methodSym, requestSym) let enumDeclaration = enumDeclaration(enumSym, procs) result = newStmtList() diff --git a/src/nerve/service.nim b/src/nerve/service.nim index 247ccd7..164e37f 100644 --- a/src/nerve/service.nim +++ b/src/nerve/service.nim @@ -1,4 +1,4 @@ -import macros, tables +import macros, tables, sequtils import types, common, server, client, factories @@ -12,6 +12,18 @@ proc toStmtList(nodes: seq[NimNode]): NimNode = for node in nodes: result.add(node) +proc serverInjection(node: NimNode): seq[Table[string, NimNode]] = + let injectStmt = node.findChild(it.kind == nnkCall and it[0] == ident("inject") and it[1].kind == nnkStmtList) + if injectStmt.kind != nnkNilLit: + for child in injectStmt[1]: + if child.kind == nnkVarSection: + for declaration in child: + result.add({ + "ident": declaration[0], + "type": paramType(declaration), + "default": declaration[2] + }.toTable) + proc procDefs(node: NimNode): seq[NimNode] = # Gets all the proc definitions from the statement list for child in node: @@ -35,17 +47,18 @@ proc compiletimeReference(name: NimNode): NimNode = proc rpcService*(name: NimNode, uri: string, body: NimNode): NimNode = let procs = procDefs(body) let nameStr = name.strVal() + let injections = serverInjection(body) let serviceType = rpcServiceName(nameStr) result = newStmtList() result.add(serviceImports()) if not defined(js): - result.add(procs.toStmtList()) + result.add(procs.mapIt(localProc(it, injections)).toStmtList()) result.add(networkProcs(procs)) result.add(rpcServiceType(nameStr, procs)) result.add(rpcUriConst(nameStr, uri)) if not defined(js): result.add(serverDispatch(nameStr, procs)) - result.add(rpcServerFactory(nameStr, serviceType, procs)) + result.add(rpcServerFactory(nameStr, serviceType, procs, injections)) result.add(rpcClientFactory(nameStr, serviceType, procs)) result.add(compiletimeReference(name)) if defined(nerveRpcDebug): diff --git a/src/nerve/types.nim b/src/nerve/types.nim index e63e555..7bf049d 100644 --- a/src/nerve/types.nim +++ b/src/nerve/types.nim @@ -8,4 +8,4 @@ type RpcServiceInst* = object of RootObj type RpcService* = distinct string proc `$`*(s: RpcService): string {.borrow.} -type NerveDriver* = proc (req: JsObject): Future[JsObject] +type NerveDriver* = proc (req: JsObject): Future[JsObject] {.gcsafe.} diff --git a/tests/greetingService.nim b/tests/greetingService.nim index fbfb67d..4bd6737 100644 --- a/tests/greetingService.nim +++ b/tests/greetingService.nim @@ -2,5 +2,12 @@ import nerve, nerve/web service GreetingService, "/api/greeting": + inject: + var + id = 100 + count: int + var uuid = "asdf" + proc greet(greeting = wstring("Hello"), name = wstring("World")): Future[wstring] = + echo uuid fwrap(greeting & " " & name) diff --git a/tests/server.nim b/tests/server.nim index ebc4255..5143068 100644 --- a/tests/server.nim +++ b/tests/server.nim @@ -6,7 +6,7 @@ let server = newAsyncHttpServer() proc generateCb(): proc (req: Request): Future[void] {.gcsafe.} = let personServer = PersonService.newServer() - let greetingServer = GreetingService.newServer() + let greetingServer = GreetingService.newServer(count = 1) let fileServer = FileService.newServer() proc cb(req: Request) {.async, gcsafe.} = From 202cb6c2e41b1d5f87365ed9bbf864a04611b854 Mon Sep 17 00:00:00 2001 From: nepeckman Date: Thu, 25 Jul 2019 17:25:53 -0400 Subject: [PATCH 21/22] Prep for 0.2.0 release --- README.md | 121 +++++++++++++++++++++++--------------- nerve.nimble | 6 +- src/nerve/drivers.nim | 2 +- tests/greetingService.nim | 1 - 4 files changed, 77 insertions(+), 53 deletions(-) diff --git a/README.md b/README.md index e660d68..9b03e12 100644 --- a/README.md +++ b/README.md @@ -17,10 +17,9 @@ Nerve is available on Nim's builtin package manager, [nimble](https://github.com # Hello World ```nim # api.nim -import nerve, nerve/utils +import nerve, nerve/web -rpc Hello, "/api/hello": # The identifier for the rpc object - # As well as the url the service will use +service HelloService, "/api/hello": # Normal Nim proc definition proc helloWorld(): Future[wstring] = # Return type must be a future @@ -31,74 +30,102 @@ rpc Hello, "/api/hello": # The identifier for the rpc object result = fwrap(greeting & " " & name) # Utility function for declaring and completing a future # server.nim -import asynchttpserver, asyncdispatch, json -import nerve/utils, api - -var server = newAsyncHttpServer() -proc cb(req: Request) {.async.} = - case req.url.path - of "/": # Send index html - await req.respond(Http200, """Testing""") - of "/client.js": # Send client file (make sure to compile it first) - await req.respond(Http200, readFile("client.js")) - of Hello.rpcUri: # Const string provided to rpc macro - await req.respond(Http200, $ await Hello.routeRpc(req.body)) # Do the RPC dispatch and return the response - else: # Not found - await req.respond(Http404, "Not found") - -waitFor server.serve(Port(8080), cb) +import asynchttpserver, asyncdispatch, nerve, nerve/web +import api + +let server = newAsyncHttpServer() + +proc generateCb(): proc (req: Request): Future[void] {.gcsafe.} = + # Generate server callback in a function to avoid making the rpc server global + # A threadlocal var could be used instead, or a manual gcsafe annotation + + let helloServer = HelloService.newServer() + + proc cb(req: Request) {.async, gcsafe.} = + case req.url.path + of HelloService.rpcUri: # Const string provided to service + # Do the rpc dispatch for the service, with the given server + await req.respond(Http200, $ await HelloService.routeRpc(helloServer, req.body)) + of "/client.js": # Send client file (make sure to compile it first) + let headers = newHttpHeaders() + headers["Content-Type"] = "application/javascript" + await req.respond(Http200, readFile("client.js"), headers) + of "/": # Send index html + await req.respond(Http200, """Testing""") + else: # Not found + await req.respond(Http404, "Not Found") + + result = cb + +waitFor server.serve(Port(1234), generateCb()) # client.nim -import asyncjs +import nerve, nerve/promises import api +# This file can be compiled for native or JS targets + +const host = if defined(js): "" else: "http://127.0.0.1:1234" proc main() {.async.} = - echo await Hello.greet("Hello", "Nerve") # prints "Hello Nerve" to the console + let helloClient = HelloService.newHttpClient(host) + + echo await helloClient.greet("Hello", "Nerve") # prints Hello World -discard main() +when defined(js): + discard main() +else: + waitFor main() ``` -### `rpc` macro +### `service` macro ```nim -macro rpc(name, uri, body: untyped): untyped +macro service*(name: untyped, uri: untyped = nil, body: untyped = nil): untyped ``` -Nerve's `rpc` macro contains most of the functionality of the framework. It takes an identifier, a uri, and a list of normal Nim procedures as its body. It produces an object (accessible via the identifier) that has fields for each of the given procedures. The object's type is generated with it, but it extends the `RpcServer` type provided by Nerve. When compiled for Nim's native target, the macro also generates a dispatch function. When compiled for Nim's JS target, the macro modifies the body of each provided procedure, replacing the server code with the code necessary to create a request, send it, and return the response. The provided procedures must have a return type of `Future[T]`, as the client will always use these functions asynchronusly. +Nerve's `service` macro contains most of the functionality of the framework. It takes an identifier, an optional uri, and a list of normal Nim procedures as its body. It produces a RpcService (accessible via the identifier) that can be instantiated into either a client or a server object with fields for each of the provided procs. The client/server object's type is generated with it, but it extends the `RpcServerInst` type provided by Nerve. The macro generates functions to construct new clients and servers, accessible with the service identified. When compiled for Nim's native target, the macro also generates a dispatch function. The clients (available for both native and JS targets) are provided with a driver to handle constructing and sending the requests. The provided procedures must have a return type of `Future[T]`, as the client will always use these functions asynchronusly. -As the files with the `rpc` macro need to be compiled for both native and JS targets, those files should focus _only_ on the API functionality. Server instantiation and heavier server logic should go elsewhere. Be aware that any types used by the API files also need to be accessible on both targets. +As the files with the `service` macro need to be compiled for both native and JS targets, those files should focus _only_ on the API functionality. Server instantiation and heavier server logic should go elsewhere. Be aware that any types used by the API files also need to be accessible on both targets. -# Utilities -Nerve provides some utility functions, located in `nerve/utils`. These utilities are provided to make +# nerve/drivers +`type NerveDriver* = proc (req: JsObject): Future[JsObject] {.gcsafe.}` +As stated earlier, Nerve uses drivers to power its clients. The driver recieves a completed JSON RPC object, and is responsible for sending that to the server and returning the response. The `drivers` module provides common drivers (such as an http driver), but user defined drivers can be used as well. + +# nerve/web +Nerve provides a web module to ease some web compatibility issues. The `web` module provides some function and type aliases to allow the same code to be compiled for JS and native targets. It also provides: ### `wstring` -The implementaion of `wstring` (web string) is dependant on the compile target. On the native target, `wstring` is an alias for native Nim string. On the client, it is an alias for JavaScript strings (`cstring` type in Nim). This target dependant alias is needed for full stack Nim code. On the server, Nim's native string serializes to a JavaScript string when the response is serialized to JSON. As the client is receiving this JavaScript string, it must be told to expect a JavaScript string. `wstring` *must* be used instead of `string` in any type or function exposed to both the server and the client. +The implementaion of `wstring` (web string) is dependant on the compile target. On the native target, `wstring` is an alias for native Nim string. On the client, it is an alias for JavaScript strings (`cstring` type in Nim). This target dependant alias is needed for full stack Nim code. On the server, Nim's native string serializes to a JavaScript string when the response is serialized to JSON. As the client is receiving this JavaScript string, it must be told to expect a JavaScript string. For any server expecting JS clients, `wstring` *must* be used instead of `string` in any type or function exposed to both the server and the client. -### `rpcUri` -```nim -macro rpcUri(rpc: RpcServer): untyped -``` -This macro takes an RPC object and returns the constant uri string. This is a macro rather than a normal procedure so the constant string can be used in `case` branches. +# nerve/promises +The `promsies` module is an extension of the asyncdispatch module for native targets, and the asyncjs module for the JS target. It exports both of those modules, providing all of the typical async functionality of both targets. It also provides some helper functions for dealing with futures, including future chaining with `then`. -### `routeRpc` +# Errors +Errors in RPC calls are propogated to the client. The client code will throw an `RpcError` with information from the error thrown on the server. If the server responds with a non-200 error code, the client throws an `InvalidResponseError`. The server throws errors for incorrect requests, per the JSON-RPC spec. + +# Server Injection (experimental) +All of the parameters for the RPC procedures must come from the client. However, Nerve provides a method for injecting variables from the server (such as client connection references, or anything that doesn't serialize well). To define variables for injection, place an `inject` statement in the service declaration. In the inject statement, include `var` definitions for the desired variables. These variable can then be used in any of the RPC procs. The actual injection is done in the `newServer` constructor, where the injected variables are provided to the server. ```nim -macro routeRpc*(rpc: RpcServer, req: JsonNode): untyped +service GreetingService, "/api/greeting": -macro routeRpc*(rpc: RpcServer, req: string): untyped -``` -This macro takes an RPC object and an RPC request, either a string or JSON. The client sends requests over HTTP, as the body of a post request. The client should always send valid JSON and a valid request. The macro inserts a link to a generated dispatch function from the `rpc` macro. + inject: + var + id = 100 + count: int + var uuid = "asdf" -### `fwrap` -```nim -proc fwrap*[T](it: T): Future[T] -``` -A simple proc for wrapping a future, added to assist with future returns from RPC procs. + proc greet(greeting = wstring("Hello"), name = wstring("World")): Future[wstring] = + echo uuid + fwrap(greeting & " " & name) -# Errors -Errors in RPC calls are propogated to the client. The client code will throw an `RpcError` with information from the error thrown on the server. If the server responds with a non-200 error code, the client throws an `InvalidResponseError`. The server throws errors for incorrect requests, per the JSON-RPC spec. +let server = GreetingService.newServer(count = 1, uuid = "fdsa") +``` # Gotchas Nerve trys to be as low friction as possible. However there are a couple edges to watch for. 1) Usages of Nim strings. As stated earlier, Nim strings don't serialize well, and wstrings need to be used for any type compiled under both native and js targets. 2) Procedures under the same RPC server must have different names. +3) Errors for the server injection might reference generated procedures. # Roadmap -Nerve was written primarily for my use, as a way to speed up the process of writing web APIs. It has the majority of features I set out to include, but I'm open to new features (or pull requests) if anyone else finds this project useful. +1) Configuration macros. Inform Nerve if it should generate a server, client, or both. +2) A `whenServer` statement to allow situational evaluation for servers. +3) Implement servers for the JS client. diff --git a/nerve.nimble b/nerve.nimble index 13853fb..a4182fd 100644 --- a/nerve.nimble +++ b/nerve.nimble @@ -1,8 +1,8 @@ # Package -version = "0.1.0" +version = "0.2.0" author = "nepeckman" -description = "A new awesome nimble package" +description = "An RPC framework" license = "MIT" srcDir = "src" @@ -12,8 +12,6 @@ srcDir = "src" requires "nim >= 0.19.6" -requires "httpbeast 0.2.1" - task itests, "Runs intergration tests": exec "nimble js tests/client.nim" exec "nimble c -r tests/server.nim" diff --git a/src/nerve/drivers.nim b/src/nerve/drivers.nim index 16a7f0f..8cef9b6 100644 --- a/src/nerve/drivers.nim +++ b/src/nerve/drivers.nim @@ -15,6 +15,6 @@ else: result = proc (req: JsObject): Future[JsObject] = let msg = newJsObject() msg["method"] = cstring"POST" - msg["body"] = JSON.stringify(req) + msg["body"] = $ req result = fetch(cstring(uri), msg) .then(respToJson) diff --git a/tests/greetingService.nim b/tests/greetingService.nim index 4bd6737..8f5f275 100644 --- a/tests/greetingService.nim +++ b/tests/greetingService.nim @@ -9,5 +9,4 @@ service GreetingService, "/api/greeting": var uuid = "asdf" proc greet(greeting = wstring("Hello"), name = wstring("World")): Future[wstring] = - echo uuid fwrap(greeting & " " & name) From bde409a90f65429b303e97e5b03a0e32d4d4c001 Mon Sep 17 00:00:00 2001 From: nepeckman Date: Thu, 25 Jul 2019 17:34:30 -0400 Subject: [PATCH 22/22] Added main module documentation --- src/nerve.nim | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/src/nerve.nim b/src/nerve.nim index 29c2f23..be2254f 100644 --- a/src/nerve.nim +++ b/src/nerve.nim @@ -2,12 +2,17 @@ import macros import nerve/service, nerve/types, nerve/common, nerve/web, nerve/drivers 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 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: @@ -16,34 +21,45 @@ macro newServer*(rpc: static[RpcService], injections: varargs[untyped]): untyped result.add(injection) 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 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)) 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: