mirror of
https://github.com/logos-storage/nim-serde.git
synced 2026-01-02 13:43:06 +00:00
refactor: reimplement CBOR deserialization with type-based fromCbor interface
This commit is contained in:
parent
d3c3774391
commit
056a78e95f
@ -1,16 +1,20 @@
|
||||
# This file is a modified version of Emery Hemingway’s 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)
|
||||
|
||||
@ -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",
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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))
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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:
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user