Improve error messages and add tests for the JSON macro.

This commit is contained in:
Dominik Picheta
2017-04-08 22:06:57 +02:00
parent cc223ff7d8
commit 658467a31f
2 changed files with 100 additions and 35 deletions

View File

@@ -124,6 +124,9 @@ type
state: seq[ParserState]
filename: string
JsonKindError* = object of ValueError ## raised by the ``to`` macro if the
## JSON kind is incorrect.
{.deprecated: [TJsonEventKind: JsonEventKind, TJsonError: JsonError,
TJsonParser: JsonParser, TTokKind: TokKind].}
@@ -1289,14 +1292,24 @@ proc createJsonIndexer(jsonNode: NimNode,
indexNode
)
proc getEnum(node: JsonNode, T: typedesc): T =
# TODO: Exceptions.
template verifyJsonKind(node: JsonNode, kinds: set[JsonNodeKind],
ast: string) =
if node.kind notin kinds:
let msg = "Incorrect JSON kind. Wanted '$1' in '$2' but got '$3'." % [
$kinds,
ast,
$node.kind
]
raise newException(JsonKindError, msg)
proc getEnum(node: JsonNode, ast: string, T: typedesc): T =
verifyJsonKind(node, {JString}, ast)
return parseEnum[T](node.getStr())
proc toIdentNode(typeNode: NimNode): NimNode =
## Converts a Sym type node (returned by getType et al.) into an
## Ident node. Placing Sym type nodes is unsound (according to @Araq)
## so this is necessary.
## Ident node. Placing Sym type nodes inside the resulting code AST is
## unsound (according to @Araq) so this is necessary.
case typeNode.kind
of nnkSym:
return newIdentNode($typeNode)
@@ -1317,7 +1330,8 @@ proc createIfStmtForOf(ofBranch, jsonNode, kindType,
# -> getEnum(`jsonNode`, `kindType`)
let getEnumSym = bindSym("getEnum")
let getEnumCall = newCall(getEnumSym, jsonNode, kindType)
let astStrLit = toStrLit(jsonNode)
let getEnumCall = newCall(getEnumSym, jsonNode, astStrLit, kindType)
var cond = newEmptyNode()
for ofCond in ofBranch:
@@ -1398,7 +1412,8 @@ proc processObjField(field, jsonNode: NimNode): seq[NimNode] =
# Add the "case" field's value.
let kindType = toIdentNode(getTypeInst(field[0]))
let getEnumSym = bindSym("getEnum")
let getEnumCall = newCall(getEnumSym, kindJsonNode, kindType)
let astStrLit = toStrLit(kindJsonNode)
let getEnumCall = newCall(getEnumSym, kindJsonNode, astStrLit, kindType)
exprColonExpr.add(getEnumCall)
# Iterate through each `of` branch.
@@ -1439,25 +1454,25 @@ proc processType(typeName: NimNode, obj: NimNode,
of "float":
result = quote do:
(
assert `jsonNode`.kind == JFloat;
verifyJsonKind(`jsonNode`, {JFloat}, astToStr(`jsonNode`));
`jsonNode`.fnum
)
of "string":
result = quote do:
(
assert `jsonNode`.kind in {JString, JNull};
verifyJsonKind(`jsonNode`, {JString, JNull}, astToStr(`jsonNode`));
if `jsonNode`.kind == JNull: nil else: `jsonNode`.str
)
of "int":
result = quote do:
(
assert `jsonNode`.kind == JInt;
verifyJsonKind(`jsonNode`, {JInt}, astToStr(`jsonNode`));
`jsonNode`.num.int
)
of "bool":
result = quote do:
(
assert `jsonNode`.kind == JBool;
verifyJsonKind(`jsonNode`, {JBool}, astToStr(`jsonNode`));
`jsonNode`.bval
)
else:

View File

@@ -2,33 +2,33 @@ discard """
file: "tjsonmacro.nim"
output: ""
"""
import json, macros, strutils
type
Point[T] = object
x, y: T
ReplayEventKind* = enum
FoodAppeared, FoodEaten, DirectionChanged
ReplayEvent* = object
time*: float
case kind*: ReplayEventKind
of FoodAppeared, FoodEaten:
foodPos*: Point[float]
of DirectionChanged:
playerPos*: float
Replay* = ref object
events*: seq[ReplayEvent]
test: int
test2: string
test3: bool
testNil: string
import json, strutils
when isMainModule:
# Tests inspired by own use case (with some additional tests).
# This should succeed.
type
Point[T] = object
x, y: T
ReplayEventKind* = enum
FoodAppeared, FoodEaten, DirectionChanged
ReplayEvent* = object
time*: float
case kind*: ReplayEventKind
of FoodAppeared, FoodEaten:
foodPos*: Point[float]
of DirectionChanged:
playerPos*: float
Replay* = ref object
events*: seq[ReplayEvent]
test: int
test2: string
test3: bool
testNil: string
var x = Replay(
events: @[
ReplayEvent(
@@ -53,7 +53,57 @@ when isMainModule:
doAssert y.test == 18827361
doAssert y.test2 == "hello world"
doAssert y.test3
doAssert y.testNil == nil
doAssert y.testNil.isNil
# TODO: Test for custom object variants (without an enum).
# TODO: Test for object variant with an else branch.
# Tests that verify the error messages for invalid data.
# TODO:
block:
type
Person = object
name: string
age: int
var node = %{
"name": %"Dominik"
}
try:
discard to(node, Person)
doAssert false
except KeyError as exc:
doAssert("age" in exc.msg)
except:
doAssert false
node["age"] = %false
try:
discard to(node, Person)
doAssert false
except JsonKindError as exc:
doAssert("age" in exc.msg)
except:
doAssert false
type
PersonAge = enum
Fifteen, Sixteen
PersonCase = object
name: string
case age: PersonAge
of Fifteen:
discard
of Sixteen:
id: string
try:
discard to(node, PersonCase)
doAssert false
except JsonKindError as exc:
doAssert("age" in exc.msg)
except:
doAssert false