autocodec 0.1.0

Derive macro for automatic binary protocol serialization and deserialization
Documentation
  • Coverage
  • 67.65%
    23 out of 34 items documented4 out of 12 items with examples
  • Size
  • Source code size: 63.1 kB This is the summed size of all the files inside the crates.io package for this release.
  • Documentation size: 908.58 kB This is the summed size of all files generated by rustdoc for all configured targets
  • Ø build duration
  • this release: 17s Average build duration of successful builds.
  • all releases: 17s Average build duration of successful builds in releases after 2024-10-23.
  • Links
  • MichaelDuPlessis/autocodec
    0 0 0
  • crates.io
  • Dependencies
  • Versions
  • Owners
  • MichaelDuPlessis

autocodec

A Rust derive macro for automatic binary protocol serialization and deserialization. Zero runtime dependencies.

Features

  • #[derive(Codec)] on structs and enums — generates encode/decode automatically
  • Big-endian (network byte order) by default, per-field or container-level little-endian override
  • Configurable length prefixes (u8, u16, u32, u64)
  • Custom enum discriminants via #[repr(u8)] + native = N syntax
  • Bitfield packing with #[codec(bits = N)]
  • Field validation, padding, magic constants, skip with custom defaults
  • Custom codec delegation via #[codec(with = "module")]
  • Zero-copy parsing with Bytes<'a>
  • Allocation guards against malicious inputs (16 MiB limit)
  • Computed encoded_size() without allocation
  • Error context with field names for debugging
  • Composable — any Codec type works as a field

Quick Start

use autocodec::{Codec, CodecError};

#[derive(Debug, PartialEq, Codec)]
struct Header {
    version: u16,
    length: u32,
}

#[derive(Debug, PartialEq, Codec)]
enum Message {
    Ping,
    Data { id: u32, payload: Vec<u8> },
    Ack(u64),
}

// Encode
let header = Header { version: 1, length: 128 };
let mut buf = Vec::new();
header.encode(&mut buf);

// Decode
let (decoded, remaining) = Header::decode(&buf).unwrap();
assert_eq!(decoded, header);

// Decode exact (errors on trailing bytes)
let decoded = Header::decode_exact(&buf).unwrap();

// Pre-calculate size without encoding
assert_eq!(header.encoded_size(), 6);

Supported Types

Type Wire format
u8u64, u128 N bytes, big-endian
i8i64, i128 N bytes, big-endian
f32, f64 IEEE 754, big-endian
bool 1 byte
String u32 length + UTF-8
Vec<T> u32 length + elements
Option<T> u8 tag + value
[T; N] N elements (no prefix)
Box<T> transparent
Box<[T]> u32 length + elements (like Vec)
(A, B, ...) sequential (up to 8)
HashMap<K, V> u32 length + pairs
Bytes<'a> u32 length + bytes (zero-copy)

Attributes

Container-level

#[derive(Codec)]
#[codec(endian = "little")]              // default endianness for all fields
#[codec(discriminant_type = "u16")]      // enum discriminant size

Enum discriminants

Use Rust's native #[repr] and = N syntax:

#[derive(Codec)]
#[repr(u8)]
enum Command {
    Ping = 1,
    Pong = 2,
    Data = 10,
}

Or use #[codec(discriminant = N)] on individual variants.

Field-level

#[codec(endian = "little")]              // per-field endianness
#[codec(len = "u8")]                     // length prefix type for Vec/String
#[codec(min_len = 1)]                    // minimum length
#[codec(max_len = 256)]                  // maximum length
#[codec(skip)]                           // not on wire, uses Default
#[codec(skip, default = "42")]           // not on wire, custom default
#[codec(padding = 3)]                    // N zero bytes after field
#[codec(magic = 0xDEADBEEF)]             // constant value, error on mismatch
#[codec(validate = "is_valid")]          // fn(&T) -> bool, error if false
#[codec(with = "my_module")]             // custom encode/decode functions
#[codec(bits = 4)]                       // bitfield: pack into N bits

Bitfields

Consecutive fields annotated with #[codec(bits = N)] are packed into the minimum number of bytes (MSB-first):

#[derive(Codec)]
struct TcpFlags {
    #[codec(bits = 4)]
    version: u8,
    #[codec(bits = 4)]
    header_len: u8,
    #[codec(bits = 1)]
    syn: u8,
    #[codec(bits = 1)]
    ack: u8,
    #[codec(bits = 6)]
    reserved: u8,
    // Total: 16 bits = 2 bytes on the wire
}

Zero-Copy Parsing

For read-only access to byte slices without allocation:

use autocodec::Bytes;

let data = [0, 0, 0, 3, 0xAA, 0xBB, 0xCC];
let (bytes, rest) = Bytes::decode(&data).unwrap();
assert_eq!(&*bytes, &[0xAA, 0xBB, 0xCC]);
// `bytes` borrows directly from `data` — no copy

Error Handling

Errors include field context for debugging:

// "in field `version`: not enough bytes: needed 2, have 1"

Error types:

  • NotEnoughBytes — input too short
  • InvalidUtf8 — string not valid UTF-8
  • UnknownDiscriminant — enum variant not recognized
  • TooShort / TooLong — length constraint violated
  • BadMagic — magic constant mismatch
  • ValidationFailed — custom validation returned false
  • TrailingBytes — unexpected data after decode_exact
  • AllocationTooLarge — length prefix exceeds 16 MiB safety limit
  • FieldError — wraps any of the above with the field name

About

This library was entirely AI-generated (by Kiro) under my supervision and direction. I reviewed all the code, so any questionable decisions are mine — not the AI's.

License

MIT