From 9d3da40c0a6682b100c1d5c3d32f490b3b7642cc Mon Sep 17 00:00:00 2001 From: munna0908 Date: Sat, 31 May 2025 17:38:35 +0530 Subject: [PATCH] feat: refactor CBOR parser to use direct exceptions instead of Result type --- config.nims | 6 +- serde/cbor/deserializer.nim | 438 +++++++++++++---------------- serde/cbor/errors.nim | 7 +- serde/cbor/helpers.nim | 33 ++- serde/cbor/jsonhook.nim | 72 ++--- serde/cbor/serializer.nim | 87 +++--- serde/cbor/types.nim | 55 ++-- serde/json/deserializer.nim | 18 +- serde/json/errors.nim | 3 +- serde/json/helpers.nim | 6 +- serde/json/stdjson.nim | 2 +- serde/utils/errors.nim | 3 - serde/utils/pragmas.nim | 15 +- tests/benchmark.nim | 74 +++++ tests/cbor/testObjects.nim | 93 +++--- tests/cbor/testPrimitives.nim | 44 ++- tests/config.nims | 2 +- tests/json/deserialize/objects.nim | 3 +- tests/json/testSerialize.nim | 9 +- tests/test.nimble | 1 - 20 files changed, 498 insertions(+), 473 deletions(-) create mode 100644 tests/benchmark.nim diff --git a/config.nims b/config.nims index e01d099..05a7794 100644 --- a/config.nims +++ b/config.nims @@ -1,5 +1,7 @@ ---styleCheck:usages ---styleCheck:error +--styleCheck: + usages +--styleCheck: + error # begin Nimble config (version 1) when fileExists("nimble.paths"): diff --git a/serde/cbor/deserializer.nim b/serde/cbor/deserializer.nim index 8721c03..6007104 100644 --- a/serde/cbor/deserializer.nim +++ b/serde/cbor/deserializer.nim @@ -18,10 +18,11 @@ export macros {.push raises: [].} -func isIndefinite*(c: CborParser): bool {.inline.} = c.minor == 31 +func isIndefinite*(c: CborParser): bool {.inline.} = ## Return true if the parser is positioned on an item of indefinite length. + c.minor == 31 -proc open*(c: var CborParser; s: Stream) = +proc open*(c: var CborParser, s: Stream) = ## Begin parsing a stream of CBOR in binary form. ## The parser will be initialized in an EOF state, call ## ``next`` to advance it before parsing. @@ -29,7 +30,7 @@ proc open*(c: var CborParser; s: Stream) = c.kind = cborEof c.intVal = 0 -proc next*(c: var CborParser): ?!void = +proc next*(c: var CborParser) {.raises: [CborParseError].} = ## Advance the parser to the initial or next event. try: if c.s.atEnd: @@ -41,7 +42,7 @@ proc next*(c: var CborParser): ?!void = mb = ib shr 5 c.minor = ib and 0b11111 case c.minor - of 0..23: + of 0 .. 23: c.intVal = c.minor.uint64 of 24: c.intVal = c.s.readChar.uint64 @@ -50,12 +51,12 @@ proc next*(c: var CborParser): ?!void = c.intVal = (c.intVal shl 8) or c.s.readChar.uint64 of 26: c.intVal = c.s.readChar.uint64 - for _ in 1..3: + for _ in 1 .. 3: {.unroll.} c.intVal = (c.intVal shl 8) or c.s.readChar.uint64 of 27: c.intVal = c.s.readChar.uint64 - for _ in 1..7: + for _ in 1 .. 7: {.unroll.} c.intVal = (c.intVal shl 8) or c.s.readChar.uint64 else: @@ -83,333 +84,284 @@ proc next*(c: var CborParser): ?!void = else: c.kind = CborEventKind.cborSimple else: - return failure(newCborError("unhandled major type " & $mb)) - success() + raise newCborError("unhandled major type " & $mb) except IOError as e: - return failure(e) + raise newException(CborParseError, e.msg, e) except OSError as e: - return failure(e) + raise newException(CborParseError, e.msg, e) -proc nextUInt*(c: var CborParser): ?!BiggestUInt = +proc nextUInt*(c: var CborParser): BiggestUInt {.raises: [CborParseError].} = ## Parse the integer value that the parser is positioned on. - if c.kind != CborEventKind.cborPositive: - return failure(newCborError("Expected positive integer, got " & $c.kind)) - let val = c.intVal.BiggestUInt + parseAssert( + c.kind == CborEventKind.cborPositive, "Expected positive integer, got " & $c.kind + ) + result = c.intVal.BiggestUInt - ?c.next() - return success(val) + c.next() -proc nextInt*(c: var CborParser): ?!BiggestInt = +proc nextInt*(c: var CborParser): BiggestInt {.raises: [CborParseError].} = ## Parse the integer value that the parser is positioned on. - var val: BiggestInt case c.kind of CborEventKind.cborPositive: - val = c.intVal.BiggestInt + result = c.intVal.BiggestInt of CborEventKind.cborNegative: - val = -1.BiggestInt - c.intVal.BiggestInt + result = -1.BiggestInt - c.intVal.BiggestInt else: - return failure(newCborError("Expected integer, got " & $c.kind)) + raise newCborError("Expected integer, got " & $c.kind) - ?c.next() - return success(val) + c.next() -proc nextFloat*(c: var CborParser): ?!float64 = +proc nextFloat*(c: var CborParser): float64 {.raises: [CborParseError].} = ## Parse the float value that the parser is positioned on. - var val: float64 - if c.kind != CborEventKind.cborFloat: - return failure(newCborError("Expected float, got " & $c.kind)) + parseAssert(c.kind == CborEventKind.cborFloat, "Expected float, got " & $c.kind) case c.minor of 25: - val = floatSingle(c.intVal.uint16).float64 + result = floatSingle(c.intVal.uint16).float64 of 26: - val = cast[float32](c.intVal).float64 + result = cast[float32](c.intVal).float64 of 27: - val = cast[float64](c.intVal) + result = cast[float64](c.intVal) else: discard - ?c.next() - return success(val) + c.next() -func bytesLen*(c: CborParser): ?!int = +func bytesLen*(c: CborParser): int {.raises: [CborParseError].} = ## Return the length of the byte string that the parser is positioned on. - if c.kind != CborEventKind.cborBytes: - return failure(newCborError("Expected bytes, got " & $c.kind)) - return success(c.intVal.int) + parseAssert(c.kind == CborEventKind.cborBytes, "Expected bytes, got " & $c.kind) + c.intVal.int -proc nextBytes*(c: var CborParser; buf: var openArray[byte]): ?!void = +proc nextBytes*( + c: var CborParser, buf: var openArray[byte] +) {.raises: [CborParseError].} = ## Read the bytes that the parser is positioned on and advance. try: - if c.kind != CborEventKind.cborBytes: - return failure(newCborError("Expected bytes, got " & $c.kind)) - if buf.len != c.intVal.int: - return failure(newCborError("Buffer length mismatch: expected " & - $c.intVal.int & ", got " & $buf.len)) + parseAssert(c.kind == CborEventKind.cborBytes, "Expected bytes, got " & $c.kind) + parseAssert( + buf.len == c.intVal.int, + "Buffer length mismatch: expected " & $c.intVal.int & ", got " & $buf.len, + ) if buf.len > 0: let n = c.s.readData(buf[0].addr, buf.len) - if n != buf.len: - return failure(newCborError("truncated read of CBOR data")) - ?c.next() - success() + parseAssert(n == buf.len, "truncated read of CBOR data") + c.next() except OSError as e: - return failure(e.msg) + raise newException(CborParseError, e.msg, e) except IOError as e: - return failure(e.msg) + raise newException(CborParseError, e.msg, e) -proc nextBytes*(c: var CborParser): ?!seq[byte] = +proc nextBytes*(c: var CborParser): seq[byte] {.raises: [CborParseError].} = ## Read the bytes that the parser is positioned on into a seq and advance. - var val = newSeq[byte](c.intVal.int) - let nextRes = nextBytes(c, val) - if nextRes.isFailure: - return failure(nextRes.error) + result = newSeq[byte](c.intVal.int) + nextBytes(c, result) - return success(val) - -func textLen*(c: CborParser): ?!int = +func textLen*(c: CborParser): int {.raises: [CborParseError].} = ## Return the length of the text that the parser is positioned on. - if c.kind != CborEventKind.cborText: - return failure(newCborError("Expected text, got " & $c.kind)) - return success(c.intVal.int) + parseAssert(c.kind == CborEventKind.cborText, "Expected text, got " & $c.kind) + c.intVal.int -proc nextText*(c: var CborParser; buf: var string): ?!void = +proc nextText*(c: var CborParser, buf: var string) {.raises: [ + CborParseError].} = ## Read the text that the parser is positioned on into a string and advance. try: - if c.kind != CborEventKind.cborText: - return failure(newCborError("Expected text, got " & $c.kind)) + parseAssert(c.kind == CborEventKind.cborText, "Expected text, got " & $c.kind) buf.setLen c.intVal.int if buf.len > 0: let n = c.s.readData(buf[0].addr, buf.len) - if n != buf.len: - return failure(newCborError("truncated read of CBOR data")) - ?c.next() - success() + parseAssert(n == buf.len, "truncated read of CBOR data") + c.next() except IOError as e: - return failure(e.msg) + raise newException(CborParseError, e.msg, e) except OSError as e: - return failure(e.msg) + raise newException(CborParseError, e.msg, e) -proc nextText*(c: var CborParser): ?!string = +proc nextText*(c: var CborParser): string {.raises: [CborParseError].} = ## Read the text that the parser is positioned on into a string and advance. - var buf: string - let nextRes = nextText(c, buf) - if nextRes.isFailure: - return failure(nextRes.error) + nextText(c, result) - return success(buf) - -func arrayLen*(c: CborParser): ?!int = +func arrayLen*(c: CborParser): int {.raises: [CborParseError].} = ## Return the length of the array that the parser is positioned on. - if c.kind != CborEventKind.cborArray: - return failure(newCborError("Expected array, got " & $c.kind)) + parseAssert(c.kind == CborEventKind.cborArray, "Expected array, got " & $c.kind) + c.intVal.int - return success(c.intVal.int) - -func mapLen*(c: CborParser): ?!int = +func mapLen*(c: CborParser): int {.raises: [CborParseError].} = ## Return the length of the map that the parser is positioned on. - if c.kind != CborEventKind.cborMap: - return failure(newCborError("Expected map, got " & $c.kind)) + parseAssert(c.kind == CborEventKind.cborMap, "Expected map, got " & $c.kind) + c.intVal.int - return success(c.intVal.int) - -func tag*(c: CborParser): ?!uint64 = +func tag*(c: CborParser): uint64 {.raises: [CborParseError].} = ## Return the tag value the parser is positioned on. - if c.kind != CborEventKind.cborTag: - return failure(newCborError("Expected tag, got " & $c.kind)) + parseAssert(c.kind == CborEventKind.cborTag, "Expected tag, got " & $c.kind) + c.intVal - return success(c.intVal) - -proc skipNode*(c: var CborParser): ?!void = +proc skipNode*(c: var CborParser) {.raises: [CborParseError].} = ## Skip the item the parser is positioned on. try: case c.kind of CborEventKind.cborEof: - return failure(newCborError("end of CBOR stream")) + raise newCborError("end of CBOR stream") of CborEventKind.cborPositive, CborEventKind.cborNegative, CborEventKind.cborSimple: - return c.next() + c.next() of CborEventKind.cborBytes, CborEventKind.cborText: if c.isIndefinite: - ?c.next() + c.next() while c.kind != CborEventKind.cborBreak: - if c.kind != CborEventKind.cborBytes: - return failure(newCborError("expected bytes, got " & $c.kind)) - for _ in 1..c.intVal.int: discard readChar(c.s) - return c.next() + parseAssert( + c.kind == CborEventKind.cborBytes, "expected bytes, got " & $c.kind + ) + for _ in 1 .. c.intVal.int: + discard readChar(c.s) + c.next() else: - for _ in 1..c.intVal.int: discard readChar(c.s) - return c.next() + for _ in 1 .. c.intVal.int: + discard readChar(c.s) + c.next() of CborEventKind.cborArray: if c.isIndefinite: - ?c.next() + c.next() while c.kind != CborEventKind.cborBreak: - ?c.skipNode() - return c.next() + c.skipNode() + c.next() else: let len = c.intVal - ?c.next() - for i in 1..len: - ?c.skipNode() + c.next() + for i in 1 .. len: + c.skipNode() of CborEventKind.cborMap: let mapLen = c.intVal.int if c.isIndefinite: - ?c.next() + c.next() while c.kind != CborEventKind.cborBreak: - ?c.skipNode() - return c.next() + c.skipNode() + c.next() else: - ?c.next() + c.next() for _ in 1 .. mapLen: - ?c.skipNode() + c.skipNode() of CborEventKind.cborTag: - ?c.next() - return c.skipNode() + c.next() + c.skipNode() of CborEventKind.cborFloat: - without f =? c.nextFloat(), error: - return failure(error) + discard c.nextFloat() of CborEventKind.cborBreak: discard - success() - except OSError as e: - return failure(e.msg) - except IOError as e: - return failure(e.msg) + except OSError as os: + raise newException(CborParseError, os.msg, os) + except IOError as io: + raise newException(CborParseError, io.msg, io) - - -proc nextNode*(c: var CborParser): ?!CborNode = +proc nextNode*(c: var CborParser): CborNode {.raises: [CborParseError].} = ## Parse the item the parser is positioned on into a ``CborNode``. ## This is cheap for numbers or simple values but expensive ## for nested types. try: - var next: CborNode case c.kind of CborEventKind.cborEof: - return failure(newCborError("end of CBOR stream")) + raise newCborError("end of CBOR stream") of CborEventKind.cborPositive: - next = CborNode(kind: cborUnsigned, uint: c.intVal) - ?c.next() + result = CborNode(kind: cborUnsigned, uint: c.intVal) + c.next() of CborEventKind.cborNegative: - next = CborNode(kind: cborNegative, int: -1 - c.intVal.int64) - ?c.next() + result = CborNode(kind: cborNegative, int: -1 - c.intVal.int64) + c.next() of CborEventKind.cborBytes: if c.isIndefinite: - next = CborNode(kind: cborBytes, bytes: newSeq[byte]()) - ?c.next() + result = CborNode(kind: cborBytes, bytes: newSeq[byte]()) + c.next() while c.kind != CborEventKind.cborBreak: - if c.kind != CborEventKind.cborBytes: - return failure(newCborError("Expected bytes, got " & $c.kind)) + parseAssert( + c.kind == CborEventKind.cborBytes, "Expected bytes, got " & $c.kind + ) let chunkLen = c.intVal.int - pos = next.bytes.len - next.bytes.setLen(pos+chunkLen) - let n = c.s.readData(next.bytes[pos].addr, chunkLen) - if n != chunkLen: - return failure(newCborError("truncated read of CBOR data")) - ?c.next() + pos = result.bytes.len + result.bytes.setLen(pos + chunkLen) + let n = c.s.readData(result.bytes[pos].addr, chunkLen) + parseAssert(n == chunkLen, "truncated read of CBOR data") + c.next() else: - without rawBytes =? c.nextBytes(), error: - return failure(error) - next = CborNode(kind: cborBytes, bytes: rawBytes) + result = CborNode(kind: cborBytes, bytes: c.nextBytes()) of CborEventKind.cborText: if c.isIndefinite: - next = CborNode(kind: cborText, text: "") - ?c.next() + result = CborNode(kind: cborText, text: "") + c.next() while c.kind != CborEventKind.cborBreak: - if c.kind != CborEventKind.cborText: - return failure(newCborError("Expected text, got " & $c.kind)) + parseAssert(c.kind == CborEventKind.cborText, "Expected text, got " & $c.kind) let chunkLen = c.intVal.int - pos = next.text.len - next.text.setLen(pos+chunkLen) - let n = c.s.readData(next.text[pos].addr, chunkLen) - if n != chunkLen: - return failure(newCborError("truncated read of CBOR data")) - ?c.next() - ?c.next() + pos = result.text.len + result.text.setLen(pos + chunkLen) + let n = c.s.readData(result.text[pos].addr, chunkLen) + parseAssert(n == chunkLen, "truncated read of CBOR data") + c.next() + c.next() else: - without text =? c.nextText(), error: - return failure(error) - next = CborNode(kind: cborText, text: text) + result = CborNode(kind: cborText, text: c.nextText()) of CborEventKind.cborArray: - next = CborNode(kind: cborArray, seq: newSeq[CborNode](c.intVal)) + result = CborNode(kind: cborArray, seq: newSeq[CborNode](c.intVal)) if c.isIndefinite: - ?c.next() + c.next() while c.kind != CborEventKind.cborBreak: - without node =? c.nextNode(), error: - return failure(error) - next.seq.add(node) - ?c.next() + result.seq.add(c.nextNode()) + c.next() else: - ?c.next() - for i in 0..next.seq.high: - without node =? c.nextNode(), error: - return failure(error) - next.seq[i] = node + c.next() + for i in 0 .. result.seq.high: + result.seq[i] = c.nextNode() of CborEventKind.cborMap: let mapLen = c.intVal.int - next = CborNode(kind: cborMap, map: initOrderedTable[CborNode, CborNode]( - mapLen.nextPowerOfTwo)) + result = CborNode( + kind: cborMap, map: initOrderedTable[CborNode, CborNode]( + mapLen.nextPowerOfTwo) + ) if c.isIndefinite: - ?c.next() + c.next() while c.kind != CborEventKind.cborBreak: - without key =? c.nextNode(), error: - return failure(error) - without val =? c.nextNode(), error: - return failure(error) - next.map[key] = val - ?c.next() + result.map[c.nextNode()] = c.nextNode() + c.next() else: - ?c.next() + c.next() for _ in 1 .. mapLen: - without key =? c.nextNode(), error: - return failure(error) - without val =? c.nextNode(), error: - return failure(error) - next.map[key] = val + result.map[c.nextNode()] = c.nextNode() of CborEventKind.cborTag: let tag = c.intVal - ?c.next() - without node =? c.nextNode(), error: - return failure(error) - next = node - next.tag = some tag + c.next() + result = c.nextNode() + result.tag = some tag of CborEventKind.cborSimple: case c.minor of 24: - next = CborNode(kind: cborSimple, simple: c.intVal.uint8) + result = CborNode(kind: cborSimple, simple: c.intVal.uint8) else: - next = CborNode(kind: cborSimple, simple: c.minor) - ?c.next() + result = CborNode(kind: cborSimple, simple: c.minor) + c.next() of CborEventKind.cborFloat: - without f =? c.nextFloat(), error: - return failure(error) - next = CborNode(kind: cborFloat, float: f) + result = CborNode(kind: cborFloat, float: c.nextFloat()) of CborEventKind.cborBreak: discard - success(next) except OSError as e: - return failure(e.msg) + raise newException(CborParseError, e.msg, e) except IOError as e: - return failure(e.msg) + raise newException(CborParseError, e.msg, e) except CatchableError as e: - return failure(e.msg) + raise newException(CborParseError, e.msg, e) except Exception as e: raise newException(Defect, e.msg, e) - -proc readCbor*(s: Stream): ?!CborNode = +proc readCbor*(s: Stream): CborNode {.raises: [CborParseError].} = ## Parse a stream into a CBOR object. var parser: CborParser parser.open(s) - ?parser.next() + parser.next() parser.nextNode() -proc parseCbor*(s: string): ?!CborNode = +proc parseCbor*(s: string): CborNode {.raises: [CborParseError].} = ## Parse a string into a CBOR object. ## A wrapper over stream parsing. readCbor(newStringStream s) -proc `$`*(n: CborNode): string = +proc `$`*(n: CborNode): string {.raises: [CborParseError].} = ## Get a ``CborNode`` in diagnostic notation. result = "" if n.tag.isSome: @@ -429,7 +381,7 @@ proc `$`*(n: CborNode): string = result.add escape n.text of cborArray: result.add "[" - for i in 0.. 0: @@ -451,12 +403,19 @@ proc `$`*(n: CborNode): string = discard of cborSimple: case n.simple - of 20: result.add "false" - of 21: result.add "true" - of 22: result.add "null" - of 23: result.add "undefined" - of 31: discard # break code for indefinite-length items - else: result.add "simple(" & $n.simple & ")" + of 20: + result.add "false" + of 21: + result.add "true" + of 22: + result.add "null" + of 23: + result.add "undefined" + of 31: + discard + # break code for indefinite-length items + else: + result.add "simple(" & $n.simple & ")" of cborFloat: case n.float.classify of fcNan: @@ -468,14 +427,11 @@ proc `$`*(n: CborNode): string = else: result.add $n.float of cborRaw: - without val =? parseCbor(n.raw), error: - return error.msg - result.add $val + result.add $parseCbor(n.raw) if n.tag.isSome: result.add(")") - -proc getInt*(n: CborNode; default: int = 0): int = +proc getInt*(n: CborNode, default: int = 0): int = ## Get the numerical value of a ``CborNode`` or a fallback. case n.kind of cborUnsigned: n.uint.int @@ -494,7 +450,7 @@ proc parseTime(n: CborNode): Time = else: assert false -proc fromCbor*(_: type DateTime; n: CborNode): ?!DateTime = +proc fromCbor*(_: type DateTime, n: CborNode): ?!DateTime = ## Parse a `DateTime` from the tagged string representation ## defined in RCF7049 section 2.4.1. var v: DateTime @@ -506,9 +462,10 @@ proc fromCbor*(_: type DateTime; n: CborNode): ?!DateTime = elif n.tag.get == 1 and n.kind in {cborUnsigned, cborNegative, cborFloat}: v = parseTime(n).utc return success(v) - except ValueError as e: return failure(e) + except ValueError as e: + return failure(e) -proc fromCbor*(_: type Time; n: CborNode): ?!Time = +proc fromCbor*(_: type Time, n: CborNode): ?!Time = ## Parse a `Time` from the tagged string representation ## defined in RCF7049 section 2.4.1. var v: Time @@ -520,17 +477,18 @@ proc fromCbor*(_: type Time; n: CborNode): ?!Time = elif n.tag.get == 1 and n.kind in {cborUnsigned, cborNegative, cborFloat}: v = parseTime(n) return success(v) - except ValueError as e: return failure(e) + except ValueError as e: + return failure(e) func isTagged*(n: CborNode): bool = ## Check if a CBOR item has a tag. n.tag.isSome -func hasTag*(n: CborNode; tag: Natural): bool = +func hasTag*(n: CborNode, tag: Natural): bool = ## Check if a CBOR item has a tag. n.tag.isSome and n.tag.get == (uint64)tag -proc `tag=`*(result: var CborNode; tag: Natural) = +proc `tag=`*(result: var CborNode, tag: Natural) = ## Tag a CBOR item. result.tag = some(tag.uint64) @@ -541,7 +499,7 @@ func tag*(n: CborNode): uint64 = func isBool*(n: CborNode): bool = (n.kind == cborSimple) and (n.simple in {20, 21}) -func getBool*(n: CborNode; default = false): bool = +func getBool*(n: CborNode, default = false): bool = ## Get the boolean value of a ``CborNode`` or a fallback. if n.kind == cborSimple: case n.simple @@ -555,31 +513,28 @@ func isNull*(n: CborNode): bool = ## Return true if ``n`` is a CBOR null. (n.kind == cborSimple) and (n.simple == 22) -proc getUnsigned*(n: CborNode; default: uint64 = 0): uint64 = +proc getUnsigned*(n: CborNode, default: uint64 = 0): uint64 = ## Get the numerical value of a ``CborNode`` or a fallback. case n.kind of cborUnsigned: n.uint of cborNegative: n.int.uint64 else: default -proc getSigned*(n: CborNode; default: int64 = 0): int64 = +proc getSigned*(n: CborNode, default: int64 = 0): int64 = ## Get the numerical value of a ``CborNode`` or a fallback. case n.kind of cborUnsigned: n.uint.int64 of cborNegative: n.int else: default -func getFloat*(n: CborNode; default = 0.0): float = +func getFloat*(n: CborNode, default = 0.0): float = ## Get the floating-poing value of a ``CborNode`` or a fallback. - if n.kind == cborFloat: - n.float - else: - default + if n.kind == cborFloat: n.float else: default -proc fromCbor*[T: distinct](_: type T; n: CborNode): ?!T = +proc fromCbor*[T: distinct](_: type T, n: CborNode): ?!T = success T(?T.distinctBase.fromCbor(n)) -proc fromCbor*[T: SomeUnsignedInt](_: type T; n: CborNode): ?!T = +proc fromCbor*[T: SomeUnsignedInt](_: type T, n: CborNode): ?!T = expectCborKind(T, {cborUnsigned}, n) var v = T(n.uint) if v.BiggestUInt == n.uint: @@ -587,7 +542,7 @@ proc fromCbor*[T: SomeUnsignedInt](_: type T; n: CborNode): ?!T = else: return failure(newCborError("Value overflow for unsigned integer")) -proc fromCbor*[T: SomeSignedInt](_: type T; n: CborNode): ?!T = +proc fromCbor*[T: SomeSignedInt](_: type T, n: CborNode): ?!T = expectCborKind(T, {cborUnsigned, cborNegative}, n) if n.kind == cborUnsigned: var v = T(n.uint) @@ -602,31 +557,31 @@ proc fromCbor*[T: SomeSignedInt](_: type T; n: CborNode): ?!T = else: return failure(newCborError("Value overflow for signed integer")) -proc fromCbor*[T: SomeFloat](_: type T; n: CborNode): ?!T = +proc fromCbor*[T: SomeFloat](_: type T, n: CborNode): ?!T = expectCborKind(T, {cborFloat}, n) return success(T(n.float)) -proc fromCbor*(_: type seq[byte]; n: CborNode): ?!seq[byte] = +proc fromCbor*(_: type seq[byte], n: CborNode): ?!seq[byte] = expectCborKind(seq[byte], cborBytes, n) return success(n.bytes) -proc fromCbor*(_: type string; n: CborNode): ?!string = +proc fromCbor*(_: type string, n: CborNode): ?!string = expectCborKind(string, cborText, n) return success(n.text) -proc fromCbor*(_: type bool; n: CborNode): ?!bool = +proc fromCbor*(_: type bool, n: CborNode): ?!bool = if not n.isBool: return failure(newCborError("Expected boolean, got " & $n.kind)) return success(n.getBool) -proc fromCbor*[T](_: type seq[T]; n: CborNode): ?!seq[T] = +proc fromCbor*[T](_: type seq[T], n: CborNode): ?!seq[T] = expectCborKind(seq[T], cborArray, n) var arr = newSeq[T](n.seq.len) for i, elem in n.seq: arr[i] = ?T.fromCbor(elem) success arr -proc fromCbor*[T: tuple](_: type T; n: CborNode): ?!T = +proc fromCbor*[T: tuple](_: type T, n: CborNode): ?!T = expectCborKind(T, cborArray, n) var res = T.default if n.seq.len != T.tupleLen: @@ -737,7 +692,7 @@ proc fromCbor*[T: tuple](_: type T; n: CborNode): ?!T = # except Exception as e: # raise newException(Defect, e.msg, e) -proc fromCbor*[T: ref](_: type T; n: CborNode): ?!T = +proc fromCbor*[T: ref](_: type T, n: CborNode): ?!T = when T is ref: if n.isNull: return success(T.default) @@ -749,7 +704,7 @@ proc fromCbor*[T: ref](_: type T; n: CborNode): ?!T = resRef[] = res.value return success(resRef) -proc fromCbor*[T: object](_: type T; n: CborNode): ?!T = +proc fromCbor*[T: object](_: type T, n: CborNode): ?!T = when T is CborNode: return success T(n) @@ -784,7 +739,6 @@ proc fromCbor*[T: object](_: type T; n: CborNode): ?!T = except Exception as e: raise newException(Defect, e.msg, e) -proc fromCbor*[T: ref object or object](_: type T; str: string): ?!T = - var n = ?parseCbor(str) +proc fromCbor*[T: ref object or object](_: type T, str: string): ?!T = + var n = ?(parseCbor(str)).catch T.fromCbor(n) - diff --git a/serde/cbor/errors.nim b/serde/cbor/errors.nim index db6cdc5..e028312 100644 --- a/serde/cbor/errors.nim +++ b/serde/cbor/errors.nim @@ -10,8 +10,7 @@ proc newUnexpectedKindError*( ): ref UnexpectedKindError = newException( UnexpectedKindError, - "deserialization to " & $expectedType & " failed: expected " & - expectedKinds & + "deserialization to " & $expectedType & " failed: expected " & expectedKinds & " but got " & $cbor.kind, ) @@ -37,3 +36,7 @@ proc newUnexpectedKindError*( proc newCborError*(msg: string): ref CborParseError = newException(CborParseError, msg) + +proc parseAssert*(check: bool, msg = "") {.inline.} = + if not check: + raise newException(CborParseError, msg) diff --git a/serde/cbor/helpers.nim b/serde/cbor/helpers.nim index 4332dab..9810c6c 100644 --- a/serde/cbor/helpers.nim +++ b/serde/cbor/helpers.nim @@ -5,22 +5,26 @@ import ./types import ./errors from macros import newDotExpr, newIdentNode, strVal -template expectCborKind*(expectedType: type, expectedKinds: set[CborNodeKind], - cbor: CborNode) = +template expectCborKind*( + expectedType: type, expectedKinds: set[CborNodeKind], cbor: CborNode +) = if cbor.kind notin expectedKinds: return failure(newUnexpectedKindError(expectedType, expectedKinds, cbor)) -template expectCborKind*(expectedType: type, expectedKind: CborNodeKind, - cbor: CborNode) = +template expectCborKind*( + expectedType: type, expectedKind: CborNodeKind, cbor: CborNode +) = expectCborKind(expectedType, {expectedKind}, cbor) -template expectCborKind*(expectedType: type, expectedKinds: set[CborEventKind], - cbor: CborNode) = +template expectCborKind*( + expectedType: type, expectedKinds: set[CborEventKind], cbor: CborNode +) = if cbor.kind notin expectedKinds: return failure(newUnexpectedKindError(expectedType, expectedKinds, cbor)) -template expectCborKind*(expectedType: type, expectedKind: CborEventKind, - cbor: CborNode) = +template expectCborKind*( + expectedType: type, expectedKind: CborEventKind, cbor: CborNode +) = expectCborKind(expectedType, {expectedKind}, cbor) macro dot*(obj: object, fld: string): untyped = @@ -29,15 +33,20 @@ macro dot*(obj: object, fld: string): untyped = func floatSingle*(half: uint16): float32 = ## Convert a 16-bit float to 32-bits. - func ldexp(x: float64; exponent: int): float64 {.importc: "ldexp", - header: "".} + func ldexp( + x: float64, exponent: int + ): float64 {.importc: "ldexp", header: "".} let exp = (half shr 10) and 0x1f mant = float64(half and 0x3ff) - val = if exp == 0: + val = + if exp == 0: ldexp(mant, -24) elif exp != 31: ldexp(mant + 1024, exp.int - 25) else: if mant == 0: Inf else: NaN - if (half and 0x8000) == 0: val else: -val + if (half and 0x8000) == 0: + val + else: + -val diff --git a/serde/cbor/jsonhook.nim b/serde/cbor/jsonhook.nim index faccd1a..fe0b39b 100644 --- a/serde/cbor/jsonhook.nim +++ b/serde/cbor/jsonhook.nim @@ -7,39 +7,39 @@ import ./types import ./errors import ./deserializer -proc toJson*(n: CborNode): JsonNode = - case n.kind: - of cborUnsigned: - newJInt n.uint.BiggestInt - of cborNegative: - newJInt n.int.BiggestInt - of cborBytes: - newJString base64.encode(cast[string](n.bytes), safe = true) - of cborText: - newJString n.text - of cborArray: - let a = newJArray() - for e in n.seq.items: - a.add(e.toJson) - a - of cborMap: - let o = newJObject() - for k, v in n.map.pairs: - if k.kind == cborText: - o[k.text] = v.toJson - else: - o[$k] = v.toJson - o - of cborTag: nil - of cborSimple: - if n.isBool: - newJBool(n.getBool()) - elif n.isNull: - newJNull() - else: nil - of cborFloat: - newJFloat n.float - of cborRaw: - without parsed =? parseCbor(n.raw), error: - raise newCborError(error.msg) - toJson(parsed) +proc toJson*(n: CborNode): JsonNode {.raises: [CborParseError].} = + case n.kind + of cborUnsigned: + newJInt n.uint.BiggestInt + of cborNegative: + newJInt n.int.BiggestInt + of cborBytes: + newJString base64.encode(cast[string](n.bytes), safe = true) + of cborText: + newJString n.text + of cborArray: + let a = newJArray() + for e in n.seq.items: + a.add(e.toJson) + a + of cborMap: + let o = newJObject() + for k, v in n.map.pairs: + if k.kind == cborText: + o[k.text] = v.toJson + else: + o[$k] = v.toJson + o + of cborTag: + nil + of cborSimple: + if n.isBool: + newJBool(n.getBool()) + elif n.isNull: + newJNull() + else: + nil + of cborFloat: + newJFloat n.float + of cborRaw: + toJson(parseCbor(n.raw)) diff --git a/serde/cbor/serializer.nim b/serde/cbor/serializer.nim index d557931..a222f0d 100644 --- a/serde/cbor/serializer.nim +++ b/serde/cbor/serializer.nim @@ -40,7 +40,7 @@ func floatHalf(single: float32): uint16 = func initialByte(major, minor: Natural): uint8 {.inline.} = uint8((major shl 5) or (minor and 0b11111)) -proc writeInitial[T: SomeInteger](str: Stream; m: uint8; n: T): ?!void = +proc writeInitial[T: SomeInteger](str: Stream, m: uint8, n: T): ?!void = ## Write the initial integer of a CBOR item. try: let m = m shl 5 @@ -58,27 +58,27 @@ proc writeInitial[T: SomeInteger](str: Stream; m: uint8; n: T): ?!void = str.write(n.uint8) elif uint64(n) <= uint64(uint16.high): str.write(m or 25'u8) - str.write((uint8)n shr 8) - str.write((uint8)n) + str.write((uint8) n shr 8) + str.write((uint8) n) elif uint64(n) <= uint64(uint32.high): str.write(m or 26'u8) for i in countdown(24, 8, 8): {.unroll.} - str.write((uint8)n shr i) - str.write((uint8)n) + str.write((uint8) n shr i) + str.write((uint8) n) else: str.write(m or 27'u8) for i in countdown(56, 8, 8): {.unroll.} - str.write((uint8)n shr i) - str.write((uint8)n) + str.write((uint8) n shr i) + str.write((uint8) n) success() except IOError as e: return failure(e.msg) except OSError as o: return failure(o.msg) -proc writeCborArrayLen*(str: Stream; len: Natural): ?!void = +proc writeCborArrayLen*(str: Stream, len: Natural): ?!void = ## Write a marker to the stream that initiates an array of ``len`` items. str.writeInitial(4, len) @@ -88,7 +88,7 @@ proc writeCborIndefiniteArrayLen*(str: Stream): ?!void = ## of definite lengths. catch str.write(initialByte(4, 31)) -proc writeCborMapLen*(str: Stream; len: Natural): ?!void = +proc writeCborMapLen*(str: Stream, len: Natural): ?!void = ## Write a marker to the stream that initiates an map of ``len`` pairs. str.writeInitial(5, len) @@ -102,11 +102,11 @@ proc writeCborBreak*(str: Stream): ?!void = ## Write a marker to the stream that ends an indefinite array or map. catch str.write(initialByte(7, 31)) -proc writeCborTag*(str: Stream; tag: Natural): ?!void {.inline.} = +proc writeCborTag*(str: Stream, tag: Natural): ?!void {.inline.} = ## Write a tag for the next CBOR item to a binary stream. str.writeInitial(6, tag) -proc writeCbor*(str: Stream; buf: pointer; len: int): ?!void = +proc writeCbor*(str: Stream, buf: pointer, len: int): ?!void = ## Write a raw buffer to a CBOR `Stream`. ?str.writeInitial(BytesMajor, len) if len > 0: @@ -115,45 +115,45 @@ proc writeCbor*(str: Stream; buf: pointer; len: int): ?!void = proc isSorted*(n: CborNode): ?!bool {.gcsafe.} -proc writeCbor(str: Stream; v: SomeUnsignedInt): ?!void = +proc writeCbor(str: Stream, v: SomeUnsignedInt): ?!void = str.writeInitial(0, v) -proc writeCbor*(str: Stream; v: SomeSignedInt): ?!void = +proc writeCbor*(str: Stream, v: SomeSignedInt): ?!void = if v < 0: - ?str.writeInitial(1, -1-v) + ?str.writeInitial(1, -1 - v) else: ?str.writeInitial(0, v) success() -proc writeCbor*(str: Stream; v: seq[byte]): ?!void = +proc writeCbor*(str: Stream, v: seq[byte]): ?!void = ?str.writeInitial(BytesMajor, v.len) if v.len > 0: return catch str.writeData(unsafeAddr v[0], v.len) success() -proc writeCbor*(str: Stream; v: string): ?!void = +proc writeCbor*(str: Stream, v: string): ?!void = ?str.writeInitial(TextMajor, v.len) return catch str.write(v) -proc writeCbor*[T: char or uint8 or int8](str: Stream; v: openArray[T]): ?!void = +proc writeCbor*[T: char or uint8 or int8](str: Stream, v: openArray[T]): ?!void = ?str.writeInitial(BytesMajor, v.len) if v.len > 0: return catch str.writeData(unsafeAddr v[0], v.len) success() -proc writeCbor*[T: array or seq](str: Stream; v: T): ?!void = +proc writeCbor*[T: array or seq](str: Stream, v: T): ?!void = ?str.writeInitial(4, v.len) for e in v.items: ?str.writeCbor(e) success() -proc writeCbor*(str: Stream; v: tuple): ?!void = +proc writeCbor*(str: Stream, v: tuple): ?!void = ?str.writeInitial(4, v.tupleLen) for e in v.fields: ?str.writeCbor(e) success() -proc writeCbor*[T: ptr | ref](str: Stream; v: T): ?!void = +proc writeCbor*[T: ptr | ref](str: Stream, v: T): ?!void = if system.`==`(v, nil): # Major type 7 return catch str.write(Null) @@ -161,10 +161,10 @@ proc writeCbor*[T: ptr | ref](str: Stream; v: T): ?!void = ?str.writeCbor(v[]) success() -proc writeCbor*(str: Stream; v: bool): ?!void = +proc writeCbor*(str: Stream, v: bool): ?!void = return catch str.write(initialByte(7, (if v: 21 else: 20))) -proc writeCbor*[T: SomeFloat](str: Stream; v: T): ?!void = +proc writeCbor*[T: SomeFloat](str: Stream, v: T): ?!void = try: case v.classify of fcNormal, fcSubnormal: @@ -198,31 +198,31 @@ proc writeCbor*[T: SomeFloat](str: Stream; v: T): ?!void = return success() of fcZero: str.write initialByte(7, 25) - str.write((char)0x00) + str.write((char) 0x00) of fcNegZero: str.write initialByte(7, 25) - str.write((char)0x80) + str.write((char) 0x80) of fcInf: str.write initialByte(7, 25) - str.write((char)0x7c) + str.write((char) 0x7c) of fcNan: str.write initialByte(7, 25) - str.write((char)0x7e) + str.write((char) 0x7e) of fcNegInf: str.write initialByte(7, 25) - str.write((char)0xfc) - str.write((char)0x00) + str.write((char) 0xfc) + str.write((char) 0x00) success() except IOError as io: return failure(io.msg) except OSError as os: return failure(os.msg) -proc writeCbor*(str: Stream; v: CborNode): ?!void = +proc writeCbor*(str: Stream, v: CborNode): ?!void = try: if v.tag.isSome: ?str.writeCborTag(v.tag.get) - case v.kind: + case v.kind of cborUnsigned: ?str.writeCbor(v.uint) of cborNegative: @@ -263,7 +263,7 @@ proc writeCbor*(str: Stream; v: CborNode): ?!void = except CatchableError as e: return failure(e.msg) -proc writeCbor*[T: object](str: Stream; v: T): ?!void = +proc writeCbor*[T: object](str: Stream, v: T): ?!void = var n: uint for _, _ in v.fieldPairs: inc n @@ -273,7 +273,7 @@ proc writeCbor*[T: object](str: Stream; v: T): ?!void = ?str.writeCbor(f) success() -proc writeCborArray*(str: Stream; args: varargs[CborNode, toCbor]): ?!void = +proc writeCborArray*(str: Stream, args: varargs[CborNode, toCbor]): ?!void = ## Encode to a CBOR array in binary form. This magic doesn't ## always work, some arguments may need to be explicitly ## converted with ``toCbor`` before passing. @@ -308,7 +308,8 @@ proc isSorted(n: CborNode): ?!bool = return failure(error.msg) let thisRaw = res.raw if lastRaw != "": - if cmp(lastRaw, thisRaw) > 0: return success(false) + if cmp(lastRaw, thisRaw) > 0: + return success(false) lastRaw = thisRaw success(true) @@ -321,7 +322,7 @@ proc sort*(n: var CborNode): ?!void = return failure(error) if tmp.hasKey(res): tmp[res] = move(val) - sort(tmp) do (x, y: tuple[k: CborNode; v: CborNode]) -> int: + sort(tmp) do(x, y: tuple[k: CborNode, v: CborNode]) -> int: result = cmp(x.k.raw, y.k.raw) n.map = move tmp success() @@ -330,21 +331,22 @@ proc sort*(n: var CborNode): ?!void = except Exception as e: raise newException(Defect, e.msg, e) -proc writeCbor*(str: Stream; dt: DateTime): ?!void = +proc writeCbor*(str: Stream, dt: DateTime): ?!void = ## Write a `DateTime` using the tagged string representation ## defined in RCF7049 section 2.4.1. ?writeCborTag(str, 0) ?writeCbor(str, format(dt, dateTimeFormat)) success() -proc writeCbor*(str: Stream; t: Time): ?!void = +proc writeCbor*(str: Stream, t: Time): ?!void = ## Write a `Time` using the tagged numerical representation ## defined in RCF7049 section 2.4.1. ?writeCborTag(str, 1) ?writeCbor(str, t.toUnix) success() -func toCbor*(x: CborNode): ?!CborNode = success(x) +func toCbor*(x: CborNode): ?!CborNode = + success(x) func toCbor*(x: SomeInteger): ?!CborNode = if x > 0: @@ -369,7 +371,7 @@ func toCbor*(pairs: openArray[(CborNode, CborNode)]): ?!CborNode = except Exception as e: raise newException(Defect, e.msg, e) -func toCbor*(tag: uint64; val: CborNode): ?!CborNode = +func toCbor*(tag: uint64, val: CborNode): ?!CborNode = without res =? toCbor(val), error: return failure(error.msg) var cnode = res @@ -392,11 +394,11 @@ func toCbor*(x: pointer): ?!CborNode = return failure("pointer is not nil") success(CborNode(kind: cborSimple, simple: 22)) -func initCborBytes*[T: char|byte](buf: openArray[T]): CborNode = +func initCborBytes*[T: char | byte](buf: openArray[T]): CborNode = ## Create a CBOR byte string from `buf`. result = CborNode(kind: cborBytes, bytes: newSeq[byte](buf.len)) - for i in 0..= (1, 4): switch("hint", "XCannotRaiseY:off") when (NimMajor, NimMinor, NimPatch) >= (1, 6, 11): - switch("warning", "BareExcept:off") \ No newline at end of file + switch("warning", "BareExcept:off") diff --git a/tests/json/deserialize/objects.nim b/tests/json/deserialize/objects.nim index f295f91..93edeb9 100644 --- a/tests/json/deserialize/objects.nim +++ b/tests/json/deserialize/objects.nim @@ -99,7 +99,8 @@ suite "json - deserialize objects": myint: int let expected = MyRef(mystring: "abc", myint: 1) - let byteArray = """{ + let byteArray = + """{ "mystring": "abc", "myint": 1 }""".toBytes diff --git a/tests/json/testSerialize.nim b/tests/json/testSerialize.nim index c51dbc3..4338f34 100644 --- a/tests/json/testSerialize.nim +++ b/tests/json/testSerialize.nim @@ -50,7 +50,8 @@ suite "json serialization - serialize": let json = %*{"myobj": myobj, "mystuint": mystuint} - let expected = """{ + let expected = + """{ "myobj": { "mystring": "abc", "myint": 123, @@ -69,7 +70,8 @@ suite "json serialization - serialize": let obj = %MyObj(mystring: "abc", myint: 1, mybool: true) - let expected = """{ + let expected = + """{ "mystring": "abc", "myint": 1 }""".flatten @@ -83,7 +85,8 @@ suite "json serialization - serialize": let obj = %MyRef(mystring: "abc", myint: 1) - let expected = """{ + let expected = + """{ "mystring": "abc", "myint": 1 }""".flatten diff --git a/tests/test.nimble b/tests/test.nimble index 897430e..fc7114f 100644 --- a/tests/test.nimble +++ b/tests/test.nimble @@ -9,4 +9,3 @@ requires "questionable >= 0.10.13 & < 0.11.0" task test, "Run the test suite": exec "nimble install -d -y" exec "nim c -r test" - \ No newline at end of file