2018-08-28 21:00:33 +01:00
|
|
|
import macros, json, options, typetraits
|
2018-05-22 20:46:19 +01:00
|
|
|
|
|
|
|
template expect*(actual, expected: JsonNodeKind, argName: string) =
|
2018-08-28 21:00:33 +01:00
|
|
|
if actual != expected: raise newException(ValueError, "Parameter [" & argName & "] expected " & $expected & " but got " & $actual)
|
|
|
|
|
2018-08-28 21:18:52 +01:00
|
|
|
template expectType*(actual: JsonNodeKind, expected: typedesc, argName: string, allowNull = false) =
|
2018-08-28 21:00:33 +01:00
|
|
|
var expType: JsonNodeKind
|
|
|
|
when expected is array:
|
|
|
|
expType = JArray
|
|
|
|
elif expected is object:
|
|
|
|
expType = JObject
|
|
|
|
elif expected is int:
|
|
|
|
expType = JInt
|
|
|
|
elif expected is float:
|
|
|
|
expType = JFloat
|
|
|
|
elif expected is bool:
|
|
|
|
expType = JBool
|
|
|
|
elif expected is string:
|
|
|
|
expType = JString
|
|
|
|
else:
|
|
|
|
const eStr = "Unable to convert " & expected.name & " to JSON for expectType"
|
|
|
|
{.fatal: eStr}
|
|
|
|
if actual != expType:
|
2018-08-28 21:18:52 +01:00
|
|
|
if allowNull == false or (allowNull and actual != JNull):
|
|
|
|
raise newException(ValueError, "Parameter [" & argName & "] expected " & expected.name & " but got " & $actual)
|
2018-05-22 20:46:19 +01:00
|
|
|
|
2018-05-30 16:04:17 +01:00
|
|
|
proc `%`*(n: byte{not lit}): JsonNode =
|
|
|
|
result = newJInt(int(n))
|
|
|
|
|
|
|
|
proc `%`*(n: uint64{not lit}): JsonNode =
|
|
|
|
result = newJInt(int(n))
|
|
|
|
|
2018-08-16 10:08:56 +01:00
|
|
|
proc `%`*(n: ref SomeInteger): JsonNode =
|
2018-08-15 14:19:16 +01:00
|
|
|
if n.isNil:
|
|
|
|
result = newJNull()
|
|
|
|
else:
|
|
|
|
result = newJInt(n[])
|
2018-05-30 16:04:17 +01:00
|
|
|
|
2018-08-28 21:00:33 +01:00
|
|
|
proc `%`*[T](option: Option[T]): JsonNode =
|
|
|
|
if option.isSome:
|
|
|
|
result = `%`(option.get)
|
|
|
|
else:
|
|
|
|
result = newJNull()
|
|
|
|
|
2018-05-23 18:00:30 +01:00
|
|
|
# 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)
|
2018-05-22 20:46:19 +01:00
|
|
|
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)
|
2018-05-30 16:04:17 +01:00
|
|
|
proc fromJson(n: JsonNode, argName: string, result: var uint64)
|
2018-05-22 20:46:19 +01:00
|
|
|
proc fromJson(n: JsonNode, argName: string, result: var ref int64)
|
|
|
|
proc fromJson(n: JsonNode, argName: string, result: var ref int)
|
|
|
|
|
2018-08-28 21:00:33 +01:00
|
|
|
proc fromJson[T](n: JsonNode, argName: string, result: var Option[T]) =
|
2018-08-28 21:18:52 +01:00
|
|
|
n.kind.expectType(T, argName, true) # Allow JNull
|
2018-08-28 21:00:33 +01:00
|
|
|
if n.kind != JNull:
|
|
|
|
var val: T
|
|
|
|
fromJson(n, argName, val)
|
|
|
|
result = some(val)
|
|
|
|
|
2018-05-23 18:04:16 +01:00
|
|
|
# This can't be forward declared: https://github.com/nim-lang/Nim/issues/7868
|
2018-05-22 20:46:19 +01:00
|
|
|
proc fromJson[T: enum](n: JsonNode, argName: string, result: var T) =
|
|
|
|
n.kind.expect(JInt, argName)
|
|
|
|
result = n.getInt().T
|
|
|
|
|
2018-05-23 18:04:16 +01:00
|
|
|
# This can't be forward declared: https://github.com/nim-lang/Nim/issues/7868
|
2018-05-22 20:46:19 +01:00
|
|
|
proc fromJson[T: object](n: JsonNode, argName: string, result: var T) =
|
|
|
|
n.kind.expect(JObject, argName)
|
2018-05-30 16:04:17 +01:00
|
|
|
for k, v in fieldPairs(result):
|
2018-05-22 20:46:19 +01:00
|
|
|
fromJson(n[k], k, v)
|
|
|
|
|
2018-05-23 18:05:38 +01:00
|
|
|
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()
|
|
|
|
|
2018-05-22 20:46:19 +01:00
|
|
|
proc fromJson[T: ref object](n: JsonNode, argName: string, result: var T) =
|
|
|
|
n.kind.expect(JObject, argName)
|
|
|
|
result = new T
|
|
|
|
for k, v in fieldpairs(result[]):
|
|
|
|
fromJson(n[k], k, v)
|
|
|
|
|
|
|
|
proc fromJson(n: JsonNode, argName: string, result: var int64) =
|
|
|
|
n.kind.expect(JInt, argName)
|
|
|
|
result = n.getInt()
|
|
|
|
|
2018-05-30 16:04:17 +01:00
|
|
|
proc fromJson(n: JsonNode, argName: string, result: var uint64) =
|
|
|
|
n.kind.expect(JInt, argName)
|
|
|
|
result = n.getInt().uint64
|
|
|
|
|
2018-05-22 20:46:19 +01:00
|
|
|
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]) =
|
|
|
|
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 =
|
|
|
|
fromJson(args, argName, result)
|
|
|
|
|
2018-05-23 18:00:30 +01:00
|
|
|
proc expectArrayLen(node: NimNode, jsonIdent: untyped, length: int) =
|
2018-05-22 20:46:19 +01:00
|
|
|
let
|
2018-05-23 18:00:30 +01:00
|
|
|
identStr = jsonIdent.repr
|
2018-05-22 20:46:19 +01:00
|
|
|
expectedStr = "Expected " & $length & " Json parameter(s) but got "
|
|
|
|
node.add(quote do:
|
2018-05-23 18:00:30 +01:00
|
|
|
`jsonIdent`.kind.expect(JArray, `identStr`)
|
|
|
|
if `jsonIdent`.len != `length`:
|
|
|
|
raise newException(ValueError, `expectedStr` & $`jsonIdent`.len)
|
2018-05-22 20:46:19 +01:00
|
|
|
)
|
|
|
|
|
2018-05-23 18:00:30 +01:00
|
|
|
proc jsonToNim*(assignIdent, paramType, jsonIdent: NimNode, paramNameStr: string): NimNode =
|
|
|
|
# verify input and load a Nim type from json data
|
2018-05-23 18:06:32 +01:00
|
|
|
# note: does not create `assignIdent`, so can be used for `result` variables
|
2018-05-22 20:46:19 +01:00
|
|
|
result = newStmtList()
|
|
|
|
# unpack each parameter and provide assignments
|
|
|
|
result.add(quote do:
|
|
|
|
`assignIdent` = `unpackArg`(`jsonIdent`, `paramNameStr`, type(`paramType`))
|
2018-05-23 18:00:30 +01:00
|
|
|
)
|
|
|
|
|
2018-07-17 10:39:22 +07:00
|
|
|
proc calcActualParamCount(parameters: 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)
|
2018-07-17 10:06:29 +07:00
|
|
|
for i in 1 ..< parameters.len:
|
|
|
|
inc(result, parameters[i].len-2)
|
|
|
|
|
2018-05-23 18:00:30 +01:00
|
|
|
proc jsonToNim*(parameters, jsonIdent: NimNode): NimNode =
|
|
|
|
# Add code to verify input and load parameters into Nim types
|
|
|
|
result = newStmtList()
|
|
|
|
if not parameters.isNil:
|
|
|
|
# initial parameter array length check
|
2018-07-17 10:06:29 +07:00
|
|
|
result.expectArrayLen(jsonIdent, calcActualParamCount(parameters))
|
|
|
|
|
2018-05-23 18:00:30 +01:00
|
|
|
# unpack each parameter and provide assignments
|
2018-07-17 10:06:29 +07:00
|
|
|
var pos = 0
|
2018-05-23 18:00:30 +01:00
|
|
|
for i in 1 ..< parameters.len:
|
|
|
|
let
|
2018-07-17 10:06:29 +07:00
|
|
|
param = parameters[i]
|
|
|
|
paramType = param[^2]
|
|
|
|
|
2018-07-17 10:39:22 +07:00
|
|
|
# processing multiple variables of one type
|
|
|
|
# e.g. (a, b: T), including common (a: U, b: V) form
|
2018-07-17 10:06:29 +07:00
|
|
|
for j in 0 ..< param.len-2:
|
|
|
|
let
|
|
|
|
paramIdent = param[j]
|
|
|
|
paramName = $paramIdent
|
|
|
|
jsonElement = quote do:
|
|
|
|
`jsonIdent`.elems[`pos`]
|
|
|
|
inc pos
|
|
|
|
# declare variable before assignment
|
|
|
|
result.add(quote do:
|
|
|
|
var `paramIdent`: `paramType`
|
|
|
|
)
|
|
|
|
# unpack Nim type and assign from json
|
|
|
|
result.add jsonToNim(paramIdent, paramType, jsonElement, paramName)
|