nim-json-rpc/json_rpc/client.nim
andri lim 26a6cb1790
Client also handle error message if id is null (#196)
* Client also handle error message if id is null

* Reduce compiler warnings in testethcalls

* Fix gcsafe error
2024-01-21 12:06:18 +07:00

154 lines
5.2 KiB
Nim

# json-rpc
# Copyright (c) 2019-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
std/[json, tables, macros],
chronos,
results,
./jsonmarshal,
./private/jrpc_sys,
./private/client_handler_wrapper,
./private/shared_wrapper,
./errors
from strutils import replace
export
chronos,
tables,
jsonmarshal,
RequestParamsTx,
results
type
RpcClient* = ref object of RootRef
awaiting*: Table[RequestId, Future[JsonString]]
lastId: int
onDisconnect*: proc() {.gcsafe, raises: [].}
onProcessMessage*: proc(client: RpcClient, line: string):
Result[bool, string] {.gcsafe, raises: [].}
GetJsonRpcRequestHeaders* = proc(): seq[(string, string)] {.gcsafe, raises: [].}
{.push gcsafe, raises: [].}
# ------------------------------------------------------------------------------
# Public helpers
# ------------------------------------------------------------------------------
func requestTxEncode*(name: string, params: RequestParamsTx, id: RequestId): string =
let req = requestTx(name, params, id)
JrpcSys.encode(req)
# ------------------------------------------------------------------------------
# Public functions
# ------------------------------------------------------------------------------
proc getNextId*(client: RpcClient): RequestId =
client.lastId += 1
RequestId(kind: riNumber, num: client.lastId)
method call*(client: RpcClient, name: string,
params: RequestParamsTx): Future[JsonString]
{.base, gcsafe, async.} =
doAssert(false, "`RpcClient.call` not implemented")
method call*(client: RpcClient, name: string,
params: JsonNode): Future[JsonString]
{.base, gcsafe, async.} =
await client.call(name, params.paramsTx)
method close*(client: RpcClient): Future[void] {.base, gcsafe, async.} =
doAssert(false, "`RpcClient.close` not implemented")
proc processMessage*(client: RpcClient, line: string): Result[void, string] =
if client.onProcessMessage.isNil.not:
let fallBack = client.onProcessMessage(client, line).valueOr:
return err(error)
if not fallBack:
return ok()
# Note: this doesn't use any transport code so doesn't need to be
# differentiated.
try:
let response = JrpcSys.decode(line, ResponseRx)
if response.jsonrpc.isNone:
return err("missing or invalid `jsonrpc`")
if response.id.isNone:
if response.error.isSome:
let error = JrpcSys.encode(response.error.get)
return err(error)
else:
return err("missing or invalid response id")
var requestFut: Future[JsonString]
let id = response.id.get
if not client.awaiting.pop(id, requestFut):
let msg = "Cannot find message id \"" & $id & "\":"
requestFut.fail(newException(JsonRpcError, msg))
return ok()
if response.error.isSome:
let error = JrpcSys.encode(response.error.get)
requestFut.fail(newException(JsonRpcError, error))
return ok()
# Up to this point, the result should contains something
if response.result.string.len == 0:
let msg = "missing or invalid response result"
requestFut.fail(newException(JsonRpcError, msg))
return ok()
requestFut.complete(response.result)
return ok()
except CatchableError as exc:
return err(exc.msg)
# ------------------------------------------------------------------------------
# Signature processing
# ------------------------------------------------------------------------------
macro createRpcSigs*(clientType: untyped, filePath: static[string]): untyped =
## Takes a file of forward declarations in Nim and builds them into RPC
## calls, based on their parameters.
## Inputs are marshalled to json, and results are put into the signature's
## Nim type.
cresteSignaturesFromString(clientType, staticRead($filePath.replace('\\', '/')))
macro createRpcSigsFromString*(clientType: untyped, sigString: static[string]): untyped =
## Takes a string of forward declarations in Nim and builds them into RPC
## calls, based on their parameters.
## Inputs are marshalled to json, and results are put into the signature's
## Nim type.
cresteSignaturesFromString(clientType, sigString)
macro createSingleRpcSig*(clientType: untyped, alias: static[string], procDecl: untyped): untyped =
## Takes a single forward declarations in Nim and builds them into RPC
## calls, based on their parameters.
## Inputs are marshalled to json, and results are put into the signature's
## Nim type.
doAssert procDecl.len == 1, "Only accept single proc definition"
let procDecl = procDecl[0]
procDecl.expectKind nnkProcDef
result = createRpcFromSig(clientType, procDecl, ident(alias))
macro createRpcSigsFromNim*(clientType: untyped, procList: untyped): untyped =
## Takes a list of forward declarations in Nim and builds them into RPC
## calls, based on their parameters.
## Inputs are marshalled to json, and results are put into the signature's
## Nim type.
processRpcSigs(clientType, procList)
{.pop.}