refactor: reimplement CBOR deserialization with type-based fromCbor interface

This commit is contained in:
munna0908 2025-05-28 11:49:50 +05:30
parent d3c3774391
commit 056a78e95f
No known key found for this signature in database
GPG Key ID: 2FFCD637E937D3E6
8 changed files with 210 additions and 106 deletions

View File

@ -1,16 +1,20 @@
# This file is a modified version of Emery Hemingways CBOR library for Nim,
# originally available at https://github.com/ehmry/cbor-nim and released under The Unlicense.
import std/[math, streams, options, tables, strutils, times, typetraits]
import std/[math, streams, options, tables, strutils, times, typetraits, macros]
import ./types
import ./helpers
import ../utils/types
import ../utils/types as utilsTypes
import ../utils/pragmas
import ./errors
import pkg/questionable
import pkg/questionable/results
export results
export types
export pragmas
export utilsTypes
{.push raises: [].}
@ -95,7 +99,6 @@ proc nextUInt*(c: var CborParser): ?!BiggestUInt =
?c.next()
return success(val)
proc nextInt*(c: var CborParser): ?!BiggestInt =
## Parse the integer value that the parser is positioned on.
var val: BiggestInt
@ -146,7 +149,7 @@ proc nextBytes*(c: var CborParser; buf: var openArray[byte]): ?!void =
let n = c.s.readData(buf[0].addr, buf.len)
if n != buf.len:
return failure(newCborError("truncated read of CBOR data"))
tryNext(c)
?c.next()
success()
except OSError as e:
return failure(e.msg)
@ -178,14 +181,13 @@ proc nextText*(c: var CborParser; buf: var string): ?!void =
let n = c.s.readData(buf[0].addr, buf.len)
if n != buf.len:
return failure(newCborError("truncated read of CBOR data"))
tryNext(c)
?c.next()
success()
except IOError as e:
return failure(e.msg)
except OSError as e:
return failure(e.msg)
proc nextText*(c: var CborParser): ?!string =
## Read the text that the parser is positioned on into a string and advance.
var buf: string
@ -195,20 +197,26 @@ proc nextText*(c: var CborParser): ?!string =
return success(buf)
func arrayLen*(c: CborParser): int =
func arrayLen*(c: CborParser): ?!int =
## Return the length of the array that the parser is positioned on.
assert(c.kind == CborEventKind.cborArray, $c.kind)
c.intVal.int
if c.kind != CborEventKind.cborArray:
return failure(newCborError("Expected array, got " & $c.kind))
func mapLen*(c: CborParser): int =
return success(c.intVal.int)
func mapLen*(c: CborParser): ?!int =
## Return the length of the map that the parser is positioned on.
assert(c.kind == CborEventKind.cborMap, $c.kind)
c.intVal.int
if c.kind != CborEventKind.cborMap:
return failure(newCborError("Expected map, got " & $c.kind))
func tag*(c: CborParser): uint64 =
return success(c.intVal.int)
func tag*(c: CborParser): ?!uint64 =
## Return the tag value the parser is positioned on.
assert(c.kind == CborEventKind.cborTag, $c.kind)
c.intVal
if c.kind != CborEventKind.cborTag:
return failure(newCborError("Expected tag, got " & $c.kind))
return success(c.intVal)
proc skipNode*(c: var CborParser): ?!void =
## Skip the item the parser is positioned on.
@ -221,7 +229,7 @@ proc skipNode*(c: var CborParser): ?!void =
return c.next()
of CborEventKind.cborBytes, CborEventKind.cborText:
if c.isIndefinite:
tryNext(c)
?c.next()
while c.kind != CborEventKind.cborBreak:
if c.kind != CborEventKind.cborBytes:
return failure(newCborError("expected bytes, got " & $c.kind))
@ -232,28 +240,28 @@ proc skipNode*(c: var CborParser): ?!void =
return c.next()
of CborEventKind.cborArray:
if c.isIndefinite:
tryNext(c)
?c.next()
while c.kind != CborEventKind.cborBreak:
trySkip(c)
?c.skipNode()
return c.next()
else:
let len = c.intVal
tryNext(c)
?c.next()
for i in 1..len:
trySkip(c)
?c.skipNode()
of CborEventKind.cborMap:
let mapLen = c.intVal.int
if c.isIndefinite:
tryNext(c)
?c.next()
while c.kind != CborEventKind.cborBreak:
trySkip(c)
?c.skipNode()
return c.next()
else:
tryNext(c)
?c.next()
for _ in 1 .. mapLen:
trySkip(c)
?c.skipNode()
of CborEventKind.cborTag:
tryNext(c)
?c.next()
return c.skipNode()
of CborEventKind.cborFloat:
without f =? c.nextFloat(), error:
@ -279,14 +287,14 @@ proc nextNode*(c: var CborParser): ?!CborNode =
return failure(newCborError("end of CBOR stream"))
of CborEventKind.cborPositive:
next = CborNode(kind: cborUnsigned, uint: c.intVal)
tryNext(c)
?c.next()
of CborEventKind.cborNegative:
next = CborNode(kind: cborNegative, int: -1 - c.intVal.int64)
tryNext(c)
?c.next()
of CborEventKind.cborBytes:
if c.isIndefinite:
next = CborNode(kind: cborBytes, bytes: newSeq[byte]())
tryNext(c)
?c.next()
while c.kind != CborEventKind.cborBreak:
if c.kind != CborEventKind.cborBytes:
return failure(newCborError("Expected bytes, got " & $c.kind))
@ -297,7 +305,7 @@ proc nextNode*(c: var CborParser): ?!CborNode =
let n = c.s.readData(next.bytes[pos].addr, chunkLen)
if n != chunkLen:
return failure(newCborError("truncated read of CBOR data"))
tryNext(c)
?c.next()
else:
without rawBytes =? c.nextBytes(), error:
return failure(error)
@ -305,7 +313,7 @@ proc nextNode*(c: var CborParser): ?!CborNode =
of CborEventKind.cborText:
if c.isIndefinite:
next = CborNode(kind: cborText, text: "")
tryNext(c)
?c.next()
while c.kind != CborEventKind.cborBreak:
if c.kind != CborEventKind.cborText:
return failure(newCborError("Expected text, got " & $c.kind))
@ -316,8 +324,8 @@ proc nextNode*(c: var CborParser): ?!CborNode =
let n = c.s.readData(next.text[pos].addr, chunkLen)
if n != chunkLen:
return failure(newCborError("truncated read of CBOR data"))
tryNext(c)
tryNext(c)
?c.next()
?c.next()
else:
without text =? c.nextText(), error:
return failure(error)
@ -325,14 +333,14 @@ proc nextNode*(c: var CborParser): ?!CborNode =
of CborEventKind.cborArray:
next = CborNode(kind: cborArray, seq: newSeq[CborNode](c.intVal))
if c.isIndefinite:
tryNext(c)
?c.next()
while c.kind != CborEventKind.cborBreak:
without node =? c.nextNode(), error:
return failure(error)
next.seq.add(node)
tryNext(c)
?c.next()
else:
tryNext(c)
?c.next()
for i in 0..next.seq.high:
without node =? c.nextNode(), error:
return failure(error)
@ -342,16 +350,16 @@ proc nextNode*(c: var CborParser): ?!CborNode =
next = CborNode(kind: cborMap, map: initOrderedTable[CborNode, CborNode](
mapLen.nextPowerOfTwo))
if c.isIndefinite:
tryNext(c)
?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
tryNext(c)
?c.next()
else:
tryNext(c)
?c.next()
for _ in 1 .. mapLen:
without key =? c.nextNode(), error:
return failure(error)
@ -360,7 +368,7 @@ proc nextNode*(c: var CborParser): ?!CborNode =
next.map[key] = val
of CborEventKind.cborTag:
let tag = c.intVal
tryNext(c)
?c.next()
without node =? c.nextNode(), error:
return failure(error)
next = node
@ -371,7 +379,7 @@ proc nextNode*(c: var CborParser): ?!CborNode =
next = CborNode(kind: cborSimple, simple: c.intVal.uint8)
else:
next = CborNode(kind: cborSimple, simple: c.minor)
tryNext(c)
?c.next()
of CborEventKind.cborFloat:
without f =? c.nextFloat(), error:
return failure(error)
@ -383,15 +391,17 @@ proc nextNode*(c: var CborParser): ?!CborNode =
return failure(e.msg)
except IOError as e:
return failure(e.msg)
except Exception as e:
except CatchableError as e:
return failure(e.msg)
except Exception as e:
raise newException(Defect, e.msg, e)
proc readCbor*(s: Stream): ?!CborNode =
## Parse a stream into a CBOR object.
var parser: CborParser
parser.open(s)
tryNext(parser)
?parser.next()
parser.nextNode()
proc parseCbor*(s: string): ?!CborNode =
@ -473,7 +483,7 @@ proc getInt*(n: CborNode; default: int = 0): int =
else: default
proc parseDateText(n: CborNode): DateTime {.raises: [TimeParseError].} =
parse(n.text, timeFormat)
parse(n.text, dateTimeFormat)
proc parseTime(n: CborNode): Time =
case n.kind
@ -557,7 +567,6 @@ proc getSigned*(n: CborNode; default: int64 = 0): int64 =
of cborNegative: n.int
else: default
func getFloat*(n: CborNode; default = 0.0): float =
## Get the floating-poing value of a ``CborNode`` or a fallback.
if n.kind == cborFloat:
@ -565,12 +574,69 @@ func getFloat*(n: CborNode; default = 0.0): float =
else:
default
proc fromCbor*[T: distinct](_: type T; n: CborNode): ?!T =
success T(?T.distinctBase.fromCbor(n))
proc fromCbor*[T: SomeUnsignedInt](_: type T; n: CborNode): ?!T =
expectCborKind(T, {cborUnsigned}, n)
var v = T(n.uint)
if v.BiggestUInt == n.uint:
return success(v)
else:
return failure(newCborError("Value overflow for unsigned integer"))
proc fromCbor*[T: SomeSignedInt](_: type T; n: CborNode): ?!T =
expectCborKind(T, {cborUnsigned, cborNegative}, n)
if n.kind == cborUnsigned:
var v = T(n.uint)
if v.BiggestUInt == n.uint:
return success(v)
else:
return failure(newCborError("Value overflow for signed integer"))
elif n.kind == cborNegative:
var v = T(n.int)
if v.BiggestInt == n.int:
return success(v)
else:
return failure(newCborError("Value overflow for signed integer"))
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] =
expectCborKind(seq[byte], cborBytes, n)
return success(n.bytes)
proc fromCbor*(_: type string; n: CborNode): ?!string =
expectCborKind(string, cborText, n)
return success(n.text)
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] =
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 =
expectCborKind(T, cborArray, n)
var res = T.default
if n.seq.len != T.tupleLen:
return failure(newCborError("Expected tuple of length " & $T.tupleLen))
var i: int
for f in fields(res):
f = ?typeof(f).fromCbor(n.seq[i])
inc i
success res
proc fromCbor*[T](v: var T; n: CborNode): ?!void =
## Return a Result containing the value if `v` can be converted from a given `CborNode`,
## or an error if conversion fails.
## Can be extended and overriden with `fromCborHook(v: var T; n: CborNode)`
## for specific types of `T`.
try:
when T is CborNode:
v = n
@ -580,14 +646,14 @@ proc fromCbor*[T](v: var T; n: CborNode): ?!void =
elif T is distinct:
return fromCbor(distinctBase v, n)
elif T is SomeUnsignedInt:
exceptCborKind(T, {cborUnsigned}, n)
expectCborKind(T, {cborUnsigned}, n)
v = T n.uint
if v.BiggestUInt == n.uint:
return success()
else:
return failure(newCborError("Value overflow for unsigned integer"))
elif T is SomeSignedInt:
exceptCborKind(T, {cborUnsigned, cborNegative}, n)
expectCborKind(T, {cborUnsigned, cborNegative}, n)
if n.kind == cborUnsigned:
v = T n.uint
if v.BiggestUInt == n.uint:
@ -606,19 +672,19 @@ proc fromCbor*[T](v: var T; n: CborNode): ?!void =
v = n.getBool
return success()
elif T is SomeFloat:
exceptCborKind(T, {cborFloat}, n)
expectCborKind(T, {cborFloat}, n)
v = T n.float
return success()
elif T is seq[byte]:
exceptCborKind(T, {cborBytes}, n)
expectCborKind(T, {cborBytes}, n)
v = n.bytes
return success()
elif T is string:
exceptCborKind(T, {cborText}, n)
expectCborKind(T, {cborText}, n)
v = n.text
return success()
elif T is seq:
exceptCborKind(T, {cborArray}, n)
expectCborKind(T, {cborArray}, n)
v.setLen n.seq.len
for i, e in n.seq:
let itemResult = fromCbor(v[i], e)
@ -627,7 +693,7 @@ proc fromCbor*[T](v: var T; n: CborNode): ?!void =
return failure(itemResult.error)
return success()
elif T is tuple:
exceptCborKind(T, {cborArray}, n)
expectCborKind(T, {cborArray}, n)
if n.seq.len != T.tupleLen:
return failure(newCborError("Expected tuple of length " & $T.tupleLen))
var i: int
@ -645,7 +711,7 @@ proc fromCbor*[T](v: var T; n: CborNode): ?!void =
if isNil(v): new(v)
return fromCbor(v[], n)
elif T is object:
exceptCborKind(T, {cborMap}, n)
expectCborKind(T, {cborMap}, n)
var
i: int
key = CborNode(kind: cborText)
@ -664,5 +730,45 @@ proc fromCbor*[T](v: var T; n: CborNode): ?!void =
return failure(newCborError("Extra fields in map"))
else:
return failure(newCborError("Unsupported type: " & $T))
except CatchableError as e:
return failure newCborError(e.msg)
except Exception as e:
return failure(newCborError(e.msg))
raise newException(Defect, e.msg, e)
proc fromCbor*[T: ref object or object](_: type T; n: CborNode): ?!T =
when T is CborNode:
return success T(n)
expectCborKind(T, {cborMap}, n)
var res =
when type(T) is ref:
T.new()
else:
T.default
try:
var
i: int
key = CborNode(kind: cborText)
for name, value in fieldPairs(
when type(T) is ref:
res[]
else:
res
):
key.text = name
if not n.map.hasKey key:
return failure(newCborError("Missing field: " & name))
else:
value = ?typeof(value).fromCbor(n.map[key])
inc i
if i == n.map.len:
return success(res)
else:
return failure(newCborError("Extra fields in map"))
except CatchableError as e:
return failure newCborError(e.msg)
except Exception as e:
raise newException(Defect, e.msg, e)

