Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
114 changes: 52 additions & 62 deletions serialization.nim
Original file line number Diff line number Diff line change
@@ -1,14 +1,16 @@
import
std/typetraits,
stew/shims/macros, faststreams/[inputs, outputs],
stew/shims/macros,
faststreams/[inputs, outputs],
./serialization/[object_serialization, errors, formats]

export
inputs, outputs, object_serialization, errors, formats
export inputs, outputs, object_serialization, errors, formats

template encode*(Format: type, value: auto, params: varargs[untyped]): auto =
template encode*(
Format: type SerializationFormat, value: auto, params: varargs[untyped]
): auto =
mixin init, Writer, writeValue, PreferredOutputType
block: # https://github.com/nim-lang/Nim/issues/22874
block: # https://github.com/nim-lang/Nim/issues/22874
{.noSideEffect.}:
# We assume that there is no side-effects here, because we are
# using a `memoryOutput`. The computed side-effects are coming
Expand All @@ -24,21 +26,23 @@ template encode*(Format: type, value: auto, params: varargs[untyped]): auto =
raise (ref Defect)() # a memoryOutput cannot have an IOError

# TODO Nim cannot make sense of this initialization by var param?
proc readValue*(reader: var auto, T: type): T {.gcsafe, raises: [SerializationError, IOError].} =
proc readValue*(
reader: var auto, T: type
): T {.gcsafe, raises: [SerializationError, IOError].} =
{.warning[ProveInit]: false.}
mixin readValue
result = default(T)
reader.readValue(result)
{.warning[ProveInit]: true.}

