nim-json-rpc/json_rpc/client.nim

154 lines
5.2 KiB
Nim
Raw Normal View History

2023-12-14 08:34:13 +07:00
# json-rpc
# Copyright (c) 2019-2024 Status Research & Development GmbH
2023-12-14 08:34:13 +07:00
# 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
2018-06-14 16:52:41 +01:00
type
2018-07-14 10:51:54 +03:00
RpcClient* = ref object of RootRef
2024-01-04 07:49:19 +07:00
awaiting*: Table[RequestId, Future[JsonString]]
lastId: int
onDisconnect*: proc() {.gcsafe, raises: [].}
2024-01-07 13:58:35 +07:00
onProcessMessage*: proc(client: RpcClient, line: string):
Result[bool, string] {.gcsafe, raises: [].}
GetJsonRpcRequestHeaders* = proc(): seq[(string, string)] {.gcsafe, raises: [].}
{.push gcsafe, raises: [].}
# ------------------------------------------------------------------------------
# Public helpers
# ------------------------------------------------------------------------------
2018-06-22 19:05:32 +01:00
func requestTxEncode*(name: string, params: RequestParamsTx, id: RequestId): string =
let req = requestTx(name, params, id)
JrpcSys.encode(req)
2018-06-14 16:52:41 +01:00
# ------------------------------------------------------------------------------
# Public functions
# ------------------------------------------------------------------------------
2022-03-04 19:13:29 +00:00
proc getNextId*(client: RpcClient): RequestId =
client.lastId += 1
RequestId(kind: riNumber, num: client.lastId)
2018-06-22 19:05:32 +01:00
method call*(client: RpcClient, name: string,
2024-01-04 07:49:19 +07:00
params: RequestParamsTx): Future[JsonString]
{.base, gcsafe, async.} =
doAssert(false, "`RpcClient.call` not implemented")
2018-06-14 16:52:41 +01:00
method call*(client: RpcClient, name: string,
2024-01-04 07:49:19 +07:00
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] =
2024-01-07 13:58:35 +07:00
if client.onProcessMessage.isNil.not:
let fallBack = client.onProcessMessage(client, line).valueOr:
return err(error)
if not fallBack:
return ok()
2018-07-14 10:51:54 +03:00
# Note: this doesn't use any transport code so doesn't need to be
# differentiated.
try:
let response = JrpcSys.decode(line, ResponseRx)
2018-06-14 16:52:41 +01:00
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")
2018-06-14 16:52:41 +01:00
2024-01-04 07:49:19 +07:00
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 =
2018-06-14 16:52:41 +01:00
## 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.}