feat: refactor CBOR parser to use direct exceptions instead of Result type

This commit is contained in:
munna0908 2025-05-31 17:38:35 +05:30
parent ff37e61b54
commit 9d3da40c0a
No known key found for this signature in database
GPG Key ID: 2FFCD637E937D3E6
20 changed files with 498 additions and 473 deletions

View File

@ -1,5 +1,7 @@
--styleCheck:usages --styleCheck:
--styleCheck:error usages
--styleCheck:
error
# begin Nimble config (version 1) # begin Nimble config (version 1)
when fileExists("nimble.paths"): when fileExists("nimble.paths"):

View File

@ -18,10 +18,11 @@ export macros
{.push raises: [].} {.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. ## 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. ## Begin parsing a stream of CBOR in binary form.
## The parser will be initialized in an EOF state, call ## The parser will be initialized in an EOF state, call
## ``next`` to advance it before parsing. ## ``next`` to advance it before parsing.
@ -29,7 +30,7 @@ proc open*(c: var CborParser; s: Stream) =
c.kind = cborEof c.kind = cborEof
c.intVal = 0 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. ## Advance the parser to the initial or next event.
try: try:
if c.s.atEnd: if c.s.atEnd:
@ -41,7 +42,7 @@ proc next*(c: var CborParser): ?!void =
mb = ib shr 5 mb = ib shr 5
c.minor = ib and 0b11111 c.minor = ib and 0b11111
case c.minor case c.minor
of 0..23: of 0 .. 23:
c.intVal = c.minor.uint64 c.intVal = c.minor.uint64
of 24: of 24:
c.intVal = c.s.readChar.uint64 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 c.intVal = (c.intVal shl 8) or c.s.readChar.uint64
of 26: of 26:
c.intVal = c.s.readChar.uint64 c.intVal = c.s.readChar.uint64
for _ in 1..3: for _ in 1 .. 3:
{.unroll.} {.unroll.}
c.intVal = (c.intVal shl 8) or c.s.readChar.uint64 c.intVal = (c.intVal shl 8) or c.s.readChar.uint64
of 27: of 27:
c.intVal = c.s.readChar.uint64 c.intVal = c.s.readChar.uint64
for _ in 1..7: for _ in 1 .. 7:
{.unroll.} {.unroll.}
c.intVal = (c.intVal shl 8) or c.s.readChar.uint64 c.intVal = (c.intVal shl 8) or c.s.readChar.uint64
else: else:
@ -83,333 +84,284 @@ proc next*(c: var CborParser): ?!void =
else: else:
c.kind = CborEventKind.cborSimple c.kind = CborEventKind.cborSimple
else: else:
return failure(newCborError("unhandled major type " & $mb)) raise newCborError("unhandled major type " & $mb)
success()
except IOError as e: except IOError as e:
return failure(e) raise newException(CborParseError, e.msg, e)
except OSError as 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. ## Parse the integer value that the parser is positioned on.
if c.kind != CborEventKind.cborPositive: parseAssert(
return failure(newCborError("Expected positive integer, got " & $c.kind)) c.kind == CborEventKind.cborPositive, "Expected positive integer, got " & $c.kind
let val = c.intVal.BiggestUInt )
result = c.intVal.BiggestUInt
?c.next() c.next()
return success(val)
proc nextInt*(c: var CborParser): ?!BiggestInt = proc nextInt*(c: var CborParser): BiggestInt {.raises: [CborParseError].} =
## Parse the integer value that the parser is positioned on. ## Parse the integer value that the parser is positioned on.
var val: BiggestInt
case c.kind case c.kind
of CborEventKind.cborPositive: of CborEventKind.cborPositive:
val = c.intVal.BiggestInt result = c.intVal.BiggestInt
of CborEventKind.cborNegative: of CborEventKind.cborNegative:
val = -1.BiggestInt - c.intVal.BiggestInt result = -1.BiggestInt - c.intVal.BiggestInt
else: else:
return failure(newCborError("Expected integer, got " & $c.kind)) raise newCborError("Expected integer, got " & $c.kind)
?c.next() c.next()
return success(val)
proc nextFloat*(c: var CborParser): ?!float64 = proc nextFloat*(c: var CborParser): float64 {.raises: [CborParseError].} =
## Parse the float value that the parser is positioned on. ## Parse the float value that the parser is positioned on.
var val: float64 parseAssert(c.kind == CborEventKind.cborFloat, "Expected float, got " & $c.kind)
if c.kind != CborEventKind.cborFloat:
return failure(newCborError("Expected float, got " & $c.kind))
case c.minor case c.minor
of 25: of 25:
val = floatSingle(c.intVal.uint16).float64 result = floatSingle(c.intVal.uint16).float64
of 26: of 26:
val = cast[float32](c.intVal).float64 result = cast[float32](c.intVal).float64
of 27: of 27:
val = cast[float64](c.intVal) result = cast[float64](c.intVal)
else: else:
discard discard
?c.next() c.next()
return success(val)
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. ## Return the length of the byte string that the parser is positioned on.
if c.kind != CborEventKind.cborBytes: parseAssert(c.kind == CborEventKind.cborBytes, "Expected bytes, got " & $c.kind)
return failure(newCborError("Expected bytes, got " & $c.kind)) c.intVal.int
return success(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. ## Read the bytes that the parser is positioned on and advance.
try: try:
if c.kind != CborEventKind.cborBytes: parseAssert(c.kind == CborEventKind.cborBytes, "Expected bytes, got " & $c.kind)
return failure(newCborError("Expected bytes, got " & $c.kind)) parseAssert(
if buf.len != c.intVal.int: buf.len == c.intVal.int,
return failure(newCborError("Buffer length mismatch: expected " & "Buffer length mismatch: expected " & $c.intVal.int & ", got " & $buf.len,
$c.intVal.int & ", got " & $buf.len)) )
if buf.len > 0: if buf.len > 0:
let n = c.s.readData(buf[0].addr, buf.len) let n = c.s.readData(buf[0].addr, buf.len)
if n != buf.len: parseAssert(n == buf.len, "truncated read of CBOR data")
return failure(newCborError("truncated read of CBOR data")) c.next()
?c.next()
success()
except OSError as e: except OSError as e:
return failure(e.msg) raise newException(CborParseError, e.msg, e)
except IOError as 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. ## Read the bytes that the parser is positioned on into a seq and advance.
var val = newSeq[byte](c.intVal.int) result = newSeq[byte](c.intVal.int)
let nextRes = nextBytes(c, val) nextBytes(c, result)
if nextRes.isFailure:
return failure(nextRes.error)
return success(val) func textLen*(c: CborParser): int {.raises: [CborParseError].} =
func textLen*(c: CborParser): ?!int =
## Return the length of the text that the parser is positioned on. ## Return the length of the text that the parser is positioned on.
if c.kind != CborEventKind.cborText: parseAssert(c.kind == CborEventKind.cborText, "Expected text, got " & $c.kind)
return failure(newCborError("Expected text, got " & $c.kind)) c.intVal.int
return success(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. ## Read the text that the parser is positioned on into a string and advance.
try: try:
if c.kind != CborEventKind.cborText: parseAssert(c.kind == CborEventKind.cborText, "Expected text, got " & $c.kind)
return failure(newCborError("Expected text, got " & $c.kind))
buf.setLen c.intVal.int buf.setLen c.intVal.int
if buf.len > 0: if buf.len > 0:
let n = c.s.readData(buf[0].addr, buf.len) let n = c.s.readData(buf[0].addr, buf.len)
if n != buf.len: parseAssert(n == buf.len, "truncated read of CBOR data")
return failure(newCborError("truncated read of CBOR data")) c.next()
?c.next()
success()
except IOError as e: except IOError as e:
return failure(e.msg) raise newException(CborParseError, e.msg, e)
except OSError as 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. ## Read the text that the parser is positioned on into a string and advance.
var buf: string nextText(c, result)
let nextRes = nextText(c, buf)
if nextRes.isFailure:
return failure(nextRes.error)
return success(buf) func arrayLen*(c: CborParser): int {.raises: [CborParseError].} =
func arrayLen*(c: CborParser): ?!int =
## Return the length of the array that the parser is positioned on. ## Return the length of the array that the parser is positioned on.
if c.kind != CborEventKind.cborArray: parseAssert(c.kind == CborEventKind.cborArray, "Expected array, got " & $c.kind)
return failure(newCborError("Expected array, got " & $c.kind)) c.intVal.int
return success(c.intVal.int) func mapLen*(c: CborParser): int {.raises: [CborParseError].} =
func mapLen*(c: CborParser): ?!int =
## Return the length of the map that the parser is positioned on. ## Return the length of the map that the parser is positioned on.
if c.kind != CborEventKind.cborMap: parseAssert(c.kind == CborEventKind.cborMap, "Expected map, got " & $c.kind)
return failure(newCborError("Expected map, got " & $c.kind)) c.intVal.int
return success(c.intVal.int) func tag*(c: CborParser): uint64 {.raises: [CborParseError].} =
func tag*(c: CborParser): ?!uint64 =
## Return the tag value the parser is positioned on. ## Return the tag value the parser is positioned on.
if c.kind != CborEventKind.cborTag: parseAssert(c.kind == CborEventKind.cborTag, "Expected tag, got " & $c.kind)
return failure(newCborError("Expected tag, got " & $c.kind)) c.intVal
return success(c.intVal) proc skipNode*(c: var CborParser) {.raises: [CborParseError].} =
proc skipNode*(c: var CborParser): ?!void =
## Skip the item the parser is positioned on. ## Skip the item the parser is positioned on.
try: try:
case c.kind case c.kind
of CborEventKind.cborEof: of CborEventKind.cborEof:
return failure(newCborError("end of CBOR stream")) raise newCborError("end of CBOR stream")
of CborEventKind.cborPositive, CborEventKind.cborNegative, of CborEventKind.cborPositive, CborEventKind.cborNegative,
CborEventKind.cborSimple: CborEventKind.cborSimple:
return c.next() c.next()
of CborEventKind.cborBytes, CborEventKind.cborText: of CborEventKind.cborBytes, CborEventKind.cborText:
if c.isIndefinite: if c.isIndefinite:
?c.next() c.next()
while c.kind != CborEventKind.cborBreak: while c.kind != CborEventKind.cborBreak:
if c.kind != CborEventKind.cborBytes: parseAssert(
return failure(newCborError("expected bytes, got " & $c.kind)) c.kind == CborEventKind.cborBytes, "expected bytes, got " & $c.kind
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()
else: else:
for _ in 1..c.intVal.int: discard readChar(c.s) for _ in 1 .. c.intVal.int:
return c.next() discard readChar(c.s)
c.next()
of CborEventKind.cborArray: of CborEventKind.cborArray:
if c.isIndefinite: if c.isIndefinite:
?c.next() c.next()
while c.kind != CborEventKind.cborBreak: while c.kind != CborEventKind.cborBreak:
?c.skipNode() c.skipNode()
return c.next() c.next()
else: else:
let len = c.intVal let len = c.intVal
?c.next() c.next()
for i in 1..len: for i in 1 .. len:
?c.skipNode() c.skipNode()
of CborEventKind.cborMap: of CborEventKind.cborMap:
let mapLen = c.intVal.int let mapLen = c.intVal.int
if c.isIndefinite: if c.isIndefinite:
?c.next() c.next()
while c.kind != CborEventKind.cborBreak: while c.kind != CborEventKind.cborBreak:
?c.skipNode() c.skipNode()
return c.next() c.next()
else: else:
?c.next() c.next()
for _ in 1 .. mapLen: for _ in 1 .. mapLen:
?c.skipNode() c.skipNode()
of CborEventKind.cborTag: of CborEventKind.cborTag:
?c.next() c.next()
return c.skipNode() c.skipNode()
of CborEventKind.cborFloat: of CborEventKind.cborFloat:
without f =? c.nextFloat(), error: discard c.nextFloat()
return failure(error)
of CborEventKind.cborBreak: of CborEventKind.cborBreak:
discard discard
success() except OSError as os:
except OSError as e: raise newException(CborParseError, os.msg, os)
return failure(e.msg) except IOError as io:
except IOError as e: raise newException(CborParseError, io.msg, io)
return failure(e.msg)
proc nextNode*(c: var CborParser): CborNode {.raises: [CborParseError].} =
proc nextNode*(c: var CborParser): ?!CborNode =
## Parse the item the parser is positioned on into a ``CborNode``. ## Parse the item the parser is positioned on into a ``CborNode``.
## This is cheap for numbers or simple values but expensive ## This is cheap for numbers or simple values but expensive
## for nested types. ## for nested types.
try: try:
var next: CborNode
case c.kind case c.kind
of CborEventKind.cborEof: of CborEventKind.cborEof:
return failure(newCborError("end of CBOR stream")) raise newCborError("end of CBOR stream")
of CborEventKind.cborPositive: of CborEventKind.cborPositive:
next = CborNode(kind: cborUnsigned, uint: c.intVal) result = CborNode(kind: cborUnsigned, uint: c.intVal)
?c.next() c.next()
of CborEventKind.cborNegative: of CborEventKind.cborNegative:
next = CborNode(kind: cborNegative, int: -1 - c.intVal.int64) result = CborNode(kind: cborNegative, int: -1 - c.intVal.int64)
?c.next() c.next()
of CborEventKind.cborBytes: of CborEventKind.cborBytes:
if c.isIndefinite: if c.isIndefinite:
next = CborNode(kind: cborBytes, bytes: newSeq[byte]()) result = CborNode(kind: cborBytes, bytes: newSeq[byte]())
?c.next() c.next()
while c.kind != CborEventKind.cborBreak: while c.kind != CborEventKind.cborBreak:
if c.kind != CborEventKind.cborBytes: parseAssert(
return failure(newCborError("Expected bytes, got " & $c.kind)) c.kind == CborEventKind.cborBytes, "Expected bytes, got " & $c.kind
)
let let
chunkLen = c.intVal.int chunkLen = c.intVal.int
pos = next.bytes.len pos = result.bytes.len
next.bytes.setLen(pos+chunkLen) result.bytes.setLen(pos + chunkLen)
let n = c.s.readData(next.bytes[pos].addr, chunkLen) let n = c.s.readData(result.bytes[pos].addr, chunkLen)
if n != chunkLen: parseAssert(n == chunkLen, "truncated read of CBOR data")
return failure(newCborError("truncated read of CBOR data")) c.next()
?c.next()
else: else:
without rawBytes =? c.nextBytes(), error: result = CborNode(kind: cborBytes, bytes: c.nextBytes())
return failure(error)
next = CborNode(kind: cborBytes, bytes: rawBytes)
of CborEventKind.cborText: of CborEventKind.cborText:
if c.isIndefinite: if c.isIndefinite:
next = CborNode(kind: cborText, text: "") result = CborNode(kind: cborText, text: "")
?c.next() c.next()
while c.kind != CborEventKind.cborBreak: while c.kind != CborEventKind.cborBreak:
if c.kind != CborEventKind.cborText: parseAssert(c.kind == CborEventKind.cborText, "Expected text, got " & $c.kind)
return failure(newCborError("Expected text, got " & $c.kind))
let let
chunkLen = c.intVal.int chunkLen = c.intVal.int
pos = next.text.len pos = result.text.len
next.text.setLen(pos+chunkLen) result.text.setLen(pos + chunkLen)
let n = c.s.readData(next.text[pos].addr, chunkLen) let n = c.s.readData(result.text[pos].addr, chunkLen)
if n != chunkLen: parseAssert(n == chunkLen, "truncated read of CBOR data")
return failure(newCborError("truncated read of CBOR data")) c.next()
?c.next() c.next()
?c.next()
else: else:
without text =? c.nextText(), error: result = CborNode(kind: cborText, text: c.nextText())
return failure(error)
next = CborNode(kind: cborText, text: text)
of CborEventKind.cborArray: of CborEventKind.cborArray:
next = CborNode(kind: cborArray, seq: newSeq[CborNode](c.intVal)) result = CborNode(kind: cborArray, seq: newSeq[CborNode](c.intVal))
if c.isIndefinite: if c.isIndefinite:
?c.next() c.next()
while c.kind != CborEventKind.cborBreak: while c.kind != CborEventKind.cborBreak:
without node =? c.nextNode(), error: result.seq.add(c.nextNode())
return failure(error) c.next()
next.seq.add(node)
?c.next()
else: else:
?c.next() c.next()
for i in 0..next.seq.high: for i in 0 .. result.seq.high:
without node =? c.nextNode(), error: result.seq[i] = c.nextNode()
return failure(error)
next.seq[i] = node
of CborEventKind.cborMap: of CborEventKind.cborMap:
let mapLen = c.intVal.int let mapLen = c.intVal.int
next = CborNode(kind: cborMap, map: initOrderedTable[CborNode, CborNode]( result = CborNode(
mapLen.nextPowerOfTwo)) kind: cborMap, map: initOrderedTable[CborNode, CborNode](
mapLen.nextPowerOfTwo)
)
if c.isIndefinite: if c.isIndefinite:
?c.next() c.next()
while c.kind != CborEventKind.cborBreak: while c.kind != CborEventKind.cborBreak:
without key =? c.nextNode(), error: result.map[c.nextNode()] = c.nextNode()
return failure(error) c.next()
without val =? c.nextNode(), error:
return failure(error)
next.map[key] = val
?c.next()
else: else:
?c.next() c.next()
for _ in 1 .. mapLen: for _ in 1 .. mapLen:
without key =? c.nextNode(), error: result.map[c.nextNode()] = c.nextNode()
return failure(error)
without val =? c.nextNode(), error:
return failure(error)
next.map[key] = val
of CborEventKind.cborTag: of CborEventKind.cborTag:
let tag = c.intVal let tag = c.intVal
?c.next() c.next()
without node =? c.nextNode(), error: result = c.nextNode()
return failure(error) result.tag = some tag
next = node
next.tag = some tag
of CborEventKind.cborSimple: of CborEventKind.cborSimple:
case c.minor case c.minor
of 24: of 24:
next = CborNode(kind: cborSimple, simple: c.intVal.uint8) result = CborNode(kind: cborSimple, simple: c.intVal.uint8)
else: else:
next = CborNode(kind: cborSimple, simple: c.minor) result = CborNode(kind: cborSimple, simple: c.minor)
?c.next() c.next()
of CborEventKind.cborFloat: of CborEventKind.cborFloat:
without f =? c.nextFloat(), error: result = CborNode(kind: cborFloat, float: c.nextFloat())
return failure(error)
next = CborNode(kind: cborFloat, float: f)
of CborEventKind.cborBreak: of CborEventKind.cborBreak:
discard discard
success(next)
except OSError as e: except OSError as e:
return failure(e.msg) raise newException(CborParseError, e.msg, e)
except IOError as e: except IOError as e:
return failure(e.msg) raise newException(CborParseError, e.msg, e)
except CatchableError as e: except CatchableError as e:
return failure(e.msg) raise newException(CborParseError, e.msg, e)
except Exception as e: except Exception as e:
raise newException(Defect, e.msg, e) raise newException(Defect, e.msg, e)
proc readCbor*(s: Stream): CborNode {.raises: [CborParseError].} =
proc readCbor*(s: Stream): ?!CborNode =
## Parse a stream into a CBOR object. ## Parse a stream into a CBOR object.
var parser: CborParser var parser: CborParser
parser.open(s) parser.open(s)
?parser.next() parser.next()
parser.nextNode() parser.nextNode()
proc parseCbor*(s: string): ?!CborNode = proc parseCbor*(s: string): CborNode {.raises: [CborParseError].} =
## Parse a string into a CBOR object. ## Parse a string into a CBOR object.
## A wrapper over stream parsing. ## A wrapper over stream parsing.
readCbor(newStringStream s) readCbor(newStringStream s)
proc `$`*(n: CborNode): string = proc `$`*(n: CborNode): string {.raises: [CborParseError].} =
## Get a ``CborNode`` in diagnostic notation. ## Get a ``CborNode`` in diagnostic notation.
result = "" result = ""
if n.tag.isSome: if n.tag.isSome:
@ -429,7 +381,7 @@ proc `$`*(n: CborNode): string =
result.add escape n.text result.add escape n.text
of cborArray: of cborArray:
result.add "[" result.add "["
for i in 0..<n.seq.high: for i in 0 ..< n.seq.high:
result.add $(n.seq[i]) result.add $(n.seq[i])
result.add ", " result.add ", "
if n.seq.len > 0: if n.seq.len > 0:
@ -451,12 +403,19 @@ proc `$`*(n: CborNode): string =
discard discard
of cborSimple: of cborSimple:
case n.simple case n.simple
of 20: result.add "false" of 20:
of 21: result.add "true" result.add "false"
of 22: result.add "null" of 21:
of 23: result.add "undefined" result.add "true"
of 31: discard # break code for indefinite-length items of 22:
else: result.add "simple(" & $n.simple & ")" 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: of cborFloat:
case n.float.classify case n.float.classify
of fcNan: of fcNan:
@ -468,14 +427,11 @@ proc `$`*(n: CborNode): string =
else: else:
result.add $n.float result.add $n.float
of cborRaw: of cborRaw:
without val =? parseCbor(n.raw), error: result.add $parseCbor(n.raw)
return error.msg
result.add $val
if n.tag.isSome: if n.tag.isSome:
result.add(")") 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. ## Get the numerical value of a ``CborNode`` or a fallback.
case n.kind case n.kind
of cborUnsigned: n.uint.int of cborUnsigned: n.uint.int
@ -494,7 +450,7 @@ proc parseTime(n: CborNode): Time =
else: else:
assert false assert false
proc fromCbor*(_: type DateTime; n: CborNode): ?!DateTime = proc fromCbor*(_: type DateTime, n: CborNode): ?!DateTime =
## Parse a `DateTime` from the tagged string representation ## Parse a `DateTime` from the tagged string representation
## defined in RCF7049 section 2.4.1. ## defined in RCF7049 section 2.4.1.
var v: DateTime 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}: elif n.tag.get == 1 and n.kind in {cborUnsigned, cborNegative, cborFloat}:
v = parseTime(n).utc v = parseTime(n).utc
return success(v) 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 ## Parse a `Time` from the tagged string representation
## defined in RCF7049 section 2.4.1. ## defined in RCF7049 section 2.4.1.
var v: Time 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}: elif n.tag.get == 1 and n.kind in {cborUnsigned, cborNegative, cborFloat}:
v = parseTime(n) v = parseTime(n)
return success(v) return success(v)
except ValueError as e: return failure(e) except ValueError as e:
return failure(e)
func isTagged*(n: CborNode): bool = func isTagged*(n: CborNode): bool =
## Check if a CBOR item has a tag. ## Check if a CBOR item has a tag.
n.tag.isSome 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. ## Check if a CBOR item has a tag.
n.tag.isSome and n.tag.get == (uint64)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. ## Tag a CBOR item.
result.tag = some(tag.uint64) result.tag = some(tag.uint64)
@ -541,7 +499,7 @@ func tag*(n: CborNode): uint64 =
func isBool*(n: CborNode): bool = func isBool*(n: CborNode): bool =
(n.kind == cborSimple) and (n.simple in {20, 21}) (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. ## Get the boolean value of a ``CborNode`` or a fallback.
if n.kind == cborSimple: if n.kind == cborSimple:
case n.simple case n.simple
@ -555,31 +513,28 @@ func isNull*(n: CborNode): bool =
## Return true if ``n`` is a CBOR null. ## Return true if ``n`` is a CBOR null.
(n.kind == cborSimple) and (n.simple == 22) (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. ## Get the numerical value of a ``CborNode`` or a fallback.
case n.kind case n.kind
of cborUnsigned: n.uint of cborUnsigned: n.uint
of cborNegative: n.int.uint64 of cborNegative: n.int.uint64
else: default 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. ## Get the numerical value of a ``CborNode`` or a fallback.
case n.kind case n.kind
of cborUnsigned: n.uint.int64 of cborUnsigned: n.uint.int64
of cborNegative: n.int of cborNegative: n.int
else: default 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. ## Get the floating-poing value of a ``CborNode`` or a fallback.
if n.kind == cborFloat: if n.kind == cborFloat: n.float else: default
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)) 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) expectCborKind(T, {cborUnsigned}, n)
var v = T(n.uint) var v = T(n.uint)
if v.BiggestUInt == n.uint: if v.BiggestUInt == n.uint:
@ -587,7 +542,7 @@ proc fromCbor*[T: SomeUnsignedInt](_: type T; n: CborNode): ?!T =
else: else:
return failure(newCborError("Value overflow for unsigned integer")) 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) expectCborKind(T, {cborUnsigned, cborNegative}, n)
if n.kind == cborUnsigned: if n.kind == cborUnsigned:
var v = T(n.uint) var v = T(n.uint)
@ -602,31 +557,31 @@ proc fromCbor*[T: SomeSignedInt](_: type T; n: CborNode): ?!T =
else: else:
return failure(newCborError("Value overflow for signed integer")) 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) expectCborKind(T, {cborFloat}, n)
return success(T(n.float)) 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) expectCborKind(seq[byte], cborBytes, n)
return success(n.bytes) return success(n.bytes)
proc fromCbor*(_: type string; n: CborNode): ?!string = proc fromCbor*(_: type string, n: CborNode): ?!string =
expectCborKind(string, cborText, n) expectCborKind(string, cborText, n)
return success(n.text) return success(n.text)
proc fromCbor*(_: type bool; n: CborNode): ?!bool = proc fromCbor*(_: type bool, n: CborNode): ?!bool =
if not n.isBool: if not n.isBool:
return failure(newCborError("Expected boolean, got " & $n.kind)) return failure(newCborError("Expected boolean, got " & $n.kind))
return success(n.getBool) 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) expectCborKind(seq[T], cborArray, n)
var arr = newSeq[T](n.seq.len) var arr = newSeq[T](n.seq.len)
for i, elem in n.seq: for i, elem in n.seq:
arr[i] = ?T.fromCbor(elem) arr[i] = ?T.fromCbor(elem)
success arr success arr
proc fromCbor*[T: tuple](_: type T; n: CborNode): ?!T = proc fromCbor*[T: tuple](_: type T, n: CborNode): ?!T =
expectCborKind(T, cborArray, n) expectCborKind(T, cborArray, n)
var res = T.default var res = T.default
if n.seq.len != T.tupleLen: if n.seq.len != T.tupleLen:
@ -737,7 +692,7 @@ proc fromCbor*[T: tuple](_: type T; n: CborNode): ?!T =
# except Exception as e: # except Exception as e:
# raise newException(Defect, e.msg, 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: when T is ref:
if n.isNull: if n.isNull:
return success(T.default) return success(T.default)
@ -749,7 +704,7 @@ proc fromCbor*[T: ref](_: type T; n: CborNode): ?!T =
resRef[] = res.value resRef[] = res.value
return success(resRef) 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: when T is CborNode:
return success T(n) return success T(n)
@ -784,7 +739,6 @@ proc fromCbor*[T: object](_: type T; n: CborNode): ?!T =
except Exception as e: except Exception as e:
raise newException(Defect, e.msg, e) raise newException(Defect, e.msg, e)
proc fromCbor*[T: ref object or object](_: type T; str: string): ?!T = proc fromCbor*[T: ref object or object](_: type T, str: string): ?!T =
var n = ?parseCbor(str) var n = ?(parseCbor(str)).catch
T.fromCbor(n) T.fromCbor(n)

