mirror of
https://github.com/logos-storage/nim-json-rpc.git
synced 2026-01-08 00:23:12 +00:00
Add framework to support more optional types (#206)
* Add framework to support more optional types This PR add a framework to rpc server by using more templates than macros to handle optional types. Now rpc server recognize both `std/options` and `results.Opt` as optional type for the parameters. If needed user can add more optional types by overloading `rpc_isOptional` template. Now aliases to optional types also works. * Don't expose types used internally by the wrapper
This commit is contained in:
parent
2157e8980d
commit
165e54133d
@ -12,6 +12,7 @@ import
|
||||
stew/[byteutils, objects],
|
||||
json_serialization,
|
||||
json_serialization/std/[options],
|
||||
json_serialization/stew/results,
|
||||
../errors,
|
||||
./jrpc_sys,
|
||||
./shared_wrapper,
|
||||
@ -20,8 +21,26 @@ import
|
||||
export
|
||||
jsonmarshal
|
||||
|
||||
type
|
||||
RpcSetup = object
|
||||
numFields: int
|
||||
numOptionals: int
|
||||
minLength: int
|
||||
|
||||
{.push gcsafe, raises: [].}
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# Optional resolvers
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
template rpc_isOptional(_: auto): bool = false
|
||||
template rpc_isOptional[T](_: results.Opt[T]): bool = true
|
||||
template rpc_isOptional[T](_: options.Option[T]): bool = true
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# Run time helpers
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
proc unpackArg(args: JsonString, argName: string, argType: type): argType
|
||||
{.gcsafe, raises: [JsonRpcError].} =
|
||||
## This where input parameters are decoded from JSON into
|
||||
@ -33,78 +52,95 @@ proc unpackArg(args: JsonString, argName: string, argType: type): argType
|
||||
"Parameter [" & argName & "] of type '" &
|
||||
$argType & "' could not be decoded: " & err.msg)
|
||||
|
||||
proc expectArrayLen(node, paramsIdent: NimNode, length: int) =
|
||||
## Make sure positional params meets the handler expectation
|
||||
let
|
||||
expected = "Expected " & $length & " Json parameter(s) but got "
|
||||
node.add quote do:
|
||||
if `paramsIdent`.positional.len != `length`:
|
||||
raise newException(RequestDecodeError, `expected` &
|
||||
$`paramsIdent`.positional.len)
|
||||
# ------------------------------------------------------------------------------
|
||||
# Compile time helpers
|
||||
# ------------------------------------------------------------------------------
|
||||
func hasOptionals(setup: RpcSetup): bool {.compileTime.} =
|
||||
setup.numOptionals > 0
|
||||
|
||||
iterator paramsRevIter(params: NimNode): tuple[name, ntype: NimNode] =
|
||||
## Bacward iterator of handler parameters
|
||||
for i in countdown(params.len-1,1):
|
||||
let arg = params[i]
|
||||
let argType = arg[^2]
|
||||
for j in 0 ..< arg.len-2:
|
||||
yield (arg[j], argType)
|
||||
func rpcSetupImpl[T](val: T): RpcSetup {.compileTime.} =
|
||||
## Counting number of fields, optional fields, and
|
||||
## minimum fields needed by a rpc method
|
||||
mixin rpc_isOptional
|
||||
var index = 1
|
||||
for field in fields(val):
|
||||
inc result.numFields
|
||||
if rpc_isOptional(field):
|
||||
inc result.numOptionals
|
||||
else:
|
||||
result.minLength = index
|
||||
inc index
|
||||
|
||||
proc isOptionalArg(typeNode: NimNode): bool =
|
||||
# typed version
|
||||
(typeNode.kind == nnkCall and
|
||||
typeNode.len > 1 and
|
||||
typeNode[1].kind in {nnkIdent, nnkSym} and
|
||||
typeNode[1].strVal == "Option") or
|
||||
|
||||
# untyped version
|
||||
(typeNode.kind == nnkBracketExpr and
|
||||
typeNode[0].kind == nnkIdent and
|
||||
typeNode[0].strVal == "Option")
|
||||
|
||||
proc expectOptionalArrayLen(node: NimNode,
|
||||
parameters: NimNode,
|
||||
paramsIdent: NimNode,
|
||||
maxLength: int): int =
|
||||
## Validate if parameters sent by client meets
|
||||
## minimum expectation of server
|
||||
var minLength = maxLength
|
||||
|
||||
for arg, typ in paramsRevIter(parameters):
|
||||
if not typ.isOptionalArg: break
|
||||
dec minLength
|
||||
func rpcSetupFromType(T: type): RpcSetup {.compileTime.} =
|
||||
var dummy: T
|
||||
rpcSetupImpl(dummy)
|
||||
|
||||
template expectOptionalParamsLen(params: RequestParamsRx,
|
||||
minLength, maxLength: static[int]) =
|
||||
## Make sure positional params with optional fields
|
||||
## meets the handler expectation
|
||||
let
|
||||
expected = "Expected at least " & $minLength & " and maximum " &
|
||||
$maxLength & " Json parameter(s) but got "
|
||||
|
||||
node.add quote do:
|
||||
if `paramsIdent`.positional.len < `minLength`:
|
||||
raise newException(RequestDecodeError, `expected` &
|
||||
$`paramsIdent`.positional.len)
|
||||
if params.positional.len < minLength:
|
||||
raise newException(RequestDecodeError,
|
||||
expected & $params.positional.len)
|
||||
|
||||
minLength
|
||||
template expectParamsLen(params: RequestParamsRx, length: static[int]) =
|
||||
## Make sure positional params meets the handler expectation
|
||||
let
|
||||
expected = "Expected " & $length & " Json parameter(s) but got "
|
||||
|
||||
proc containsOptionalArg(params: NimNode): bool =
|
||||
## Is one of handler parameters an optional?
|
||||
for n, t in paramsIter(params):
|
||||
if t.isOptionalArg:
|
||||
return true
|
||||
if params.positional.len != length:
|
||||
raise newException(RequestDecodeError,
|
||||
expected & $params.positional.len)
|
||||
|
||||
proc jsonToNim(paramVar: NimNode,
|
||||
paramType: NimNode,
|
||||
paramVal: NimNode,
|
||||
paramName: string): NimNode =
|
||||
template setupPositional(setup: static[RpcSetup], params: RequestParamsRx) =
|
||||
## Generate code to check positional params length
|
||||
when setup.hasOptionals:
|
||||
expectOptionalParamsLen(params, setup.minLength, setup.numFields)
|
||||
else:
|
||||
expectParamsLen(params, setup.numFields)
|
||||
|
||||
template len(params: RequestParamsRx): int =
|
||||
params.positional.len
|
||||
|
||||
template notNull(params: RequestParamsRx, pos: int): bool =
|
||||
params.positional[pos].kind != JsonValueKind.Null
|
||||
|
||||
template val(params: RequestParamsRx, pos: int): auto =
|
||||
params.positional[pos].param
|
||||
|
||||
template unpackPositional(params: RequestParamsRx,
|
||||
paramVar: auto,
|
||||
paramName: static[string],
|
||||
pos: static[int],
|
||||
setup: static[RpcSetup],
|
||||
paramType: type) =
|
||||
## Convert a positional parameter from Json into Nim
|
||||
result = quote do:
|
||||
`paramVar` = `unpackArg`(`paramVal`, `paramName`, `paramType`)
|
||||
|
||||
proc calcActualParamCount(params: NimNode): int =
|
||||
## this proc is needed to calculate the actual parameter count
|
||||
## not matter what is the declaration form
|
||||
## e.g. (a: U, b: V) vs. (a, b: T)
|
||||
for n, t in paramsIter(params):
|
||||
inc result
|
||||
template innerNode() =
|
||||
paramVar = unpackArg(params.val(pos), paramName, paramType)
|
||||
|
||||
# e.g. (A: int, B: Option[int], C: string, D: Option[int], E: Option[string])
|
||||
when rpc_isOptional(paramVar):
|
||||
when pos >= setup.minLength:
|
||||
# allow both empty and null after mandatory args
|
||||
# D & E fall into this category
|
||||
if params.len > pos and params.notNull(pos):
|
||||
innerNode()
|
||||
else:
|
||||
# allow null param for optional args between/before mandatory args
|
||||
# B fall into this category
|
||||
if params.notNull(pos):
|
||||
innerNode()
|
||||
else:
|
||||
# mandatory args
|
||||
# A and C fall into this category
|
||||
# unpack Nim type and assign from json
|
||||
if params.notNull(pos):
|
||||
innerNode()
|
||||
|
||||
proc makeType(typeName, params: NimNode): NimNode =
|
||||
## Generate type section contains an object definition
|
||||
@ -120,60 +156,6 @@ proc makeType(typeName, params: NimNode): NimNode =
|
||||
obj[2] = recList
|
||||
typeSec
|
||||
|
||||
proc setupPositional(params, paramsIdent: NimNode): (NimNode, int) =
|
||||
## Generate code to check positional params length
|
||||
var
|
||||
minLength = 0
|
||||
code = newStmtList()
|
||||
|
||||
if params.containsOptionalArg():
|
||||
# more elaborate parameters array check
|
||||
minLength = code.expectOptionalArrayLen(params, paramsIdent,
|
||||
calcActualParamCount(params))
|
||||
else:
|
||||
# simple parameters array length check
|
||||
code.expectArrayLen(paramsIdent, calcActualParamCount(params))
|
||||
|
||||
(code, minLength)
|
||||
|
||||
proc setupPositional(code: NimNode;
|
||||
paramsObj, paramsIdent, paramIdent, paramType: NimNode;
|
||||
pos, minLength: int) =
|
||||
## processing multiple params of one type
|
||||
## e.g. (a, b: T), including common (a: U, b: V) form
|
||||
let
|
||||
paramName = $paramIdent
|
||||
paramVal = quote do:
|
||||
`paramsIdent`.positional[`pos`].param
|
||||
paramKind = quote do:
|
||||
`paramsIdent`.positional[`pos`].kind
|
||||
paramVar = quote do:
|
||||
`paramsObj`.`paramIdent`
|
||||
innerNode = jsonToNim(paramVar, paramType, paramVal, paramName)
|
||||
|
||||
# e.g. (A: int, B: Option[int], C: string, D: Option[int], E: Option[string])
|
||||
if paramType.isOptionalArg:
|
||||
if pos >= minLength:
|
||||
# allow both empty and null after mandatory args
|
||||
# D & E fall into this category
|
||||
code.add quote do:
|
||||
if `paramsIdent`.positional.len > `pos` and
|
||||
`paramKind` != JsonValueKind.Null:
|
||||
`innerNode`
|
||||
else:
|
||||
# allow null param for optional args between/before mandatory args
|
||||
# B fall into this category
|
||||
code.add quote do:
|
||||
if `paramKind` != JsonValueKind.Null:
|
||||
`innerNode`
|
||||
else:
|
||||
# mandatory args
|
||||
# A and C fall into this category
|
||||
# unpack Nim type and assign from json
|
||||
code.add quote do:
|
||||
if `paramKind` != JsonValueKind.Null:
|
||||
`innerNode`
|
||||
|
||||
proc makeParams(retType: NimNode, params: NimNode): seq[NimNode] =
|
||||
## Convert rpc params into handler params
|
||||
result.add retType
|
||||
@ -262,13 +244,14 @@ proc wrapServerHandler*(methName: string, params, procBody, procWrapper: NimNode
|
||||
paramsIdent = genSym(nskParam, "rpcParams")
|
||||
returnType = params[0]
|
||||
hasParams = params.len > 1 # not including return type
|
||||
(posSetup, minLength) = setupPositional(params, paramsIdent)
|
||||
rpcSetup = ident"rpcSetup"
|
||||
handler = makeHandler(handlerName, params, procBody, returnType)
|
||||
named = setupNamed(paramsObj, paramsIdent, params)
|
||||
|
||||
if hasParams:
|
||||
setup.add makeType(typeName, params)
|
||||
setup.add quote do:
|
||||
const `rpcSetup` = rpcSetupFromType(`typeName`)
|
||||
var `paramsObj`: `typeName`
|
||||
|
||||
# unpack each parameter and provide assignments
|
||||
@ -278,8 +261,15 @@ proc wrapServerHandler*(methName: string, params, procBody, procWrapper: NimNode
|
||||
executeParams: seq[NimNode]
|
||||
|
||||
for paramIdent, paramType in paramsIter(params):
|
||||
positional.setupPositional(paramsObj, paramsIdent,
|
||||
paramIdent, paramType, pos, minLength)
|
||||
let paramName = $paramIdent
|
||||
positional.add quote do:
|
||||
unpackPositional(`paramsIdent`,
|
||||
`paramsObj`.`paramIdent`,
|
||||
`paramName`,
|
||||
`pos`,
|
||||
`rpcSetup`,
|
||||
`paramType`)
|
||||
|
||||
executeParams.add quote do:
|
||||
`paramsObj`.`paramIdent`
|
||||
inc pos
|
||||
@ -287,7 +277,7 @@ proc wrapServerHandler*(methName: string, params, procBody, procWrapper: NimNode
|
||||
if hasParams:
|
||||
setup.add quote do:
|
||||
if `paramsIdent`.kind == rpPositional:
|
||||
`posSetup`
|
||||
setupPositional(`rpcSetup`, `paramsIdent`)
|
||||
`positional`
|
||||
else:
|
||||
`named`
|
||||
@ -297,7 +287,7 @@ proc wrapServerHandler*(methName: string, params, procBody, procWrapper: NimNode
|
||||
# still be checked (RPC spec)
|
||||
setup.add quote do:
|
||||
if `paramsIdent`.kind == rpPositional:
|
||||
`posSetup`
|
||||
expectParamsLen(`paramsIdent`, 0)
|
||||
|
||||
let
|
||||
awaitedResult = ident "awaitedResult"
|
||||
|
||||
@ -137,7 +137,7 @@ proc route*(router: RpcRouter, req: RequestRx):
|
||||
let methodName = req.meth.get # this Opt already validated
|
||||
debug "Error occurred within RPC",
|
||||
methodName = methodName, err = err.msg
|
||||
return serverError(methodName & " raised an exception",
|
||||
return serverError("`" & methodName & "` raised an exception",
|
||||
escapeJson(err.msg).JsonString).
|
||||
wrapError(req.id)
|
||||
|
||||
|
||||
@ -56,7 +56,7 @@ suite "Socket batch call":
|
||||
check r[1].result.string == "\"apple: green\""
|
||||
|
||||
check r[2].error.isSome
|
||||
check r[2].error.get == """{"code":-32000,"message":"get_except raised an exception","data":"get_except error"}"""
|
||||
check r[2].error.get == """{"code":-32000,"message":"`get_except` raised an exception","data":"get_except error"}"""
|
||||
check r[2].result.string.len == 0
|
||||
|
||||
test "rpc call after batch call":
|
||||
@ -95,7 +95,7 @@ suite "HTTP batch call":
|
||||
check r[1].result.string == "\"apple: green\""
|
||||
|
||||
check r[2].error.isSome
|
||||
check r[2].error.get == """{"code":-32000,"message":"get_except raised an exception","data":"get_except error"}"""
|
||||
check r[2].error.get == """{"code":-32000,"message":"`get_except` raised an exception","data":"get_except error"}"""
|
||||
check r[2].result.string.len == 0
|
||||
|
||||
test "rpc call after batch call":
|
||||
@ -134,7 +134,7 @@ suite "Websocket batch call":
|
||||
check r[1].result.string == "\"apple: green\""
|
||||
|
||||
check r[2].error.isSome
|
||||
check r[2].error.get == """{"code":-32000,"message":"get_except raised an exception","data":"get_except error"}"""
|
||||
check r[2].error.get == """{"code":-32000,"message":"`get_except` raised an exception","data":"get_except error"}"""
|
||||
check r[2].result.string.len == 0
|
||||
|
||||
test "rpc call after batch call":
|
||||
|
||||
@ -10,11 +10,39 @@
|
||||
import
|
||||
unittest2,
|
||||
../json_rpc/router,
|
||||
json_serialization/std/options
|
||||
json_serialization/std/options,
|
||||
json_serialization/stew/results
|
||||
|
||||
var server = RpcRouter()
|
||||
|
||||
server.rpc("optional") do(A: int, B: Option[int], C: string, D: Option[int], E: Option[string]) -> string:
|
||||
type
|
||||
OptAlias[T] = results.Opt[T]
|
||||
|
||||
server.rpc("std_option") do(A: int, B: Option[int], C: string, D: Option[int], E: Option[string]) -> string:
|
||||
var res = "A: " & $A
|
||||
res.add ", B: " & $B.get(99)
|
||||
res.add ", C: " & C
|
||||
res.add ", D: " & $D.get(77)
|
||||
res.add ", E: " & E.get("none")
|
||||
return res
|
||||
|
||||
server.rpc("results_opt") do(A: int, B: Opt[int], C: string, D: Opt[int], E: Opt[string]) -> string:
|
||||
var res = "A: " & $A
|
||||
res.add ", B: " & $B.get(99)
|
||||
res.add ", C: " & C
|
||||
res.add ", D: " & $D.get(77)
|
||||
res.add ", E: " & E.get("none")
|
||||
return res
|
||||
|
||||
server.rpc("mixed_opt") do(A: int, B: Opt[int], C: string, D: Option[int], E: Opt[string]) -> string:
|
||||
var res = "A: " & $A
|
||||
res.add ", B: " & $B.get(99)
|
||||
res.add ", C: " & C
|
||||
res.add ", D: " & $D.get(77)
|
||||
res.add ", E: " & E.get("none")
|
||||
return res
|
||||
|
||||
server.rpc("alias_opt") do(A: int, B: OptAlias[int], C: string, D: Option[int], E: OptAlias[string]) -> string:
|
||||
var res = "A: " & $A
|
||||
res.add ", B: " & $B.get(99)
|
||||
res.add ", C: " & C
|
||||
@ -38,6 +66,54 @@ func req(meth: string, params: string): string =
|
||||
"""{"jsonrpc":"2.0", "id":0, "method": """ &
|
||||
"\"" & meth & "\", \"params\": " & params & "}"
|
||||
|
||||
template test_optional(meth: static[string]) =
|
||||
test meth & " B E, positional":
|
||||
let n = req(meth, "[44, null, \"apple\", 33]")
|
||||
let res = waitFor server.route(n)
|
||||
check res == """{"jsonrpc":"2.0","id":0,"result":"A: 44, B: 99, C: apple, D: 33, E: none"}"""
|
||||
|
||||
test meth & " B D E, positional":
|
||||
let n = req(meth, "[44, null, \"apple\"]")
|
||||
let res = waitFor server.route(n)
|
||||
check res == """{"jsonrpc":"2.0","id":0,"result":"A: 44, B: 99, C: apple, D: 77, E: none"}"""
|
||||
|
||||
test meth & " D E, positional":
|
||||
let n = req(meth, "[44, 567, \"apple\"]")
|
||||
let res = waitFor server.route(n)
|
||||
check res == """{"jsonrpc":"2.0","id":0,"result":"A: 44, B: 567, C: apple, D: 77, E: none"}"""
|
||||
|
||||
test meth & " D wrong E, positional":
|
||||
let n = req(meth, "[44, 567, \"apple\", \"banana\"]")
|
||||
let res = waitFor server.route(n)
|
||||
when meth == "std_option":
|
||||
check res == """{"jsonrpc":"2.0","id":0,"error":{"code":-32000,"message":"`std_option` raised an exception","data":"Parameter [D] of type 'Option[system.int]' could not be decoded: number expected"}}"""
|
||||
elif meth == "results_opt":
|
||||
check res == """{"jsonrpc":"2.0","id":0,"error":{"code":-32000,"message":"`results_opt` raised an exception","data":"Parameter [D] of type 'Opt[system.int]' could not be decoded: number expected"}}"""
|
||||
elif meth == "mixed_opt":
|
||||
check res == """{"jsonrpc":"2.0","id":0,"error":{"code":-32000,"message":"`mixed_opt` raised an exception","data":"Parameter [D] of type 'Option[system.int]' could not be decoded: number expected"}}"""
|
||||
else:
|
||||
check res == """{"jsonrpc":"2.0","id":0,"error":{"code":-32000,"message":"`alias_opt` raised an exception","data":"Parameter [D] of type 'Option[system.int]' could not be decoded: number expected"}}"""
|
||||
|
||||
test meth & " D extra, positional":
|
||||
let n = req(meth, "[44, 567, \"apple\", 999, \"banana\", true]")
|
||||
let res = waitFor server.route(n)
|
||||
check res == """{"jsonrpc":"2.0","id":0,"result":"A: 44, B: 567, C: apple, D: 999, E: banana"}"""
|
||||
|
||||
test meth & " B D E, named":
|
||||
let n = req(meth, """{"A": 33, "C":"banana" }""")
|
||||
let res = waitFor server.route(n)
|
||||
check res == """{"jsonrpc":"2.0","id":0,"result":"A: 33, B: 99, C: banana, D: 77, E: none"}"""
|
||||
|
||||
test meth & " B E, D front, named":
|
||||
let n = req(meth, """{"D": 8887, "A": 33, "C":"banana" }""")
|
||||
let res = waitFor server.route(n)
|
||||
check res == """{"jsonrpc":"2.0","id":0,"result":"A: 33, B: 99, C: banana, D: 8887, E: none"}"""
|
||||
|
||||
test meth & " B E, D front, extra X, named":
|
||||
let n = req(meth, """{"D": 8887, "X": false , "A": 33, "C":"banana"}""")
|
||||
let res = waitFor server.route(n)
|
||||
check res == """{"jsonrpc":"2.0","id":0,"result":"A: 33, B: 99, C: banana, D: 8887, E: none"}"""
|
||||
|
||||
suite "rpc router":
|
||||
test "no params":
|
||||
let n = req("noParams", "[]")
|
||||
@ -47,47 +123,12 @@ suite "rpc router":
|
||||
test "no params with params":
|
||||
let n = req("noParams", "[123]")
|
||||
let res = waitFor server.route(n)
|
||||
check res == """{"jsonrpc":"2.0","id":0,"error":{"code":-32000,"message":"noParams raised an exception","data":"Expected 0 Json parameter(s) but got 1"}}"""
|
||||
check res == """{"jsonrpc":"2.0","id":0,"error":{"code":-32000,"message":"`noParams` raised an exception","data":"Expected 0 Json parameter(s) but got 1"}}"""
|
||||
|
||||
test "optional B E, positional":
|
||||
let n = req("optional", "[44, null, \"apple\", 33]")
|
||||
let res = waitFor server.route(n)
|
||||
check res == """{"jsonrpc":"2.0","id":0,"result":"A: 44, B: 99, C: apple, D: 33, E: none"}"""
|
||||
|
||||
test "optional B D E, positional":
|
||||
let n = req("optional", "[44, null, \"apple\"]")
|
||||
let res = waitFor server.route(n)
|
||||
check res == """{"jsonrpc":"2.0","id":0,"result":"A: 44, B: 99, C: apple, D: 77, E: none"}"""
|
||||
|
||||
test "optional D E, positional":
|
||||
let n = req("optional", "[44, 567, \"apple\"]")
|
||||
let res = waitFor server.route(n)
|
||||
check res == """{"jsonrpc":"2.0","id":0,"result":"A: 44, B: 567, C: apple, D: 77, E: none"}"""
|
||||
|
||||
test "optional D wrong E, positional":
|
||||
let n = req("optional", "[44, 567, \"apple\", \"banana\"]")
|
||||
let res = waitFor server.route(n)
|
||||
check res == """{"jsonrpc":"2.0","id":0,"error":{"code":-32000,"message":"optional raised an exception","data":"Parameter [D] of type 'Option[system.int]' could not be decoded: number expected"}}"""
|
||||
|
||||
test "optional D extra, positional":
|
||||
let n = req("optional", "[44, 567, \"apple\", 999, \"banana\", true]")
|
||||
let res = waitFor server.route(n)
|
||||
check res == """{"jsonrpc":"2.0","id":0,"result":"A: 44, B: 567, C: apple, D: 999, E: banana"}"""
|
||||
|
||||
test "optional B D E, named":
|
||||
let n = req("optional", """{"A": 33, "C":"banana" }""")
|
||||
let res = waitFor server.route(n)
|
||||
check res == """{"jsonrpc":"2.0","id":0,"result":"A: 33, B: 99, C: banana, D: 77, E: none"}"""
|
||||
|
||||
test "optional B E, D front, named":
|
||||
let n = req("optional", """{"D": 8887, "A": 33, "C":"banana" }""")
|
||||
let res = waitFor server.route(n)
|
||||
check res == """{"jsonrpc":"2.0","id":0,"result":"A: 33, B: 99, C: banana, D: 8887, E: none"}"""
|
||||
|
||||
test "optional B E, D front, extra X, named":
|
||||
let n = req("optional", """{"D": 8887, "X": false , "A": 33, "C":"banana"}""")
|
||||
let res = waitFor server.route(n)
|
||||
check res == """{"jsonrpc":"2.0","id":0,"result":"A: 33, B: 99, C: banana, D: 8887, E: none"}"""
|
||||
test_optional("std_option")
|
||||
test_optional("results_opt")
|
||||
test_optional("mixed_opt")
|
||||
test_optional("alias_opt")
|
||||
|
||||
test "empty params":
|
||||
let n = req("emptyParams", "[]")
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user