From a96971968d62aef16fe75d9b69f13bb5f284b98d Mon Sep 17 00:00:00 2001 From: munna0908 Date: Fri, 30 May 2025 15:24:39 +0530 Subject: [PATCH] refactor: split writeCbor monolith into specialized type implementations --- serde/cbor/deserializer.nim | 230 ++++++++++++++------------- serde/cbor/serializer.nim | 288 ++++++++++++++++++---------------- tests/cbor/testObjects.nim | 167 +++++++++++++------- tests/cbor/testPrimitives.nim | 3 +- 4 files changed, 385 insertions(+), 303 deletions(-) diff --git a/serde/cbor/deserializer.nim b/serde/cbor/deserializer.nim index 0b3432d..eacf5be 100644 --- a/serde/cbor/deserializer.nim +++ b/serde/cbor/deserializer.nim @@ -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) + diff --git a/serde/cbor/serializer.nim b/serde/cbor/serializer.nim index 2c0b014..d557931 100644 --- a/serde/cbor/serializer.nim +++ b/serde/cbor/serializer.nim @@ -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) - - diff --git a/tests/cbor/testObjects.nim b/tests/cbor/testObjects.nim index d9f9f17..ab22498 100644 --- a/tests/cbor/testObjects.nim +++ b/tests/cbor/testObjects.nim @@ -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..