Allow dynamic RPC method names in the 'rpc' macro

This commit is contained in:
Zahary Karadjov 2021-11-24 14:41:50 +02:00 committed by zah
parent c455198d4f
commit 7ff4559cc0
4 changed files with 71 additions and 68 deletions

View File

@ -141,14 +141,8 @@ macro rpc*(server: RpcRouter, path: string, body: untyped): untyped =
parameters = body.findChild(it.kind == nnkFormalParams) parameters = body.findChild(it.kind == nnkFormalParams)
# all remote calls have a single parameter: `params: JsonNode` # all remote calls have a single parameter: `params: JsonNode`
paramsIdent = newIdentNode"params" paramsIdent = newIdentNode"params"
# procs are generated from the stripped path rpcProcImpl = genSym(nskProc)
pathStr = $path rpcProcWrapper = genSym(nskProc)
# strip non alphanumeric
procNameStr = pathStr.makeProcName
# public rpc proc
procName = newIdentNode(procNameStr)
# when parameters present: proc that contains our rpc body
doMain = newIdentNode(procNameStr & "DoMain")
var var
setup = jsonToNim(parameters, paramsIdent) setup = jsonToNim(parameters, paramsIdent)
procBody = if body.kind == nnkStmtList: body else: body.body procBody = if body.kind == nnkStmtList: body else: body.body
@ -158,26 +152,26 @@ macro rpc*(server: RpcRouter, path: string, body: untyped): untyped =
# delegate async proc allows return and setting of result as native type # delegate async proc allows return and setting of result as native type
result.add quote do: result.add quote do:
proc `doMain`(`paramsIdent`: JsonNode): Future[`ReturnType`] {.async.} = proc `rpcProcImpl`(`paramsIdent`: JsonNode): Future[`ReturnType`] {.async.} =
`setup` `setup`
`procBody` `procBody`
if ReturnType == ident"JsonNode": if ReturnType == ident"JsonNode":
# `JsonNode` results don't need conversion # `JsonNode` results don't need conversion
result.add quote do: result.add quote do:
proc `procName`(`paramsIdent`: JsonNode): Future[StringOfJson] {.async, gcsafe.} = proc `rpcProcWrapper`(`paramsIdent`: JsonNode): Future[StringOfJson] {.async, gcsafe.} =
return StringOfJson($(await `doMain`(`paramsIdent`))) return StringOfJson($(await `rpcProcImpl`(`paramsIdent`)))
elif ReturnType == ident"StringOfJson": elif ReturnType == ident"StringOfJson":
result.add quote do: result.add quote do:
proc `procName`(`paramsIdent`: JsonNode): Future[StringOfJson] {.async, gcsafe.} = proc `rpcProcWrapper`(`paramsIdent`: JsonNode): Future[StringOfJson] {.async, gcsafe.} =
return await `doMain`(`paramsIdent`) return await `rpcProcImpl`(`paramsIdent`)
else: else:
result.add quote do: result.add quote do:
proc `procName`(`paramsIdent`: JsonNode): Future[StringOfJson] {.async, gcsafe.} = proc `rpcProcWrapper`(`paramsIdent`: JsonNode): Future[StringOfJson] {.async, gcsafe.} =
return StringOfJson($(%(await `doMain`(`paramsIdent`)))) return StringOfJson($(%(await `rpcProcImpl`(`paramsIdent`))))
result.add quote do: result.add quote do:
`server`.register(`path`, `procName`) `server`.register(`path`, `rpcProcWrapper`)
when defined(nimDumpRpcs): when defined(nimDumpRpcs):
echo "\n", pathStr, ": ", result.repr echo "\n", pathStr, ": ", result.repr

View File

