nim-json-rpc/json_rpc/jsonmarshal.nim
Zahary Karadjov 26953344c3 Turn some potentially dangerous templates into functions
Be careful when creating templates. If the input parameters are
referenced within the body multiple times, this may lead to multiple
evaluations of functions with side-effects.
2018-11-12 23:51:24 +02:00

192 lines
6.7 KiB
Nim

import macros, json, options, typetraits
proc expect*(actual, expected: JsonNodeKind, argName: string) =
if actual != expected: raise newException(ValueError, "Parameter [" & argName & "] expected " & $expected & " but got " & $actual)
proc expectType*(actual: JsonNodeKind, expected: typedesc, argName: string, allowNull = false) =
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:
if allowNull == false or (allowNull and actual != JNull):
raise newException(ValueError, "Parameter [" & argName & "] expected " & expected.name & " but got " & $actual)
proc `%`*(n: byte{not lit}): JsonNode =
result = newJInt(int(n))
proc `%`*(n: uint64{not lit}): JsonNode =
result = newJInt(int(n))
proc `%`*(n: ref SomeInteger): JsonNode =
if n.isNil:
result = newJNull()
else:
result = newJInt(n[])
proc `%`*[T](option: Option[T]): JsonNode =
if option.isSome:
result = `%`(option.get)
else:
result = newJNull()
# 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)
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)
proc fromJson(n: JsonNode, argName: string, result: var uint64)
proc fromJson(n: JsonNode, argName: string, result: var ref int64)
proc fromJson(n: JsonNode, argName: string, result: var ref int)
proc fromJson[T](n: JsonNode, argName: string, result: var Option[T]) =
n.kind.expectType(T, argName, true) # Allow JNull
if n.kind != JNull:
var val: T
fromJson(n, argName, val)
result = some(val)
# This can't be forward declared: https://github.com/nim-lang/Nim/issues/7868
proc fromJson[T: enum](n: JsonNode, argName: string, result: var T) =
n.kind.expect(JInt, argName)
result = n.getInt().T
# This can't be forward declared: https://github.com/nim-lang/Nim/issues/7868
proc fromJson[T: object](n: JsonNode, argName: string, result: var T) =
n.kind.expect(JObject, argName)
for k, v in fieldPairs(result):
fromJson(n[k], k, v)
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()
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()
proc fromJson(n: JsonNode, argName: string, result: var uint64) =
n.kind.expect(JInt, argName)
result = n.getInt().uint64
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)
proc expectArrayLen(node: NimNode, jsonIdent: untyped, length: int) =
let
identStr = jsonIdent.repr
expectedStr = "Expected " & $length & " Json parameter(s) but got "
node.add(quote do:
`jsonIdent`.kind.expect(JArray, `identStr`)
if `jsonIdent`.len != `length`:
raise newException(ValueError, `expectedStr` & $`jsonIdent`.len)
)
proc jsonToNim*(assignIdent, paramType, jsonIdent: NimNode, paramNameStr: string): NimNode =
# verify input and load a Nim type from json data
# note: does not create `assignIdent`, so can be used for `result` variables
result = newStmtList()
# unpack each parameter and provide assignments
result.add(quote do:
`assignIdent` = `unpackArg`(`jsonIdent`, `paramNameStr`, type(`paramType`))
)
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)
for i in 1 ..< parameters.len:
inc(result, parameters[i].len-2)
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
result.expectArrayLen(jsonIdent, calcActualParamCount(parameters))
# unpack each parameter and provide assignments
var pos = 0
for i in 1 ..< parameters.len:
let
param = parameters[i]
paramType = param[^2]
# processing multiple variables of one type
# e.g. (a, b: T), including common (a: U, b: V) form
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)