View File

@ -10,8 +10,7 @@ proc newUnexpectedKindError*(
): ref UnexpectedKindError = ): ref UnexpectedKindError =
newException( newException(
UnexpectedKindError, UnexpectedKindError,
"deserialization to " & $expectedType & " failed: expected " & "deserialization to " & $expectedType & " failed: expected " & expectedKinds &
expectedKinds &
" but got " & $cbor.kind, " but got " & $cbor.kind,
) )
@ -37,3 +36,7 @@ proc newUnexpectedKindError*(
proc newCborError*(msg: string): ref CborParseError = proc newCborError*(msg: string): ref CborParseError =
newException(CborParseError, msg) newException(CborParseError, msg)
proc parseAssert*(check: bool, msg = "") {.inline.} =
if not check:
raise newException(CborParseError, msg)

View File

@ -5,22 +5,26 @@ import ./types
import ./errors import ./errors
from macros import newDotExpr, newIdentNode, strVal from macros import newDotExpr, newIdentNode, strVal
template expectCborKind*(expectedType: type, expectedKinds: set[CborNodeKind], template expectCborKind*(
cbor: CborNode) = expectedType: type, expectedKinds: set[CborNodeKind], cbor: CborNode
) =
if cbor.kind notin expectedKinds: if cbor.kind notin expectedKinds:
return failure(newUnexpectedKindError(expectedType, expectedKinds, cbor)) return failure(newUnexpectedKindError(expectedType, expectedKinds, cbor))
template expectCborKind*(expectedType: type, expectedKind: CborNodeKind, template expectCborKind*(
cbor: CborNode) = expectedType: type, expectedKind: CborNodeKind, cbor: CborNode
) =
expectCborKind(expectedType, {expectedKind}, cbor) expectCborKind(expectedType, {expectedKind}, cbor)
template expectCborKind*(expectedType: type, expectedKinds: set[CborEventKind], template expectCborKind*(
cbor: CborNode) = expectedType: type, expectedKinds: set[CborEventKind], cbor: CborNode
) =
if cbor.kind notin expectedKinds: if cbor.kind notin expectedKinds:
return failure(newUnexpectedKindError(expectedType, expectedKinds, cbor)) return failure(newUnexpectedKindError(expectedType, expectedKinds, cbor))
template expectCborKind*(expectedType: type, expectedKind: CborEventKind, template expectCborKind*(
cbor: CborNode) = expectedType: type, expectedKind: CborEventKind, cbor: CborNode
) =
expectCborKind(expectedType, {expectedKind}, cbor) expectCborKind(expectedType, {expectedKind}, cbor)
macro dot*(obj: object, fld: string): untyped = macro dot*(obj: object, fld: string): untyped =
@ -29,15 +33,20 @@ macro dot*(obj: object, fld: string): untyped =
func floatSingle*(half: uint16): float32 = func floatSingle*(half: uint16): float32 =
## Convert a 16-bit float to 32-bits. ## Convert a 16-bit float to 32-bits.
func ldexp(x: float64; exponent: int): float64 {.importc: "ldexp", func ldexp(
header: "<math.h>".} x: float64, exponent: int
): float64 {.importc: "ldexp", header: "<math.h>".}
let let
exp = (half shr 10) and 0x1f exp = (half shr 10) and 0x1f
mant = float64(half and 0x3ff) mant = float64(half and 0x3ff)
val = if exp == 0: val =
if exp == 0:
ldexp(mant, -24) ldexp(mant, -24)
elif exp != 31: elif exp != 31:
ldexp(mant + 1024, exp.int - 25) ldexp(mant + 1024, exp.int - 25)
else: else:
if mant == 0: Inf else: NaN if mant == 0: Inf else: NaN
if (half and 0x8000) == 0: val else: -val if (half and 0x8000) == 0:
val
else:
-val

View File

@ -7,8 +7,8 @@ import ./types
import ./errors import ./errors
import ./deserializer import ./deserializer
proc toJson*(n: CborNode): JsonNode = proc toJson*(n: CborNode): JsonNode {.raises: [CborParseError].} =
case n.kind: case n.kind
of cborUnsigned: of cborUnsigned:
newJInt n.uint.BiggestInt newJInt n.uint.BiggestInt
of cborNegative: of cborNegative:
@ -30,16 +30,16 @@ proc toJson*(n: CborNode): JsonNode =
else: else:
o[$k] = v.toJson o[$k] = v.toJson
o o
of cborTag: nil of cborTag:
nil
of cborSimple: of cborSimple:
if n.isBool: if n.isBool:
newJBool(n.getBool()) newJBool(n.getBool())
elif n.isNull: elif n.isNull:
newJNull() newJNull()
else: nil else:
nil
of cborFloat: of cborFloat:
newJFloat n.float newJFloat n.float
of cborRaw: of cborRaw:
without parsed =? parseCbor(n.raw), error: toJson(parseCbor(n.raw))
raise newCborError(error.msg)
toJson(parsed)

View File

@ -40,7 +40,7 @@ func floatHalf(single: float32): uint16 =
func initialByte(major, minor: Natural): uint8 {.inline.} = func initialByte(major, minor: Natural): uint8 {.inline.} =
uint8((major shl 5) or (minor and 0b11111)) 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. ## Write the initial integer of a CBOR item.
try: try:
let m = m shl 5 let m = m shl 5
@ -58,27 +58,27 @@ proc writeInitial[T: SomeInteger](str: Stream; m: uint8; n: T): ?!void =
str.write(n.uint8) str.write(n.uint8)
elif uint64(n) <= uint64(uint16.high): elif uint64(n) <= uint64(uint16.high):
str.write(m or 25'u8) str.write(m or 25'u8)
str.write((uint8)n shr 8) str.write((uint8) n shr 8)
str.write((uint8)n) str.write((uint8) n)
elif uint64(n) <= uint64(uint32.high): elif uint64(n) <= uint64(uint32.high):
str.write(m or 26'u8) str.write(m or 26'u8)
for i in countdown(24, 8, 8): for i in countdown(24, 8, 8):
{.unroll.} {.unroll.}
str.write((uint8)n shr i) str.write((uint8) n shr i)
str.write((uint8)n) str.write((uint8) n)
else: else:
str.write(m or 27'u8) str.write(m or 27'u8)
for i in countdown(56, 8, 8): for i in countdown(56, 8, 8):
{.unroll.} {.unroll.}
str.write((uint8)n shr i) str.write((uint8) n shr i)
str.write((uint8)n) str.write((uint8) n)
success() success()
except IOError as e: except IOError as e:
return failure(e.msg) return failure(e.msg)
except OSError as o: except OSError as o:
return failure(o.msg) 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. ## Write a marker to the stream that initiates an array of ``len`` items.
str.writeInitial(4, len) str.writeInitial(4, len)
@ -88,7 +88,7 @@ proc writeCborIndefiniteArrayLen*(str: Stream): ?!void =
## of definite lengths. ## of definite lengths.
catch str.write(initialByte(4, 31)) 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. ## Write a marker to the stream that initiates an map of ``len`` pairs.
str.writeInitial(5, len) 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. ## Write a marker to the stream that ends an indefinite array or map.
catch str.write(initialByte(7, 31)) 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. ## Write a tag for the next CBOR item to a binary stream.
str.writeInitial(6, tag) 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`. ## Write a raw buffer to a CBOR `Stream`.
?str.writeInitial(BytesMajor, len) ?str.writeInitial(BytesMajor, len)
if len > 0: if len > 0:
@ -115,45 +115,45 @@ proc writeCbor*(str: Stream; buf: pointer; len: int): ?!void =
proc isSorted*(n: CborNode): ?!bool {.gcsafe.} proc isSorted*(n: CborNode): ?!bool {.gcsafe.}
proc writeCbor(str: Stream; v: SomeUnsignedInt): ?!void = proc writeCbor(str: Stream, v: SomeUnsignedInt): ?!void =
str.writeInitial(0, v) str.writeInitial(0, v)
proc writeCbor*(str: Stream; v: SomeSignedInt): ?!void = proc writeCbor*(str: Stream, v: SomeSignedInt): ?!void =
if v < 0: if v < 0:
?str.writeInitial(1, -1-v) ?str.writeInitial(1, -1 - v)
else: else:
?str.writeInitial(0, v) ?str.writeInitial(0, v)
success() success()
proc writeCbor*(str: Stream; v: seq[byte]): ?!void = proc writeCbor*(str: Stream, v: seq[byte]): ?!void =
?str.writeInitial(BytesMajor, v.len) ?str.writeInitial(BytesMajor, v.len)
if v.len > 0: if v.len > 0:
return catch str.writeData(unsafeAddr v[0], v.len) return catch str.writeData(unsafeAddr v[0], v.len)
success() success()
proc writeCbor*(str: Stream; v: string): ?!void = proc writeCbor*(str: Stream, v: string): ?!void =
?str.writeInitial(TextMajor, v.len) ?str.writeInitial(TextMajor, v.len)
return catch str.write(v) 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) ?str.writeInitial(BytesMajor, v.len)
if v.len > 0: if v.len > 0:
return catch str.writeData(unsafeAddr v[0], v.len) return catch str.writeData(unsafeAddr v[0], v.len)
success() 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) ?str.writeInitial(4, v.len)
for e in v.items: for e in v.items:
?str.writeCbor(e) ?str.writeCbor(e)
success() success()
proc writeCbor*(str: Stream; v: tuple): ?!void = proc writeCbor*(str: Stream, v: tuple): ?!void =
?str.writeInitial(4, v.tupleLen) ?str.writeInitial(4, v.tupleLen)
for e in v.fields: for e in v.fields:
?str.writeCbor(e) ?str.writeCbor(e)
success() 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): if system.`==`(v, nil):
# Major type 7 # Major type 7
return catch str.write(Null) return catch str.write(Null)
@ -161,10 +161,10 @@ proc writeCbor*[T: ptr | ref](str: Stream; v: T): ?!void =
?str.writeCbor(v[]) ?str.writeCbor(v[])
success() 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))) 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: try:
case v.classify case v.classify
of fcNormal, fcSubnormal: of fcNormal, fcSubnormal:
@ -198,31 +198,31 @@ proc writeCbor*[T: SomeFloat](str: Stream; v: T): ?!void =
return success() return success()
of fcZero: of fcZero:
str.write initialByte(7, 25) str.write initialByte(7, 25)
str.write((char)0x00) str.write((char) 0x00)
of fcNegZero: of fcNegZero:
str.write initialByte(7, 25) str.write initialByte(7, 25)
str.write((char)0x80) str.write((char) 0x80)
of fcInf: of fcInf:
str.write initialByte(7, 25) str.write initialByte(7, 25)
str.write((char)0x7c) str.write((char) 0x7c)
of fcNan: of fcNan:
str.write initialByte(7, 25) str.write initialByte(7, 25)
str.write((char)0x7e) str.write((char) 0x7e)
of fcNegInf: of fcNegInf:
str.write initialByte(7, 25) str.write initialByte(7, 25)
str.write((char)0xfc) str.write((char) 0xfc)
str.write((char)0x00) str.write((char) 0x00)
success() success()
except IOError as io: except IOError as io:
return failure(io.msg) return failure(io.msg)
except OSError as os: except OSError as os:
return failure(os.msg) return failure(os.msg)
proc writeCbor*(str: Stream; v: CborNode): ?!void = proc writeCbor*(str: Stream, v: CborNode): ?!void =
try: try:
if v.tag.isSome: if v.tag.isSome:
?str.writeCborTag(v.tag.get) ?str.writeCborTag(v.tag.get)
case v.kind: case v.kind
of cborUnsigned: of cborUnsigned:
?str.writeCbor(v.uint) ?str.writeCbor(v.uint)
of cborNegative: of cborNegative:
@ -263,7 +263,7 @@ proc writeCbor*(str: Stream; v: CborNode): ?!void =
except CatchableError as e: except CatchableError as e:
return failure(e.msg) 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 var n: uint
for _, _ in v.fieldPairs: for _, _ in v.fieldPairs:
inc n inc n
@ -273,7 +273,7 @@ proc writeCbor*[T: object](str: Stream; v: T): ?!void =
?str.writeCbor(f) ?str.writeCbor(f)
success() 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 ## Encode to a CBOR array in binary form. This magic doesn't
## always work, some arguments may need to be explicitly ## always work, some arguments may need to be explicitly
## converted with ``toCbor`` before passing. ## converted with ``toCbor`` before passing.
@ -308,7 +308,8 @@ proc isSorted(n: CborNode): ?!bool =
return failure(error.msg) return failure(error.msg)
let thisRaw = res.raw let thisRaw = res.raw
if lastRaw != "": if lastRaw != "":
if cmp(lastRaw, thisRaw) > 0: return success(false) if cmp(lastRaw, thisRaw) > 0:
return success(false)
lastRaw = thisRaw lastRaw = thisRaw
success(true) success(true)
@ -321,7 +322,7 @@ proc sort*(n: var CborNode): ?!void =
return failure(error) return failure(error)
if tmp.hasKey(res): if tmp.hasKey(res):
tmp[res] = move(val) 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) result = cmp(x.k.raw, y.k.raw)
n.map = move tmp n.map = move tmp
success() success()
@ -330,21 +331,22 @@ proc sort*(n: var CborNode): ?!void =
except Exception as e: except Exception as e:
raise newException(Defect, e.msg, 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 ## Write a `DateTime` using the tagged string representation
## defined in RCF7049 section 2.4.1. ## defined in RCF7049 section 2.4.1.
?writeCborTag(str, 0) ?writeCborTag(str, 0)
?writeCbor(str, format(dt, dateTimeFormat)) ?writeCbor(str, format(dt, dateTimeFormat))
success() success()
proc writeCbor*(str: Stream; t: Time): ?!void = proc writeCbor*(str: Stream, t: Time): ?!void =
## Write a `Time` using the tagged numerical representation ## Write a `Time` using the tagged numerical representation
## defined in RCF7049 section 2.4.1. ## defined in RCF7049 section 2.4.1.
?writeCborTag(str, 1) ?writeCborTag(str, 1)
?writeCbor(str, t.toUnix) ?writeCbor(str, t.toUnix)
success() success()
func toCbor*(x: CborNode): ?!CborNode = success(x) func toCbor*(x: CborNode): ?!CborNode =
success(x)
func toCbor*(x: SomeInteger): ?!CborNode = func toCbor*(x: SomeInteger): ?!CborNode =
if x > 0: if x > 0:
@ -369,7 +371,7 @@ func toCbor*(pairs: openArray[(CborNode, CborNode)]): ?!CborNode =
except Exception as e: except Exception as e:
raise newException(Defect, e.msg, 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: without res =? toCbor(val), error:
return failure(error.msg) return failure(error.msg)
var cnode = res var cnode = res
@ -392,11 +394,11 @@ func toCbor*(x: pointer): ?!CborNode =
return failure("pointer is not nil") return failure("pointer is not nil")
success(CborNode(kind: cborSimple, simple: 22)) 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`. ## Create a CBOR byte string from `buf`.
result = CborNode(kind: cborBytes, bytes: newSeq[byte](buf.len)) result = CborNode(kind: cborBytes, bytes: newSeq[byte](buf.len))
for i in 0..<buf.len: for i in 0 ..< buf.len:
result.bytes[i] = (byte)buf[i] result.bytes[i] = (byte) buf[i]
func initCborBytes*(len: int): CborNode = func initCborBytes*(len: int): CborNode =
## Create a CBOR byte string of ``len`` bytes. ## Create a CBOR byte string of ``len`` bytes.
@ -417,8 +419,7 @@ func initCborArray*(len: Natural): CborNode =
func initCborMap*(initialSize = tables.defaultInitialSize): CborNode = func initCborMap*(initialSize = tables.defaultInitialSize): CborNode =
## Initialize a CBOR map. ## Initialize a CBOR map.
CborNode(kind: cborMap, CborNode(kind: cborMap, map: initOrderedTable[CborNode, CborNode](initialSize))
map: initOrderedTable[CborNode, CborNode](initialSize))
func initCbor*(items: varargs[CborNode, toCbor]): CborNode = func initCbor*(items: varargs[CborNode, toCbor]): CborNode =
## Initialize a CBOR arrary. ## Initialize a CBOR arrary.

View File

@ -20,16 +20,16 @@ const
type type
CborEventKind* {.pure.} = enum CborEventKind* {.pure.} = enum
## enumeration of events that may occur while parsing ## enumeration of events that may occur while parsing
cborEof, cborEof
cborPositive, cborPositive
cborNegative, cborNegative
cborBytes, cborBytes
cborText, cborText
cborArray, cborArray
cborMap, cborMap
cborTag, cborTag
cborSimple, cborSimple
cborFloat, cborFloat
cborBreak cborBreak
CborParser* = object ## CBOR parser state. CborParser* = object ## CBOR parser state.
@ -40,15 +40,15 @@ type
type type
CborNodeKind* = enum CborNodeKind* = enum
cborUnsigned = 0, cborUnsigned = 0
cborNegative = 1, cborNegative = 1
cborBytes = 2, cborBytes = 2
cborText = 3, cborText = 3
cborArray = 4, cborArray = 4
cborMap = 5, cborMap = 5
cborTag = 6, cborTag = 6
cborSimple = 7, cborSimple = 7
cborFloat, cborFloat
cborRaw cborRaw
CborNode* = object CborNode* = object
@ -106,7 +106,7 @@ func `==`*(x, y: CborNode): bool =
else: else:
false false
func `==`*(x: CborNode; y: SomeInteger): bool = func `==`*(x: CborNode, y: SomeInteger): bool =
case x.kind case x.kind
of cborUnsigned: of cborUnsigned:
x.uint == y x.uint == y
@ -115,11 +115,12 @@ func `==`*(x: CborNode; y: SomeInteger): bool =
else: else:
false false
func `==`*(x: CborNode; y: string): bool = func `==`*(x: CborNode, y: string): bool =
x.kind == cborText and x.text == y x.kind == cborText and x.text == y
func `==`*(x: CborNode; y: SomeFloat): bool = func `==`*(x: CborNode, y: SomeFloat): bool =
if x.kind == cborFloat: x.float == y if x.kind == cborFloat:
x.float == y
func hash(x: CborNode): Hash = func hash(x: CborNode): Hash =
var h = hash(get(x.tag, 0)) var h = hash(get(x.tag, 0))
@ -151,11 +152,11 @@ func hash(x: CborNode): Hash =
h = x.raw.hash h = x.raw.hash
!$h !$h
proc `[]`*(n, k: CborNode): CborNode = n.map[k] proc `[]`*(n, k: CborNode): CborNode = ## Retrieve a value from a CBOR map.
## Retrieve a value from a CBOR map. n.map[k]
proc `[]=`*(n: var CborNode; k, v: sink CborNode) = n.map[k] = v proc `[]=`*(n: var CborNode, k, v: sink CborNode) = ## Assign a pair in a CBOR map.
## Assign a pair in a CBOR map. n.map[k] = v
func len*(node: CborNode): int = func len*(node: CborNode): int =
## Return the logical length of a ``CborNode``, that is the ## Return the logical length of a ``CborNode``, that is the

View File

@ -32,7 +32,6 @@ export types
logScope: logScope:
topics = "nimserde json deserializer" topics = "nimserde json deserializer"
proc fromJson*(T: type enum, json: JsonNode): ?!T = proc fromJson*(T: type enum, json: JsonNode): ?!T =
expectJsonKind(string, JString, json) expectJsonKind(string, JString, json)
without val =? parseEnum[T](json.str).catch, error: without val =? parseEnum[T](json.str).catch, error:
@ -105,8 +104,7 @@ proc fromJson*(_: type seq[byte], json: JsonNode): ?!seq[byte] =
expectJsonKind(seq[byte], JString, json) expectJsonKind(seq[byte], JString, json)
hexToSeqByte(json.getStr).catch hexToSeqByte(json.getStr).catch
proc fromJson*[N: static[int], T: array[N, byte]](_: type T, proc fromJson*[N: static[int], T: array[N, byte]](_: type T, json: JsonNode): ?!T =
json: JsonNode): ?!T =
expectJsonKind(T, JString, json) expectJsonKind(T, JString, json)
T.fromHex(json.getStr).catch T.fromHex(json.getStr).catch
@ -141,8 +139,7 @@ proc fromJson*(T: typedesc[StUint or StInt], json: JsonNode): ?!T =
catch parse(jsonStr, T) catch parse(jsonStr, T)
proc fromJson*[T](_: type Option[T], json: JsonNode): ?!Option[T] = proc fromJson*[T](_: type Option[T], json: JsonNode): ?!Option[T] =
if json.isNil or json.kind == JNull or json.isEmptyString or if json.isNil or json.kind == JNull or json.isEmptyString or json.isNullString:
json.isNullString:
return success(none T) return success(none T)
without val =? T.fromJson(json), error: without val =? T.fromJson(json), error:
return failure(error) return failure(error)
@ -225,8 +222,7 @@ proc fromJson*[T: ref object or object](_: type T, json: JsonNode): ?!T =
value = parsed value = parsed
# not Option[T] # not Option[T]
elif opts.key in json and jsonVal =? json{opts.key}.catch and elif opts.key in json and jsonVal =? json{opts.key}.catch and not jsonVal.isNil:
not jsonVal.isNil:
without parsed =? typeof(value).fromJson(jsonVal), e: without parsed =? typeof(value).fromJson(jsonVal), e:
trace "failed to deserialize field", trace "failed to deserialize field",
`type` = $typeof(value), json = jsonVal, error = e.msg `type` = $typeof(value), json = jsonVal, error = e.msg
@ -278,7 +274,9 @@ proc fromJson*[T: SomeInteger or SomeFloat or openArray[byte] or bool or enum](
success newSeq[T]() success newSeq[T]()
else: else:
if T is enum: if T is enum:
let err = newSerdeError("Cannot deserialize a seq[enum]: not yet implemented, PRs welcome") let err = newSerdeError(
"Cannot deserialize a seq[enum]: not yet implemented, PRs welcome"
)
return failure err return failure err
let jsn = ?JsonNode.parse(json) let jsn = ?JsonNode.parse(json)
@ -291,7 +289,9 @@ proc fromJson*[T: SomeInteger or SomeFloat or openArray[byte] or bool or enum](
success seq[T].none success seq[T].none
else: else:
if T is enum: if T is enum:
let err = newSerdeError("Cannot deserialize a seq[enum]: not yet implemented, PRs welcome") let err = newSerdeError(
"Cannot deserialize a seq[enum]: not yet implemented, PRs welcome"
)
return failure err return failure err
let jsn = ?JsonNode.parse(json) let jsn = ?JsonNode.parse(json)
Option[seq[T]].fromJson(jsn) Option[seq[T]].fromJson(jsn)

View File

@ -2,7 +2,6 @@ import ./stdjson
import ../utils/types import ../utils/types
import std/sets import std/sets
proc newUnexpectedKindError*( proc newUnexpectedKindError*(
expectedType: type, expectedKinds: string, json: JsonNode expectedType: type, expectedKinds: string, json: JsonNode
): ref UnexpectedKindError = ): ref UnexpectedKindError =

View File

@ -8,8 +8,9 @@ template expectJsonKind*(
if json.isNil or json.kind notin expectedKinds: if json.isNil or json.kind notin expectedKinds:
return failure(newUnexpectedKindError(expectedType, expectedKinds, json)) return failure(newUnexpectedKindError(expectedType, expectedKinds, json))
template expectJsonKind*(expectedType: type, expectedKind: JsonNodeKind, template expectJsonKind*(
json: JsonNode) = expectedType: type, expectedKind: JsonNodeKind, json: JsonNode
) =
expectJsonKind(expectedType, {expectedKind}, json) expectJsonKind(expectedType, {expectedKind}, json)
proc fieldKeys*[T](obj: T): seq[string] = proc fieldKeys*[T](obj: T): seq[string] =
@ -26,7 +27,6 @@ func keysNotIn*[T](json: JsonNode, obj: T): HashSet[string] =
let objKeys = obj.fieldKeys.toHashSet let objKeys = obj.fieldKeys.toHashSet
difference(jsonKeys, objKeys) difference(jsonKeys, objKeys)
func isEmptyString*(json: JsonNode): bool = func isEmptyString*(json: JsonNode): bool =
return json.kind == JString and json.getStr == "" return json.kind == JString and json.getStr == ""

View File

@ -9,6 +9,3 @@ proc mapErrTo*[E1: ref CatchableError, E2: SerdeError](
proc newSerdeError*(msg: string): ref SerdeError = proc newSerdeError*(msg: string): ref SerdeError =
newException(SerdeError, msg) newException(SerdeError, msg)

View File

@ -6,10 +6,8 @@ export types
{.push raises: [].} {.push raises: [].}
template serialize*(key = "", ignore = false, template serialize*(key = "", ignore = false, mode = SerdeMode.OptOut) {.pragma.}
mode = SerdeMode.OptOut) {.pragma.} template deserialize*(key = "", ignore = false, mode = SerdeMode.OptOut) {.pragma.}
template deserialize*(key = "", ignore = false,
mode = SerdeMode.OptOut) {.pragma.}
proc isDefault[T](paramValue: T): bool {.compileTime.} = proc isDefault[T](paramValue: T): bool {.compileTime.} =
when T is SerdeMode: when T is SerdeMode:
@ -25,8 +23,7 @@ template expectMissingPragmaParam*(value, pragma, name, msg) =
if paramName == name and not paramValue.isDefault: if paramName == name and not paramValue.isDefault:
raiseAssert(msg) raiseAssert(msg)
template getSerdeFieldOptions*(pragma, fieldName, template getSerdeFieldOptions*(pragma, fieldName, fieldValue): SerdeFieldOptions =
fieldValue): SerdeFieldOptions =
var opts = SerdeFieldOptions(key: fieldName, ignore: false) var opts = SerdeFieldOptions(key: fieldName, ignore: false)
when fieldValue.hasCustomPragma(pragma): when fieldValue.hasCustomPragma(pragma):
fieldValue.expectMissingPragmaParam( fieldValue.expectMissingPragmaParam(
@ -46,14 +43,12 @@ template getSerdeMode*(T, pragma): SerdeMode =
T.expectMissingPragmaParam( T.expectMissingPragmaParam(
pragma, pragma,
"key", "key",
"Cannot set " & astToStr(pragma) & " 'key' on '" & $T & "Cannot set " & astToStr(pragma) & " 'key' on '" & $T & "' type definition.",
"' type definition.",
) )
T.expectMissingPragmaParam( T.expectMissingPragmaParam(
pragma, pragma,
"ignore", "ignore",
"Cannot set " & astToStr(pragma) & " 'ignore' on '" & $T & "Cannot set " & astToStr(pragma) & " 'ignore' on '" & $T & "' type definition.",
"' type definition.",
) )
let (_, _, mode) = T.getCustomPragmaVal(pragma) let (_, _, mode) = T.getCustomPragmaVal(pragma)
mode mode

74
tests/benchmark.nim Normal file
View File

@ -0,0 +1,74 @@
import pkg/serde
import std/[times]
import pkg/questionable
import pkg/questionable/results
import pkg/stew/byteutils
import pkg/stint
import serde/json/serializer
import serde/cbor/serializer
import serde/cbor/deserializer
type Inner {.serialize.} = object
size: uint64
type CustomPoint {.serialize.} = object
u: uint64 # Unsigned integer
n: int # Signed integer
b: seq[byte] # Byte sequence
t: string # Text string
arr: seq[int] # Integer sequence
tag: float # Floating point
flag: bool # Boolean
inner: Inner # Nested object
innerArr: seq[Inner] # Sequence of objects
proc generateCustomPoint(): CustomPoint =
CustomPoint(
u: 1234567890,
n: -1234567890,
b: "hello world".toBytes,
t: "hello world",
arr: @[1, 2, 3, 4, 5],
tag: 3.14,
flag: true,
inner: Inner(size: 1234567890),
innerArr: @[Inner(size: 1234567890), Inner(size: 9543210)],
)
proc benchmark(): void =
let point = generateCustomPoint()
var jsonStr = ""
var cborStr = ""
let jsonStartTime = cpuTime()
for i in 1 .. 100000:
jsonStr = toJson(point)
let jsonEndTime = cpuTime()
let jsonDuration = jsonEndTime - jsonStartTime
let cborStartTime = cpuTime()
for i in 1 .. 100000:
cborStr = encode(point).tryValue
let cborEndTime = cpuTime()
let cborDuration = cborEndTime - cborStartTime
let jsonDeserializeStartTime = cpuTime()
for i in 1 .. 100000:
assert CustomPoint.fromJson(jsonStr).isSuccess
let jsonDeserializeEndTime = cpuTime()
let jsonDeserializeDuration = jsonDeserializeEndTime - jsonDeserializeStartTime
let cborDeserializeStartTime = cpuTime()
for i in 1 .. 100000:
assert CustomPoint.fromCbor(cborStr).isSuccess
let cborDeserializeEndTime = cpuTime()
let cborDeserializeDuration = cborDeserializeEndTime - cborDeserializeStartTime
echo "JSON Serialization Time: ", jsonDuration
echo "CBOR Serialization Time: ", cborDuration
echo "JSON Deserialization Time: ", jsonDeserializeDuration
echo "CBOR Deserialization Time: ", cborDeserializeDuration
when isMainModule:
benchmark()

View File

@ -24,7 +24,9 @@ type
# Enum type to test enum serialization # Enum type to test enum serialization
CustomColor = enum CustomColor = enum
Red, Green, Blue Red
Green
Blue
# Object combining different custom types # Object combining different custom types
CustomObject = object CustomObject = object
@ -98,14 +100,14 @@ proc writeCbor*(str: Stream, val: CustomPoint): ?!void =
# Helper function to create CBOR data for testing # Helper function to create CBOR data for testing
proc createPointCbor(x, y: int): CborNode = proc createPointCbor(x, y: int): CborNode =
result = CborNode(kind: cborArray) result = CborNode(kind: cborArray)
result.seq = @[ result.seq =
@[
CborNode(kind: cborUnsigned, uint: x.uint64), CborNode(kind: cborUnsigned, uint: x.uint64),
CborNode(kind: cborUnsigned, uint: y.uint64) CborNode(kind: cborUnsigned, uint: y.uint64),
] ]
# Creates a CBOR map node representing a CustomObject # Creates a CBOR map node representing a CustomObject
proc createObjectCbor(name: string, point: CustomPoint, proc createObjectCbor(name: string, point: CustomPoint, color: CustomColor): CborNode =
color: CustomColor): CborNode =
result = CborNode(kind: cborMap) result = CborNode(kind: cborMap)
result.map = initOrderedTable[CborNode, CborNode]() result.map = initOrderedTable[CborNode, CborNode]()
@ -122,7 +124,6 @@ proc createObjectCbor(name: string, point: CustomPoint,
CborNode(kind: cborNegative, int: color.int) CborNode(kind: cborNegative, int: color.int)
suite "CBOR deserialization": suite "CBOR deserialization":
test "deserializes object with custom types": test "deserializes object with custom types":
# Create a test point # Create a test point
let point = CustomPoint(x: 15, y: 25) let point = CustomPoint(x: 15, y: 25)
@ -143,7 +144,6 @@ suite "CBOR deserialization":
check deserializedObj.point.y == 25 check deserializedObj.point.y == 25
check deserializedObj.color == Green check deserializedObj.color == Green
test "serialize and deserialize object with all supported wire types": test "serialize and deserialize object with all supported wire types":
# Setup test data with various types # Setup test data with various types
# 1. Create reference objects # 1. Create reference objects
@ -164,9 +164,9 @@ suite "CBOR deserialization":
tag: 1.5, # float tag: 1.5, # float
flag: true, # boolean flag: true, # boolean
inner: Inner(s: "inner!", nums: @[10, 20]), # nested object inner: Inner(s: "inner!", nums: @[10, 20]), # nested object
innerArr: @[ # array of objects innerArr:
Inner(s: "first", nums: @[1, 2]), @[ # array of objects
Inner(s: "second", nums: @[3, 4, 5]) Inner(s: "first", nums: @[1, 2]), Inner(s: "second", nums: @[3, 4, 5])
], ],
coordinates: (x: 10, y: 20, label: "test"), # tuple coordinates: (x: 10, y: 20, label: "test"), # tuple
refInner: refInner, # reference to object refInner: refInner, # reference to object
@ -174,7 +174,7 @@ suite "CBOR deserialization":
refNil: nil, # nil reference refNil: nil, # nil reference
customPoint: CustomPoint(x: 15, y: 25), # custom type customPoint: CustomPoint(x: 15, y: 25), # custom type
time: getTime(), # time time: getTime(), # time
date: now().utc # date date: now().utc, # date
) )
# Test serialization using encode helper # Test serialization using encode helper
@ -192,9 +192,7 @@ suite "CBOR deserialization":
check cborData == encodedStr check cborData == encodedStr
# Parse CBOR data back to CborNode # Parse CBOR data back to CborNode
let parseResult = parseCbor(cborData) let node = parseCbor(cborData)
check parseResult.isSuccess
let node = parseResult.tryValue
# Deserialize CborNode to CompositeNested object # Deserialize CborNode to CompositeNested object
let res = CompositeNested.fromCbor(node) let res = CompositeNested.fromCbor(node)
@ -218,7 +216,7 @@ suite "CBOR deserialization":
# 3. Check sequence of objects # 3. Check sequence of objects
check roundtrip.innerArr.len == original.innerArr.len check roundtrip.innerArr.len == original.innerArr.len
for i in 0..<roundtrip.innerArr.len: for i in 0 ..< roundtrip.innerArr.len:
check roundtrip.innerArr[i].s == original.innerArr[i].s check roundtrip.innerArr[i].s == original.innerArr[i].s
check roundtrip.innerArr[i].nums == original.innerArr[i].nums check roundtrip.innerArr[i].nums == original.innerArr[i].nums
@ -238,4 +236,3 @@ suite "CBOR deserialization":
# 7. Check custom type # 7. Check custom type
check roundtrip.customPoint.x == original.customPoint.x check roundtrip.customPoint.x == original.customPoint.x
check roundtrip.customPoint.y == original.customPoint.y check roundtrip.customPoint.y == original.customPoint.y

View File

@ -6,11 +6,12 @@ import pkg/serde/cbor
import pkg/questionable import pkg/questionable
import pkg/questionable/results import pkg/questionable/results
proc findVectorsFile: string = proc findVectorsFile(): string =
var parent = getCurrentDir() var parent = getCurrentDir()
while parent != "/": while parent != "/":
result = parent / "tests" / "cbor" / "test_vector.json" result = parent / "tests" / "cbor" / "test_vector.json"
if fileExists result: return if fileExists result:
return
parent = parent.parentDir parent = parent.parentDir
raiseAssert "Could not find test vectors" raiseAssert "Could not find test vectors"
@ -23,11 +24,8 @@ suite "decode":
control = $v["decoded"] control = $v["decoded"]
name = v["name"].getStr name = v["name"].getStr
test name: test name:
let let controlCbor = base64.decode v["cbor"].getStr
controlCbor = base64.decode v["cbor"].getStr let js = parseCbor(controlCbor).toJson()
without c =? parseCbor(controlCbor), error:
fail()
let js = c.toJson()
if js.isNil: if js.isNil:
fail() fail()
else: else:
@ -40,11 +38,8 @@ suite "diagnostic":
control = v["diagnostic"].getStr control = v["diagnostic"].getStr
name = v["name"].getStr name = v["name"].getStr
test name: test name:
let let controlCbor = base64.decode v["cbor"].getStr
controlCbor = base64.decode v["cbor"].getStr check($parseCbor(controlCbor) == control)
without c =? parseCbor(controlCbor), error:
fail()
check($c == control)
suite "roundtrip": suite "roundtrip":
for v in js.items: for v in js.items:
@ -53,10 +48,8 @@ suite "roundtrip":
controlB64 = v["cbor"].getStr controlB64 = v["cbor"].getStr
controlCbor = base64.decode controlB64 controlCbor = base64.decode controlB64
name = v["name"].getStr name = v["name"].getStr
without c =? parseCbor(controlCbor), error:
fail()
test name: test name:
without testCbor =? encode(c), error: without testCbor =? encode(parseCbor(controlCbor)), error:
fail() fail()
if controlCbor != testCbor: if controlCbor != testCbor:
let testB64 = base64.encode(testCbor) let testB64 = base64.encode(testCbor)
@ -68,17 +61,11 @@ suite "hooks":
without bin =? encode(dt), error: without bin =? encode(dt), error:
fail() fail()
without node =? parseCbor(bin), error: check(parseCbor(bin).text == $dt)
fail()
check(node.text == $dt)
test "Time": test "Time":
let t = now().toTime let t = now().toTime
var var bin = encode(t).tryValue
bin = encode(t).tryValue check(parseCbor(bin).getInt == t.toUnix)
without node =? parseCbor(bin), error:
fail()
check(node.getInt == t.toUnix)
test "tag": test "tag":
var c = toCbor("foo").tryValue var c = toCbor("foo").tryValue
@ -87,7 +74,8 @@ test "tag":
test "sorting": test "sorting":
var map = initCborMap() var map = initCborMap()
var keys = @[ var keys =
@[
toCbor(10).tryValue, toCbor(10).tryValue,
toCbor(100).tryValue, toCbor(100).tryValue,
toCbor(-1).tryValue, toCbor(-1).tryValue,
@ -99,7 +87,8 @@ test "sorting":
] ]
shuffle(keys) shuffle(keys)
for k in keys: map[k] = toCbor(0).tryValue for k in keys:
map[k] = toCbor(0).tryValue
check not map.isSorted.tryValue check not map.isSorted.tryValue
check sort(map).isSuccess check sort(map).isSuccess
check map.isSorted.tryValue check map.isSorted.tryValue
@ -109,4 +98,5 @@ test "invalid wire type":
let result = int.fromCbor(node) let result = int.fromCbor(node)
check result.isFailure check result.isFailure
check $result.error.msg == "deserialization to int failed: expected {cborUnsigned, cborNegative} but got cborText" check $result.error.msg ==
"deserialization to int failed: expected {cborUnsigned, cborNegative} but got cborText"

View File

@ -99,7 +99,8 @@ suite "json - deserialize objects":
myint: int myint: int
let expected = MyRef(mystring: "abc", myint: 1) let expected = MyRef(mystring: "abc", myint: 1)
let byteArray = """{ let byteArray =
"""{
"mystring": "abc", "mystring": "abc",
"myint": 1 "myint": 1
}""".toBytes }""".toBytes

View File

@ -50,7 +50,8 @@ suite "json serialization - serialize":
let json = %*{"myobj": myobj, "mystuint": mystuint} let json = %*{"myobj": myobj, "mystuint": mystuint}
let expected = """{ let expected =
"""{
"myobj": { "myobj": {
"mystring": "abc", "mystring": "abc",
"myint": 123, "myint": 123,
@ -69,7 +70,8 @@ suite "json serialization - serialize":
let obj = %MyObj(mystring: "abc", myint: 1, mybool: true) let obj = %MyObj(mystring: "abc", myint: 1, mybool: true)
let expected = """{ let expected =
"""{
"mystring": "abc", "mystring": "abc",
"myint": 1 "myint": 1
}""".flatten }""".flatten
@ -83,7 +85,8 @@ suite "json serialization - serialize":
let obj = %MyRef(mystring: "abc", myint: 1) let obj = %MyRef(mystring: "abc", myint: 1)
let expected = """{ let expected =
"""{
"mystring": "abc", "mystring": "abc",
"myint": 1 "myint": 1
}""".flatten }""".flatten

View File

@ -9,4 +9,3 @@ requires "questionable >= 0.10.13 & < 0.11.0"
task test, "Run the test suite": task test, "Run the test suite":
exec "nimble install -d -y" exec "nimble install -d -y"
exec "nim c -r test" exec "nim c -r test"