refactor: split writeCbor monolith into specialized type implementations

This commit is contained in:
munna0908 2025-05-30 15:24:39 +05:30
parent 056a78e95f
commit a96971968d
No known key found for this signature in database
GPG Key ID: 2FFCD637E937D3E6
4 changed files with 385 additions and 303 deletions

View File

@ -2,9 +2,9 @@
# originally available at https://github.com/ehmry/cbor-nim and released under The Unlicense.
import std/[math, streams, options, tables, strutils, times, typetraits, macros]
import ./types
import ./types as cborTypes
import ./helpers
import ../utils/types as utilsTypes
import ../utils/types
import ../utils/pragmas
import ./errors
import pkg/questionable
@ -13,8 +13,8 @@ import pkg/questionable/results
export results
export types
export pragmas
export utilsTypes
export cborTypes
export macros
{.push raises: [].}
@ -636,117 +636,126 @@ proc fromCbor*[T: tuple](_: type T; n: CborNode): ?!T =
success res
proc fromCbor*[T](v: var T; n: CborNode): ?!void =
try:
when T is CborNode:
v = n
result = success()
elif compiles(fromCborHook(v, n)):
return fromCborHook(v, n)
elif T is distinct:
return fromCbor(distinctBase v, n)
elif T is SomeUnsignedInt:
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:
expectCborKind(T, {cborUnsigned, cborNegative}, n)
if n.kind == cborUnsigned:
v = T n.uint
if v.BiggestUInt == n.uint:
return success()
else:
return failure(newCborError("Value overflow for un signed integer"))
elif n.kind == cborNegative:
v = T n.int
if v.BiggestInt == n.int:
return success()
else:
return failure(newCborError("Value overflow for signed integer"))
elif T is bool:
if not n.isBool:
return failure(newCborError("Expected boolean, got " & $n.kind))
v = n.getBool
return success()
elif T is SomeFloat:
expectCborKind(T, {cborFloat}, n)
v = T n.float
return success()
elif T is seq[byte]:
expectCborKind(T, {cborBytes}, n)
v = n.bytes
return success()
elif T is string:
expectCborKind(T, {cborText}, n)
v = n.text
return success()
elif T is seq:
expectCborKind(T, {cborArray}, n)
v.setLen n.seq.len
for i, e in n.seq:
let itemResult = fromCbor(v[i], e)
if itemResult.isFailure:
v.setLen 0
return failure(itemResult.error)
return success()
elif T is tuple:
expectCborKind(T, {cborArray}, n)
if n.seq.len != T.tupleLen:
return failure(newCborError("Expected tuple of length " & $T.tupleLen))
var i: int
for f in fields(v):
let itemResult = fromCbor(f, n.seq[i])
if itemResult.isFailure:
return failure(itemResult.error)
inc i
return success()
elif T is ref:
if n.isNull:
v = nil
return success()
else:
if isNil(v): new(v)
return fromCbor(v[], n)
elif T is object:
expectCborKind(T, {cborMap}, n)
var
i: int
key = CborNode(kind: cborText)
for s, _ in fieldPairs(v):
key.text = s
if not n.map.hasKey key:
return failure(newCborError("Missing field: " & s))
else:
let fieldResult = fromCbor(v.dot(s), n.map[key])
if fieldResult.isFailure:
return failure(fieldResult.error)
inc i
if i == n.map.len:
return success()
else:
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:
raise newException(Defect, e.msg, e)
# proc fromCbor*[T](v: var T; n: CborNode): ?!void =
# try:
# when T is CborNode:
# v = n
# result = success()
# elif compiles(fromCborHook(v, n)):
# return fromCborHook(v, n)
# elif T is distinct:
# return fromCbor(distinctBase v, n)
# elif T is SomeUnsignedInt:
# 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:
# expectCborKind(T, {cborUnsigned, cborNegative}, n)
# if n.kind == cborUnsigned:
# v = T n.uint
# if v.BiggestUInt == n.uint:
# return success()
# else:
# return failure(newCborError("Value overflow for un signed integer"))
# elif n.kind == cborNegative:
# v = T n.int
# if v.BiggestInt == n.int:
# return success()
# else:
# return failure(newCborError("Value overflow for signed integer"))
# elif T is bool:
# if not n.isBool:
# return failure(newCborError("Expected boolean, got " & $n.kind))
# v = n.getBool
# return success()
# elif T is SomeFloat:
# expectCborKind(T, {cborFloat}, n)
# v = T n.float
# return success()
# elif T is seq[byte]:
# expectCborKind(T, {cborBytes}, n)
# v = n.bytes
# return success()
# elif T is string:
# expectCborKind(T, {cborText}, n)
# v = n.text
# return success()
# elif T is seq:
# expectCborKind(T, {cborArray}, n)
# v.setLen n.seq.len
# for i, e in n.seq:
# let itemResult = fromCbor(v[i], e)
# if itemResult.isFailure:
# v.setLen 0
# return failure(itemResult.error)
# return success()
# elif T is tuple:
# expectCborKind(T, {cborArray}, n)
# if n.seq.len != T.tupleLen:
# return failure(newCborError("Expected tuple of length " & $T.tupleLen))
# var i: int
# for f in fields(v):
# let itemResult = fromCbor(f, n.seq[i])
# if itemResult.isFailure:
# return failure(itemResult.error)
# inc i
# return success()
# elif T is ref:
# if n.isNull:
# v = nil
# return success()
# else:
# if isNil(v): new(v)
# return fromCbor(v[], n)
# elif T is object:
# expectCborKind(T, {cborMap}, n)
# var
# i: int
# key = CborNode(kind: cborText)
# for s, _ in fieldPairs(v):
# key.text = s
# if not n.map.hasKey key:
# return failure(newCborError("Missing field: " & s))
# else:
# let fieldResult = fromCbor(v.dot(s), n.map[key])
# if fieldResult.isFailure:
# return failure(fieldResult.error)
# inc i
# if i == n.map.len:
# return success()
# else:
# 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:
# raise newException(Defect, e.msg, e)
proc fromCbor*[T: ref object or object](_: 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)
else:
var resRef = T.new()
let res = typeof(resRef[]).fromCbor(n)
if res.isFailure:
return failure(newCborError(res.error.msg))
resRef[] = res.value
return success(resRef)
proc fromCbor*[T: 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
var res = T.default
let mode = getSerdeMode(T, deserialize)
try:
var
i: int
@ -772,3 +781,8 @@ proc fromCbor*[T: ref object or object](_: type T; n: CborNode): ?!T =
return failure newCborError(e.msg)
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)
T.fromCbor(n)