template decode*(Format: distinct type,
input: string,
RecordType: distinct type,
params: varargs[untyped]): auto =
# TODO, this is dusplicated only due to a Nim bug:
# If `input` was `string|openArray[byte]`, it won't match `seq[byte]`
template decode*(
Format: type SerializationFormat,
input: string|openArray[char]|seq[byte]|openArray[byte],
RecordType: type,
params: varargs[untyped],
): auto =
mixin init, Reader
block: # https://github.com/nim-lang/Nim/issues/22874
block: # https://github.com/nim-lang/Nim/issues/22874
{.noSideEffect.}:
# We assume that there are no side-effects here, because we are
# using a `memoryInput`. The computed side-effects are coming
Expand All @@ -52,31 +56,12 @@ template decode*(Format: distinct type,
except IOError:
raise (ref Defect)() # memory inputs cannot raise an IOError

template decode*(Format: distinct type,
input: openArray[byte],
RecordType: distinct type,
params: varargs[untyped]): auto =
# TODO, this is dusplicated only due to a Nim bug:
# If `input` was `string|openArray[byte]`, it won't match `seq[byte]`
mixin init, Reader
block: # https://github.com/nim-lang/Nim/issues/22874
{.noSideEffect.}:
# We assume that there are no side-effects here, because we are
# using a `memoryInput`. The computed side-effects are coming
# from the fact that the dynamic dispatch mechanisms used in
# faststreams may be reading from a file or a network device.
try:
var stream = unsafeMemoryInput(input)
type ReaderType = Reader(Format)
var reader = unpackArgs(init, [ReaderType, stream, params])
reader.readValue(RecordType)
except IOError:
raise (ref Defect)() # memory inputs cannot raise an IOError

template loadFile*(Format: distinct type,
filename: string,
RecordType: distinct type,
params: varargs[untyped]): auto =
template loadFile*(
Format: type SerializationFormat,
filename: string,
RecordType: type,
params: varargs[untyped],
): auto =
mixin init, Reader, readValue

var stream = memFileInput(filename)
Expand All @@ -87,13 +72,20 @@ template loadFile*(Format: distinct type,
finally:
close stream

template loadFile*[RecordType](Format: type,
filename: string,
record: var RecordType,
params: varargs[untyped]) =
template loadFile*[RecordType](
Format: type SerializationFormat,
filename: string,
record: var RecordType,
params: varargs[untyped],
) =
record = loadFile(Format, filename, RecordType, params)

template saveFile*(Format: type, filename: string, value: auto, params: varargs[untyped]) =
template saveFile*(
Format: type SerializationFormat,
filename: string,
value: auto,
params: varargs[untyped],
) =
mixin init, Writer, writeValue

var stream = fileOutput(filename)
Expand All @@ -107,29 +99,24 @@ template saveFile*(Format: type, filename: string, value: auto, params: varargs[
template borrowSerialization*(Alias: type) {.dirty.} =
bind distinctBase

proc writeValue*[Writer](
writer: var Writer, value: Alias) {.raises: [IOError].} =
proc writeValue*[Writer](writer: var Writer, value: Alias) {.raises: [IOError].} =
mixin writeValue
writeValue(writer, distinctBase value)

proc readValue*[Reader](reader: var Reader, value: var Alias) =
mixin readValue
value = Alias reader.readValue(distinctBase Alias)

template borrowSerialization*(Alias: distinct type,
OriginalType: distinct type) {.dirty.} =

proc writeValue*[Writer](
writer: var Writer, value: Alias) {.raises: [IOError].} =
template borrowSerialization*(Alias: type, OriginalType: type) {.dirty.} =
proc writeValue*[Writer](writer: var Writer, value: Alias) {.raises: [IOError].} =
mixin writeValue
writeValue(writer, OriginalType value)

proc readValue*[Reader](reader: var Reader, value: var Alias) =
mixin readValue
value = Alias reader.readValue(OriginalType)

template serializesAsBase*(SerializedType: distinct type,
Format: distinct type) =
template serializesAsBase*(SerializedType: type, Format: type SerializationFormat) =
mixin Reader, Writer

type ReaderType = Reader(Format)
Expand All @@ -143,25 +130,28 @@ template serializesAsBase*(SerializedType: distinct type,
mixin readValue
value = SerializedType reader.readValue(distinctBase SerializedType)

macro serializesAsBaseIn*(SerializedType: type,
Formats: varargs[untyped]) =
macro serializesAsBaseIn*(SerializedType: type, Formats: varargs[untyped]) =
result = newStmtList()
for Fmt in Formats:
result.add newCall(bindSym"serializesAsBase", SerializedType, Fmt)

template readValue*(stream: InputStream,
Format: type,
ValueType: type,
params: varargs[untyped]): untyped =
template readValue*(
stream: InputStream,
Format: type SerializationFormat,
ValueType: type,
params: varargs[untyped],
): untyped =
mixin Reader, init, readValue
type ReaderType = Reader(Format)
var reader = unpackArgs(init, [ReaderType, stream, params])
readValue reader, ValueType

template writeValue*(stream: OutputStream,
Format: type,
value: auto,
params: varargs[untyped]) =
template writeValue*(
stream: OutputStream,
Format: type SerializationFormat,
value: auto,
params: varargs[untyped],
) =
mixin Writer, init, writeValue
type WriterType = Writer(Format)
var writer = unpackArgs(init, [WriterType, stream, params])
Expand Down
2 changes: 1 addition & 1 deletion serialization/errors.nim
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,6 @@ type
CustomSerializationError* = object of SerializationError

method formatMsg*(err: ref SerializationError, filename: string): string
{.gcsafe, base, raises: [Defect].} =
{.gcsafe, base, raises: [].} =
"Serialisation error while processing " & filename & ":" & err.msg

25 changes: 19 additions & 6 deletions serialization/formats.nim
Original file line number Diff line number Diff line change
Expand Up @@ -4,27 +4,32 @@ import
type
DefaultFlavor* = object

SerializationFormat* {.inheritable, pure.} = object
## Marker type for serialization formats created with `serializationFormat`
## and `createFlavor`, for which encode/decoode and other serialization-based
## formats are supported

template serializationFormatImpl(Name: untyped,
mimeTypeName: static string = "") {.dirty.} =
# This indirection is required in order to be able to generate the
# `mimeType` accessor template. Without the indirection, the template
# mechanism of Nim will try to expand the `mimeType` param in the position
# of the `mimeType` template name which will result in error.
type Name* = object
type Name* = object of SerializationFormat
template mimeType*(T: type Name): string = mimeTypeName

template serializationFormat*(Name: untyped, mimeType: static string = "") =
serializationFormatImpl(Name, mimeType)

template setReader*(Format, FormatReader: distinct type) =
template setReader*(Format: type SerializationFormat, FormatReader: distinct type) =
when arity(FormatReader) > 1:
template ReaderType*(T: type Format, F: distinct type = DefaultFlavor): type = FormatReader[F]
template Reader*(T: type Format, F: distinct type = DefaultFlavor): type = FormatReader[F]
else:
template ReaderType*(T: type Format): type = FormatReader
template Reader*(T: type Format): type = FormatReader

template setWriter*(Format, FormatWriter, PreferredOutput: distinct type) =
template setWriter*(Format: type SerializationFormat, FormatWriter, PreferredOutput: distinct type) =
when arity(FormatWriter) > 1:
template WriterType*(T: type Format, F: distinct type = DefaultFlavor): type = FormatWriter[F]
template Writer*(T: type Format, F: distinct type = DefaultFlavor): type = FormatWriter[F]
Expand All @@ -34,12 +39,20 @@ template setWriter*(Format, FormatWriter, PreferredOutput: distinct type) =

template PreferredOutputType*(T: type Format): type = PreferredOutput

template createFlavor*(ModifiedFormat, FlavorName: untyped) =
type FlavorName* = object
template createFlavor*(
ModifiedFormat: type SerializationFormat,
FlavorName: untyped,
mimeTypeName: static string = ""
) =
type FlavorName* = object of SerializationFormat
template Reader*(T: type FlavorName): type = Reader(ModifiedFormat, FlavorName)
template Writer*(T: type FlavorName): type = Writer(ModifiedFormat, FlavorName)
template PreferredOutputType*(T: type FlavorName): type = PreferredOutputType(ModifiedFormat)
template mimeType*(T: type FlavorName): string = mimeType(ModifiedFormat)
template mimeType*(T: type FlavorName): string =
when mimeTypeName == "":
mimeType(ModifiedFormat)
else:
mimeTypeName

template toObjectType(T: type): untyped =
typeof(T()[])
Expand Down
9 changes: 9 additions & 0 deletions tests/otherencode.nim
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
type
Base64* = object
Base64Pad* = object
Base64Types* = Base64 | Base64Pad

func encode*(
btype: typedesc[Base64Types], inbytes: openArray[byte]
): string {.inline.} =
discard
8 changes: 6 additions & 2 deletions tests/test_reader.nim
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import
unittest2,
../serialization
../serialization,
./otherencode

{.used.}

Expand All @@ -23,7 +24,10 @@ suite "object serialization":
test "readValue":
let z = Xyz.decode("", TestObj)
check z.number == 13

var r: XyzReader
let x = r.readValue(TestObj)
check x.number == 13

# Make sure we don't encroach on other uses of "encode"
discard Base64Pad.encode(@[byte 1, 2 ,3])
Loading