mirror of
https://github.com/status-im/nim-serialization.git
synced 2025-01-27 04:04:57 +00:00
Better support for case objects; Some internal documentation
This commit is contained in:
parent
171dbde4b1
commit
60613bac5d
@ -1,6 +1,7 @@
|
|||||||
type
|
type
|
||||||
SerializationError* = object of CatchableError
|
SerializationError* = object of CatchableError
|
||||||
UnexpectedEofError* = object of SerializationError
|
UnexpectedEofError* = object of SerializationError
|
||||||
|
CustomSerializationError* = object of SerializationError
|
||||||
|
|
||||||
method formatMsg*(err: ref SerializationError, filename: string): string {.gcsafe, base.} =
|
method formatMsg*(err: ref SerializationError, filename: string): string {.gcsafe, base.} =
|
||||||
"Serialisation error while processing " & filename
|
"Serialisation error while processing " & filename
|
||||||
|
@ -1,10 +1,68 @@
|
|||||||
import
|
import
|
||||||
stew/shims/macros
|
stew/shims/macros, stew/objects
|
||||||
|
|
||||||
template dontSerialize* {.pragma.}
|
template dontSerialize* {.pragma.}
|
||||||
## Specifies that a certain field should be ignored for
|
## Specifies that a certain field should be ignored for
|
||||||
## the purposes of serialization
|
## the purposes of serialization
|
||||||
|
|
||||||
|
template enumInstanceSerializedFields*(obj: auto,
|
||||||
|
fieldNameVar, fieldVar,
|
||||||
|
body: untyped) =
|
||||||
|
## Expands a block over all serialized fields of an object.
|
||||||
|
##
|
||||||
|
## Inside the block body, the passed `fieldNameVar` identifier
|
||||||
|
## will refer to the name of each field as a string. `fieldVar`
|
||||||
|
## will refer to the field value.
|
||||||
|
##
|
||||||
|
## The order of visited fields matches the order of the fields in
|
||||||
|
## the object definition unless `serialziedFields` is used to specify
|
||||||
|
## a different order. Fields marked with the `dontSerialize` pragma
|
||||||
|
## are skipped.
|
||||||
|
##
|
||||||
|
## If the visited object is a case object, only the currently active
|
||||||
|
## fields will be visited. During de-serialization, case discriminators
|
||||||
|
## will be read first and the iteration will continue depending on the
|
||||||
|
## value being deserialized.
|
||||||
|
##
|
||||||
|
for fieldNameVar, fieldVar in fieldPairs(obj):
|
||||||
|
when not hasCustomPragma(fieldVar, dontSerialize):
|
||||||
|
body
|
||||||
|
|
||||||
|
macro enumAllSerializedFields*(T: type,
|
||||||
|
fieldNameVar, fieldTypeVar,
|
||||||
|
body: untyped): untyped =
|
||||||
|
## Expands a block over all fields of a type
|
||||||
|
##
|
||||||
|
## Inside the block body, the passed `fieldNameVar` identifier
|
||||||
|
## will refer to the name of each field as a string. `fieldTypeVar`
|
||||||
|
## is an identifier that will refer to the field's type.
|
||||||
|
##
|
||||||
|
## Please note that the main difference between
|
||||||
|
## `enumInstanceSerializedFields` and `enumAllSerializedFields`
|
||||||
|
## is that the later will visit all fields of case objects.
|
||||||
|
##
|
||||||
|
## The order of visited fields matches the order of the fields in
|
||||||
|
## the object definition unless `serialziedFields` is used to specify
|
||||||
|
## a different order. Fields marked with the `dontSerialize` pragma
|
||||||
|
## are skipped.
|
||||||
|
##
|
||||||
|
var T = getImpl(getType(T)[1])
|
||||||
|
result = newStmtList()
|
||||||
|
|
||||||
|
for field in recordFields(T):
|
||||||
|
if field.readPragma("dontSerialize") != nil:
|
||||||
|
continue
|
||||||
|
|
||||||
|
let
|
||||||
|
fieldName = newLit($field.name)
|
||||||
|
fieldType = field.typ
|
||||||
|
|
||||||
|
result.add quote do:
|
||||||
|
block:
|
||||||
|
template `fieldNameVar`: auto = `fieldName`
|
||||||
|
type `fieldTypeVar` = `fieldType`
|
||||||
|
`body`
|
||||||
|
|
||||||
type
|
type
|
||||||
FieldMarkerImpl*[name: static string] = object
|
FieldMarkerImpl*[name: static string] = object
|
||||||
|
|
||||||
@ -15,90 +73,36 @@ type
|
|||||||
|
|
||||||
FieldReadersTable*[RecordType, Reader] = openarray[FieldReader[RecordType, Reader]]
|
FieldReadersTable*[RecordType, Reader] = openarray[FieldReader[RecordType, Reader]]
|
||||||
|
|
||||||
template eachSerializedFieldImpl*[T](x: T, op: untyped) =
|
|
||||||
when false:
|
|
||||||
static: echo treeRepr(T.getTypeImpl)
|
|
||||||
|
|
||||||
for k, v in fieldPairs(x):
|
|
||||||
when true: # not hasCustomPragma(v, dontSerialize):
|
|
||||||
op(k, v)
|
|
||||||
|
|
||||||
proc totalSerializedFieldsImpl(T: type): int =
|
proc totalSerializedFieldsImpl(T: type): int =
|
||||||
mixin eachSerializedFieldImpl
|
mixin enumAllSerializedFields
|
||||||
|
enumAllSerializedFields(T, fieldName, fieldType): inc result
|
||||||
proc helper: int =
|
|
||||||
var dummy: T
|
|
||||||
template countFields(k, v) = inc result
|
|
||||||
eachSerializedFieldImpl(dummy, countFields)
|
|
||||||
|
|
||||||
const res = helper()
|
|
||||||
return res
|
|
||||||
|
|
||||||
template totalSerializedFields*(T: type): int =
|
template totalSerializedFields*(T: type): int =
|
||||||
(static(totalSerializedFieldsImpl(T)))
|
(static(totalSerializedFieldsImpl(T)))
|
||||||
|
|
||||||
macro serialziedFields*(T: typedesc, fields: varargs[untyped]): untyped =
|
|
||||||
var body = newStmtList()
|
|
||||||
let
|
|
||||||
ins = genSym(nskParam, "instance")
|
|
||||||
op = genSym(nskParam, "op")
|
|
||||||
|
|
||||||
for field in fields:
|
|
||||||
body.add quote do: `op`(`ins`.`field`)
|
|
||||||
|
|
||||||
result = quote do:
|
|
||||||
template eachSerializedFieldImpl*(`ins`: `T`, `op`: untyped) {.inject.} =
|
|
||||||
`body`
|
|
||||||
|
|
||||||
template serializeFields*(value: auto, fieldName, fieldValue, body: untyped) =
|
|
||||||
# TODO: this would be nicer as a for loop macro
|
|
||||||
mixin eachSerializedFieldImpl
|
|
||||||
|
|
||||||
template op(fieldName, fieldValue: untyped) = body
|
|
||||||
eachSerializedFieldImpl(value, op)
|
|
||||||
|
|
||||||
template deserializeFields*(value: auto, fieldName, fieldValue, body: untyped) =
|
|
||||||
# TODO: this would be nicer as a for loop macro
|
|
||||||
mixin eachSerializedFieldImpl
|
|
||||||
|
|
||||||
template op(fieldName, fieldValue: untyped) = body
|
|
||||||
eachSerializedFieldImpl(value, op)
|
|
||||||
|
|
||||||
macro customSerialization*(field: untyped, definition): untyped =
|
macro customSerialization*(field: untyped, definition): untyped =
|
||||||
discard
|
discard
|
||||||
|
|
||||||
proc hasDontSerialize(pragmas: NimNode): bool =
|
proc makeFieldReadersTable(RecordType, Reader: distinct type):
|
||||||
if pragmas == nil: return false
|
seq[FieldReader[RecordType, Reader]] =
|
||||||
let dontSerialize = bindSym "dontSerialize"
|
mixin enumAllSerializedFields
|
||||||
for p in pragmas:
|
|
||||||
if p == dontSerialize:
|
|
||||||
return true
|
|
||||||
|
|
||||||
macro makeFieldReadersTable(RecordType, Reader: distinct type): untyped =
|
enumAllSerializedFields(RecordType, fieldName, FieldType):
|
||||||
var obj = RecordType.getType[1].getImpl
|
proc readField(obj: var RecordType, reader: var Reader) {.nimcall.} =
|
||||||
|
try:
|
||||||
|
obj.field(fieldName) = reader.readValue(FieldType)
|
||||||
|
except SerializationError:
|
||||||
|
raise
|
||||||
|
except CatchableError as err:
|
||||||
|
reader.handleReadException(`RecordType`, fieldName,
|
||||||
|
obj.field(fieldName), err)
|
||||||
|
|
||||||
result = newTree(nnkBracket)
|
result.add((fieldName, readField))
|
||||||
|
|
||||||
for field in recordFields(obj):
|
|
||||||
let
|
|
||||||
fieldIdent = field.name
|
|
||||||
fieldName = newLit($fieldIdent)
|
|
||||||
if not hasDontSerialize(field.pragmas):
|
|
||||||
var handler = quote do:
|
|
||||||
return proc (obj: var `RecordType`, reader: var `Reader`) {.nimcall.} =
|
|
||||||
try:
|
|
||||||
reader.readValue(obj.`fieldIdent`)
|
|
||||||
except SerializationError:
|
|
||||||
raise
|
|
||||||
except CatchableError as err:
|
|
||||||
reader.readValueFailed(`RecordType`, `fieldName`, obj.`fieldIdent`, err)
|
|
||||||
|
|
||||||
result.add newTree(nnkTupleConstr, fieldName, handler[0])
|
|
||||||
|
|
||||||
proc fieldReadersTable*(RecordType, Reader: distinct type):
|
proc fieldReadersTable*(RecordType, Reader: distinct type):
|
||||||
ptr seq[FieldReader[RecordType, Reader]] {.gcsafe.} =
|
ptr seq[FieldReader[RecordType, Reader]] {.gcsafe.} =
|
||||||
mixin readValue
|
mixin readValue
|
||||||
var tbl {.global.} = @(makeFieldReadersTable(RecordType, Reader))
|
var tbl {.global.} = makeFieldReadersTable(RecordType, Reader)
|
||||||
{.gcsafe.}:
|
{.gcsafe.}:
|
||||||
return addr(tbl)
|
return addr(tbl)
|
||||||
|
|
||||||
@ -116,3 +120,53 @@ proc findFieldReader*(fieldsTable: FieldReadersTable,
|
|||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
|
||||||
|
macro setSerializedFields*(T: typedesc, fields: varargs[untyped]): untyped =
|
||||||
|
var fieldsArray = newTree(nnkBracket)
|
||||||
|
for f in fields: fieldsArray.add newCall(bindSym"ident", newLit($f))
|
||||||
|
|
||||||
|
template payload(T: untyped, fieldsArray) {.dirty.} =
|
||||||
|
bind default, quote, add, getType, newStmtList, newLit, newDotExpr, `$`, `[]`
|
||||||
|
|
||||||
|
macro enumInstanceSerializedFields*(ins: T,
|
||||||
|
fieldNameVar, fieldVar,
|
||||||
|
body: untyped): untyped =
|
||||||
|
var
|
||||||
|
fields = fieldsArray
|
||||||
|
res = newStmtList()
|
||||||
|
|
||||||
|
for field in fields:
|
||||||
|
let
|
||||||
|
fieldName = newLit($field)
|
||||||
|
fieldAccessor = newDotExpr(ins, field)
|
||||||
|
|
||||||
|
res.add quote do:
|
||||||
|
block:
|
||||||
|
const `fieldNameVar` = `fieldName`
|
||||||
|
template `fieldVar`: auto = `fieldAccessor`
|
||||||
|
`body`
|
||||||
|
|
||||||
|
return res
|
||||||
|
|
||||||
|
macro enumAllSerializedFields*(typ: type T,
|
||||||
|
fieldNameVar, fieldTypeVar,
|
||||||
|
body: untyped): untyped =
|
||||||
|
var
|
||||||
|
fields = fieldsArray
|
||||||
|
res = newStmtList()
|
||||||
|
typ = getType(typ)
|
||||||
|
|
||||||
|
for field in fields:
|
||||||
|
let
|
||||||
|
fieldName = newLit($field)
|
||||||
|
fieldAccessor = newDotExpr(typ, field)
|
||||||
|
|
||||||
|
res.add quote do:
|
||||||
|
block:
|
||||||
|
const `fieldNameVar` = `fieldName`
|
||||||
|
type `fieldTypeVar` = type(default(`typ`).`field`)
|
||||||
|
`body`
|
||||||
|
|
||||||
|
return res
|
||||||
|
|
||||||
|
return getAst(payload(T, fieldsArray))
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user