| Crates.io | senax-encoder |
| lib.rs | senax-encoder |
| version | 0.2.2 |
| created_at | 2025-05-24 06:36:44.59129+00 |
| updated_at | 2026-01-07 02:48:51.863681+00 |
| description | A fast, compact, and schema-evolution-friendly binary serialization library for Rust. |
| homepage | |
| repository | https://github.com/yossyX/senax-encoder |
| max_upload_size | |
| id | 1687059 |
| size | 403,683 |
A fast, compact, and schema-evolution-friendly binary serialization library for Rust.
You can control encoding/decoding behavior using the following attributes:
#[senax(disable_encode)] — Generates stub implementations (unimplemented!) for Encode and Decode traits. Useful for improving build efficiency during development when you're not yet ready to fully implement serialization.#[senax(disable_pack)] — Generates stub implementations (unimplemented!) for Pack and Unpack traits. Can be combined with disable_encode.#[senax(id = N)] — Assigns a custom field or variant ID (u64). 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. For Option<T>, this means None.#[senax(skip_encode)] — This field is not written during encoding. On decode, it is set to Default::default().#[senax(skip_decode)] — This field is ignored during decoding and always set to Default::default(). It is still encoded if present.#[senax(skip_default)] — This field is not written during encoding if its value equals the default value. On decode, missing fields are set to Default::default().#[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.// Disable Encode/Decode during development
#[derive(Encode, Decode)]
#[senax(disable_encode)]
struct WorkInProgress {
#[senax(id=1)]
field1: i32,
}
// Disable Pack/Unpack only
#[derive(Pack, Unpack)]
#[senax(disable_pack)]
struct AnotherStruct {
field1: i32,
}
// Disable both
#[derive(Encode, Decode, Pack, Unpack)]
#[senax(disable_encode, disable_pack)]
struct AllDisabled {
field1: i32,
}
The following optional features enable support for popular crates and types:
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.bigdecimal — Enables encoding/decoding of bigdecimal::BigDecimal (stored as scientific notation string).indexmap — Enables encoding/decoding of IndexMap and IndexSet collections.fxhash — Enables encoding/decoding of fxhash::FxHashMap and fxhash::FxHashSet (fast hash collections).ahash — Enables encoding/decoding of ahash::AHashMap and ahash::AHashSet (high-performance hash collections).smol_str — Enables encoding/decoding of smol_str::SmolStr (small string optimization).serde_json — Enables encoding/decoding of serde_json::Value for dynamic JSON data.Add to your Cargo.toml:
[dependencies]
senax-encoder = "0.1"
Basic usage:
use senax_encoder::{Encode, Decode};
#[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()) };
// Schema evolution support (with field IDs)
let mut bytes = senax_encoder::encode(&user).unwrap();
let decoded: User = senax_encoder::decode(&mut bytes).unwrap();
assert_eq!(user, decoded);
// Compact encoding (without field IDs, smaller size)
let mut packed = senax_encoder::pack(&user).unwrap();
let unpacked: User = senax_encoder::unpack(&mut packed).unwrap();
assert_eq!(user, unpacked);
#[derive(Encode, Decode)]
struct MyStruct {
#[senax(id=1)]
foo: u32,
bar: Option<String>,
}
let mut bytes = senax_encoder::encode(&value)?;
let value2: MyStruct = senax_encoder::decode(&mut bytes)?;
// Pack for maximum compactness (no field IDs, smaller size)
let mut bytes = senax_encoder::pack(&value)?;
let value2: MyStruct = senax_encoder::unpack(&mut bytes)?;
// Note: pack/unpack is field-order dependent and doesn't support schema evolution
// Use when you need maximum performance and size optimization
#[senax(id=...)] only if you need to resolve a collision.Option become None if missing.default will cause a decode error if missing.indexmap, chrono, rust_decimal, uuid, ulid, serde_json, etc.u8~u128, i8~i128, f32, f64, bool, String, Bytes (zero-copy binary data)When respective features are enabled:
DateTime<Utc>, DateTime<Local>, NaiveDate, NaiveTimeUuidUlidDecimalBigDecimal (stored as scientific notation string)IndexMap, IndexSetFxHashMap, FxHashSet (fast hash collections)AHashMap, AHashSet (high-performance hash collections)SmolStr (small string optimization)Value (dynamic JSON data)The senax-encoder supports automatic type conversion for compatible types during decoding, enabling schema evolution. However, certain conversions are not supported due to precision or data loss concerns.
u16 → u32)f64 can be decoded as f32 (with potential precision loss)f32 ↔ f64 (bidirectional since v0.2.2 with string format)i128) can be decoded as f32 or f64rust_decimal::Decimal or bigdecimal::BigDecimalT can be decoded as Option<T>Option<T> cannot be automatically decoded as T (use explicit handling)Example of compatible schema evolution:
// Version 1
#[derive(Encode, Decode)]
struct User {
id: u32, // Will be compatible with u64 in v2
name: String,
}
// Version 2 - Compatible changes
#[derive(Encode, Decode)]
struct User {
id: u64, // ✅ u32 → u64 automatic conversion
name: String,
email: Option<String>, // ✅ New optional field
#[senax(default)]
age: u32, // ✅ New field with default
}
When implementing custom Encoder and Decoder traits for your types, follow these important guidelines to ensure proper binary format consistency:
encode() call that writes all necessary data atomically.// ❌ WRONG: Multiple separate encode calls
impl Encoder for MyType {
fn encode(&self, writer: &mut BytesMut) -> Result<()> {
self.field1.encode(writer)?; // First encode call
self.field2.encode(writer)?; // Second encode call - WRONG!
Ok(())
}
}
// ✅ CORRECT: Single encode call with tuple
impl Encoder for MyType {
fn encode(&self, writer: &mut BytesMut) -> Result<()> {
(self.field1, self.field2).encode(writer) // Single encode call
}
}
use senax_encoder::{Encoder, Decoder, EncoderError};
use bytes::{BytesMut, Bytes};
struct Point3D {
x: f64,
y: f64,
z: f64,
}
impl Encoder for Point3D {
fn encode(&self, writer: &mut BytesMut) -> senax_encoder::Result<()> {
// ✅ Encode as single tuple
(self.x, self.y, self.z).encode(writer)
}
fn is_default(&self) -> bool {
self.x == 0.0 && self.y == 0.0 && self.z == 0.0
}
}
impl Decoder for Point3D {
fn decode(reader: &mut Bytes) -> senax_encoder::Result<Self> {
// ✅ Decode the same tuple structure
let (x, y, z) = <(f64, f64, f64)>::decode(reader)?;
Ok(Point3D { x, y, z })
}
}
struct CustomFormat {
header: String,
data: Vec<u8>,
checksum: u32,
}
impl Encoder for CustomFormat {
fn encode(&self, writer: &mut BytesMut) -> senax_encoder::Result<()> {
// ✅ Group all fields into a single tuple
(
&self.header,
&self.data,
self.checksum
).encode(writer)
}
fn is_default(&self) -> bool {
self.header.is_empty() && self.data.is_empty() && self.checksum == 0
}
}
impl Decoder for CustomFormat {
fn decode(reader: &mut Bytes) -> senax_encoder::Result<Self> {
// ✅ Decode the same tuple structure
let (header, data, checksum) = <(String, Vec<u8>, u32)>::decode(reader)?;
Ok(CustomFormat { header, data, checksum })
}
}
Format consistency: Each value gets exactly one tag in the binary format
Schema evolution: The library can properly skip unknown fields during forward/backward compatibility
Note: For most use cases, prefer using #[derive(Encode, Decode)] which automatically follows these best practices.
Licensed under either of
at your option.
Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions.