Table of Contents

Creating codecs

Every codec implements ICodec<T> through two methods, encode and decode. CodecKit ships with primitives for scalar types and combinators for building up from there.

Primitives

Leaf codecs for individual values. All in Rowles.LeanCorpus.Codecs.CodecKit.Primitives.

using Rowles.LeanCorpus.Codecs.CodecKit;
using Rowles.LeanCorpus.Codecs.CodecKit.Primitives;

// Encode a single int
byte[] bytes = Codec.EncodeToArray(Int32LECodec.Instance, 42);

// Decode it back
int value = Codec.Decode<int>(Int32LECodec.Instance, bytes);

Available primitives:

Codec Wire format
Int8Codec, UInt8Codec Single byte
Int16LECodec, UInt16LECodec 2 bytes, little-endian
Int32LECodec, UInt32LECodec 4 bytes, little-endian
Int64LECodec, UInt64LECodec 8 bytes, little-endian
Float32LECodec, Float64LECodec IEEE 754, little-endian
VarInt32Codec, VarUInt32Codec Variable-length integer
VarInt64Codec, VarUInt64Codec Variable-length long
BoolCodec Single byte (0x00 / 0x01)
Utf8StringCodec [VarUInt32 length][UTF-8 bytes]
BytesOwnedCodec [VarUInt32 length][bytes] (allocates byte[])
BytesBorrowedCodec Like BytesOwnedCodec but returns a ReadOnlySequence<byte> slice
MagicCodec Fixed magic bytes, validated on decode

Building a record codec

RecordBuilder<T> composes multiple fields into a single codec:

using Rowles.LeanCorpus.Codecs.CodecKit;
using Rowles.LeanCorpus.Codecs.CodecKit.Codecs;
using Rowles.LeanCorpus.Codecs.CodecKit.Primitives;

// Define a data type
public sealed record SegmentHeader(int DocCount, long MaxDoc, string Name);

// Build a codec for it
var headerCodec = new RecordBuilder<SegmentHeader>()
    .Field("docCount", h => h.DocCount, VarInt32Codec.Instance)
    .Field("maxDoc",   h => h.MaxDoc,   VarInt64Codec.Instance)
    .Field("name",     h => h.Name,     Utf8StringCodec.Instance)
    .Build((docCount, maxDoc, name) => new SegmentHeader(docCount, maxDoc, name));

// Use it
var header = new SegmentHeader(100, 99, "seg_0");
byte[] bytes = Codec.EncodeToArray(headerCodec, header);
var decoded = Codec.Decode<SegmentHeader>(headerCodec, bytes);

Build accepts up to 16 typed parameters. For more fields use Build(Func<FieldValues, T>).

Constant fields

Fields that don't vary between instances (magic bytes, padding) use Constant:

var codec = new RecordBuilder<MyType>()
    .Constant("magic", new MagicCodec("LC"u8))
    .Field("version", t => t.Version, Int32LECodec.Instance)
    .Build(version => new MyType(version));

Constants are validated on decode but not passed to the factory.

Dependent fields

When a field's codec depends on a previously-read field:

var codec = new RecordBuilder<MyType>()
    .Field("count", t => t.Count, VarInt32Codec.Instance)
    .Field<int, string[]>("items", t => t.Items,
        count => Codec.Repeat(Utf8StringCodec.Instance, count))
    .Build((count, items) => new MyType(count, items));

Optional fields

using Rowles.LeanCorpus.Codecs.CodecKit.Internal;

var optionalString = Utf8StringCodec.Instance.Optional(BoolCodec.Instance);
// Wire format: [bool hasValue][value?]

Or with a sentinel value:

var optionalInt = Int32LECodec.Instance.Optional(sentinel: -1);
// Wire format: [int32 value]; -1 means absent

Version envelopes

Wraps a codec with [version:byte][VarInt64 bodyLen][body]. Known versions dispatch to version cases. Unknown versions pass raw bytes to a forward-compat delegate:

var envelope = Codec.VersionEnvelope<byte[], int>(
    versionCodec: UInt8Codec.Instance,
    bodyLengthCodec: VarInt64Codec.Instance,
    unknown: (version, bytes) => bytes,
    cases:
    [
        Codec.VersionCase<byte[], byte[]>(1, "v1-body", BytesOwnedCodec.Instance),
        Codec.VersionCase<byte[], byte[]>(2, "v2-body", BytesOwnedCodec.Instance),
    ]);

Adding checksums

using Rowles.LeanCorpus.Codecs.CodecKit.Checksum;

var withChecksum = innerCodec.WithChecksum(
    ChecksumAlgorithms.Crc32,
    ChecksumPlacement.Trailer);

Wire format with Trailer: [body][checksum]. With Header: [checksum][body]. Decode verifies and throws ChecksumMismatchException on mismatch.

Adding compression

var withCompression = innerCodec.WithCompression();
// Wire format: [VarUInt32 compressedLen][deflate(body)]

Compresses the encoded body with deflate. Decode decompresses and passes to the inner codec.

See also