mirror of
https://github.com/logos-storage/nim-json-rpc.git
synced 2026-02-20 21:53:10 +00:00
Use nim-json-serialization for RPCs (#172)
* Use nim-json-serialization for RPCs * Add distinct serializers for RPC types * Avoid ConvFromXtoItselfNotNeeded in testing * Remove redundent `==` template * Rename Eth1JsonRpc to JsonRpc * Fix generic instantiation clash --------- Co-authored-by: jangko <jangko128@gmail.com>
This commit is contained in:
parent
08d7fccfe2
commit
819b6fed37
@ -1,120 +1,10 @@
|
||||
import
|
||||
pkg/[chronos, chronos/apps/http/httptable, chronicles],
|
||||
stew/byteutils,
|
||||
./websocketclientimpl,
|
||||
../client
|
||||
|
||||
export client
|
||||
# this weird arrangement is to avoid clash
|
||||
# between Json.encode and Base64Pad.encode
|
||||
|
||||
{.push raises: [Defect].}
|
||||
|
||||
logScope:
|
||||
topics = "JSONRPC-WS-CLIENT"
|
||||
|
||||
import std/[uri, strutils]
|
||||
import pkg/websock/[websock, extensions/compression/deflate]
|
||||
|
||||
type
|
||||
RpcWebSocketClient* = ref object of RpcClient
|
||||
transport*: WSSession
|
||||
uri*: Uri
|
||||
loop*: Future[void]
|
||||
getHeaders*: GetJsonRpcRequestHeaders
|
||||
|
||||
proc new*(
|
||||
T: type RpcWebSocketClient, getHeaders: GetJsonRpcRequestHeaders = nil): T =
|
||||
T(getHeaders: getHeaders)
|
||||
|
||||
proc newRpcWebSocketClient*(
|
||||
getHeaders: GetJsonRpcRequestHeaders = nil): RpcWebSocketClient =
|
||||
## Creates a new client instance.
|
||||
RpcWebSocketClient.new(getHeaders)
|
||||
|
||||
method call*(self: RpcWebSocketClient, name: string,
|
||||
params: JsonNode): Future[Response] {.async, gcsafe.} =
|
||||
## Remotely calls the specified RPC method.
|
||||
let id = self.getNextId()
|
||||
var value = $rpcCallNode(name, params, id) & "\r\n"
|
||||
if self.transport.isNil:
|
||||
raise newException(ValueError,
|
||||
"Transport is not initialised (missing a call to connect?)")
|
||||
|
||||
# completed by processMessage.
|
||||
var newFut = newFuture[Response]()
|
||||
# add to awaiting responses
|
||||
self.awaiting[id] = newFut
|
||||
|
||||
await self.transport.send(value)
|
||||
return await newFut
|
||||
|
||||
proc processData(client: RpcWebSocketClient) {.async.} =
|
||||
var error: ref CatchableError
|
||||
|
||||
let ws = client.transport
|
||||
try:
|
||||
while ws.readyState != ReadyState.Closed:
|
||||
var value = await ws.recvMsg()
|
||||
|
||||
if value.len == 0:
|
||||
# transmission ends
|
||||
break
|
||||
|
||||
client.processMessage(string.fromBytes(value))
|
||||
except CatchableError as e:
|
||||
error = e
|
||||
|
||||
await client.transport.close()
|
||||
|
||||
client.transport = nil
|
||||
|
||||
if client.awaiting.len != 0:
|
||||
if error.isNil:
|
||||
error = newException(IOError, "Transport was closed while waiting for response")
|
||||
for k, v in client.awaiting:
|
||||
v.fail(error)
|
||||
client.awaiting.clear()
|
||||
if not client.onDisconnect.isNil:
|
||||
client.onDisconnect()
|
||||
|
||||
proc addExtraHeaders(
|
||||
headers: var HttpTable,
|
||||
client: RpcWebSocketClient,
|
||||
extraHeaders: HttpTable) =
|
||||
# Apply client instance overrides
|
||||
if client.getHeaders != nil:
|
||||
for header in client.getHeaders():
|
||||
headers.add(header[0], header[1])
|
||||
|
||||
# Apply call specific overrides
|
||||
for header in extraHeaders.stringItems:
|
||||
headers.add(header.key, header.value)
|
||||
|
||||
# Apply default origin
|
||||
discard headers.hasKeyOrPut("Origin", "http://localhost")
|
||||
|
||||
proc connect*(
|
||||
client: RpcWebSocketClient,
|
||||
uri: string,
|
||||
extraHeaders: HttpTable = default(HttpTable),
|
||||
compression = false,
|
||||
hooks: seq[Hook] = @[],
|
||||
flags: set[TLSFlags] = {}) {.async.} =
|
||||
proc headersHook(ctx: Hook, headers: var HttpTable): Result[void, string] =
|
||||
headers.addExtraHeaders(client, extraHeaders)
|
||||
ok()
|
||||
var ext: seq[ExtFactory] = if compression: @[deflateFactory()]
|
||||
else: @[]
|
||||
let uri = parseUri(uri)
|
||||
let ws = await WebSocket.connect(
|
||||
uri=uri,
|
||||
factories=ext,
|
||||
hooks=hooks & Hook(append: headersHook),
|
||||
flags=flags)
|
||||
client.transport = ws
|
||||
client.uri = uri
|
||||
client.loop = processData(client)
|
||||
|
||||
method close*(client: RpcWebSocketClient) {.async.} =
|
||||
await client.loop.cancelAndWait()
|
||||
if not client.transport.isNil:
|
||||
await client.transport.close()
|
||||
client.transport = nil
|
||||
export
|
||||
websocketclientimpl,
|
||||
client
|
||||
|
||||
119
json_rpc/clients/websocketclientimpl.nim
Normal file
119
json_rpc/clients/websocketclientimpl.nim
Normal file
@ -0,0 +1,119 @@
|
||||
import
|
||||
std/[uri, strutils],
|
||||
pkg/websock/[websock, extensions/compression/deflate],
|
||||
pkg/[chronos, chronos/apps/http/httptable, chronicles],
|
||||
stew/byteutils
|
||||
|
||||
# avoid clash between Json.encode and Base64Pad.encode
|
||||
import ../client except encode
|
||||
|
||||
{.push raises: [Defect].}
|
||||
|
||||
logScope:
|
||||
topics = "JSONRPC-WS-CLIENT"
|
||||
|
||||
type
|
||||
RpcWebSocketClient* = ref object of RpcClient
|
||||
transport*: WSSession
|
||||
uri*: Uri
|
||||
loop*: Future[void]
|
||||
getHeaders*: GetJsonRpcRequestHeaders
|
||||
|
||||
proc new*(
|
||||
T: type RpcWebSocketClient, getHeaders: GetJsonRpcRequestHeaders = nil): T =
|
||||
T(getHeaders: getHeaders)
|
||||
|
||||
proc newRpcWebSocketClient*(
|
||||
getHeaders: GetJsonRpcRequestHeaders = nil): RpcWebSocketClient =
|
||||
## Creates a new client instance.
|
||||
RpcWebSocketClient.new(getHeaders)
|
||||
|
||||
method call*(self: RpcWebSocketClient, name: string,
|
||||
params: JsonNode): Future[Response] {.async, gcsafe.} =
|
||||
## Remotely calls the specified RPC method.
|
||||
let id = self.getNextId()
|
||||
var value = $rpcCallNode(name, params, id) & "\r\n"
|
||||
if self.transport.isNil:
|
||||
raise newException(ValueError,
|
||||
"Transport is not initialised (missing a call to connect?)")
|
||||
|
||||
# completed by processMessage.
|
||||
var newFut = newFuture[Response]()
|
||||
# add to awaiting responses
|
||||
self.awaiting[id] = newFut
|
||||
|
||||
await self.transport.send(value)
|
||||
return await newFut
|
||||
|
||||
proc processData(client: RpcWebSocketClient) {.async.} =
|
||||
var error: ref CatchableError
|
||||
|
||||
let ws = client.transport
|
||||
try:
|
||||
while ws.readyState != ReadyState.Closed:
|
||||
var value = await ws.recvMsg()
|
||||
|
||||
if value.len == 0:
|
||||
# transmission ends
|
||||
break
|
||||
|
||||
client.processMessage(string.fromBytes(value))
|
||||
except CatchableError as e:
|
||||
error = e
|
||||
|
||||
await client.transport.close()
|
||||
|
||||
client.transport = nil
|
||||
|
||||
if client.awaiting.len != 0:
|
||||
if error.isNil:
|
||||
error = newException(IOError, "Transport was closed while waiting for response")
|
||||
for k, v in client.awaiting:
|
||||
v.fail(error)
|
||||
client.awaiting.clear()
|
||||
if not client.onDisconnect.isNil:
|
||||
client.onDisconnect()
|
||||
|
||||
proc addExtraHeaders(
|
||||
headers: var HttpTable,
|
||||
client: RpcWebSocketClient,
|
||||
extraHeaders: HttpTable) =
|
||||
# Apply client instance overrides
|
||||
if client.getHeaders != nil:
|
||||
for header in client.getHeaders():
|
||||
headers.add(header[0], header[1])
|
||||
|
||||
# Apply call specific overrides
|
||||
for header in extraHeaders.stringItems:
|
||||
headers.add(header.key, header.value)
|
||||
|
||||
# Apply default origin
|
||||
discard headers.hasKeyOrPut("Origin", "http://localhost")
|
||||
|
||||
proc connect*(
|
||||
client: RpcWebSocketClient,
|
||||
uri: string,
|
||||
extraHeaders: HttpTable = default(HttpTable),
|
||||
compression = false,
|
||||
hooks: seq[Hook] = @[],
|
||||
flags: set[TLSFlags] = {}) {.async.} =
|
||||
proc headersHook(ctx: Hook, headers: var HttpTable): Result[void, string] =
|
||||
headers.addExtraHeaders(client, extraHeaders)
|
||||
ok()
|
||||
var ext: seq[ExtFactory] = if compression: @[deflateFactory()]
|
||||
else: @[]
|
||||
let uri = parseUri(uri)
|
||||
let ws = await WebSocket.connect(
|
||||
uri=uri,
|
||||
factories=ext,
|
||||
hooks=hooks & Hook(append: headersHook),
|
||||
flags=flags)
|
||||
client.transport = ws
|
||||
client.uri = uri
|
||||
client.loop = processData(client)
|
||||
|
||||
method close*(client: RpcWebSocketClient) {.async.} =
|
||||
await client.loop.cancelAndWait()
|
||||
if not client.transport.isNil:
|
||||
await client.transport.close()
|
||||
client.transport = nil
|
||||
@ -1,161 +1,110 @@
|
||||
import
|
||||
std/[macros, json, options, typetraits],
|
||||
stew/[byteutils, objects]
|
||||
std/[macros, json, typetraits],
|
||||
stew/[byteutils, objects],
|
||||
json_serialization,
|
||||
json_serialization/lexer,
|
||||
json_serialization/std/[options, sets, tables]
|
||||
|
||||
export json, options
|
||||
export json, options, json_serialization
|
||||
|
||||
Json.createFlavor JsonRpc
|
||||
|
||||
# Avoid templates duplicating the string in the executable.
|
||||
const errDeserializePrefix = "Error deserializing stream for type '"
|
||||
|
||||
template wrapErrors(reader, value, actions: untyped): untyped =
|
||||
## Convert read errors to `UnexpectedValue` for the purpose of marshalling.
|
||||
try:
|
||||
actions
|
||||
except Exception as err:
|
||||
reader.raiseUnexpectedValue(errDeserializePrefix & $type(value) & "': " & err.msg)
|
||||
|
||||
# Bytes.
|
||||
|
||||
proc readValue*(r: var JsonReader[JsonRpc], value: var byte) =
|
||||
## Implement separate read serialization for `byte` to avoid
|
||||
## 'can raise Exception' for `readValue(value, uint8)`.
|
||||
wrapErrors r, value:
|
||||
case r.lexer.tok
|
||||
of tkInt:
|
||||
if r.lexer.absIntVal in 0'u32 .. byte.high:
|
||||
value = byte(r.lexer.absIntVal)
|
||||
else:
|
||||
r.raiseIntOverflow r.lexer.absIntVal, true
|
||||
of tkNegativeInt:
|
||||
r.raiseIntOverflow r.lexer.absIntVal, true
|
||||
else:
|
||||
r.raiseUnexpectedToken etInt
|
||||
r.lexer.next()
|
||||
|
||||
proc writeValue*(w: var JsonWriter[JsonRpc], value: byte) =
|
||||
json_serialization.writeValue(w, uint8(value))
|
||||
|
||||
# Enums.
|
||||
|
||||
proc readValue*(r: var JsonReader[JsonRpc], value: var (enum)) =
|
||||
wrapErrors r, value:
|
||||
value = type(value) json_serialization.readValue(r, uint64)
|
||||
|
||||
proc writeValue*(w: var JsonWriter[JsonRpc], value: (enum)) =
|
||||
json_serialization.writeValue(w, uint64(value))
|
||||
|
||||
# Other base types.
|
||||
|
||||
macro genDistinctSerializers(types: varargs[untyped]): untyped =
|
||||
## Implements distinct serialization pass-throughs for `types`.
|
||||
result = newStmtList()
|
||||
for ty in types:
|
||||
result.add(quote do:
|
||||
|
||||
proc readValue*(r: var JsonReader[JsonRpc], value: var `ty`) =
|
||||
wrapErrors r, value:
|
||||
json_serialization.readValue(r, value)
|
||||
|
||||
proc writeValue*(w: var JsonWriter[JsonRpc], value: `ty`) {.raises: [IOError].} =
|
||||
json_serialization.writeValue(w, value)
|
||||
)
|
||||
|
||||
genDistinctSerializers bool, int, float, string, int64, uint64, uint32, ref int64, ref int
|
||||
|
||||
# Sequences and arrays.
|
||||
|
||||
proc readValue*[T](r: var JsonReader[JsonRpc], value: var seq[T]) =
|
||||
wrapErrors r, value:
|
||||
json_serialization.readValue(r, value)
|
||||
|
||||
proc writeValue*[T](w: var JsonWriter[JsonRpc], value: seq[T]) =
|
||||
json_serialization.writeValue(w, value)
|
||||
|
||||
proc readValue*[N: static[int]](r: var JsonReader[JsonRpc], value: var array[N, byte]) =
|
||||
## Read an array while allowing partial data.
|
||||
wrapErrors r, value:
|
||||
r.skipToken tkBracketLe
|
||||
if r.lexer.tok != tkBracketRi:
|
||||
for i in low(value) .. high(value):
|
||||
readValue(r, value[i])
|
||||
if r.lexer.tok == tkBracketRi:
|
||||
break
|
||||
else:
|
||||
r.skipToken tkComma
|
||||
r.skipToken tkBracketRi
|
||||
|
||||
# High level generic unpacking.
|
||||
|
||||
proc unpackArg[T](args: JsonNode, argName: string, argType: typedesc[T]): T {.raises: [ValueError].} =
|
||||
if args.isNil:
|
||||
raise newException(ValueError, argName & ": unexpected null value")
|
||||
try:
|
||||
result = JsonRpc.decode($args, argType)
|
||||
except CatchableError as err:
|
||||
raise newException(ValueError,
|
||||
"Parameter [" & argName & "] of type '" & $argType & "' could not be decoded: " & err.msg)
|
||||
|
||||
proc expect*(actual, expected: JsonNodeKind, argName: string) =
|
||||
if actual != expected:
|
||||
raise newException(
|
||||
ValueError, "Parameter [" & argName & "] expected " & $expected & " but got " & $actual)
|
||||
|
||||
proc `%`*(n: ref SomeInteger): JsonNode =
|
||||
if n.isNil:
|
||||
newJNull()
|
||||
else:
|
||||
newJInt(n[])
|
||||
|
||||
# Compiler requires forward decl when processing out of module
|
||||
proc fromJson*(n: JsonNode, argName: string, result: var bool)
|
||||
proc fromJson*(n: JsonNode, argName: string, result: var int)
|
||||
proc fromJson*(n: JsonNode, argName: string, result: var byte)
|
||||
proc fromJson*(n: JsonNode, argName: string, result: var float)
|
||||
proc fromJson*(n: JsonNode, argName: string, result: var string)
|
||||
proc fromJson*[T](n: JsonNode, argName: string, result: var seq[T])
|
||||
proc fromJson*[N, T](n: JsonNode, argName: string, result: var array[N, T])
|
||||
proc fromJson*(n: JsonNode, argName: string, result: var int64)
|
||||
proc fromJson*(n: JsonNode, argName: string, result: var uint64)
|
||||
proc fromJson*(n: JsonNode, argName: string, result: var uint32)
|
||||
proc fromJson*(n: JsonNode, argName: string, result: var ref int64)
|
||||
proc fromJson*(n: JsonNode, argName: string, result: var ref int)
|
||||
proc fromJson*[T](n: JsonNode, argName: string, result: var Option[T])
|
||||
|
||||
# This can't be forward declared: https://github.com/nim-lang/Nim/issues/7868
|
||||
proc fromJson*[T: enum](n: JsonNode, argName: string, result: var T) =
|
||||
n.kind.expect(JInt, argName)
|
||||
|
||||
let v = n.getBiggestInt()
|
||||
if not checkedEnumAssign(result, v):
|
||||
raise (ref ValueError)(
|
||||
msg: "Unknown enum ordinal for " & name(T) & ": " & $v)
|
||||
|
||||
# This can't be forward declared: https://github.com/nim-lang/Nim/issues/7868
|
||||
proc fromJson*[T: object|tuple](n: JsonNode, argName: string, result: var T) =
|
||||
n.kind.expect(JObject, argName)
|
||||
for k, v in fieldPairs(result):
|
||||
if v is Option and not n.hasKey(k):
|
||||
fromJson(newJNull(), k, v)
|
||||
else:
|
||||
fromJson(n[k], k, v)
|
||||
|
||||
# same as `proc `%`*[T: object](o: T): JsonNode` in json.nim from the stdlib
|
||||
# TODO this PR removes the need for this: https://github.com/nim-lang/Nim/pull/14638
|
||||
proc `%`*[T: tuple](o: T): JsonNode =
|
||||
## Construct JsonNode from tuples and objects.
|
||||
result = newJObject()
|
||||
for k, v in o.fieldPairs: result[k] = %v
|
||||
|
||||
proc fromJson*[T](n: JsonNode, argName: string, result: var Option[T]) =
|
||||
# Allow JNull for options
|
||||
if n.kind != JNull:
|
||||
var val: T
|
||||
fromJson(n, argName, val)
|
||||
result = some(val)
|
||||
|
||||
proc fromJson*(n: JsonNode, argName: string, result: var bool) =
|
||||
n.kind.expect(JBool, argName)
|
||||
result = n.getBool()
|
||||
|
||||
proc fromJson*(n: JsonNode, argName: string, result: var int) =
|
||||
n.kind.expect(JInt, argName)
|
||||
result = n.getInt()
|
||||
|
||||
proc fromJson*[T: ref object](n: JsonNode, argName: string, result: var T) =
|
||||
if n.kind == JNull:
|
||||
result = nil
|
||||
return
|
||||
n.kind.expect(JObject, argName)
|
||||
result = new T
|
||||
fromJson(n, argName, result[])
|
||||
|
||||
proc fromJson*(n: JsonNode, argName: string, result: var int64) =
|
||||
n.kind.expect(JInt, argName)
|
||||
result = n.getBiggestInt().int64
|
||||
|
||||
proc fromJson*(n: JsonNode, argName: string, result: var uint64) =
|
||||
n.kind.expect(JInt, argName)
|
||||
let asInt = n.getBiggestInt()
|
||||
# signed -> unsigned conversions are unchecked
|
||||
# https://github.com/nim-lang/RFCs/issues/175
|
||||
if asInt < 0:
|
||||
raise newException(
|
||||
ValueError, "JSON-RPC input is an unexpected negative value")
|
||||
result = uint64(asInt)
|
||||
|
||||
proc fromJson*(n: JsonNode, argName: string, result: var uint32) =
|
||||
n.kind.expect(JInt, argName)
|
||||
let asInt = n.getBiggestInt()
|
||||
# signed -> unsigned conversions are unchecked
|
||||
# https://github.com/nim-lang/RFCs/issues/175
|
||||
if asInt < 0:
|
||||
raise newException(
|
||||
ValueError, "JSON-RPC input is an unexpected negative value")
|
||||
if asInt > BiggestInt(uint32.high()):
|
||||
raise newException(
|
||||
ValueError, "JSON-RPC input is too large for uint32")
|
||||
|
||||
result = uint32(asInt)
|
||||
|
||||
proc fromJson*(n: JsonNode, argName: string, result: var ref int64) =
|
||||
n.kind.expect(JInt, argName)
|
||||
new result
|
||||
result[] = n.getInt()
|
||||
|
||||
proc fromJson*(n: JsonNode, argName: string, result: var ref int) =
|
||||
n.kind.expect(JInt, argName)
|
||||
new result
|
||||
result[] = n.getInt()
|
||||
|
||||
proc fromJson*(n: JsonNode, argName: string, result: var byte) =
|
||||
n.kind.expect(JInt, argName)
|
||||
let v = n.getInt()
|
||||
if v > 255 or v < 0:
|
||||
raise newException(ValueError, "Parameter \"" & argName & "\" value out of range for byte: " & $v)
|
||||
result = byte(v)
|
||||
|
||||
proc fromJson*(n: JsonNode, argName: string, result: var float) =
|
||||
n.kind.expect(JFloat, argName)
|
||||
result = n.getFloat()
|
||||
|
||||
proc fromJson*(n: JsonNode, argName: string, result: var string) =
|
||||
n.kind.expect(JString, argName)
|
||||
result = n.getStr()
|
||||
|
||||
proc fromJson*[T](n: JsonNode, argName: string, result: var seq[T]) =
|
||||
when T is byte:
|
||||
if n.kind == JString:
|
||||
result = hexToSeqByte n.getStr()
|
||||
return
|
||||
|
||||
n.kind.expect(JArray, argName)
|
||||
result = newSeq[T](n.len)
|
||||
for i in 0 ..< n.len:
|
||||
fromJson(n[i], argName, result[i])
|
||||
|
||||
proc fromJson*[N, T](n: JsonNode, argName: string, result: var array[N, T]) =
|
||||
n.kind.expect(JArray, argName)
|
||||
if n.len > result.len:
|
||||
raise newException(ValueError, "Parameter \"" & argName & "\" item count is too big for array")
|
||||
for i in 0 ..< n.len:
|
||||
fromJson(n[i], argName, result[i])
|
||||
|
||||
proc unpackArg[T](args: JsonNode, argName: string, argtype: typedesc[T]): T =
|
||||
mixin fromJson
|
||||
if args.isNil:
|
||||
raise (ref ValueError)(msg: argName & ": unexpected null value")
|
||||
{.gcsafe.}:
|
||||
fromJson(args, argName, result)
|
||||
|
||||
proc expectArrayLen(node, jsonIdent: NimNode, length: int) =
|
||||
let
|
||||
identStr = jsonIdent.repr
|
||||
@ -218,7 +167,7 @@ proc jsonToNim*(assignIdent, paramType, jsonIdent: NimNode, paramNameStr: string
|
||||
`unpackArg`(`jsonIdent`, `paramNameStr`, type(`paramType`))
|
||||
|
||||
if optional:
|
||||
result.add(quote do: `assignIdent` = `some`(`unpackNode`))
|
||||
result.add(quote do: `assignIdent` = some(`unpackNode`))
|
||||
else:
|
||||
result.add(quote do: `assignIdent` = `unpackNode`)
|
||||
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import
|
||||
std/[macros, options, strutils, tables],
|
||||
std/[macros, strutils, tables],
|
||||
chronicles, chronos, json_serialization/writer,
|
||||
./jsonmarshal, ./errors
|
||||
|
||||
@ -122,11 +122,6 @@ proc tryRoute*(router: RpcRouter, data: JsonNode, fut: var Future[StringOfJson])
|
||||
fut = rpc(jParams)
|
||||
return true
|
||||
|
||||
proc makeProcName(s: string): string =
|
||||
result = ""
|
||||
for c in s:
|
||||
if c.isAlphaNumeric: result.add c
|
||||
|
||||
proc hasReturnType(params: NimNode): bool =
|
||||
if params != nil and params.len > 0 and params[0] != nil and
|
||||
params[0].kind != nnkEmpty:
|
||||
@ -162,21 +157,20 @@ macro rpc*(server: RpcRouter, path: string, body: untyped): untyped =
|
||||
`setup`
|
||||
`procBody`
|
||||
|
||||
if ReturnType == ident"JsonNode":
|
||||
# `JsonNode` results don't need conversion
|
||||
result.add quote do:
|
||||
proc `rpcProcWrapper`(`paramsIdent`: JsonNode): Future[StringOfJson] {.async, gcsafe.} =
|
||||
return StringOfJson($(await `rpcProcImpl`(`paramsIdent`)))
|
||||
elif ReturnType == ident"StringOfJson":
|
||||
result.add quote do:
|
||||
proc `rpcProcWrapper`(`paramsIdent`: JsonNode): Future[StringOfJson] {.async, gcsafe.} =
|
||||
return await `rpcProcImpl`(`paramsIdent`)
|
||||
else:
|
||||
result.add quote do:
|
||||
proc `rpcProcWrapper`(`paramsIdent`: JsonNode): Future[StringOfJson] {.async, gcsafe.} =
|
||||
return StringOfJson($(%(await `rpcProcImpl`(`paramsIdent`))))
|
||||
let
|
||||
awaitedResult = ident "awaitedResult"
|
||||
doEncode = quote do: encode(JsonRpc, `awaitedResult`)
|
||||
maybeWrap =
|
||||
if ReturnType == ident"StringOfJson": doEncode
|
||||
else: ident"StringOfJson".newCall doEncode
|
||||
|
||||
result.add quote do:
|
||||
proc `rpcProcWrapper`(`paramsIdent`: JsonNode): Future[StringOfJson] {.async, gcsafe.} =
|
||||
# Avoid 'yield in expr not lowered' with an intermediate variable.
|
||||
# See: https://github.com/nim-lang/Nim/issues/17849
|
||||
let `awaitedResult` = await `rpcProcImpl`(`paramsIdent`)
|
||||
return `maybeWrap`
|
||||
|
||||
`server`.register(`path`, `rpcProcWrapper`)
|
||||
|
||||
when defined(nimDumpRpcs):
|
||||
|
||||
@ -61,7 +61,7 @@ template hexQuantityStr*(value: string): HexQuantityStr = value.HexQuantityStr
|
||||
# Converters
|
||||
|
||||
import json
|
||||
from ../json_rpc/rpcserver import expect
|
||||
import ../json_rpc/jsonmarshal
|
||||
|
||||
proc `%`*(value: HexDataStr): JsonNode =
|
||||
if not value.validate:
|
||||
@ -75,21 +75,31 @@ proc `%`*(value: HexQuantityStr): JsonNode =
|
||||
else:
|
||||
result = %(value.string)
|
||||
|
||||
proc fromJson*(n: JsonNode, argName: string, result: var HexDataStr) =
|
||||
# Note that '0x' is stripped after validation
|
||||
n.kind.expect(JString, argName)
|
||||
let hexStr = n.getStr()
|
||||
if not hexStr.hexDataStr.validate:
|
||||
raise newException(ValueError, "Parameter \"" & argName & "\" value is not valid as a Ethereum data \"" & hexStr & "\"")
|
||||
result = hexStr[2..hexStr.high].hexDataStr
|
||||
proc writeValue*(w: var JsonWriter[JsonRpc], val: HexDataStr) {.raises: [IOError].} =
|
||||
writeValue(w, val.string)
|
||||
|
||||
proc fromJson*(n: JsonNode, argName: string, result: var HexQuantityStr) =
|
||||
proc writeValue*(w: var JsonWriter[JsonRpc], val: HexQuantityStr) {.raises: [IOError].} =
|
||||
writeValue(w, $val.string)
|
||||
|
||||
proc readValue*(r: var JsonReader[JsonRpc], v: var HexDataStr) =
|
||||
# Note that '0x' is stripped after validation
|
||||
n.kind.expect(JString, argName)
|
||||
let hexStr = n.getStr()
|
||||
if not hexStr.hexQuantityStr.validate:
|
||||
raise newException(ValueError, "Parameter \"" & argName & "\" value is not valid as an Ethereum hex quantity \"" & hexStr & "\"")
|
||||
result = hexStr[2..hexStr.high].hexQuantityStr
|
||||
try:
|
||||
let hexStr = readValue(r, string)
|
||||
if not hexStr.hexDataStr.validate:
|
||||
raise newException(ValueError, "Value for '" & $v.type & "' is not valid as a Ethereum data \"" & hexStr & "\"")
|
||||
v = hexStr[2..hexStr.high].hexDataStr
|
||||
except Exception as err:
|
||||
r.raiseUnexpectedValue("Error deserializing for '" & $v.type & "' stream: " & err.msg)
|
||||
|
||||
proc readValue*(r: var JsonReader[JsonRpc], v: var HexQuantityStr) =
|
||||
# Note that '0x' is stripped after validation
|
||||
try:
|
||||
let hexStr = readValue(r, string)
|
||||
if not hexStr.hexQuantityStr.validate:
|
||||
raise newException(ValueError, "Value for '" & $v.type & "' is not valid as a Ethereum data \"" & hexStr & "\"")
|
||||
v = hexStr[2..hexStr.high].hexQuantityStr
|
||||
except Exception as err:
|
||||
r.raiseUnexpectedValue("Error deserializing for '" & $v.type & "' stream: " & err.msg)
|
||||
|
||||
# testing
|
||||
|
||||
@ -128,7 +138,7 @@ when isMainModule:
|
||||
source = "x1234"
|
||||
x = hexQuantityStr source
|
||||
check %x != %source
|
||||
|
||||
|
||||
suite "Hex data":
|
||||
test "Even length":
|
||||
let
|
||||
|
||||
@ -1,8 +1,7 @@
|
||||
import
|
||||
../json_rpc/router
|
||||
|
||||
template `==`*(a, b: distinct (string|StringOfJson)): bool =
|
||||
string(a) == string(b)
|
||||
converter toStr*(value: distinct (string|StringOfJson)): string = string(value)
|
||||
|
||||
template `==`*(a: StringOfJson, b: JsonNode): bool =
|
||||
parseJson(string a) == b
|
||||
|
||||
@ -1,5 +1,4 @@
|
||||
import json, stint
|
||||
from ../json_rpc/rpcserver import expect
|
||||
import stint, ../json_rpc/jsonmarshal
|
||||
|
||||
template stintStr(n: UInt256|Int256): JsonNode =
|
||||
var s = n.toHex
|
||||
@ -11,22 +10,25 @@ proc `%`*(n: UInt256): JsonNode = n.stintStr
|
||||
|
||||
proc `%`*(n: Int256): JsonNode = n.stintStr
|
||||
|
||||
# allows UInt256 to be passed as a json string
|
||||
proc fromJson*(n: JsonNode, argName: string, result: var UInt256) =
|
||||
# expects base 16 string, starting with "0x"
|
||||
n.kind.expect(JString, argName)
|
||||
let hexStr = n.getStr()
|
||||
if hexStr.len > 64 + 2: # including "0x"
|
||||
raise newException(ValueError, "Parameter \"" & argName & "\" value too long for UInt256: " & $hexStr.len)
|
||||
result = hexStr.parse(StUint[256], 16) # TODO: Handle errors
|
||||
proc writeValue*(w: var JsonWriter[JsonRpc], val: UInt256) =
|
||||
writeValue(w, val.stintStr)
|
||||
|
||||
# allows ref UInt256 to be passed as a json string
|
||||
proc fromJson*(n: JsonNode, argName: string, result: var ref UInt256) =
|
||||
# expects base 16 string, starting with "0x"
|
||||
n.kind.expect(JString, argName)
|
||||
let hexStr = n.getStr()
|
||||
if hexStr.len > 64 + 2: # including "0x"
|
||||
raise newException(ValueError, "Parameter \"" & argName & "\" value too long for UInt256: " & $hexStr.len)
|
||||
new result
|
||||
result[] = hexStr.parse(StUint[256], 16) # TODO: Handle errors
|
||||
proc writeValue*(w: var JsonWriter[JsonRpc], val: ref UInt256) =
|
||||
writeValue(w, val[].stintStr)
|
||||
|
||||
proc readValue*(r: var JsonReader[JsonRpc], v: var UInt256) =
|
||||
## Allows UInt256 to be passed as a json string.
|
||||
## Expects base 16 string, starting with "0x".
|
||||
try:
|
||||
let hexStr = r.readValue string
|
||||
if hexStr.len > 64 + 2: # including "0x"
|
||||
raise newException(ValueError, "Value for '" & $v.type & "' too long for UInt256: " & $hexStr.len)
|
||||
v = hexStr.parse(StUint[256], 16) # TODO: Handle errors
|
||||
except Exception as err:
|
||||
r.raiseUnexpectedValue("Error deserializing for '" & $v.type & "' stream: " & err.msg)
|
||||
|
||||
proc readValue*(r: var JsonReader[JsonRpc], v: var ref UInt256) =
|
||||
## Allows ref UInt256 to be passed as a json string.
|
||||
## Expects base 16 string, starting with "0x".
|
||||
readValue(r, v[])
|
||||
|
||||
|
||||
@ -36,7 +36,7 @@ let
|
||||
"y": %"test"
|
||||
}
|
||||
},
|
||||
"c": %1.23}
|
||||
"c": %1.0}
|
||||
|
||||
var s = newRpcSocketServer(["localhost:8545"])
|
||||
|
||||
@ -201,12 +201,12 @@ suite "Server types":
|
||||
inp2 = MyOptional()
|
||||
r1 = waitFor s.executeMethod("rpc.optional", %[%inp1])
|
||||
r2 = waitFor s.executeMethod("rpc.optional", %[%inp2])
|
||||
check r1 == %inp1
|
||||
check r2 == %inp2
|
||||
check r1 == JsonRpc.encode inp1
|
||||
check r2 == JsonRpc.encode inp2
|
||||
|
||||
test "Return statement":
|
||||
let r = waitFor s.executeMethod("rpc.testReturns", %[])
|
||||
check r == %1234
|
||||
check r == JsonRpc.encode 1234
|
||||
|
||||
test "Runtime errors":
|
||||
expect ValueError:
|
||||
@ -224,7 +224,7 @@ suite "Server types":
|
||||
|
||||
test "Multiple variables of one type":
|
||||
let r = waitFor s.executeMethod("rpc.multiVarsOfOneType", %[%"hello", %"world"])
|
||||
check r == %"hello world"
|
||||
check r == JsonRpc.encode "hello world"
|
||||
|
||||
test "Optional arg":
|
||||
let
|
||||
@ -233,37 +233,37 @@ suite "Server types":
|
||||
r1 = waitFor s.executeMethod("rpc.optionalArg", %[%117, %int1])
|
||||
r2 = waitFor s.executeMethod("rpc.optionalArg", %[%117])
|
||||
r3 = waitFor s.executeMethod("rpc.optionalArg", %[%117, newJNull()])
|
||||
check r1 == %int1
|
||||
check r2 == %int2
|
||||
check r3 == %int2
|
||||
check r1 == JsonRpc.encode int1
|
||||
check r2 == JsonRpc.encode int2
|
||||
check r3 == JsonRpc.encode int2
|
||||
|
||||
test "Optional arg2":
|
||||
let r1 = waitFor s.executeMethod("rpc.optionalArg2", %[%"A", %"B"])
|
||||
check r1 == %"AB"
|
||||
check r1 == JsonRpc.encode "AB"
|
||||
|
||||
let r2 = waitFor s.executeMethod("rpc.optionalArg2", %[%"A", %"B", newJNull()])
|
||||
check r2 == %"AB"
|
||||
check r2 == JsonRpc.encode "AB"
|
||||
|
||||
let r3 = waitFor s.executeMethod("rpc.optionalArg2", %[%"A", %"B", newJNull(), newJNull()])
|
||||
check r3 == %"AB"
|
||||
check r3 == JsonRpc.encode "AB"
|
||||
|
||||
let r4 = waitFor s.executeMethod("rpc.optionalArg2", %[%"A", %"B", newJNull(), %"D"])
|
||||
check r4 == %"ABD"
|
||||
check r4 == JsonRpc.encode "ABD"
|
||||
|
||||
let r5 = waitFor s.executeMethod("rpc.optionalArg2", %[%"A", %"B", %"C", %"D"])
|
||||
check r5 == %"ABCD"
|
||||
check r5 == JsonRpc.encode "ABCD"
|
||||
|
||||
let r6 = waitFor s.executeMethod("rpc.optionalArg2", %[%"A", %"B", %"C", newJNull()])
|
||||
check r6 == %"ABC"
|
||||
check r6 == JsonRpc.encode "ABC"
|
||||
|
||||
let r7 = waitFor s.executeMethod("rpc.optionalArg2", %[%"A", %"B", %"C"])
|
||||
check r7 == %"ABC"
|
||||
check r7 == JsonRpc.encode "ABC"
|
||||
|
||||
test "Mixed optional arg":
|
||||
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 == JsonRpc.encode OptionalFields(a: 10, b: some(11), c: "hello", d: some(12), e: some("world"))
|
||||
var bx = waitFor s.executeMethod("rpc.mixedOptionalArg", %[%10, newJNull(), %"hello"])
|
||||
check bx == %OptionalFields(a: 10, c: "hello")
|
||||
check bx == JsonRpc.encode OptionalFields(a: 10, c: "hello")
|
||||
|
||||
test "Non-built-in optional types":
|
||||
let
|
||||
@ -271,33 +271,33 @@ suite "Server types":
|
||||
testOpts1 = MyOptionalNotBuiltin(val: some(t2))
|
||||
testOpts2 = MyOptionalNotBuiltin()
|
||||
var r = waitFor s.executeMethod("rpc.optionalArgNotBuiltin", %[%testOpts1])
|
||||
check r == %t2.y
|
||||
check r == JsonRpc.encode t2.y
|
||||
var r2 = waitFor s.executeMethod("rpc.optionalArgNotBuiltin", %[])
|
||||
check r2 == %"Empty1"
|
||||
check r2 == JsonRpc.encode "Empty1"
|
||||
var r3 = waitFor s.executeMethod("rpc.optionalArgNotBuiltin", %[%testOpts2])
|
||||
check r3 == %"Empty2"
|
||||
check r3 == JsonRpc.encode "Empty2"
|
||||
|
||||
test "Manually set up JSON for optionals":
|
||||
# Check manual set up json with optionals
|
||||
let opts1 = parseJson("""{"o1": true}""")
|
||||
var r1 = waitFor s.executeMethod("rpc.optInObj", %[%"0x31ded", opts1])
|
||||
check r1 == %1
|
||||
check r1 == JsonRpc.encode 1
|
||||
let opts2 = parseJson("""{"o2": true}""")
|
||||
var r2 = waitFor s.executeMethod("rpc.optInObj", %[%"0x31ded", opts2])
|
||||
check r2 == %2
|
||||
check r2 == JsonRpc.encode 2
|
||||
let opts3 = parseJson("""{"o3": true}""")
|
||||
var r3 = waitFor s.executeMethod("rpc.optInObj", %[%"0x31ded", opts3])
|
||||
check r3 == %4
|
||||
check r3 == JsonRpc.encode 4
|
||||
# Combinations
|
||||
let opts4 = parseJson("""{"o1": true, "o3": true}""")
|
||||
var r4 = waitFor s.executeMethod("rpc.optInObj", %[%"0x31ded", opts4])
|
||||
check r4 == %5
|
||||
check r4 == JsonRpc.encode 5
|
||||
let opts5 = parseJson("""{"o2": true, "o3": true}""")
|
||||
var r5 = waitFor s.executeMethod("rpc.optInObj", %[%"0x31ded", opts5])
|
||||
check r5 == %6
|
||||
check r5 == JsonRpc.encode 6
|
||||
let opts6 = parseJson("""{"o1": true, "o2": true}""")
|
||||
var r6 = waitFor s.executeMethod("rpc.optInObj", %[%"0x31ded", opts6])
|
||||
check r6 == %3
|
||||
check r6 == JsonRpc.encode 3
|
||||
|
||||
s.stop()
|
||||
waitFor s.closeWait()
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user