Generated documentation of serialization formats.
A set of typeclasses, primitives, combinators, and TH utilities for documenting serialization formats in a mostly automatic fashion.
serdoc
Unified serialization with semi-automatic documentation
Introduction
SerDoc provides:
- A unified interface for serialization formats ("codecs"), in the form of a 'Serializable' typeclass.
- A mini-EDSL (
FieldInfo
) for describing serialization formats as first-class data structures, and a typeclass (HasInfo
) to link them to codecs and serializable Haskell types. - Building blocks and utility code for implementing
Codec
,Serializable
, andHasInfo
for existing or new serialization formats.
It also includes an implementation of these typeclasses for the binary
package.
Components
SerDoc is split up into two sub-projects:
serdoc-core (this library)
, which provides the typeclasses and building blocksserdoc-binary
, which provides instances forbinary
How To Use
Serializing and deserializing values
For encoding, it's straightforward - use encode
.
For decoding, you have a few options, depending on the codec you use. The most general form is decodeM
; apart from that, a family of similar functions is provided, following the conventions:
- Monadic decoders have an
M
suffix; pure decoders (where the decoding Monad isExcept err
orIdentity
) have noM
suffix (e.g.,decode
vs.decodeM
). - Flavors that ignore any remaining unconsumed input have a
_
suffix (e.g.decode_
). - Flavors that convert decoding errors to
Either
s have anEither
suffix (e.g.decodeMEither
); these require that the decoding monad isExcept err
orExceptT err m
.
Keep in mind that depending on how the codec works, the serialized data may be returned / consumed via the Encoded
type, or passed by (mutable) reference through the Context
object. The API purposefully supports both ways, because a given codec may only support one or the other.
Implementing HasInfo
and Serializable
for an existing codec
- For newtype wrappers that use the same serialization format as their wrapped payloads, the easiest way is to use
GeneralizedNewtypeDeriving
to derive instances for both typeclasses. - For newtype wrappers that should implement a different serialization format, you may need to hand-write instances; if you do this, take special care to ensure that the
HasInfo
instance matches the actual serialization. - Lists and some other data structures are supported out of the box and require no explicit instance; they are serialized using a 32-bit list length followed by the serialized list elements, in order. If you need a different representation, then newtype-wrapping may be necessary.
- For enumeration types, a generic wrapper,
ViaEnum
, is provided, which you can use in combination with theDerivingVia
extension; alternatively, you can useenumInfo
,encodeEnum
, anddecodeEnum
to write the instances yourself. String
, due to being just a type alias for[Char]
, will only serialize iff an instance forChar
exists. However, it is usually preferred to convert your strings toText
.- For record types, consider using
deriveSerDoc
(found inData.SerDoc.TH
). This Template Haskell function will generate matching instances for both typeclasses, following the convention of serializing all fields in the order they appear in the type declaration, and labelled by their Haskell field names. Obviously this will only work if instances for each of the record fields exist.
Adding your own codecs
A codec is indicated using a phantom type; no values of that type ever need to exist at runtime, we merely use it to identify the codec we want, so we can define it as a constructorless data
type (like Void
), e.g.:
data MyFantasticCodec
We then need a Codec
instance, which is where we define:
- A type for a context passed to each invocation of
encode
anddecodeM
; this can be anything you want, depending on the needs of your codec. If the codec does not require any context, use()
. - A monadic data type used for encoding,
MonadEncode
. For pure codecs, this can beIdentity
; if you serialize directly to something like a file handle or network socket, it will typically have to beIO
, or someMonadIO
. - A monadic data type used for decoding,
MonadDecode
. Since decoding can fail, this will typically involve not just the required effects for the decoding process itself, but also some form of error handling. It is recommended to useExcept err
for pure codecs, andExceptT err IO
(orMonadIO m => ExceptT err m
) for codecs that require IO, whereerr
is an appropriate error data structure for your codec. - The default encoding to use for enum types (optional, defaults to
Word16
).
Providing instances for a reasonable set of primitive values and data structures is highly recommended; a minimum viable set might be:
()
Bool
Int
,Int8
,Int16
,Int32
,Int64
Word
,Word8
,Word16
,Word32
,Word64
- Whatever type you picked for the default enum encoding
[a]
Maybe a
Either a b
- Tuples up to 7 elements
ByteString
.