@ -1,4 +1,5 @@
import import
std/tables,
chronos, chronos,
./router, ./router,
./jsonmarshal ./jsonmarshal
@ -20,6 +21,11 @@ template rpc*(server: RpcServer, path: string, body: untyped): untyped =
template hasMethod*(server: RpcServer, methodName: string): bool = template hasMethod*(server: RpcServer, methodName: string): bool =
server.router.hasMethod(methodName) server.router.hasMethod(methodName)
proc executeMethod*(server: RpcServer,
methodName: string,
args: JsonNode): Future[StringOfJson] =
server.router.procs[methodName](args)
# Wrapper for message processing # Wrapper for message processing
proc route*(server: RpcServer, line: string): Future[string] {.gcsafe.} = proc route*(server: RpcServer, line: string): Future[string] {.gcsafe.} =

View File

@ -17,13 +17,16 @@ server.addEthRpcs()
## Generate client convenience marshalling wrappers from forward declarations ## Generate client convenience marshalling wrappers from forward declarations
createRpcSigs(RpcSocketClient, sourceDir & DirSep & "ethcallsigs.nim") createRpcSigs(RpcSocketClient, sourceDir & DirSep & "ethcallsigs.nim")
func rpcDynamicName(name: string): string =
"rpc." & name
## Create custom RPC with StUint input parameter ## Create custom RPC with StUint input parameter
server.rpc("rpc.uint256param") do(i: UInt256): server.rpc(rpcDynamicName "uint256Param") do(i: UInt256):
let r = i + 1.stUint(256) let r = i + 1.stUint(256)
return %r return %r
## Create custom RPC with StUInt return parameter ## Create custom RPC with StUInt return parameter
server.rpc("rpc.testreturnuint256") do() -> UInt256: server.rpc(rpcDynamicName "testReturnUint256") do() -> UInt256:
let r: UInt256 = "0x1234567890abcdef".parse(UInt256, 16) let r: UInt256 = "0x1234567890abcdef".parse(UInt256, 16)
return r return r
@ -31,15 +34,15 @@ proc testLocalCalls: Future[seq[StringOfJson]] =
## Call RPCs created with `rpc` locally. ## Call RPCs created with `rpc` locally.
## This simply demonstrates async calls of the procs generated by the `rpc` macro. ## This simply demonstrates async calls of the procs generated by the `rpc` macro.
let let
uint256Param = rpcUInt256Param(%[%"0x1234567890"]) uint256Param = server.executeMethod("rpc.uint256Param", %[%"0x1234567890"])
returnUint256 = rpcTestReturnUInt256(%[]) returnUint256 = server.executeMethod("rpc.testReturnUint256", %[])
return all(uint256Param, returnUint256) return all(uint256Param, returnUint256)
proc testRemoteUInt256: Future[seq[Response]] = proc testRemoteUInt256: Future[seq[Response]] =
## Call function remotely on server, testing `stint` types ## Call function remotely on server, testing `stint` types
let let
uint256Param = client.call("rpc.uint256param", %[%"0x1234567890"]) uint256Param = client.call("rpc.uint256Param", %[%"0x1234567890"])
returnUint256 = client.call("rpc.testreturnuint256", %[]) returnUint256 = client.call("rpc.testReturnUint256", %[])
return all(uint256Param, returnUint256) return all(uint256Param, returnUint256)
proc testSigCalls: Future[seq[string]] = proc testSigCalls: Future[seq[string]] =

View File

