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 ```nim
type type
HeaderKind = enum hkOne, hkTwo, hkThree HeaderKind = enum hkOne, hkTwo, hkThree
Header = ref object Header = ref object
kind: HeaderKind kind: HeaderKind
size: int64 size: int64
@ -88,7 +88,7 @@ type
name: string name: string
router.rpc("updateData") do(myObj: MyObject, newData: DataBlob) -> DataBlob: router.rpc("updateData") do(myObj: MyObject, newData: DataBlob) -> DataBlob:
result = myObj.data result = myObj.data
myObj.data = newData 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`. 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 ## Marshalling
Note that `array` parameters are explicitly checked for length, and will return an error node if the length differs from their declaration size. 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 #### Returns
`Future[string]`: This will be the stringified JSON response, which can be the JSON RPC result or a JSON wrapped error. `Future[string]`: This will be the stringified JSON response, which can be the JSON RPC result or a JSON wrapped error.
### `route` by `JsonNode` ### `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. 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 #### Returns
`Future[JsonNode]`: The JSON RPC result or a JSON wrapped error. `Future[JsonNode]`: The JSON RPC result or a JSON wrapped error.
### `tryRoute` ### `tryRoute`
This `route` variant allows you to invoke a call if possible, without raising an exception. 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 #### 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`. `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. 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. 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 # Create a socket server for transport
var srv = newRpcSocketServer("localhost", Port(8585)) 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: srv.rpc("hello") do(input: string) -> string:
result = "Hello " & input 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. 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: for j in 0 ..< arg.len-2:
yield (arg[j], argType) 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 = proc isOptionalArg(typeNode: NimNode): bool =
if typeNode.kind != nnkBracketExpr: if typeNode.kind != nnkBracketExpr:
result = false result = false
@ -159,20 +166,12 @@ proc isOptionalArg(typeNode: NimNode): bool =
result = typeNode[0].kind == nnkIdent and result = typeNode[0].kind == nnkIdent and
typeNode[0].strVal == "Option" typeNode[0].strVal == "Option"
proc expectOptionalArrayLen(node, parameters: NimNode, jsonIdent: untyped, maxLength: int) = proc expectOptionalArrayLen(node, parameters: NimNode, jsonIdent: untyped, maxLength: int): int =
var var minLength = maxLength
meetOptional = false
minLength = 0
idx = 0
for arg, typ in paramsIter(parameters): for arg, typ in paramsRevIter(parameters):
if typ.isOptionalArg: if not typ.isOptionalArg: break
if not meetOptional: minLength = idx dec minLength
meetOptional = true
else:
if meetOptional:
macros.error("cannot have regular parameters: `" & $arg & "` after optional one", arg)
inc idx
let let
identStr = jsonIdent.repr identStr = jsonIdent.repr
@ -184,6 +183,8 @@ proc expectOptionalArrayLen(node, parameters: NimNode, jsonIdent: untyped, maxLe
raise newException(ValueError, `expectedStr` & $`jsonIdent`.len) raise newException(ValueError, `expectedStr` & $`jsonIdent`.len)
) )
result = minLength
proc containsOptionalArg(params: NimNode): bool = proc containsOptionalArg(params: NimNode): bool =
for n, t in paramsIter(params): for n, t in paramsIter(params):
if t.isOptionalArg: if t.isOptionalArg:
@ -214,10 +215,10 @@ proc jsonToNim*(params, jsonIdent: NimNode): NimNode =
# Add code to verify input and load params into Nim types # Add code to verify input and load params into Nim types
result = newStmtList() result = newStmtList()
if not params.isNil: if not params.isNil:
let paramsWithOptionalArg = params.containsOptionalArg() var minLength = 0
if paramsWithOptionalArg: if params.containsOptionalArg():
# more elaborate parameters array check # more elaborate parameters array check
result.expectOptionalArrayLen(params, jsonIdent, minLength = result.expectOptionalArrayLen(params, jsonIdent,
calcActualParamCount(params)) calcActualParamCount(params))
else: else:
# simple parameters array length check # simple parameters array length check
@ -241,11 +242,18 @@ proc jsonToNim*(params, jsonIdent: NimNode): NimNode =
if paramType.isOptionalArg: if paramType.isOptionalArg:
let let
nullAble = pos < minLength
innerType = paramType[1] innerType = paramType[1]
innerNode = jsonToNim(paramIdent, innerType, jsonElement, paramName, true) 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: else:
# unpack Nim type and assign from json # unpack Nim type and assign from json
result.add jsonToNim(paramIdent, paramType, jsonElement, paramName) 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: else:
result = MyOptional(maybeInt: some(val)) 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 # Tests
suite "Server types": suite "Server types":
test "On macro registration": test "On macro registration":
@ -170,5 +187,12 @@ suite "Server types":
check r1 == %int1 check r1 == %int1
check r2 == %int2 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() s.stop()
waitFor s.closeWait() waitFor s.closeWait()