Improve error messages and add tests for the JSON macro.
This commit is contained in:
parent
cc223ff7d8
commit
658467a31f
2 changed files with 100 additions and 35 deletions
|
|
@ -124,6 +124,9 @@ type
|
||||||
state: seq[ParserState]
|
state: seq[ParserState]
|
||||||
filename: string
|
filename: string
|
||||||
|
|
||||||
|
JsonKindError* = object of ValueError ## raised by the ``to`` macro if the
|
||||||
|
## JSON kind is incorrect.
|
||||||
|
|
||||||
{.deprecated: [TJsonEventKind: JsonEventKind, TJsonError: JsonError,
|
{.deprecated: [TJsonEventKind: JsonEventKind, TJsonError: JsonError,
|
||||||
TJsonParser: JsonParser, TTokKind: TokKind].}
|
TJsonParser: JsonParser, TTokKind: TokKind].}
|
||||||
|
|
||||||
|
|
@ -1289,14 +1292,24 @@ proc createJsonIndexer(jsonNode: NimNode,
|
||||||
indexNode
|
indexNode
|
||||||
)
|
)
|
||||||
|
|
||||||
proc getEnum(node: JsonNode, T: typedesc): T =
|
template verifyJsonKind(node: JsonNode, kinds: set[JsonNodeKind],
|
||||||
# TODO: Exceptions.
|
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())
|
return parseEnum[T](node.getStr())
|
||||||
|
|
||||||
proc toIdentNode(typeNode: NimNode): NimNode =
|
proc toIdentNode(typeNode: NimNode): NimNode =
|
||||||
## Converts a Sym type node (returned by getType et al.) into an
|
## Converts a Sym type node (returned by getType et al.) into an
|
||||||
## Ident node. Placing Sym type nodes is unsound (according to @Araq)
|
## Ident node. Placing Sym type nodes inside the resulting code AST is
|
||||||
## so this is necessary.
|
## unsound (according to @Araq) so this is necessary.
|
||||||
case typeNode.kind
|
case typeNode.kind
|
||||||
of nnkSym:
|
of nnkSym:
|
||||||
return newIdentNode($typeNode)
|
return newIdentNode($typeNode)
|
||||||
|
|
@ -1317,7 +1330,8 @@ proc createIfStmtForOf(ofBranch, jsonNode, kindType,
|
||||||
|
|
||||||
# -> getEnum(`jsonNode`, `kindType`)
|
# -> getEnum(`jsonNode`, `kindType`)
|
||||||
let getEnumSym = bindSym("getEnum")
|
let getEnumSym = bindSym("getEnum")
|
||||||
let getEnumCall = newCall(getEnumSym, jsonNode, kindType)
|
let astStrLit = toStrLit(jsonNode)
|
||||||
|
let getEnumCall = newCall(getEnumSym, jsonNode, astStrLit, kindType)
|
||||||
|
|
||||||
var cond = newEmptyNode()
|
var cond = newEmptyNode()
|
||||||
for ofCond in ofBranch:
|
for ofCond in ofBranch:
|
||||||
|
|
@ -1398,7 +1412,8 @@ proc processObjField(field, jsonNode: NimNode): seq[NimNode] =
|
||||||
# Add the "case" field's value.
|
# Add the "case" field's value.
|
||||||
let kindType = toIdentNode(getTypeInst(field[0]))
|
let kindType = toIdentNode(getTypeInst(field[0]))
|
||||||
let getEnumSym = bindSym("getEnum")
|
let getEnumSym = bindSym("getEnum")
|
||||||
let getEnumCall = newCall(getEnumSym, kindJsonNode, kindType)
|
let astStrLit = toStrLit(kindJsonNode)
|
||||||
|
let getEnumCall = newCall(getEnumSym, kindJsonNode, astStrLit, kindType)
|
||||||
exprColonExpr.add(getEnumCall)
|
exprColonExpr.add(getEnumCall)
|
||||||
|
|
||||||
# Iterate through each `of` branch.
|
# Iterate through each `of` branch.
|
||||||
|
|
@ -1439,25 +1454,25 @@ proc processType(typeName: NimNode, obj: NimNode,
|
||||||
of "float":
|
of "float":
|
||||||
result = quote do:
|
result = quote do:
|
||||||
(
|
(
|
||||||
assert `jsonNode`.kind == JFloat;
|
verifyJsonKind(`jsonNode`, {JFloat}, astToStr(`jsonNode`));
|
||||||
`jsonNode`.fnum
|
`jsonNode`.fnum
|
||||||
)
|
)
|
||||||
of "string":
|
of "string":
|
||||||
result = quote do:
|
result = quote do:
|
||||||
(
|
(
|
||||||
assert `jsonNode`.kind in {JString, JNull};
|
verifyJsonKind(`jsonNode`, {JString, JNull}, astToStr(`jsonNode`));
|
||||||
if `jsonNode`.kind == JNull: nil else: `jsonNode`.str
|
if `jsonNode`.kind == JNull: nil else: `jsonNode`.str
|
||||||
)
|
)
|
||||||
of "int":
|
of "int":
|
||||||
result = quote do:
|
result = quote do:
|
||||||
(
|
(
|
||||||
assert `jsonNode`.kind == JInt;
|
verifyJsonKind(`jsonNode`, {JInt}, astToStr(`jsonNode`));
|
||||||
`jsonNode`.num.int
|
`jsonNode`.num.int
|
||||||
)
|
)
|
||||||
of "bool":
|
of "bool":
|
||||||
result = quote do:
|
result = quote do:
|
||||||
(
|
(
|
||||||
assert `jsonNode`.kind == JBool;
|
verifyJsonKind(`jsonNode`, {JBool}, astToStr(`jsonNode`));
|
||||||
`jsonNode`.bval
|
`jsonNode`.bval
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
|
|
|
||||||
|
|
@ -2,33 +2,33 @@ discard """
|
||||||
file: "tjsonmacro.nim"
|
file: "tjsonmacro.nim"
|
||||||
output: ""
|
output: ""
|
||||||
"""
|
"""
|
||||||
import json, macros, strutils
|
import json, 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
|
|
||||||
|
|
||||||
when isMainModule:
|
when isMainModule:
|
||||||
# Tests inspired by own use case (with some additional tests).
|
# Tests inspired by own use case (with some additional tests).
|
||||||
# This should succeed.
|
# 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(
|
var x = Replay(
|
||||||
events: @[
|
events: @[
|
||||||
ReplayEvent(
|
ReplayEvent(
|
||||||
|
|
@ -53,7 +53,57 @@ when isMainModule:
|
||||||
doAssert y.test == 18827361
|
doAssert y.test == 18827361
|
||||||
doAssert y.test2 == "hello world"
|
doAssert y.test2 == "hello world"
|
||||||
doAssert y.test3
|
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.
|
# 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
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue