allow optional parameters in the middle of parameters list

This commit is contained in:
andri lim 2018-11-16 20:07:39 +07:00
parent ee3ba6d5ad
commit a1fe7d57b4
3 changed files with 89 additions and 25 deletions

View File

@ -74,7 +74,7 @@ Here is a more complex parameter example:
```nim
type
HeaderKind = enum hkOne, hkTwo, hkThree
Header = ref object
kind: HeaderKind
size: int64
@ -88,7 +88,7 @@ type
name: string
router.rpc("updateData") do(myObj: MyObject, newData: DataBlob) -> DataBlob:
result = myObj.data
result = myObj.data
myObj.data = newData
```
@ -97,6 +97,37 @@ At runtime, the json is checked to ensure that it contains the correct number an
Compiling with `-d:nimDumpRpcs` will show the output code for the RPC call. To see the output of the `async` generation, add `-d:nimDumpAsync`.
### Special type : `Option[T] `
Option[T] is a special type indicating that parameter may have value or not.
* If optional parameters located in the middle of parameters list, you set it to `null` to tell the server that it has no value.
* If optional parameters located at the end of parameter list and there are no more mandatory parameters after that, those optional parameters can be omitted altogether.
```Nim
# d can be omitted, b should use null to indicate it has no value
router.rpc("updateData") do(a: int, b: Option[int], c: string, d: Option[T]):
if b.isSome:
# do something
else:
# do something else
```
* If Option[T] used as return type, it also denotes the returned value might not available.
```Nim
router.rpc("getData") do(name: string) -> Option[int]:
if name == "monkey":
result = some(4)
```
* If Option[T] used as field type of an object, it also tell us that field might be present or not, and the rpc mechanism will automatically set it to some value if it available.
```Nim
type
MyOptional = object
maybeInt: Option[int]
```
## Marshalling
Note that `array` parameters are explicitly checked for length, and will return an error node if the length differs from their declaration size.
@ -202,7 +233,7 @@ This `route` variant will handle all the conversion of `string` to `JsonNode` an
#### Returns
`Future[string]`: This will be the stringified JSON response, which can be the JSON RPC result or a JSON wrapped error.
### `route` by `JsonNode`
This variant allows simplified processing if you already have a `JsonNode`. However if the required fields are not present within `node`, exceptions will be raised.
@ -216,7 +247,7 @@ This variant allows simplified processing if you already have a `JsonNode`. Howe
#### Returns
`Future[JsonNode]`: The JSON RPC result or a JSON wrapped error.
### `tryRoute`
This `route` variant allows you to invoke a call if possible, without raising an exception.
@ -232,7 +263,7 @@ This `route` variant allows you to invoke a call if possible, without raising an
#### Returns
`bool`: `true` if the `method` field provided in `node` matches an available route. Returns `false` when the `method` cannot be found, or if `method` or `params` field cannot be found within `node`.
To see the result of a call, we need to provide Json in the expected format.
Here's an example of how that looks by manually creating the JSON. Later we will see the helper utilities that make this easier.
@ -280,7 +311,7 @@ import json_rpc/rpcserver
# Create a socket server for transport
var srv = newRpcSocketServer("localhost", Port(8585))
# srv.rpc is a shortcut for srv.router.rpc
# srv.rpc is a shortcut for srv.router.rpc
srv.rpc("hello") do(input: string) -> string:
result = "Hello " & input
@ -413,3 +444,4 @@ Licensed and distributed under either of
at your option. This file may not be copied, modified, or distributed except according to those terms.

View File

@ -151,6 +151,13 @@ iterator paramsIter(params: NimNode): tuple[name, ntype: NimNode] =
for j in 0 ..< arg.len-2:
yield (arg[j], argType)
iterator paramsRevIter(params: NimNode): tuple[name, ntype: NimNode] =
for i in countDown(params.len-1,0):
let arg = params[i]
let argType = arg[^2]
for j in 0 ..< arg.len-2:
yield (arg[j], argType)
proc isOptionalArg(typeNode: NimNode): bool =
if typeNode.kind != nnkBracketExpr:
result = false
@ -159,20 +166,12 @@ proc isOptionalArg(typeNode: NimNode): bool =
result = typeNode[0].kind == nnkIdent and
typeNode[0].strVal == "Option"
proc expectOptionalArrayLen(node, parameters: NimNode, jsonIdent: untyped, maxLength: int) =
var
meetOptional = false
minLength = 0
idx = 0
proc expectOptionalArrayLen(node, parameters: NimNode, jsonIdent: untyped, maxLength: int): int =
var minLength = maxLength
for arg, typ in paramsIter(parameters):
if typ.isOptionalArg:
if not meetOptional: minLength = idx
meetOptional = true
else:
if meetOptional:
macros.error("cannot have regular parameters: `" & $arg & "` after optional one", arg)
inc idx
for arg, typ in paramsRevIter(parameters):
if not typ.isOptionalArg: break
dec minLength
let
identStr = jsonIdent.repr
@ -184,6 +183,8 @@ proc expectOptionalArrayLen(node, parameters: NimNode, jsonIdent: untyped, maxLe
raise newException(ValueError, `expectedStr` & $`jsonIdent`.len)
)
result = minLength
proc containsOptionalArg(params: NimNode): bool =
for n, t in paramsIter(params):
if t.isOptionalArg:
@ -214,10 +215,10 @@ proc jsonToNim*(params, jsonIdent: NimNode): NimNode =
# Add code to verify input and load params into Nim types
result = newStmtList()
if not params.isNil:
let paramsWithOptionalArg = params.containsOptionalArg()
if paramsWithOptionalArg:
var minLength = 0
if params.containsOptionalArg():
# more elaborate parameters array check
result.expectOptionalArrayLen(params, jsonIdent,
minLength = result.expectOptionalArrayLen(params, jsonIdent,
calcActualParamCount(params))
else:
# simple parameters array length check
@ -241,11 +242,18 @@ proc jsonToNim*(params, jsonIdent: NimNode): NimNode =
if paramType.isOptionalArg:
let
nullAble = pos < minLength
innerType = paramType[1]
innerNode = jsonToNim(paramIdent, innerType, jsonElement, paramName, true)
result.add(quote do:
if `jsonIdent`.len >= `pos`: `innerNode`
)
if nullAble:
result.add(quote do:
if `jsonElement`.kind != JNull: `innerNode`
)
else:
result.add(quote do:
if `jsonIdent`.len >= `pos`: `innerNode`
)
else:
# unpack Nim type and assign from json
result.add jsonToNim(paramIdent, paramType, jsonElement, paramName)

View File

@ -77,6 +77,23 @@ s.rpc("rpc.optionalArg") do(val: int, obj: Option[MyOptional]) -> MyOptional:
else:
result = MyOptional(maybeInt: some(val))
type
OptionalFields = object
a: int
b: Option[int]
c: string
d: Option[int]
e: Option[string]
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
# Tests
suite "Server types":
test "On macro registration":
@ -170,5 +187,12 @@ suite "Server types":
check r1 == %int1
check r2 == %int2
test "mixed optional arg":
var ax = waitFor rpcMixedOptionalArg(%[%10, %11, %"hello", %12, %"world"])
check ax == %OptionalFields(a: 10, b: some(11), c: "hello", d: some(12), e: some("world"))
var bx = waitFor rpcMixedOptionalArg(%[%10, newJNull(), %"hello"])
check bx == %OptionalFields(a: 10, c: "hello")
s.stop()
waitFor s.closeWait()