mirror of
https://github.com/logos-storage/nim-json-rpc.git
synced 2026-01-03 22:23:09 +00:00
* Client also handle error message if id is null * Reduce compiler warnings in testethcalls * Fix gcsafe error
154 lines
5.2 KiB
Nim
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.}
|
|
|