senax-encoder 0.1.0

A fast, compact, and schema-evolution-friendly binary serialization library for Rust.
Documentation
senax-encoder-0.1.0 has been yanked.

senax-encoder

A fast, compact, and schema-evolution-friendly binary serialization library for Rust.

  • Supports struct/enum encoding with field/variant IDs for forward/backward compatibility
  • Efficient encoding for primitives, collections, Option, String, bytes, and popular crates (chrono, uuid, ulid, rust_decimal, indexmap)
  • Custom derive macros for ergonomic usage
  • Feature-gated support for optional dependencies

Features

  • Compact, efficient encoding for a wide range of types (primitives, collections, Option, String, bytes, chrono, uuid, ulid, rust_decimal, indexmap)
  • Schema evolution and version compatibility via field/variant IDs and tag-based format
  • Attribute macros for fine-grained control (custom IDs, default values, skip encode/decode, renaming, compact ID encoding)
  • Feature flags for optional support of popular crates
  • Suitable for network protocols, storage, and applications requiring forward/backward compatibility

Attribute Macros

You can control encoding/decoding behavior using the following attributes:

  • #[senax(id = N)] — Assigns a custom field or variant ID (u32 or u8, see below). Ensures stable wire format across versions.
  • #[senax(default)] — If a field is missing during decoding, its value is set to Default::default() instead of causing an error.
  • #[senax(skip_encode)] — This field is not written during encoding.
  • #[senax(skip_decode)] — This field is ignored during decoding and always set to Default::default(). It is still encoded if present.
  • #[senax(rename = "name")] — Use the given string as the logical field/variant name for ID calculation. Useful for renaming fields/variants while keeping the same wire format.
  • #[senax(u8)] — On structs/enums, encodes field/variant IDs as u8 instead of u32 (for compactness, up to 255 IDs; 0 is reserved for terminator).

Feature Flags

The following optional features enable support for popular crates and types:

  • all — Enables all optional features below at once: indexmap, chrono, rust_decimal, uuid, ulid.
  • chrono — Enables encoding/decoding of chrono::DateTime, NaiveDate, and NaiveTime types.
  • uuid — Enables encoding/decoding of uuid::Uuid.
  • ulid — Enables encoding/decoding of ulid::Ulid (shares the same tag as UUID for binary compatibility).
  • rust_decimal — Enables encoding/decoding of rust_decimal::Decimal.
  • indexmap — Enables encoding/decoding of IndexMap and IndexSet collections.

Example

use senax_encoder::{Encoder, Decoder, Encode, Decode};
use bytes::BytesMut;

#[derive(Encode, Decode, PartialEq, Debug)]
struct MyStruct {
    id: u32,
    name: String,
}

let value = MyStruct { id: 42, name: "hello".to_string() };
let mut buf = BytesMut::new();
value.encode(&mut buf).unwrap();
let decoded = MyStruct::decode(&mut buf.freeze()).unwrap();
assert_eq!(value, decoded);

Quick Start

Add to your Cargo.toml:

[dependencies]
senax-encoder = "0.1"

Basic usage:

use senax_encoder::{Encoder, Decoder, Encode, Decode};
use bytes::{BytesMut, Bytes};

#[derive(Encode, Decode, Debug, PartialEq)]
struct User {
    id: u32,
    name: String,
    email: Option<String>,
}

let user = User { id: 42, name: "Alice".into(), email: Some("alice@example.com".into()) };
let mut buf = BytesMut::new();
user.encode(&mut buf).unwrap();
let mut bytes = buf.freeze();
let decoded = User::decode(&mut bytes).unwrap();
assert_eq!(user, decoded);

Usage

1. Derive macros for automatic implementation

#[derive(Encode, Decode)]
struct MyStruct {
    #[senax(id=1)]
    foo: u32,
    bar: Option<String>,
}

2. Binary encode/decode

let mut buf = BytesMut::new();
value.encode(&mut buf)?;
let mut bytes = buf.freeze();
let value2 = MyStruct::decode(&mut bytes)?;

3. Schema evolution (adding/removing/changing fields)

  • Field IDs are automatically generated from field names (CRC32) by default.
    • Use #[senax(id=...)] only if you need to resolve a collision.
  • Because mapping is by field ID (u32):
    • Old struct → new struct:
      • New fields of type Option become None if missing.
      • New required fields without default will cause a decode error if missing.
    • New struct → old struct: unknown fields are automatically skipped.
  • No field names are stored, only u32 IDs, so field addition/removal/reordering/type changes are robust.

4. Feature flags

  • Enable only the types you need: indexmap, chrono, rust_decimal, uuid, ulid, etc.
  • Minimizes dependencies and build time.

Supported Types

  • Primitives: u8~u128, i8~i128, f32, f64, bool, String, Bytes
  • Option, Vec, arrays, HashMap, BTreeMap, Set, Tuple, Enum, Struct
  • chrono: DateTime<Utc/Local>, NaiveDate, NaiveTime
  • rust_decimal: Decimal
  • uuid: Uuid
  • ulid: Ulid
  • indexmap: IndexMap, IndexSet

Binary Format (Overview)

  • Each value is tagged binary (u8 tag + data)
  • Structs/enums use ID-based fields for robustness to order, presence, and type changes
  • See specification.md for full details

Contribution & Bug Reports

Pull requests and issues are welcome!

Related Projects

  • serde: General-purpose serialization
  • prost: Protocol Buffers
  • bincode: Fast binary serialization

senax-encoder is a Rust encoder designed for robust, safe binary exchange even across versions. Feedback and contributions are highly appreciated!