@ -37,37 +37,37 @@ let
var s = newRpcSocketServer(["localhost:8545"]) var s = newRpcSocketServer(["localhost:8545"])
# RPC definitions # RPC definitions
s.rpc("rpc.simplepath"): s.rpc("rpc.simplePath"):
return %1 return %1
s.rpc("rpc.differentparams") do(a: int, b: string): s.rpc("rpc.differentParams") do(a: int, b: string):
return %[%a, %b] return %[%a, %b]
s.rpc("rpc.arrayparam") do(arr: array[0..5, byte], b: string): s.rpc("rpc.arrayParam") do(arr: array[0..5, byte], b: string):
var res = %arr var res = %arr
res.add %b res.add %b
return %res return %res
s.rpc("rpc.seqparam") do(a: string, s: seq[int]): s.rpc("rpc.seqParam") do(a: string, s: seq[int]):
var res = newJArray() var res = newJArray()
res.add %a res.add %a
for item in s: for item in s:
res.add %int(item) res.add %int(item)
return res return res
s.rpc("rpc.objparam") do(a: string, obj: MyObject): s.rpc("rpc.objParam") do(a: string, obj: MyObject):
return %obj return %obj
s.rpc("rpc.returntypesimple") do(i: int) -> int: s.rpc("rpc.returnTypeSimple") do(i: int) -> int:
return i return i
s.rpc("rpc.returntypecomplex") do(i: int) -> Test2: s.rpc("rpc.returnTypeComplex") do(i: int) -> Test2:
return Test2(x: [1, i, 3], y: "test") return Test2(x: [1, i, 3], y: "test")
s.rpc("rpc.testreturns") do() -> int: s.rpc("rpc.testReturns") do() -> int:
return 1234 return 1234
s.rpc("rpc.multivarsofonetype") do(a, b: string) -> string: s.rpc("rpc.multiVarsOfOneType") do(a, b: string) -> string:
return a & " " & b return a & " " & b
s.rpc("rpc.optional") do(obj: MyOptional) -> MyOptional: s.rpc("rpc.optional") do(obj: MyOptional) -> MyOptional:
@ -122,102 +122,102 @@ s.rpc("rpc.optInObj") do(data: string, options: Option[MaybeOptions]) -> int:
# Tests # Tests
suite "Server types": suite "Server types":
test "On macro registration": test "On macro registration":
check s.hasMethod("rpc.simplepath") check s.hasMethod("rpc.simplePath")
check s.hasMethod("rpc.differentparams") check s.hasMethod("rpc.differentParams")
check s.hasMethod("rpc.arrayparam") check s.hasMethod("rpc.arrayParam")
check s.hasMethod("rpc.seqparam") check s.hasMethod("rpc.seqParam")
check s.hasMethod("rpc.objparam") check s.hasMethod("rpc.objParam")
check s.hasMethod("rpc.returntypesimple") check s.hasMethod("rpc.returnTypeSimple")
check s.hasMethod("rpc.returntypecomplex") check s.hasMethod("rpc.returnTypeComplex")
check s.hasMethod("rpc.testreturns") check s.hasMethod("rpc.testReturns")
check s.hasMethod("rpc.multivarsofonetype") check s.hasMethod("rpc.multiVarsOfOneType")
check s.hasMethod("rpc.optionalArg") check s.hasMethod("rpc.optionalArg")
check s.hasMethod("rpc.mixedOptionalArg") check s.hasMethod("rpc.mixedOptionalArg")
check s.hasMethod("rpc.optionalArgNotBuiltin") check s.hasMethod("rpc.optionalArgNotBuiltin")
check s.hasMethod("rpc.optInObj") check s.hasMethod("rpc.optInObj")
test "Simple paths": test "Simple paths":
let r = waitFor rpcSimplePath(%[]) let r = waitFor s.executeMethod("rpc.simplePath", %[])
check r == "1" check r == "1"
test "Different param types": test "Different param types":
let let
inp = %[%1, %"abc"] inp = %[%1, %"abc"]
r = waitFor rpcDifferentParams(inp) r = waitFor s.executeMethod("rpc.differentParams", inp)
check r == inp check r == inp
test "Array parameters": test "Array parameters":
let r1 = waitfor rpcArrayParam(%[%[1, 2, 3], %"hello"]) let r1 = waitfor s.executeMethod("rpc.arrayParam", %[%[1, 2, 3], %"hello"])
var ckR1 = %[1, 2, 3, 0, 0, 0] var ckR1 = %[1, 2, 3, 0, 0, 0]
ckR1.elems.add %"hello" ckR1.elems.add %"hello"
check r1 == ckR1 check r1 == ckR1
test "Seq parameters": test "Seq parameters":
let r2 = waitfor rpcSeqParam(%[%"abc", %[1, 2, 3, 4, 5]]) let r2 = waitfor s.executeMethod("rpc.seqParam", %[%"abc", %[1, 2, 3, 4, 5]])
var ckR2 = %["abc"] var ckR2 = %["abc"]
for i in 0..4: ckR2.add %(i + 1) for i in 0..4: ckR2.add %(i + 1)
check r2 == ckR2 check r2 == ckR2
test "Object parameters": test "Object parameters":
let r = waitfor rpcObjParam(%[%"abc", testObj]) let r = waitfor s.executeMethod("rpc.objParam", %[%"abc", testObj])
check r == testObj check r == testObj
test "Simple return types": test "Simple return types":
let let
inp = %99 inp = %99
r1 = waitfor rpcReturnTypeSimple(%[%inp]) r1 = waitfor s.executeMethod("rpc.returnTypeSimple", %[%inp])
check r1 == inp check r1 == inp
test "Complex return types": test "Complex return types":
let let
inp = 99 inp = 99
r1 = waitfor rpcReturnTypeComplex(%[%inp]) r1 = waitfor s.executeMethod("rpc.returnTypeComplex", %[%inp])
check r1 == %*{"x": %[1, inp, 3], "y": "test"} check r1 == %*{"x": %[1, inp, 3], "y": "test"}
test "Option types": test "Option types":
let let
inp1 = MyOptional(maybeInt: some(75)) inp1 = MyOptional(maybeInt: some(75))
inp2 = MyOptional() inp2 = MyOptional()
r1 = waitfor rpcOptional(%[%inp1]) r1 = waitfor s.executeMethod("rpc.optional", %[%inp1])
r2 = waitfor rpcOptional(%[%inp2]) r2 = waitfor s.executeMethod("rpc.optional", %[%inp2])
check r1 == %inp1 check r1 == %inp1
check r2 == %inp2 check r2 == %inp2
test "Return statement": test "Return statement":
let r = waitFor rpcTestReturns(%[]) let r = waitFor s.executeMethod("rpc.testReturns", %[])
check r == %1234 check r == %1234
test "Runtime errors": test "Runtime errors":
expect ValueError: expect ValueError:
# root param not array # root param not array
discard waitfor rpcArrayParam(%"test") discard waitfor s.executeMethod("rpc.arrayParam", %"test")
expect ValueError: expect ValueError:
# too big for array # too big for array
discard waitfor rpcArrayParam(%[%[0, 1, 2, 3, 4, 5, 6], %"hello"]) discard waitfor s.executeMethod("rpc.arrayParam", %[%[0, 1, 2, 3, 4, 5, 6], %"hello"])
expect ValueError: expect ValueError:
# wrong sub parameter type # wrong sub parameter type
discard waitfor rpcArrayParam(%[%"test", %"hello"]) discard waitfor s.executeMethod("rpc.arrayParam", %[%"test", %"hello"])
expect ValueError: expect ValueError:
# wrong param type # wrong param type
discard waitFor rpcDifferentParams(%[%"abc", %1]) discard waitFor s.executeMethod("rpc.differentParams", %[%"abc", %1])
test "Multiple variables of one type": test "Multiple variables of one type":
let r = waitfor rpcMultiVarsOfOneType(%[%"hello", %"world"]) let r = waitfor s.executeMethod("rpc.multiVarsOfOneType", %[%"hello", %"world"])
check r == %"hello world" check r == %"hello world"
test "Optional arg": test "Optional arg":
let let
int1 = MyOptional(maybeInt: some(75)) int1 = MyOptional(maybeInt: some(75))
int2 = MyOptional(maybeInt: some(117)) int2 = MyOptional(maybeInt: some(117))
r1 = waitFor rpcOptionalArg(%[%117, %int1]) r1 = waitFor s.executeMethod("rpc.optionalArg", %[%117, %int1])
r2 = waitFor rpcOptionalArg(%[%117]) r2 = waitFor s.executeMethod("rpc.optionalArg", %[%117])
check r1 == %int1 check r1 == %int1
check r2 == %int2 check r2 == %int2
test "Mixed optional arg": test "Mixed optional arg":
var ax = waitFor rpcMixedOptionalArg(%[%10, %11, %"hello", %12, %"world"]) var ax = waitFor s.executeMethod("rpc.mixedOptionalArg", %[%10, %11, %"hello", %12, %"world"])
check ax == %OptionalFields(a: 10, b: some(11), c: "hello", d: some(12), e: some("world")) check ax == %OptionalFields(a: 10, b: some(11), c: "hello", d: some(12), e: some("world"))
var bx = waitFor rpcMixedOptionalArg(%[%10, newJNull(), %"hello"]) var bx = waitFor s.executeMethod("rpc.mixedOptionalArg", %[%10, newJNull(), %"hello"])
check bx == %OptionalFields(a: 10, c: "hello") check bx == %OptionalFields(a: 10, c: "hello")
test "Non-built-in optional types": test "Non-built-in optional types":
@ -225,33 +225,33 @@ suite "Server types":
t2 = Test2(x: [1, 2, 3], y: "Hello") t2 = Test2(x: [1, 2, 3], y: "Hello")
testOpts1 = MyOptionalNotBuiltin(val: some(t2)) testOpts1 = MyOptionalNotBuiltin(val: some(t2))
testOpts2 = MyOptionalNotBuiltin() testOpts2 = MyOptionalNotBuiltin()
var r = waitFor rpcOptionalArgNotBuiltin(%[%testOpts1]) var r = waitFor s.executeMethod("rpc.optionalArgNotBuiltin", %[%testOpts1])
check r == %t2.y check r == %t2.y
var r2 = waitFor rpcOptionalArgNotBuiltin(%[]) var r2 = waitFor s.executeMethod("rpc.optionalArgNotBuiltin", %[])
check r2 == %"Empty1" check r2 == %"Empty1"
var r3 = waitFor rpcOptionalArgNotBuiltin(%[%testOpts2]) var r3 = waitFor s.executeMethod("rpc.optionalArgNotBuiltin", %[%testOpts2])
check r3 == %"Empty2" check r3 == %"Empty2"
test "Manually set up JSON for optionals": test "Manually set up JSON for optionals":
# Check manual set up json with optionals # Check manual set up json with optionals
let opts1 = parseJson("""{"o1": true}""") let opts1 = parseJson("""{"o1": true}""")
var r1 = waitFor rpcOptInObj(%[%"0x31ded", opts1]) var r1 = waitFor s.executeMethod("rpc.optInObj", %[%"0x31ded", opts1])
check r1 == %1 check r1 == %1
let opts2 = parseJson("""{"o2": true}""") let opts2 = parseJson("""{"o2": true}""")
var r2 = waitFor rpcOptInObj(%[%"0x31ded", opts2]) var r2 = waitFor s.executeMethod("rpc.optInObj", %[%"0x31ded", opts2])
check r2 == %2 check r2 == %2
let opts3 = parseJson("""{"o3": true}""") let opts3 = parseJson("""{"o3": true}""")
var r3 = waitFor rpcOptInObj(%[%"0x31ded", opts3]) var r3 = waitFor s.executeMethod("rpc.optInObj", %[%"0x31ded", opts3])
check r3 == %4 check r3 == %4
# Combinations # Combinations
let opts4 = parseJson("""{"o1": true, "o3": true}""") let opts4 = parseJson("""{"o1": true, "o3": true}""")
var r4 = waitFor rpcOptInObj(%[%"0x31ded", opts4]) var r4 = waitFor s.executeMethod("rpc.optInObj", %[%"0x31ded", opts4])
check r4 == %5 check r4 == %5
let opts5 = parseJson("""{"o2": true, "o3": true}""") let opts5 = parseJson("""{"o2": true, "o3": true}""")
var r5 = waitFor rpcOptInObj(%[%"0x31ded", opts5]) var r5 = waitFor s.executeMethod("rpc.optInObj", %[%"0x31ded", opts5])
check r5 == %6 check r5 == %6
let opts6 = parseJson("""{"o1": true, "o2": true}""") let opts6 = parseJson("""{"o1": true, "o2": true}""")
var r6 = waitFor rpcOptInObj(%[%"0x31ded", opts6]) var r6 = waitFor s.executeMethod("rpc.optInObj", %[%"0x31ded", opts6])
check r6 == %3 check r6 == %3
s.stop() s.stop()