mirror of
https://github.com/logos-storage/nim-serde.git
synced 2026-01-02 13:43:06 +00:00
refactor: split writeCbor monolith into specialized type implementations
This commit is contained in:
parent
056a78e95f
commit
a96971968d
@ -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)
|
||||
|
||||
|
||||
@ -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)
|
||||
|
||||
|
||||
|
||||
@ -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
|
||||
|
||||
|
||||
@ -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"
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user