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:
coffeepots 2023-12-14 01:29:11 +00:00 committed by GitHub
parent 08d7fccfe2
commit 819b6fed37
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 311 additions and 348 deletions

View File

@ -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

View 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

View File

@ -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`)

View File

@ -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):

View File

@ -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

View File

@ -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

View File

@ -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[])

View File

@ -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()