summary refs log tree commit diff stats
diff options
context:
space:
mode:
-rw-r--r--lib/pure/json.nim35
-rw-r--r--tests/stdlib/tjsonmacro.nim100
2 files changed, 100 insertions, 35 deletions
diff --git a/lib/pure/json.nim b/lib/pure/json.nim
index eca708bb7..752501465 100644
--- a/lib/pure/json.nim
+++ b/lib/pure/json.nim
@@ -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:
diff --git a/tests/stdlib/tjsonmacro.nim b/tests/stdlib/tjsonmacro.nim
index 806cbadc6..f0f0e6b56 100644
--- a/tests/stdlib/tjsonmacro.nim
+++ b/tests/stdlib/tjsonmacro.nim
@@ -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:
\ No newline at end of file
+  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
+