mirror of
https://github.com/logos-storage/nim-json-rpc.git
synced 2026-01-03 14:13:08 +00:00
157 lines
4.6 KiB
Nim
157 lines
4.6 KiB
Nim
# json-rpc
|
|
# Copyright (c) 2024 Status Research & Development GmbH
|
|
# Licensed under either of
|
|
# * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE))
|
|
# * MIT license ([LICENSE-MIT](LICENSE-MIT))
|
|
# at your option.
|
|
# This file may not be copied, modified, or distributed except according to
|
|
# those terms.
|
|
|
|
import
|
|
macros,
|
|
./shared_wrapper,
|
|
./jrpc_sys
|
|
|
|
{.push gcsafe, raises: [].}
|
|
|
|
proc createRpcProc(procName, parameters, callBody: NimNode): NimNode =
|
|
# parameters come as a tree
|
|
var paramList = newSeq[NimNode]()
|
|
for p in parameters: paramList.add(p)
|
|
|
|
let body = quote do:
|
|
{.gcsafe.}:
|
|
`callBody`
|
|
|
|
# build proc
|
|
result = newProc(procName, paramList, body)
|
|
|
|
# make proc async
|
|
result.addPragma ident"async"
|
|
result.addPragma ident"gcsafe"
|
|
# export this proc
|
|
result[0] = nnkPostfix.newTree(ident"*", newIdentNode($procName))
|
|
|
|
proc createBatchCallProc(procName, parameters, callBody: NimNode): NimNode =
|
|
# parameters come as a tree
|
|
var paramList = newSeq[NimNode]()
|
|
for p in parameters: paramList.add(p)
|
|
|
|
# build proc
|
|
result = newProc(procName, paramList, callBody)
|
|
|
|
# export this proc
|
|
result[0] = nnkPostfix.newTree(ident"*", newIdentNode($procName))
|
|
|
|
proc setupConversion(reqParams, params: NimNode): NimNode =
|
|
# populate json params
|
|
# even rpcs with no parameters have an empty json array node sent
|
|
|
|
params.expectKind nnkFormalParams
|
|
result = newStmtList()
|
|
result.add quote do:
|
|
var `reqParams` = RequestParamsTx(kind: rpPositional)
|
|
|
|
for parName, parType in paramsIter(params):
|
|
result.add quote do:
|
|
`reqParams`.positional.add encode(JrpcConv, `parName`).JsonString
|
|
|
|
proc createRpcFromSig*(clientType, rpcDecl: NimNode, alias = NimNode(nil)): NimNode =
|
|
## This procedure will generate something like this:
|
|
## - Currently it always send positional parameters to the server
|
|
##
|
|
## proc rpcApi(client: RpcClient; paramA: TypeA; paramB: TypeB): Future[RetType] =
|
|
## {.gcsafe.}:
|
|
## var reqParams = RequestParamsTx(kind: rpPositional)
|
|
## reqParams.positional.add encode(JrpcConv, paramA).JsonString
|
|
## reqParams.positional.add encode(JrpcConv, paramB).JsonString
|
|
## let res = await client.call("rpcApi", reqParams)
|
|
## result = decode(JrpcConv, res.string, typeof RetType)
|
|
|
|
# Each input parameter in the rpc signature is converted
|
|
# to json using JrpcConv.encode.
|
|
# Return types are then converted back to native Nim types.
|
|
|
|
let
|
|
params = rpcDecl.findChild(it.kind == nnkFormalParams).ensureReturnType
|
|
procName = if alias.isNil: rpcDecl.name else: alias
|
|
pathStr = $rpcDecl.name
|
|
returnType = params[0]
|
|
reqParams = ident "reqParams"
|
|
setup = setupConversion(reqParams, params)
|
|
clientIdent = ident"client"
|
|
# temporary variable to hold `Response` from rpc call
|
|
rpcResult = ident "res"
|
|
# proc return variable
|
|
procRes = ident"result"
|
|
doDecode = quote do:
|
|
`procRes` = decode(JrpcConv, `rpcResult`.string, typeof `returnType`)
|
|
maybeWrap =
|
|
if returnType.noWrap: quote do:
|
|
`procRes` = `rpcResult`
|
|
else: doDecode
|
|
|
|
batchParams = params.copy
|
|
batchIdent = ident "batch"
|
|
|
|
# insert rpc client as first parameter
|
|
params.insert(1, nnkIdentDefs.newTree(
|
|
clientIdent,
|
|
ident($clientType),
|
|
newEmptyNode()
|
|
))
|
|
|
|
# convert return type to Future
|
|
params[0] = nnkBracketExpr.newTree(ident"Future", returnType)
|
|
|
|
# perform rpc call
|
|
let callBody = quote do:
|
|
# populate request params
|
|
`setup`
|
|
|
|
# `rpcResult` is of type `JsonString`
|
|
let `rpcResult` = await `clientIdent`.call(`pathStr`, `reqParams`)
|
|
`maybeWrap`
|
|
|
|
|
|
# insert RpcBatchCallRef as first parameter
|
|
batchParams.insert(1, nnkIdentDefs.newTree(
|
|
batchIdent,
|
|
ident "RpcBatchCallRef",
|
|
newEmptyNode()
|
|
))
|
|
|
|
# remove return type
|
|
batchParams[0] = newEmptyNode()
|
|
|
|
let batchCallBody = quote do:
|
|
`setup`
|
|
`batchIdent`.batch.add RpcBatchItem(
|
|
meth: `pathStr`,
|
|
params: `reqParams`
|
|
)
|
|
|
|
# create rpc proc
|
|
result = newStmtList()
|
|
result.add createRpcProc(procName, params, callBody)
|
|
result.add createBatchCallProc(procName, batchParams, batchCallBody)
|
|
|
|
when defined(nimDumpRpcs):
|
|
echo pathStr, ":\n", result.repr
|
|
|
|
proc processRpcSigs*(clientType, parsedCode: NimNode): NimNode =
|
|
result = newStmtList()
|
|
|
|
for line in parsedCode:
|
|
if line.kind == nnkProcDef:
|
|
var procDef = createRpcFromSig(clientType, line)
|
|
result.add(procDef)
|
|
|
|
proc cresteSignaturesFromString*(clientType: NimNode, sigStrings: string): NimNode =
|
|
try:
|
|
result = processRpcSigs(clientType, sigStrings.parseStmt())
|
|
except ValueError as exc:
|
|
doAssert(false, exc.msg)
|
|
|
|
{.pop.}
|