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:error
--styleCheck:
usages
--styleCheck:
error
# begin Nimble config (version 1)
when fileExists("nimble.paths"):

View File

@ -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..<n.seq.high:
for i in 0 ..< n.seq.high:
result.add $(n.seq[i])
result.add ", "
if n.seq.len > 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)

View File

@ -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)

View File

@ -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: "<math.h>".}
func ldexp(
x: float64, exponent: int
): float64 {.importc: "ldexp", header: "<math.h>".}
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

View File

@ -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))

View File

@ -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..<buf.len:
result.bytes[i] = (byte)buf[i]
for i in 0 ..< buf.len:
result.bytes[i] = (byte) buf[i]
func initCborBytes*(len: int): CborNode =
## Create a CBOR byte string of ``len`` bytes.
@ -417,8 +419,7 @@ func initCborArray*(len: Natural): CborNode =
func initCborMap*(initialSize = tables.defaultInitialSize): CborNode =
## Initialize a CBOR map.
CborNode(kind: cborMap,
map: initOrderedTable[CborNode, CborNode](initialSize))
CborNode(kind: cborMap, map: initOrderedTable[CborNode, CborNode](initialSize))
func initCbor*(items: varargs[CborNode, toCbor]): CborNode =
## Initialize a CBOR arrary.

View File

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

View File