View File

@ -5,40 +5,28 @@ import ./types
import ./errors
from macros import newDotExpr, newIdentNode, strVal
template tryNext*(c: var CborParser) =
let nextRes = c.next()
if nextRes.isFailure:
return failure(nextRes.error)
template trySkip*(c: var CborParser) =
let skipRes = c.skipNode()
if skipRes.isFailure:
return failure(skipRes.error)
template exceptCborKind*(expectedType: type, expectedKinds: set[CborNodeKind],
template expectCborKind*(expectedType: type, expectedKinds: set[CborNodeKind],
cbor: CborNode) =
if cbor.kind notin expectedKinds:
return failure(newUnexpectedKindError(expectedType, expectedKinds, cbor))
template exceptCborKind*(expectedType: type, expectedKind: CborNodeKind,
template expectCborKind*(expectedType: type, expectedKind: CborNodeKind,
cbor: CborNode) =
exceptCborKind(expectedType, {expectedKind}, cbor)
expectCborKind(expectedType, {expectedKind}, cbor)
template exceptCborKind*(expectedType: type, expectedKinds: set[CborEventKind],
template expectCborKind*(expectedType: type, expectedKinds: set[CborEventKind],
cbor: CborNode) =
if cbor.kind notin expectedKinds:
return failure(newUnexpectedKindError(expectedType, expectedKinds, cbor))
template exceptCborKind*(expectedType: type, expectedKind: CborEventKind,
template expectCborKind*(expectedType: type, expectedKind: CborEventKind,
cbor: CborNode) =
exceptCborKind(expectedType, {expectedKind}, cbor)
expectCborKind(expectedType, {expectedKind}, cbor)
macro dot*(obj: object, fld: string): untyped =
## Turn ``obj.dot("fld")`` into ``obj.fld``.
newDotExpr(obj, newIdentNode(fld.strVal))
func floatSingle*(half: uint16): float32 =
## Convert a 16-bit float to 32-bits.
func ldexp(x: float64; exponent: int): float64 {.importc: "ldexp",

View File

@ -7,7 +7,7 @@ import ./types
import ./errors
import ./deserializer
proc toJsonHook*(n: CborNode): JsonNode =
proc toJson*(n: CborNode): JsonNode =
case n.kind:
of cborUnsigned:
newJInt n.uint.BiggestInt
@ -20,15 +20,15 @@ proc toJsonHook*(n: CborNode): JsonNode =
of cborArray:
let a = newJArray()
for e in n.seq.items:
a.add(e.toJsonHook)
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.toJsonHook
o[k.text] = v.toJson
else:
o[$k] = v.toJsonHook
o[$k] = v.toJson
o
of cborTag: nil
of cborSimple:
@ -42,4 +42,4 @@ proc toJsonHook*(n: CborNode): JsonNode =
of cborRaw:
without parsed =? parseCbor(n.raw), error:
raise newCborError(error.msg)
toJsonHook(parsed)
toJson(parsed)

View File

@ -303,19 +303,22 @@ proc sort*(n: var CborNode): ?!void =
for key, val in n.map.mpairs:
without res =? key.toRaw, error:
return failure(error)
tmp[res] = move(val)
if tmp.hasKey(res):
tmp[res] = move(val)
sort(tmp) do (x, y: tuple[k: CborNode; v: CborNode]) -> int:
result = cmp(x.k.raw, y.k.raw)
n.map = move tmp
success()
except Exception as e:
except CatchableError as e:
return failure(e.msg)
except Exception as e:
raise newException(Defect, e.msg, e)
proc writeCborHook*(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, timeFormat))
?writeCbor(str, format(dt, dateTimeFormat))
success()
proc writeCborHook*(str: Stream; t: Time): ?!void =
@ -345,8 +348,10 @@ func toCbor*(x: openArray[CborNode]): ?!CborNode =
func toCbor*(pairs: openArray[(CborNode, CborNode)]): ?!CborNode =
try:
return success(CborNode(kind: cborMap, map: pairs.toOrderedTable))
except Exception as e:
except CatchableError as e:
return failure(e.msg)
except Exception as e:
raise newException(Defect, e.msg, e)
func toCbor*(tag: uint64; val: CborNode): ?!CborNode =
without res =? toCbor(val), error:
@ -367,7 +372,6 @@ func toCbor*(x: SomeFloat): ?!CborNode =
func toCbor*(x: pointer): ?!CborNode =
## A hack to produce a CBOR null item.
assert(x.isNil)
if not x.isNil:
return failure("pointer is not nil")
success(CborNode(kind: cborSimple, simple: 22))

View File

@ -3,7 +3,8 @@
import std/[streams, tables, options, hashes, times]
const timeFormat* = initTimeFormat "yyyy-MM-dd'T'HH:mm:sszzz"
# This format is defined in RCF8949 section 3.4.1.
const dateTimeFormat* = initTimeFormat "yyyy-MM-dd'T'HH:mm:sszzz"
const
PositiveMajor* = 0'u8

View File

@ -6,8 +6,10 @@ 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:
@ -23,7 +25,8 @@ 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(
@ -43,12 +46,14 @@ 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

View File

@ -24,7 +24,7 @@ type
s: string
nums: seq[int]
CompositeNested = object
CompositeNested {.deserialize.} = object
u: uint64
n: int
b: seq[byte]
@ -37,15 +37,16 @@ type
coordinates: tuple[x: int, y: int, label: string]
refInner: ref Inner
proc fromCborHook*(v: var CustomColor, n: CborNode): ?!void =
proc fromCbor*(_: type CustomColor, n: CborNode): ?!CustomColor =
var v: CustomColor
if n.kind == cborNegative:
v = CustomColor(n.int)
result = success()
success(v)
else:
result = failure(newSerdeError("Expected signed integer, got " & $n.kind))
failure(newSerdeError("Expected signed integer, got " & $n.kind))
# Custom fromCborHook for CustomPoint
proc fromCborHook*(v: var CustomPoint, n: CborNode): ?!void =
proc fromCbor*(_: type CustomPoint, n: CborNode): ?!CustomPoint =
if n.kind == cborArray and n.seq.len == 2:
var x, y: int
let xResult = fromCbor(x, n.seq[0])
@ -56,10 +57,9 @@ proc fromCborHook*(v: var CustomPoint, n: CborNode): ?!void =
if yResult.isFailure:
return failure(yResult.error)
v = CustomPoint(x: x, y: y)
result = success()
return success(CustomPoint(x: x, y: y))
else:
result = failure(newSerdeError("Expected array of length 2 for CustomPoint"))
return failure(newSerdeError("Expected array of length 2 for CustomPoint"))
# Helper function to create CBOR data for testing
proc createPointCbor(x, y: int): CborNode =
@ -97,10 +97,11 @@ suite "CBOR deserialization":
let node = createObjectCbor("Test Object", point, Green)
# Deserialize
var deserializedObj: CustomObject
# var deserializedObj: CustomObject
# Check result
let result = fromCbor(deserializedObj, node)
let result = CustomObject.fromCbor(node)
check result.isSuccess
var deserializedObj = result.tryValue
check deserializedObj.name == "Test Object"
check deserializedObj.point.x == 15
check deserializedObj.point.y == 25
@ -148,10 +149,9 @@ suite "CBOR deserialization":
let node = parseResult.tryValue
# Deserialize to CompositeNested object
var roundtrip: CompositeNested
let deserResult = fromCbor(roundtrip, node)
check deserResult.isSuccess
let res = CompositeNested.fromCbor(node)
check res.isSuccess
let roundtrip = res.tryValue
# Check top-level fields
check roundtrip.u == original.u
check roundtrip.n == original.n

View File

@ -27,7 +27,7 @@ suite "decode":
controlCbor = base64.decode v["cbor"].getStr
without c =? parseCbor(controlCbor), error:
fail()
let js = c.toJsonHook()
let js = c.toJson()
if js.isNil:
fail()
else: