mirror of
https://github.com/nim-lang/Nim.git
synced 2026-04-21 14:55:24 +00:00
Improve error messages and add tests for the JSON macro.
This commit is contained in:
@@ -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:
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
Reference in New Issue
Block a user