View File

@ -111,152 +111,168 @@ proc writeCbor*(str: Stream; buf: pointer; len: int): ?!void =
?str.writeInitial(BytesMajor, len)
if len > 0:
return catch str.writeData(buf, len)
success()
proc isSorted*(n: CborNode): ?!bool {.gcsafe.}
proc writeCbor*[T](str: Stream; v: T): ?!void =
## Write the CBOR binary representation of a `T` to a `Stream`.
## The behavior of this procedure can be extended or overriden
## by defining `proc writeCborHook(str: Stream; v: T)` for specific
## types `T`.
proc writeCbor(str: Stream; v: SomeUnsignedInt): ?!void =
str.writeInitial(0, v)
proc writeCbor*(str: Stream; v: SomeSignedInt): ?!void =
if v < 0:
?str.writeInitial(1, -1-v)
else:
?str.writeInitial(0, v)
success()
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 =
?str.writeInitial(TextMajor, v.len)
return catch str.write(v)
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 =
?str.writeInitial(4, v.len)
for e in v.items:
?str.writeCbor(e)
success()
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 =
if system.`==`(v, nil):
# Major type 7
return catch str.write(Null)
else:
?str.writeCbor(v[])
success()
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 =
try:
when T is CborNode:
if v.tag.isSome:
?str.writeCborTag(v.tag.get)
case v.kind:
of cborUnsigned:
return str.writeCbor(v.uint)
of cborNegative:
return str.writeCbor(v.int)
of cborBytes:
?str.writeInitial(cborBytes.uint8, v.bytes.len)
for b in v.bytes.items:
str.write(b)
of cborText:
?str.writeInitial(cborText.uint8, v.text.len)
str.write(v.text)
of cborArray:
?str.writeInitial(4, v.seq.len)
for e in v.seq:
?str.writeCbor(e)
of cborMap:
without isSortedRes =? v.isSorted, error:
return failure(error)
if not isSortedRes:
return failure(newSerdeError("refusing to write unsorted map to stream"))
?str.writeInitial(5, v.map.len)
for k, f in v.map.pairs:
?str.writeCbor(k)
?str.writeCbor(f)
of cborTag:
discard
of cborSimple:
if v.simple > 31'u or v.simple == 24:
str.write(initialByte(cborSimple.uint8, 24))
str.write(v.simple)
else:
str.write(initialByte(cborSimple.uint8, v.simple))
of cborFloat:
return str.writeCbor(v.float)
of cborRaw:
str.write(v.raw)
elif compiles(writeCborHook(str, v)):
?writeCborHook(str, v)
elif T is SomeUnsignedInt:
?str.writeInitial(0, v)
elif T is SomeSignedInt:
if v < 0:
# Major type 1
?str.writeInitial(1, -1 - v)
else:
# Major type 0
?str.writeInitial(0, v)
elif T is seq[byte]:
?str.writeInitial(BytesMajor, v.len)
if v.len > 0:
str.writeData(unsafeAddr v[0], v.len)
elif T is openArray[char | uint8 | int8]:
?str.writeInitial(BytesMajor, v.len)
if v.len > 0:
str.writeData(unsafeAddr v[0], v.len)
elif T is string:
?str.writeInitial(TextMajor, v.len)
str.write(v)
elif T is array | seq:
?str.writeInitial(4, v.len)
for e in v.items:
?str.writeCbor(e)
elif T is tuple:
?str.writeInitial(4, T.tupleLen)
for f in v.fields: ?str.writeCbor(f)
elif T is ptr | ref:
if system.`==`(v, nil):
# Major type 7
str.write(Null)
else: ?str.writeCbor(v[])
elif T is object:
var n: uint
for _, _ in v.fieldPairs:
inc n
?str.writeInitial(5, n)
for k, f in v.fieldPairs:
?str.writeCbor(k)
?str.writeCbor(f)
elif T is bool:
str.write(initialByte(7, (if v: 21 else: 20)))
elif T is SomeFloat:
case v.classify
of fcNormal, fcSubnormal:
let single = v.float32
if single.float64 == v.float64:
if single.isHalfPrecise:
let half = floatHalf(single)
str.write(initialByte(7, 25))
when system.cpuEndian == bigEndian:
str.write(half)
else:
var be: uint16
swapEndian16 be.addr, half.unsafeAddr
str.write(be)
else:
str.write initialByte(7, 26)
when system.cpuEndian == bigEndian:
str.write(single)
else:
var be: uint32
swapEndian32 be.addr, single.unsafeAddr
str.write(be)
else:
str.write initialByte(7, 27)
case v.classify
of fcNormal, fcSubnormal:
let single = v.float32
if single.float64 == v.float64:
if single.isHalfPrecise:
let half = floatHalf(single)
str.write(initialByte(7, 25))
when system.cpuEndian == bigEndian:
str.write(v)
str.write(half)
else:
var be: float64
swapEndian64 be.addr, v.unsafeAddr
str.write be
return success()
of fcZero:
str.write initialByte(7, 25)
str.write((char)0x00)
of fcNegZero:
str.write initialByte(7, 25)
str.write((char)0x80)
of fcInf:
str.write initialByte(7, 25)
str.write((char)0x7c)
of fcNan:
str.write initialByte(7, 25)
str.write((char)0x7e)
of fcNegInf:
str.write initialByte(7, 25)
str.write((char)0xfc)
var be: uint16
swapEndian16 be.addr, half.unsafeAddr
str.write(be)
else:
str.write initialByte(7, 26)
when system.cpuEndian == bigEndian:
str.write(single)
else:
var be: uint32
swapEndian32 be.addr, single.unsafeAddr
str.write(be)
else:
str.write initialByte(7, 27)
when system.cpuEndian == bigEndian:
str.write(v)
else:
var be: uint64
swapEndian64 be.addr, v.unsafeAddr
str.write(be)
return success()
of fcZero:
str.write initialByte(7, 25)
str.write((char)0x00)
of fcNegZero:
str.write initialByte(7, 25)
str.write((char)0x80)
of fcInf:
str.write initialByte(7, 25)
str.write((char)0x7c)
of fcNan:
str.write initialByte(7, 25)
str.write((char)0x7e)
of fcNegInf:
str.write initialByte(7, 25)
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 =
try:
if v.tag.isSome:
?str.writeCborTag(v.tag.get)
case v.kind:
of cborUnsigned:
?str.writeCbor(v.uint)
of cborNegative:
?str.writeCbor(v.int)
of cborBytes:
?str.writeInitial(cborBytes.uint8, v.bytes.len)
for b in v.bytes.items:
str.write(b)
of cborText:
?str.writeInitial(cborText.uint8, v.text.len)
str.write(v.text)
of cborArray:
?str.writeInitial(4, v.seq.len)
for e in v.seq:
?str.writeCbor(e)
of cborMap:
without isSortedRes =? v.isSorted, error:
return failure(error)
if not isSortedRes:
return failure(newSerdeError("refusing to write unsorted map to stream"))
?str.writeInitial(5, v.map.len)
for k, f in v.map.pairs:
?str.writeCbor(k)
?str.writeCbor(f)
of cborTag:
discard
of cborSimple:
if v.simple > 31'u or v.simple == 24:
str.write(initialByte(cborSimple.uint8, 24))
str.write(v.simple)
else:
str.write(initialByte(cborSimple.uint8, v.simple))
of cborFloat:
?str.writeCbor(v.float)
of cborRaw:
str.write(v.raw)
success()
except CatchableError as e:
return failure(e.msg)
proc writeCbor*[T: object](str: Stream; v: T): ?!void =
var n: uint
for _, _ in v.fieldPairs:
inc n
?str.writeInitial(5, n)
for k, f in v.fieldPairs:
?str.writeCbor(k)
?str.writeCbor(f)
success()
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
@ -314,14 +330,14 @@ proc sort*(n: var CborNode): ?!void =
except Exception as e:
raise newException(Defect, e.msg, e)
proc writeCborHook*(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 writeCborHook*(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)
@ -407,5 +423,3 @@ func initCborMap*(initialSize = tables.defaultInitialSize): CborNode =
func initCbor*(items: varargs[CborNode, toCbor]): CborNode =
## Initialize a CBOR arrary.
CborNode(kind: cborArray, seq: @items)

View File

@ -1,42 +1,67 @@
import std/unittest
import std/options
import std/streams
import std/macros
import pkg/serde
import pkg/questionable
import pkg/questionable/results
# Custom type for testing
#[
Test types definitions
These types are used to test various aspects of CBOR serialization/deserialization:
- Basic types (integers, strings, etc.)
- Custom types with custom serialization logic
- Nested objects
- Reference types
- Collections (sequences, tuples)
]#
type
# A simple 2D point with x and y coordinates
CustomPoint = object
x: int
y: int
# Enum type to test enum serialization
CustomColor = enum
Red, Green, Blue
# Object combining different custom types
CustomObject = object
name: string
point: CustomPoint
color: CustomColor
# Simple object with a string and sequence
Inner = object
s: string
nums: seq[int]
CompositeNested {.deserialize.} = object
u: uint64
n: int
b: seq[byte]
t: string
arr: seq[int]
tag: float
flag: bool
inner: Inner
innerArr: seq[Inner]
coordinates: tuple[x: int, y: int, label: string]
refInner: ref Inner
# Reference type for testing ref object serialization
NewType = ref object
size: uint64
# Reference type for Inner object
InnerRef = ref Inner
# 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
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
# Custom deserialization for CustomColor enum
# Converts a CBOR negative integer to a CustomColor enum value
proc fromCbor*(_: type CustomColor, n: CborNode): ?!CustomColor =
var v: CustomColor
if n.kind == cborNegative:
@ -45,22 +70,28 @@ proc fromCbor*(_: type CustomColor, n: CborNode): ?!CustomColor =
else:
failure(newSerdeError("Expected signed integer, got " & $n.kind))
# Custom fromCborHook for CustomPoint
# Custom deserialization for CustomPoint
# Expects a CBOR array with exactly 2 elements representing x and y coordinates
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])
if xResult.isFailure:
return failure(xResult.error)
let yResult = fromCbor(y, n.seq[1])
if yResult.isFailure:
return failure(yResult.error)
let x = ?int.fromCbor(n.seq[0])
let y = ?int.fromCbor(n.seq[1])
return success(CustomPoint(x: x, y: y))
else:
return failure(newSerdeError("Expected array of length 2 for CustomPoint"))
# Custom serialization for CustomPoint
# Serializes a CustomPoint as a CBOR array with 2 elements: [x, y]
proc writeCbor*(str: Stream, val: CustomPoint): ?!void =
# Write array header with length 2
?str.writeCborArrayLen(2)
# Write x and y coordinates
?str.writeCbor(val.x)
str.writeCbor(val.y)
# Helper function to create CBOR data for testing
proc createPointCbor(x, y: int): CborNode =
result = CborNode(kind: cborArray)
@ -69,6 +100,7 @@ proc createPointCbor(x, y: int): CborNode =
CborNode(kind: cborUnsigned, uint: y.uint64)
]
# Creates a CBOR map node representing a CustomObject
proc createObjectCbor(name: string, point: CustomPoint,
color: CustomColor): CborNode =
result = CborNode(kind: cborMap)
@ -89,19 +121,20 @@ proc createObjectCbor(name: string, point: CustomPoint,
suite "CBOR deserialization":
test "deserializes object with custom types":
# Create a complex object
# Create a test point
let point = CustomPoint(x: 15, y: 25)
# let obj = CustomObject(name: "Test Object", point: point, color: Green)
# Create CBOR representation
# Create CBOR representation of a CustomObject
let node = createObjectCbor("Test Object", point, Green)
# Deserialize
# var deserializedObj: CustomObject
# Check result
# Deserialize CBOR to CustomObject
let result = CustomObject.fromCbor(node)
# Verify deserialization was successful
check result.isSuccess
var deserializedObj = result.tryValue
# Verify all fields were correctly deserialized
check deserializedObj.name == "Test Object"
check deserializedObj.point.x == 15
check deserializedObj.point.y == 25
@ -109,73 +142,95 @@ suite "CBOR deserialization":
test "serialize and deserialize object with all supported wire types":
var refObj = new Inner
refObj.s = "refInner"
refObj.nums = @[30, 40]
# Setup test data with various types
# 1. Create reference objects
var refInner = new Inner
refInner.s = "refInner"
refInner.nums = @[30, 40]
var refNewObj = new NewType
refNewObj.size = 42
# 2. Create a complex object with all supported types
var original = CompositeNested(
u: 42,
n: -99,
b: @[byte 1, byte 2],
t: "hi",
arr: @[1, 2, 3],
tag: 1.5,
flag: true,
inner: Inner(s: "inner!", nums: @[10, 20]),
innerArr: @[
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"),
refInner: refObj
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
)
# Serialize to CBOR with encode
# Test serialization using encode helper
without encodedStr =? encode(original), error:
fail()
# Serialize to CBOR
# Test serialization using stream API
let stream = newStringStream()
if stream.writeCbor(original).isFailure:
fail()
check not stream.writeCbor(original).isFailure
# Get the serialized CBOR data
let cborData = stream.data
# Check that both serialized forms are equal
# Verify both serialization methods produce the same result
check cborData == encodedStr
# Parse CBOR back to CborNode
# Parse CBOR data back to CborNode
let parseResult = parseCbor(cborData)
check parseResult.isSuccess
let node = parseResult.tryValue
# Deserialize to CompositeNested object
# Deserialize CborNode to CompositeNested object
let res = CompositeNested.fromCbor(node)
check res.isSuccess
let roundtrip = res.tryValue
# Check top-level fields
# Verify all fields were correctly round-tripped
# 1. Check primitive fields
check roundtrip.u == original.u
check roundtrip.n == original.n
check roundtrip.b == original.b
check roundtrip.t == original.t
check roundtrip.arr == original.arr
check abs(roundtrip.tag - original.tag) < 1e-6
check abs(roundtrip.tag - original.tag) < 1e-6 # Float comparison with epsilon
check roundtrip.flag == original.flag
# Check nested object
# 2. Check nested object fields
check roundtrip.inner.s == original.inner.s
check roundtrip.inner.nums == original.inner.nums
# Check nested array of objects
# 3. Check sequence of objects
check roundtrip.innerArr.len == original.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
# 4. Check tuple fields
check roundtrip.coordinates.x == original.coordinates.x
check roundtrip.coordinates.y == original.coordinates.y
check roundtrip.coordinates.label == original.coordinates.label
# 5. Check reference fields
check not roundtrip.refInner.isNil
check roundtrip.refInner.s == original.refInner.s
check roundtrip.refInner.nums == original.refInner.nums
# 6. Check nil reference
check roundtrip.refNil.isNil
# 7. Check custom type
check roundtrip.customPoint.x == original.customPoint.x
check roundtrip.customPoint.y == original.customPoint.y

View File

@ -105,9 +105,8 @@ test "sorting":
check map.isSorted.tryValue
test "invalid wire type":
var intValue: int
let node = CborNode(kind: cborText, text: "not an int")
let result = fromCbor(intValue, node)
let result = int.fromCbor(node)
check result.isFailure
check $result.error.msg == "deserialization to int failed: expected {cborUnsigned, cborNegative} but got cborText"