375 lines
11 KiB
Nim
375 lines
11 KiB
Nim
# json-rpc
|
|
# Copyright (c) 2019-2023 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
|
|
unittest2,
|
|
chronicles,
|
|
../json_rpc/rpcserver,
|
|
./private/helpers,
|
|
json_serialization/std/options
|
|
|
|
type
|
|
# some nested types to check object parsing
|
|
Test2 = object
|
|
x: array[0..2, int]
|
|
y: string
|
|
|
|
Test = object
|
|
a: array[0..1, int]
|
|
b: Test2
|
|
|
|
MyObject = object
|
|
a: int
|
|
b: Test
|
|
c: float
|
|
|
|
MyOptional = object
|
|
maybeInt: Option[int]
|
|
|
|
MyOptionalNotBuiltin = object
|
|
val: Option[Test2]
|
|
|
|
MyEnum = enum
|
|
Enum0
|
|
Enum1
|
|
|
|
MuscleCar = object
|
|
color: string
|
|
wheel: int
|
|
|
|
MyObject.useDefaultSerializationIn JrpcConv
|
|
Test.useDefaultSerializationIn JrpcConv
|
|
Test2.useDefaultSerializationIn JrpcConv
|
|
MyOptional.useDefaultSerializationIn JrpcConv
|
|
MyOptionalNotBuiltin.useDefaultSerializationIn JrpcConv
|
|
MuscleCar.useDefaultSerializationIn JrpcConv
|
|
|
|
proc readValue*(r: var JsonReader[JrpcConv], val: var MyEnum)
|
|
{.gcsafe, raises: [IOError, SerializationError].} =
|
|
let intVal = r.parseInt(int)
|
|
if intVal < low(MyEnum).int or intVal > high(MyEnum).int:
|
|
r.raiseUnexpectedValue("invalid enum range " & $intVal)
|
|
val = MyEnum(intVal)
|
|
|
|
let
|
|
testObj = %*{
|
|
"a": %1,
|
|
"b": %*{
|
|
"a": %[5, 0],
|
|
"b": %*{
|
|
"x": %[1, 2, 3],
|
|
"y": %"test"
|
|
}
|
|
},
|
|
"c": %1.0}
|
|
|
|
var s = newRpcSocketServer(["127.0.0.1:0"])
|
|
|
|
# RPC definitions
|
|
s.rpc("rpc.simplePath"):
|
|
return %1
|
|
|
|
s.rpc("rpc.enumParam") do(e: MyEnum):
|
|
return %[$e]
|
|
|
|
s.rpc("rpc.differentParams") do(a: int, b: string):
|
|
return %[%a, %b]
|
|
|
|
s.rpc("rpc.arrayParam") do(arr: array[0..5, byte], b: string):
|
|
var res = %arr
|
|
res.add %b
|
|
return %res
|
|
|
|
s.rpc("rpc.seqParam") do(a: string, s: seq[int]):
|
|
var res = newJArray()
|
|
res.add %a
|
|
for item in s:
|
|
res.add %int(item)
|
|
return res
|
|
|
|
s.rpc("rpc.objParam") do(a: string, obj: MyObject):
|
|
return %obj
|
|
|
|
s.rpc("rpc.returnTypeSimple") do(i: int) -> int:
|
|
return i
|
|
|
|
s.rpc("rpc.returnTypeComplex") do(i: int) -> Test2:
|
|
return Test2(x: [1, i, 3], y: "test")
|
|
|
|
s.rpc("rpc.testReturns") do() -> int:
|
|
return 1234
|
|
|
|
s.rpc("rpc.multiVarsOfOneType") do(a, b: string) -> string:
|
|
return a & " " & b
|
|
|
|
s.rpc("rpc.optional") do(obj: MyOptional) -> MyOptional:
|
|
return obj
|
|
|
|
s.rpc("rpc.optionalArg") do(val: int, obj: Option[MyOptional]) -> MyOptional:
|
|
return if obj.isSome():
|
|
obj.get()
|
|
else:
|
|
MyOptional(maybeInt: some(val))
|
|
|
|
s.rpc("rpc.optionalArg2") do(a, b: string, c, d: Option[string]) -> string:
|
|
var ret = a & b
|
|
if c.isSome: ret.add c.get()
|
|
if d.isSome: ret.add d.get()
|
|
return ret
|
|
|
|
s.rpc("echo") do(car: MuscleCar) -> JsonString:
|
|
return JrpcConv.encode(car).JsonString
|
|
|
|
type
|
|
OptionalFields = object
|
|
a: int
|
|
b: Option[int]
|
|
c: string
|
|
d: Option[int]
|
|
e: Option[string]
|
|
|
|
OptionalFields.useDefaultSerializationIn JrpcConv
|
|
|
|
s.rpc("rpc.mixedOptionalArg") do(a: int, b: Option[int], c: string,
|
|
d: Option[int], e: Option[string]) -> OptionalFields:
|
|
|
|
result.a = a
|
|
result.b = b
|
|
result.c = c
|
|
result.d = d
|
|
result.e = e
|
|
|
|
s.rpc("rpc.optionalArgNotBuiltin") do(obj: Option[MyOptionalNotBuiltin]) -> string:
|
|
return if obj.isSome:
|
|
let val = obj.get.val
|
|
if val.isSome:
|
|
obj.get.val.get.y
|
|
else:
|
|
"Empty2"
|
|
else:
|
|
"Empty1"
|
|
|
|
type
|
|
MaybeOptions = object
|
|
o1: Option[bool]
|
|
o2: Option[bool]
|
|
o3: Option[bool]
|
|
|
|
MaybeOptions.useDefaultSerializationIn JrpcConv
|
|
|
|
s.rpc("rpc.optInObj") do(data: string, options: Option[MaybeOptions]) -> int:
|
|
if options.isSome:
|
|
let o = options.get
|
|
if o.o1.isSome: result += 1
|
|
if o.o2.isSome: result += 2
|
|
if o.o3.isSome: result += 4
|
|
|
|
proc installMoreApiHandlers*(s: RpcServer, prefix: static string) =
|
|
s.rpc(prefix & ".optionalStringArg") do(a: Option[string]) -> string:
|
|
if a.isSome:
|
|
return a.get()
|
|
else:
|
|
return "nope"
|
|
|
|
s.installMoreApiHandlers("rpc")
|
|
|
|
# Tests
|
|
suite "Server types":
|
|
test "On macro registration":
|
|
check s.hasMethod("rpc.simplePath")
|
|
check s.hasMethod("rpc.differentParams")
|
|
check s.hasMethod("rpc.arrayParam")
|
|
check s.hasMethod("rpc.seqParam")
|
|
check s.hasMethod("rpc.objParam")
|
|
check s.hasMethod("rpc.returnTypeSimple")
|
|
check s.hasMethod("rpc.returnTypeComplex")
|
|
check s.hasMethod("rpc.testReturns")
|
|
check s.hasMethod("rpc.multiVarsOfOneType")
|
|
check s.hasMethod("rpc.optionalArg")
|
|
check s.hasMethod("rpc.mixedOptionalArg")
|
|
check s.hasMethod("rpc.optionalArgNotBuiltin")
|
|
check s.hasMethod("rpc.optInObj")
|
|
check s.hasMethod("rpc.optionalStringArg")
|
|
|
|
test "Simple paths":
|
|
let r = waitFor s.executeMethod("rpc.simplePath", %[])
|
|
check r == "1"
|
|
|
|
test "Enum param paths":
|
|
block:
|
|
let r = waitFor s.executeMethod("rpc.enumParam", %[%int64(Enum1)])
|
|
check r == "[\"Enum1\"]"
|
|
|
|
expect(JsonRpcError):
|
|
discard waitFor s.executeMethod("rpc.enumParam", %[(int64(42))])
|
|
|
|
test "Different param types":
|
|
let
|
|
inp = %[%1, %"abc"]
|
|
r = waitFor s.executeMethod("rpc.differentParams", inp)
|
|
check r == inp
|
|
|
|
test "Array parameters":
|
|
let r1 = waitFor s.executeMethod("rpc.arrayParam", %[%[1, 2, 3], %"hello"])
|
|
var ckR1 = %[1, 2, 3, 0, 0, 0]
|
|
ckR1.elems.add %"hello"
|
|
check r1 == ckR1
|
|
|
|
test "Seq parameters":
|
|
let r2 = waitFor s.executeMethod("rpc.seqParam", %[%"abc", %[1, 2, 3, 4, 5]])
|
|
var ckR2 = %["abc"]
|
|
for i in 0..4: ckR2.add %(i + 1)
|
|
check r2 == ckR2
|
|
|
|
test "Object parameters":
|
|
let r = waitFor s.executeMethod("rpc.objParam", %[%"abc", testObj])
|
|
check r == testObj
|
|
|
|
test "Simple return types":
|
|
let
|
|
inp = %99
|
|
r1 = waitFor s.executeMethod("rpc.returnTypeSimple", %[%inp])
|
|
check r1 == inp
|
|
|
|
test "Complex return types":
|
|
let
|
|
inp = 99
|
|
r1 = waitFor s.executeMethod("rpc.returnTypeComplex", %[%inp])
|
|
check r1 == %*{"x": %[1, inp, 3], "y": "test"}
|
|
|
|
test "Option types":
|
|
let
|
|
inp1 = MyOptional(maybeInt: some(75))
|
|
inp2 = MyOptional()
|
|
r1 = waitFor s.executeMethod("rpc.optional", %[%inp1])
|
|
r2 = waitFor s.executeMethod("rpc.optional", %[%inp2])
|
|
check r1.string == JrpcConv.encode inp1
|
|
check r2.string == JrpcConv.encode inp2
|
|
|
|
test "Return statement":
|
|
let r = waitFor s.executeMethod("rpc.testReturns", %[])
|
|
check r == JrpcConv.encode 1234
|
|
|
|
test "Runtime errors":
|
|
expect JsonRpcError:
|
|
# root param not array
|
|
discard waitFor s.executeMethod("rpc.arrayParam", %"test")
|
|
expect JsonRpcError:
|
|
# too big for array
|
|
discard waitFor s.executeMethod("rpc.arrayParam", %[%[0, 1, 2, 3, 4, 5, 6], %"hello"])
|
|
expect JsonRpcError:
|
|
# wrong sub parameter type
|
|
discard waitFor s.executeMethod("rpc.arrayParam", %[%"test", %"hello"])
|
|
expect JsonRpcError:
|
|
# wrong param type
|
|
discard waitFor s.executeMethod("rpc.differentParams", %[%"abc", %1])
|
|
|
|
test "Multiple variables of one type":
|
|
let r = waitFor s.executeMethod("rpc.multiVarsOfOneType", %[%"hello", %"world"])
|
|
check r == JrpcConv.encode "hello world"
|
|
|
|
test "Optional arg":
|
|
let
|
|
int1 = MyOptional(maybeInt: some(75))
|
|
int2 = MyOptional(maybeInt: some(117))
|
|
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 == JrpcConv.encode int1
|
|
check r2 == JrpcConv.encode int2
|
|
check r3 == JrpcConv.encode int2
|
|
|
|
test "Optional arg2":
|
|
let r1 = waitFor s.executeMethod("rpc.optionalArg2", %[%"A", %"B"])
|
|
check r1 == JrpcConv.encode "AB"
|
|
|
|
let r2 = waitFor s.executeMethod("rpc.optionalArg2", %[%"A", %"B", newJNull()])
|
|
check r2 == JrpcConv.encode "AB"
|
|
|
|
let r3 = waitFor s.executeMethod("rpc.optionalArg2", %[%"A", %"B", newJNull(), newJNull()])
|
|
check r3 == JrpcConv.encode "AB"
|
|
|
|
let r4 = waitFor s.executeMethod("rpc.optionalArg2", %[%"A", %"B", newJNull(), %"D"])
|
|
check r4 == JrpcConv.encode "ABD"
|
|
|
|
let r5 = waitFor s.executeMethod("rpc.optionalArg2", %[%"A", %"B", %"C", %"D"])
|
|
check r5 == JrpcConv.encode "ABCD"
|
|
|
|
let r6 = waitFor s.executeMethod("rpc.optionalArg2", %[%"A", %"B", %"C", newJNull()])
|
|
check r6 == JrpcConv.encode "ABC"
|
|
|
|
let r7 = waitFor s.executeMethod("rpc.optionalArg2", %[%"A", %"B", %"C"])
|
|
check r7 == JrpcConv.encode "ABC"
|
|
|
|
test "Mixed optional arg":
|
|
var ax = waitFor s.executeMethod("rpc.mixedOptionalArg", %[%10, %11, %"hello", %12, %"world"])
|
|
check ax == JrpcConv.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 == JrpcConv.encode OptionalFields(a: 10, c: "hello")
|
|
|
|
test "Non-built-in optional types":
|
|
let
|
|
t2 = Test2(x: [1, 2, 3], y: "Hello")
|
|
testOpts1 = MyOptionalNotBuiltin(val: some(t2))
|
|
testOpts2 = MyOptionalNotBuiltin()
|
|
var r = waitFor s.executeMethod("rpc.optionalArgNotBuiltin", %[%testOpts1])
|
|
check r == JrpcConv.encode t2.y
|
|
var r2 = waitFor s.executeMethod("rpc.optionalArgNotBuiltin", %[])
|
|
check r2 == JrpcConv.encode "Empty1"
|
|
var r3 = waitFor s.executeMethod("rpc.optionalArgNotBuiltin", %[%testOpts2])
|
|
check r3 == JrpcConv.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 == JrpcConv.encode 1
|
|
let opts2 = parseJson("""{"o2": true}""")
|
|
var r2 = waitFor s.executeMethod("rpc.optInObj", %[%"0x31ded", opts2])
|
|
check r2 == JrpcConv.encode 2
|
|
let opts3 = parseJson("""{"o3": true}""")
|
|
var r3 = waitFor s.executeMethod("rpc.optInObj", %[%"0x31ded", opts3])
|
|
check r3 == JrpcConv.encode 4
|
|
# Combinations
|
|
let opts4 = parseJson("""{"o1": true, "o3": true}""")
|
|
var r4 = waitFor s.executeMethod("rpc.optInObj", %[%"0x31ded", opts4])
|
|
check r4 == JrpcConv.encode 5
|
|
let opts5 = parseJson("""{"o2": true, "o3": true}""")
|
|
var r5 = waitFor s.executeMethod("rpc.optInObj", %[%"0x31ded", opts5])
|
|
check r5 == JrpcConv.encode 6
|
|
let opts6 = parseJson("""{"o1": true, "o2": true}""")
|
|
var r6 = waitFor s.executeMethod("rpc.optInObj", %[%"0x31ded", opts6])
|
|
check r6 == JrpcConv.encode 3
|
|
|
|
test "Optional String Arg":
|
|
let
|
|
data = some("some string")
|
|
r1 = waitFor s.executeMethod("rpc.optionalStringArg", %[%data])
|
|
r2 = waitFor s.executeMethod("rpc.optionalStringArg", %[])
|
|
r3 = waitFor s.executeMethod("rpc.optionalStringArg", %[newJNull()])
|
|
check r1 == %data.get()
|
|
check r2 == %"nope"
|
|
check r3 == %"nope"
|
|
|
|
test "Null object fields":
|
|
let r = waitFor s.executeMethod("echo", """{"car":{"color":"red","wheel":null}}""".JsonString)
|
|
check r == """{"color":"red","wheel":0}"""
|
|
|
|
let x = waitFor s.executeMethod("echo", """{"car":{"color":null,"wheel":77}}""".JsonString)
|
|
check x == """{"color":"","wheel":77}"""
|
|
|
|
let y = waitFor s.executeMethod("echo", """{"car":null}""".JsonString)
|
|
check y == """{"color":"","wheel":0}"""
|
|
|
|
let z = waitFor s.executeMethod("echo", "[null]".JsonString)
|
|
check z == """{"color":"","wheel":0}"""
|
|
|
|
s.stop()
|
|
waitFor s.closeWait()
|