@ -32,7 +32,6 @@ export types
logScope:
topics = "nimserde json deserializer"
proc fromJson*(T: type enum, json: JsonNode): ?!T =
expectJsonKind(string, JString, json)
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)
hexToSeqByte(json.getStr).catch
proc fromJson*[N: static[int], T: array[N, byte]](_: type T,
json: JsonNode): ?!T =
proc fromJson*[N: static[int], T: array[N, byte]](_: type T, json: JsonNode): ?!T =
expectJsonKind(T, JString, json)
T.fromHex(json.getStr).catch
@ -141,8 +139,7 @@ proc fromJson*(T: typedesc[StUint or StInt], json: JsonNode): ?!T =
catch parse(jsonStr, T)
proc fromJson*[T](_: type Option[T], json: JsonNode): ?!Option[T] =
if json.isNil or json.kind == JNull or json.isEmptyString or
json.isNullString:
if json.isNil or json.kind == JNull or json.isEmptyString or json.isNullString:
return success(none T)
without val =? T.fromJson(json), error:
return failure(error)
@ -225,8 +222,7 @@ proc fromJson*[T: ref object or object](_: type T, json: JsonNode): ?!T =
value = parsed
# not Option[T]
elif opts.key in json and jsonVal =? json{opts.key}.catch and
not jsonVal.isNil:
elif opts.key in json and jsonVal =? json{opts.key}.catch and not jsonVal.isNil:
without parsed =? typeof(value).fromJson(jsonVal), e:
trace "failed to deserialize field",
`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]()
else:
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
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
else:
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
let jsn = ?JsonNode.parse(json)
Option[seq[T]].fromJson(jsn)

View File

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

View File

@ -8,8 +8,9 @@ template expectJsonKind*(
if json.isNil or json.kind notin expectedKinds:
return failure(newUnexpectedKindError(expectedType, expectedKinds, json))
template expectJsonKind*(expectedType: type, expectedKind: JsonNodeKind,
json: JsonNode) =
template expectJsonKind*(
expectedType: type, expectedKind: JsonNodeKind, json: JsonNode
) =
expectJsonKind(expectedType, {expectedKind}, json)
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
difference(jsonKeys, objKeys)
func isEmptyString*(json: JsonNode): bool =
return json.kind == JString and json.getStr == ""

View File

@ -1,3 +1,3 @@
import std/json except `%`, `%*`, parseJson
export json except `%`, `%*`, parseJson
export json except `%`, `%*`, parseJson

View File

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

View File

@ -6,10 +6,8 @@ export types
{.push raises: [].}
template serialize*(key = "", ignore = false,
mode = SerdeMode.OptOut) {.pragma.}
template deserialize*(key = "", ignore = false,
mode = SerdeMode.OptOut) {.pragma.}
template serialize*(key = "", ignore = false, mode = SerdeMode.OptOut) {.pragma.}
template deserialize*(key = "", ignore = false, mode = SerdeMode.OptOut) {.pragma.}
proc isDefault[T](paramValue: T): bool {.compileTime.} =
when T is SerdeMode:
@ -25,8 +23,7 @@ template expectMissingPragmaParam*(value, pragma, name, msg) =
if paramName == name and not paramValue.isDefault:
raiseAssert(msg)
template getSerdeFieldOptions*(pragma, fieldName,
fieldValue): SerdeFieldOptions =
template getSerdeFieldOptions*(pragma, fieldName, fieldValue): SerdeFieldOptions =
var opts = SerdeFieldOptions(key: fieldName, ignore: false)
when fieldValue.hasCustomPragma(pragma):
fieldValue.expectMissingPragmaParam(
@ -46,14 +43,12 @@ template getSerdeMode*(T, pragma): SerdeMode =
T.expectMissingPragmaParam(
pragma,
"key",
"Cannot set " & astToStr(pragma) & " 'key' on '" & $T &
"' type definition.",
"Cannot set " & astToStr(pragma) & " 'key' on '" & $T & "' type definition.",
)
T.expectMissingPragmaParam(
pragma,
"ignore",
"Cannot set " & astToStr(pragma) & " 'ignore' on '" & $T &
"' type definition.",
"Cannot set " & astToStr(pragma) & " 'ignore' on '" & $T & "' type definition.",
)
let (_, _, mode) = T.getCustomPragmaVal(pragma)
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
CustomColor = enum
Red, Green, Blue
Red
Green
Blue
# Object combining different custom types
CustomObject = object
@ -46,22 +48,22 @@ type
# Complex object with various field types to test comprehensive serialization
CompositeNested = 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
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
coordinates: tuple[x: int, y: int, label: string] # Tuple
refInner: ref Inner # Reference to object
refNewInner: NewType # Custom reference type
refNil: ref Inner # Nil reference
customPoint: CustomPoint # Custom type
time: Time # Time
date: DateTime # DateTime
refInner: ref Inner # Reference to object
refNewInner: NewType # Custom reference type
refNil: ref Inner # Nil reference
customPoint: CustomPoint # Custom type
time: Time # Time
date: DateTime # DateTime
# Custom deserialization for CustomColor enum
# Converts a CBOR negative integer to a CustomColor enum value
@ -98,14 +100,14 @@ proc writeCbor*(str: Stream, val: CustomPoint): ?!void =
# Helper function to create CBOR data for testing
proc createPointCbor(x, y: int): CborNode =
result = CborNode(kind: cborArray)
result.seq = @[
CborNode(kind: cborUnsigned, uint: x.uint64),
CborNode(kind: cborUnsigned, uint: y.uint64)
]
result.seq =
@[
CborNode(kind: cborUnsigned, uint: x.uint64),
CborNode(kind: cborUnsigned, uint: y.uint64),
]
# Creates a CBOR map node representing a CustomObject
proc createObjectCbor(name: string, point: CustomPoint,
color: CustomColor): CborNode =
proc createObjectCbor(name: string, point: CustomPoint, color: CustomColor): CborNode =
result = CborNode(kind: cborMap)
result.map = initOrderedTable[CborNode, CborNode]()
@ -122,7 +124,6 @@ proc createObjectCbor(name: string, point: CustomPoint,
CborNode(kind: cborNegative, int: color.int)
suite "CBOR deserialization":
test "deserializes object with custom types":
# Create a test point
let point = CustomPoint(x: 15, y: 25)
@ -143,7 +144,6 @@ suite "CBOR deserialization":
check deserializedObj.point.y == 25
check deserializedObj.color == Green
test "serialize and deserialize object with all supported wire types":
# Setup test data with various types
# 1. Create reference objects
@ -156,25 +156,25 @@ suite "CBOR deserialization":
# 2. Create a complex object with all supported types
var original = CompositeNested(
u: 42, # unsigned integer
n: -99, # signed integer
b: @[byte 1, byte 2], # byte array
t: "hi", # string
arr: @[1, 2, 3], # integer array
tag: 1.5, # float
flag: true, # boolean
inner: Inner(s: "inner!", nums: @[10, 20]), # nested object
innerArr: @[ # array of objects
Inner(s: "first", nums: @[1, 2]),
Inner(s: "second", nums: @[3, 4, 5])
],
coordinates: (x: 10, y: 20, label: "test"), # tuple
refInner: refInner, # reference to object
refNewInner: refNewObj, # custom reference type
refNil: nil, # nil reference
customPoint: CustomPoint(x: 15, y: 25), # custom type
time: getTime(), # time
date: now().utc # date
u: 42, # unsigned integer
n: -99, # signed integer
b: @[byte 1, byte 2], # byte array
t: "hi", # string
arr: @[1, 2, 3], # integer array
tag: 1.5, # float
flag: true, # boolean
inner: Inner(s: "inner!", nums: @[10, 20]), # nested object
innerArr:
@[ # array of objects
Inner(s: "first", nums: @[1, 2]), Inner(s: "second", nums: @[3, 4, 5])
],
coordinates: (x: 10, y: 20, label: "test"), # tuple
refInner: refInner, # reference to object
refNewInner: refNewObj, # custom reference type
refNil: nil, # nil reference
customPoint: CustomPoint(x: 15, y: 25), # custom type
time: getTime(), # time
date: now().utc, # date
)
# Test serialization using encode helper
@ -192,9 +192,7 @@ suite "CBOR deserialization":
check cborData == encodedStr
# Parse CBOR data back to CborNode
let parseResult = parseCbor(cborData)
check parseResult.isSuccess
let node = parseResult.tryValue
let node = parseCbor(cborData)
# Deserialize CborNode to CompositeNested object
let res = CompositeNested.fromCbor(node)
@ -218,7 +216,7 @@ suite "CBOR deserialization":
# 3. Check sequence of objects
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].nums == original.innerArr[i].nums
@ -238,4 +236,3 @@ suite "CBOR deserialization":
# 7. Check custom type
check roundtrip.customPoint.x == original.customPoint.x
check roundtrip.customPoint.y == original.customPoint.y

View File

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

@ -2,4 +2,4 @@ switch("path", "..")
when (NimMajor, NimMinor) >= (1, 4):
switch("hint", "XCannotRaiseY:off")
when (NimMajor, NimMinor, NimPatch) >= (1, 6, 11):
switch("warning", "BareExcept:off")
switch("warning", "BareExcept:off")

View File

@ -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

View File

@ -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

View File

@ -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"