Make the flavors support optional when defining formats
Other changes: * The format declarations have been broken down in multiple parts. This makes it possible to structure the implementation libraries in a way such that the format name is accessible even when importing only reader or writer modules. * Flavors lead to more complicated Reader and Writer types. Instead of requiring the user to write down long type names such as `JsonReader[DefaultFlavor]`, it's now possible to refer to the reader and writer type of each format using expressions such as `Json.Reader` and `Json.Writer`. * Fixed a typo (PreferredOutput)
This commit is contained in:
parent
84ffa54554
commit
f9a1121b87
|
@ -33,11 +33,11 @@ serializationFormat Json, # This is the name of the for
|
|||
Reader = JsonReader, # The associated Reader type.
|
||||
Writer = JsonWriter, # The associated Writer type.
|
||||
|
||||
PreferedOutput = string, # APIs such as `Json.encode` will return this type.
|
||||
PreferredOutput = string, # APIs such as `Json.encode` will return this type.
|
||||
# The type must support the following operations:
|
||||
# proc initWithCapacity(_: type T, n: int)
|
||||
# proc add(v: var T, bytes: openarray[byte])
|
||||
# By default, the PreferedOutput is `seq[byte]`.
|
||||
# By default, the PreferredOutput is `seq[byte]`.
|
||||
|
||||
mimeType = "application/json", # Mime type associated with the format (Optional).
|
||||
fileExt = "json" # File extension associated with the format (Optional).
|
||||
|
@ -48,7 +48,7 @@ serializationFormat Json, # This is the name of the for
|
|||
Most of the time, you'll be using the following high-level APIs when encoding
|
||||
and decoding values:
|
||||
|
||||
#### `Format.encode(value: auto, params: varargs): Format.PreferedOutput`
|
||||
#### `Format.encode(value: auto, params: varargs): Format.PreferredOutput`
|
||||
|
||||
Encodes a value in the specified format returning the preferred output type
|
||||
for the format (usually `string` or `seq[byte]`). All extra params will be
|
||||
|
|
|
@ -1,38 +1,13 @@
|
|||
import
|
||||
typetraits,
|
||||
stew/shims/macros, faststreams,
|
||||
serialization/[object_serialization, errors]
|
||||
serialization/[object_serialization, errors, formats]
|
||||
|
||||
export
|
||||
faststreams, object_serialization, errors
|
||||
|
||||
template serializationFormatImpl(Name: untyped,
|
||||
Reader, Writer, PreferedOutput: distinct type,
|
||||
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
|
||||
template ReaderType*(T: type Name, F: distinct type = DefaultFlavor): type = Reader[F]
|
||||
template WriterType*(T: type Name, F: distinct type = DefaultFlavor): type = Writer[F]
|
||||
template PreferedOutputType*(T: type Name): type = PreferedOutput
|
||||
template mimeType*(T: type Name): string = mimeTypeName
|
||||
|
||||
template serializationFormat*(Name: untyped,
|
||||
Reader, Writer, PreferedOutput: distinct type,
|
||||
mimeType: static string = "") =
|
||||
serializationFormatImpl(Name, Reader, Writer, PreferedOutput, mimeType)
|
||||
|
||||
template createFlavor*(ModifiedFormat, FlavorName: untyped) =
|
||||
type FlavorName* = object
|
||||
template ReaderType*(T: type FlavorName): type = ReaderType(ModifiedFormat, FlavorName)
|
||||
template WriterType*(T: type FlavorName): type = WriterType(ModifiedFormat, FlavorName)
|
||||
template PreferedOutputType*(T: type FlavorName): type = PreferedOutputType(ModifiedFormat)
|
||||
template mimeType*(T: type FlavorName): string = mimeType(ModifiedFormat)
|
||||
faststreams, object_serialization, errors, formats
|
||||
|
||||
template encode*(Format: type, value: auto, params: varargs[untyped]): auto =
|
||||
mixin init, WriterType, writeValue, PreferedOutputType
|
||||
mixin init, Writer, writeValue, PreferredOutputType
|
||||
{.noSideEffect.}:
|
||||
# We assume that there is no side-effects here, because we are
|
||||
# using a `memoryOutput`. The computed side-effects are coming
|
||||
|
@ -40,10 +15,10 @@ template encode*(Format: type, value: auto, params: varargs[untyped]): auto =
|
|||
# faststreams may be writing to a file or a network device.
|
||||
try:
|
||||
var s = memoryOutput()
|
||||
type Writer = WriterType(Format)
|
||||
var writer = unpackArgs(init, [Writer, s, params])
|
||||
type WriterType = Writer(Format)
|
||||
var writer = unpackArgs(init, [WriterType, s, params])
|
||||
writeValue writer, value
|
||||
s.getOutput PreferedOutputType(Format)
|
||||
s.getOutput PreferredOutputType(Format)
|
||||
except IOError:
|
||||
raise (ref Defect)() # a memoryOutput cannot have an IOError
|
||||
|
||||
|
@ -60,7 +35,7 @@ template decode*(Format: 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, ReaderType
|
||||
mixin init, Reader
|
||||
{.noSideEffect.}:
|
||||
# We assume that there are no side-effects here, because we are
|
||||
# using a `memoryInput`. The computed side-effects are coming
|
||||
|
@ -68,7 +43,8 @@ template decode*(Format: distinct type,
|
|||
# faststreams may be reading from a file or a network device.
|
||||
try:
|
||||
var stream = unsafeMemoryInput(input)
|
||||
var reader = unpackArgs(init, [ReaderType(Format), stream, params])
|
||||
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
|
||||
|
@ -79,7 +55,7 @@ template decode*(Format: 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, ReaderType
|
||||
mixin init, Reader
|
||||
{.noSideEffect.}:
|
||||
# We assume that there are no side-effects here, because we are
|
||||
# using a `memoryInput`. The computed side-effects are coming
|
||||
|
@ -87,7 +63,8 @@ template decode*(Format: distinct type,
|
|||
# faststreams may be reading from a file or a network device.
|
||||
try:
|
||||
var stream = unsafeMemoryInput(input)
|
||||
var reader = unpackArgs(init, [ReaderType(Format), stream, params])
|
||||
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
|
||||
|
@ -96,11 +73,12 @@ template loadFile*(Format: distinct type,
|
|||
filename: string,
|
||||
RecordType: distinct type,
|
||||
params: varargs[untyped]): auto =
|
||||
mixin init, ReaderType, readValue
|
||||
mixin init, Reader, readValue
|
||||
|
||||
var stream = memFileInput(filename)
|
||||
try:
|
||||
var reader = unpackArgs(init, [ReaderType(Format), stream, params])
|
||||
type ReaderType = Reader(Format)
|
||||
var reader = unpackArgs(init, [ReaderType, stream, params])
|
||||
reader.readValue(RecordType)
|
||||
finally:
|
||||
close stream
|
||||
|
@ -112,12 +90,12 @@ template loadFile*[RecordType](Format: type,
|
|||
record = loadFile(Format, filename, RecordType, params)
|
||||
|
||||
template saveFile*(Format: type, filename: string, value: auto, params: varargs[untyped]) =
|
||||
mixin init, WriterType, writeValue
|
||||
mixin init, Writer, writeValue
|
||||
|
||||
var stream = fileOutput(filename)
|
||||
try:
|
||||
type Writer = WriterType(Format)
|
||||
var writer = unpackArgs(init, [Writer, stream, params])
|
||||
type WriterType = Writer(Format)
|
||||
var writer = unpackArgs(init, [WriterType, stream, params])
|
||||
writer.writeValue(value)
|
||||
finally:
|
||||
close stream
|
||||
|
@ -146,16 +124,16 @@ template borrowSerialization*(Alias: distinct type,
|
|||
|
||||
template serializesAsBase*(SerializedType: distinct type,
|
||||
Format: distinct type) =
|
||||
mixin ReaderType, WriterType
|
||||
mixin Reader, Writer
|
||||
|
||||
type Reader = ReaderType(Format)
|
||||
type Writer = WriterType(Format)
|
||||
type ReaderType = Reader(Format)
|
||||
type WriterType = Writer(Format)
|
||||
|
||||
template writeValue*(writer: var Writer, value: SerializedType) =
|
||||
template writeValue*(writer: var WriterType, value: SerializedType) =
|
||||
mixin writeValue
|
||||
writeValue(writer, distinctBase value)
|
||||
|
||||
template readValue*(reader: var Reader, value: var SerializedType) =
|
||||
template readValue*(reader: var ReaderType, value: var SerializedType) =
|
||||
mixin readValue
|
||||
value = SerializedType reader.readValue(distinctBase SerializedType)
|
||||
|
||||
|
@ -169,16 +147,17 @@ template readValue*(stream: InputStream,
|
|||
Format: type,
|
||||
ValueType: type,
|
||||
params: varargs[untyped]): untyped =
|
||||
mixin ReaderType, init, readValue
|
||||
var reader = unpackArgs(init, [ReaderType(Format), stream, params])
|
||||
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]) =
|
||||
mixin WriterType, init, writeValue
|
||||
type Writer = WriterType(Format)
|
||||
var writer = unpackArgs(init, [Writer, stream])
|
||||
mixin Writer, init, writeValue
|
||||
type WriterType = Writer(Format)
|
||||
var writer = unpackArgs(init, [WriterType, stream, params])
|
||||
writeValue writer, value
|
||||
|
||||
|
|
|
@ -0,0 +1,40 @@
|
|||
import
|
||||
std/typetraits
|
||||
|
||||
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
|
||||
template mimeType*(T: type Name): string = mimeTypeName
|
||||
|
||||
template serializationFormat*(Name: untyped, mimeType: static string = "") =
|
||||
serializationFormatImpl(Name, mimeType)
|
||||
|
||||
template setReader*(Format, 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) =
|
||||
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]
|
||||
else:
|
||||
template WriterType*(T: type Format): type = FormatWriter
|
||||
template Writer*(T: type Format): type = FormatWriter
|
||||
|
||||
template PreferredOutputType*(T: type Format): type = PreferredOutput
|
||||
|
||||
template createFlavor*(ModifiedFormat, FlavorName: untyped) =
|
||||
type FlavorName* = object
|
||||
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)
|
||||
|
|
@ -191,12 +191,12 @@ template writeFieldIMPL*[Writer](writer: var Writer,
|
|||
mixin writeValue
|
||||
writer.writeValue(fieldVal)
|
||||
|
||||
proc makeFieldReadersTable(RecordType, Reader: distinct type):
|
||||
seq[FieldReader[RecordType, Reader]] =
|
||||
proc makeFieldReadersTable(RecordType, ReaderType: distinct type):
|
||||
seq[FieldReader[RecordType, ReaderType]] =
|
||||
mixin enumAllSerializedFields, readFieldIMPL, handleReadException
|
||||
|
||||
enumAllSerializedFields(RecordType):
|
||||
proc readField(obj: var RecordType, reader: var Reader)
|
||||
proc readField(obj: var RecordType, reader: var ReaderType)
|
||||
{.gcsafe, nimcall, raises: [SerializationError, Defect].} =
|
||||
when RecordType is tuple:
|
||||
const i = fieldName.parseInt
|
||||
|
@ -222,17 +222,17 @@ proc makeFieldReadersTable(RecordType, Reader: distinct type):
|
|||
|
||||
result.add((fieldName, readField))
|
||||
|
||||
proc fieldReadersTable*(RecordType, Reader: distinct type):
|
||||
ptr seq[FieldReader[RecordType, Reader]] =
|
||||
proc fieldReadersTable*(RecordType, ReaderType: distinct type):
|
||||
ptr seq[FieldReader[RecordType, ReaderType]] =
|
||||
mixin readValue
|
||||
|
||||
# careful: https://github.com/nim-lang/Nim/issues/17085
|
||||
# TODO why is this even here? one could just return the function pointer
|
||||
# to the field reader directly instead of going through this seq etc
|
||||
var tbl {.threadvar.}: ref seq[FieldReader[RecordType, Reader]]
|
||||
var tbl {.threadvar.}: ref seq[FieldReader[RecordType, ReaderType]]
|
||||
if tbl == nil:
|
||||
tbl = new typeof(tbl)
|
||||
tbl[] = makeFieldReadersTable(RecordType, Reader)
|
||||
tbl[] = makeFieldReadersTable(RecordType, ReaderType)
|
||||
return addr(tbl[])
|
||||
|
||||
proc findFieldReader*(fieldsTable: FieldReadersTable,
|
||||
|
@ -342,16 +342,16 @@ proc genCustomSerializationForField(Format, field,
|
|||
|
||||
if readBody != nil:
|
||||
result.add quote do:
|
||||
type Reader = ReaderType(`Format`)
|
||||
type ReaderType = Reader(`Format`)
|
||||
proc readFieldIMPL*(F: type FieldTag[`RecordType`, `fieldName`, auto],
|
||||
`readerSym`: var Reader): `FieldType`
|
||||
`readerSym`: var ReaderType): `FieldType`
|
||||
{.raises: [IOError, SerializationError, Defect].} =
|
||||
`readBody`
|
||||
|
||||
if writeBody != nil:
|
||||
result.add quote do:
|
||||
type Writer = WriterType(`Format`)
|
||||
proc writeFieldIMPL*(`writerSym`: var Writer,
|
||||
type WriterType = Writer(`Format`)
|
||||
proc writeFieldIMPL*(`writerSym`: var WriterType,
|
||||
F: type FieldTag[`RecordType`, `fieldName`, auto],
|
||||
`valueSym`: auto,
|
||||
`holderSym`: `RecordType`)
|
||||
|
@ -364,15 +364,15 @@ proc genCustomSerializationForType(Format, typ: NimNode,
|
|||
|
||||
if readBody != nil:
|
||||
result.add quote do:
|
||||
type Reader = ReaderType(`Format`)
|
||||
proc readValue*(`readerSym`: var Reader, T: type `typ`): `typ`
|
||||
type ReaderType = Reader(`Format`)
|
||||
proc readValue*(`readerSym`: var ReaderType, T: type `typ`): `typ`
|
||||
{.raises: [IOError, SerializationError, Defect].} =
|
||||
`readBody`
|
||||
|
||||
if writeBody != nil:
|
||||
result.add quote do:
|
||||
type Writer = WriterType(`Format`)
|
||||
proc writeValue*(`writerSym`: var Writer, `valueSym`: `typ`)
|
||||
type WriterType = Writer(`Format`)
|
||||
proc writeValue*(`writerSym`: var WriterType, `valueSym`: `typ`)
|
||||
{.raises: [IOError, SerializationError, Defect].} =
|
||||
`writeBody`
|
||||
|
||||
|
|
|
@ -317,14 +317,14 @@ proc executeRoundtripTests*(Format: type) =
|
|||
roundtrip namedT
|
||||
|
||||
proc executeReaderWriterTests*(Format: type) =
|
||||
mixin init, ReaderType, WriterType
|
||||
mixin init, Reader, Writer
|
||||
|
||||
type
|
||||
Reader = ReaderType Format
|
||||
ReaderType = Reader Format
|
||||
|
||||
suite(typetraits.name(Format) & " read/write tests"):
|
||||
test "Low-level field reader test":
|
||||
let barFields = fieldReadersTable(Bar, Reader)
|
||||
let barFields = fieldReadersTable(Bar, ReaderType)
|
||||
var idx = 0
|
||||
|
||||
var fieldReader = findFieldReader(barFields[], "b", idx)
|
||||
|
@ -336,7 +336,7 @@ proc executeReaderWriterTests*(Format: type) =
|
|||
|
||||
var bytes = Format.encode("test")
|
||||
var stream = unsafeMemoryInput(bytes)
|
||||
var reader = Reader.init(stream)
|
||||
var reader = ReaderType.init(stream)
|
||||
|
||||
var bar: Bar
|
||||
fieldReader(bar, reader)
|
||||
|
@ -345,7 +345,7 @@ proc executeReaderWriterTests*(Format: type) =
|
|||
|
||||
test "Ignored fields should not be included in the field readers table":
|
||||
var pos = 0
|
||||
let bazFields = fieldReadersTable(Baz, Reader)
|
||||
let bazFields = fieldReadersTable(Baz, ReaderType)
|
||||
check:
|
||||
len(bazFields[]) == 2
|
||||
findFieldReader(bazFields[], "f", pos) != nil
|
||||
|
|
Loading…
Reference in New Issue