senax_encoder/
lib.rs

1//! # senax-encoder
2//!
3//! A fast, compact, and schema-evolution-friendly binary serialization library for Rust.
4//!
5//! - Supports struct/enum encoding with field/variant IDs for forward/backward compatibility
6//! - Efficient encoding for primitives, collections, Option, String, bytes, and popular crates (chrono, uuid, ulid, rust_decimal, indexmap, fxhash, ahash, smol_str, serde_json)
7//! - Custom derive macros for ergonomic usage
8//! - Feature-gated support for optional dependencies
9//!
10//! ## Binary Format
11//!
12//! This library uses magic numbers to distinguish between different serialization formats:
13//! - **Encode format**: Starts with magic number `0xA55A` (2 bytes, little-endian)
14//!   - Used by `encode()` and `encode_to()` functions
15//!   - Supports schema evolution with field IDs and type tags
16//! - **Pack format**: Starts with magic number `0xDADA` (2 bytes, little-endian)
17//!   - Used by `pack()` and `pack_to()` functions
18//!   - Compact format without schema evolution support
19//!
20//! Magic numbers are only added when using the convenience functions in this library.
21//! Direct trait method calls (`Encoder::encode`, `Packer::pack`) do not include magic numbers.
22//!
23//! ## Attribute Macros
24//!
25//! You can control encoding/decoding behavior using the following attributes:
26//!
27//! - `#[senax(id = N)]` — Assigns a custom field or variant ID (u64). Ensures stable wire format across versions.
28//! - `#[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`.
29//! - `#[senax(skip_encode)]` — This field is not written during encoding. On decode, it is set to `Default::default()`.
30//! - `#[senax(skip_decode)]` — This field is ignored during decoding and always set to `Default::default()`. It is still encoded if present.
31//! - `#[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()`.
32//! - `#[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.
33//!
34//! ## Feature Flags
35//!
36//! The following optional features enable support for popular crates and types:
37//!
38//! ### External Crate Support
39//! - `chrono` — Enables encoding/decoding of `chrono::DateTime`, `NaiveDate`, and `NaiveTime` types.
40//! - `uuid` — Enables encoding/decoding of `uuid::Uuid`.
41//! - `ulid` — Enables encoding/decoding of `ulid::Ulid` (shares the same tag as UUID for binary compatibility).
42//! - `rust_decimal` — Enables encoding/decoding of `rust_decimal::Decimal`.
43//! - `indexmap` — Enables encoding/decoding of `IndexMap` and `IndexSet` collections.
44//! - `fxhash` — Enables encoding/decoding of `fxhash::FxHashMap` and `fxhash::FxHashSet` (fast hash collections).
45//! - `ahash` — Enables encoding/decoding of `ahash::AHashMap` and `ahash::AHashSet` (high-performance hash collections).
46//! - `smol_str` — Enables encoding/decoding of `smol_str::SmolStr` (small string optimization).
47//! - `serde_json` — Enables encoding/decoding of `serde_json::Value` (JSON values as dynamic type).
48//! - `raw_value` — Enables encoding/decoding of `Box<serde_json::value::RawValue>` (raw JSON strings). Requires `serde_json` feature.
49
50pub mod core;
51mod features;
52
53use bytes::{Buf, BufMut, Bytes, BytesMut};
54pub use senax_encoder_derive::{Decode, Encode, Pack, Unpack};
55use std::collections::HashMap;
56use std::collections::{BTreeMap, BTreeSet, HashSet};
57use std::sync::Arc;
58
59/// Errors that can occur during encoding or decoding operations.
60#[derive(Debug, thiserror::Error)]
61pub enum EncoderError {
62    /// The value could not be encoded (e.g., unsupported type or logic error).
63    #[error("Encode error: {0}")]
64    Encode(String),
65    /// The value could not be decoded (e.g., invalid data, type mismatch, or schema evolution error).
66    #[error("Decode error: {0}")]
67    Decode(String),
68    /// The buffer did not contain enough data to complete the operation.
69    #[error("Insufficient data in buffer")]
70    InsufficientData,
71    /// Struct-specific decode error
72    #[error(transparent)]
73    StructDecode(#[from] StructDecodeError),
74    /// Enum-specific decode error
75    #[error(transparent)]
76    EnumDecode(#[from] EnumDecodeError),
77}
78
79/// The result type used throughout this crate for encode/decode operations.
80///
81/// All `Encode` and `Decode` trait methods return this type.
82pub type Result<T> = std::result::Result<T, EncoderError>;
83
84/// Derive-specific error types for struct operations
85#[derive(Debug, thiserror::Error)]
86pub enum StructDecodeError {
87    #[error("Expected struct named tag ({expected}), got {actual}")]
88    InvalidTag { expected: u8, actual: u8 },
89    #[error("Required field '{field}' not found for struct {struct_name}")]
90    MissingRequiredField {
91        field: &'static str,
92        struct_name: &'static str,
93    },
94    #[error("Field count mismatch for struct {struct_name}: expected {expected}, got {actual}")]
95    FieldCountMismatch {
96        struct_name: &'static str,
97        expected: usize,
98        actual: usize,
99    },
100    #[error("Structure hash mismatch for {struct_name}: expected 0x{expected:016X}, got 0x{actual:016X}")]
101    StructureHashMismatch {
102        struct_name: &'static str,
103        expected: u64,
104        actual: u64,
105    },
106}
107
108/// Derive-specific error types for enum operations
109#[derive(Debug, thiserror::Error)]
110pub enum EnumDecodeError {
111    #[error("Unknown enum tag: {tag} for enum {enum_name}")]
112    UnknownTag { tag: u8, enum_name: &'static str },
113    #[error("Unknown variant ID: 0x{variant_id:016X} for enum {enum_name}")]
114    UnknownVariantId {
115        variant_id: u64,
116        enum_name: &'static str,
117    },
118    #[error("Unknown unit variant ID: 0x{variant_id:016X} for enum {enum_name}")]
119    UnknownUnitVariantId {
120        variant_id: u64,
121        enum_name: &'static str,
122    },
123    #[error("Unknown named variant ID: 0x{variant_id:016X} for enum {enum_name}")]
124    UnknownNamedVariantId {
125        variant_id: u64,
126        enum_name: &'static str,
127    },
128    #[error("Unknown unnamed variant ID: 0x{variant_id:016X} for enum {enum_name}")]
129    UnknownUnnamedVariantId {
130        variant_id: u64,
131        enum_name: &'static str,
132    },
133    #[error("Required field '{field}' not found for variant {enum_name}::{variant_name}")]
134    MissingRequiredField {
135        field: &'static str,
136        enum_name: &'static str,
137        variant_name: &'static str,
138    },
139    #[error("Field count mismatch for variant {enum_name}::{variant_name}: expected {expected}, got {actual}")]
140    FieldCountMismatch {
141        enum_name: &'static str,
142        variant_name: &'static str,
143        expected: usize,
144        actual: usize,
145    },
146    #[error("Structure hash mismatch for variant {enum_name}::{variant_name}: expected 0x{expected:016X}, got 0x{actual:016X}")]
147    StructureHashMismatch {
148        enum_name: &'static str,
149        variant_name: &'static str,
150        expected: u64,
151        actual: u64,
152    },
153}
154
155/// Magic number for encoded format (0xA55A in little-endian)
156const ENCODE_MAGIC: u16 = 0xA55A;
157
158/// Magic number for packed format (0xDADA in little-endian)
159const PACK_MAGIC: u16 = 0xDADA;
160
161/// Convenience function to decode a value from bytes.
162///
163/// This function expects and verifies the encode magic number (0xA55A) at the beginning of the data.
164/// It provides schema evolution support through field IDs and type tags.
165///
166/// # Arguments
167/// * `reader` - The buffer to read the encoded bytes from.
168///
169/// # Example
170/// ```rust
171/// use senax_encoder::{encode, decode, Encode, Decode};
172/// use bytes::BytesMut;
173///
174/// #[derive(Encode, Decode, PartialEq, Debug)]
175/// struct MyStruct {
176///     id: u32,
177///     name: String,
178/// }
179///
180/// let value = MyStruct { id: 42, name: "hello".to_string() };
181/// let mut buf = encode(&value).unwrap();
182/// let decoded: MyStruct = decode(&mut buf).unwrap();
183/// assert_eq!(value, decoded);
184/// ```
185pub fn decode<T: Decoder>(reader: &mut Bytes) -> Result<T> {
186    if reader.remaining() < 2 {
187        return Err(EncoderError::InsufficientData);
188    }
189    let magic = reader.get_u16_le();
190    if magic != ENCODE_MAGIC {
191        return Err(EncoderError::Decode(format!(
192            "Invalid encode magic number: expected 0x{:04X}, got 0x{:04X}",
193            ENCODE_MAGIC, magic
194        )));
195    }
196    T::decode(reader)
197}
198
199/// Convenience function to encode a value to bytes with magic number.
200///
201/// This function adds the encode magic number (0xA55A) at the beginning of the data
202/// and provides schema evolution support through field IDs and type tags.
203///
204/// # Arguments
205/// * `value` - The value to encode.
206///
207/// # Example
208/// ```rust
209/// use senax_encoder::{encode, decode, Encode, Decode};
210/// use bytes::BytesMut;
211///
212/// #[derive(Encode, Decode, PartialEq, Debug)]
213/// struct MyStruct {
214///     id: u32,
215///     name: String,
216/// }
217///
218/// let value = MyStruct { id: 42, name: "hello".to_string() };
219/// let mut buf = encode(&value).unwrap();
220/// let decoded: MyStruct = decode(&mut buf).unwrap();
221/// assert_eq!(value, decoded);
222/// ```
223pub fn encode<T: Encoder>(value: &T) -> Result<Bytes> {
224    let mut writer = BytesMut::new();
225    writer.put_u16_le(ENCODE_MAGIC);
226    value.encode(&mut writer)?;
227    Ok(writer.freeze())
228}
229
230/// Convenience function to encode a value to an existing BytesMut buffer with magic number.
231///
232/// This function adds the encode magic number (0xA55A) at the current position in the buffer
233/// and provides schema evolution support through field IDs and type tags.
234///
235/// # Arguments
236/// * `value` - The value to encode.
237/// * `writer` - The buffer to write the encoded bytes into.
238///
239/// # Example
240/// ```rust
241/// use senax_encoder::{encode_to, decode, Encode, Decode};
242/// use bytes::{BytesMut, Bytes};
243///
244/// #[derive(Encode, Decode, PartialEq, Debug)]
245/// struct MyStruct {
246///     id: u32,
247///     name: String,
248/// }
249///
250/// let value = MyStruct { id: 42, name: "hello".to_string() };
251/// let mut buf = BytesMut::new();
252/// encode_to(&value, &mut buf).unwrap();
253/// let mut data = buf.freeze();
254/// let decoded: MyStruct = decode(&mut data).unwrap();
255/// assert_eq!(value, decoded);
256/// ```
257pub fn encode_to<T: Encoder>(value: &T, writer: &mut BytesMut) -> Result<()> {
258    writer.put_u16_le(ENCODE_MAGIC);
259    value.encode(writer)
260}
261
262/// Trait for types that can be encoded into the senax binary format.
263///
264/// Implement this trait for your type to enable serialization.
265/// Most users should use `#[derive(Encode)]` instead of manual implementation.
266///
267/// # Errors
268/// Returns `EncoderError` if the value cannot be encoded.
269pub trait Encoder {
270    /// Encode the value into the given buffer with schema evolution support.
271    ///
272    /// This method includes field IDs and type tags for forward/backward compatibility.
273    /// Use this when you need schema evolution support.
274    ///
275    /// # Arguments
276    /// * `writer` - The buffer to write the encoded bytes into.
277    fn encode(&self, writer: &mut BytesMut) -> Result<()>;
278
279    /// Returns true if this value equals its default value.
280    /// Used by `#[senax(skip_default)]` attribute to skip encoding default values.
281    fn is_default(&self) -> bool;
282}
283
284/// Trait for types that can be packed into a compact binary format.
285///
286/// This trait provides compact serialization without schema evolution support.
287/// Use this when you need maximum performance and don't require forward/backward compatibility.
288///
289/// # Errors
290/// Returns `EncoderError` if the value cannot be packed.
291pub trait Packer {
292    /// Pack the value into the given buffer without schema evolution support.
293    ///
294    /// This method stores data in a compact format without field IDs or type tags.
295    /// The format is not schema-evolution-friendly but offers better performance.
296    ///
297    /// # Arguments
298    /// * `writer` - The buffer to write the packed bytes into.
299    fn pack(&self, writer: &mut BytesMut) -> Result<()>;
300}
301
302/// Trait for types that can be decoded from the senax binary format.
303///
304/// Implement this trait for your type to enable deserialization.
305/// Most users should use `#[derive(Decode)]` instead of manual implementation.
306///
307/// # Errors
308/// Returns `EncoderError` if the value cannot be decoded or the data is invalid.
309pub trait Decoder: Sized {
310    /// Decode the value from the given buffer with schema evolution support.
311    ///
312    /// This method expects field IDs and type tags for forward/backward compatibility.
313    /// Use this when you need schema evolution support.
314    ///
315    /// # Arguments
316    /// * `reader` - The buffer to read the encoded bytes from.
317    fn decode(reader: &mut Bytes) -> Result<Self>;
318}
319
320/// Trait for types that can be unpacked from a compact binary format.
321///
322/// This trait provides compact deserialization without schema evolution support.
323/// Use this when you need maximum performance and don't require forward/backward compatibility.
324///
325/// # Errors
326/// Returns `EncoderError` if the value cannot be unpacked or the data is invalid.
327pub trait Unpacker: Sized {
328    /// Unpack the value from the given buffer without schema evolution support.
329    ///
330    /// This method reads data from a compact format without field IDs or type tags.
331    /// The format is not schema-evolution-friendly but offers better performance.
332    ///
333    /// # Arguments
334    /// * `reader` - The buffer to read the packed bytes from.
335    fn unpack(reader: &mut Bytes) -> Result<Self>;
336}
337
338/// Convenience function to pack a value to bytes with magic number.
339///
340/// This function adds the pack magic number (0xDADA) at the beginning of the data.
341/// The packed format is compact but not schema-evolution-friendly.
342///
343/// # Arguments
344/// * `value` - The value to pack.
345///
346/// # Example
347/// ```rust
348/// use senax_encoder::{pack, unpack, Pack, Unpack};
349/// use bytes::BytesMut;
350///
351/// #[derive(Pack, Unpack, PartialEq, Debug)]
352/// struct MyStruct {
353///     id: u32,
354///     name: String,
355/// }
356///
357/// let value = MyStruct { id: 42, name: "hello".to_string() };
358/// let mut buf = pack(&value).unwrap();
359/// let decoded: MyStruct = unpack(&mut buf).unwrap();
360/// assert_eq!(value, decoded);
361/// ```
362pub fn pack<T: Packer>(value: &T) -> Result<Bytes> {
363    let mut writer = BytesMut::new();
364    writer.put_u16_le(PACK_MAGIC);
365    value.pack(&mut writer)?;
366    Ok(writer.freeze())
367}
368
369/// Convenience function to pack a value to an existing BytesMut buffer with magic number.
370///
371/// This function adds the pack magic number (0xDADA) at the current position in the buffer.
372/// The packed format is compact but not schema-evolution-friendly.
373///
374/// # Arguments
375/// * `value` - The value to pack.
376/// * `writer` - The buffer to write the packed bytes into.
377///
378/// # Example
379/// ```rust
380/// use senax_encoder::{pack_to, unpack, Pack, Unpack};
381/// use bytes::{BytesMut, Bytes};
382///
383/// #[derive(Pack, Unpack, PartialEq, Debug)]
384/// struct MyStruct {
385///     id: u32,
386///     name: String,
387/// }
388///
389/// let value = MyStruct { id: 42, name: "hello".to_string() };
390/// let mut buf = BytesMut::new();
391/// pack_to(&value, &mut buf).unwrap();
392/// let mut data = buf.freeze();
393/// let decoded: MyStruct = unpack(&mut data).unwrap();
394/// assert_eq!(value, decoded);
395/// ```
396pub fn pack_to<T: Packer>(value: &T, writer: &mut BytesMut) -> Result<()> {
397    writer.put_u16_le(PACK_MAGIC);
398    value.pack(writer)
399}
400
401/// Convenience function to unpack a value from bytes.
402///
403/// This function expects and verifies the pack magic number (0xDADA) at the beginning of the data.
404/// The packed format is compact but not schema-evolution-friendly.
405///
406/// # Arguments
407/// * `reader` - The buffer to read the packed bytes from.
408///
409/// # Example
410/// ```rust
411/// use senax_encoder::{pack, unpack, Pack, Unpack};
412/// use bytes::BytesMut;
413///
414/// #[derive(Pack, Unpack, PartialEq, Debug)]
415/// struct MyStruct {
416///     id: u32,
417///     name: String,
418/// }
419///
420/// let value = MyStruct { id: 42, name: "hello".to_string() };
421/// let mut buf = pack(&value).unwrap();
422/// let decoded: MyStruct = unpack(&mut buf).unwrap();
423/// assert_eq!(value, decoded);
424/// ```
425pub fn unpack<T: Unpacker>(reader: &mut Bytes) -> Result<T> {
426    if reader.remaining() < 2 {
427        return Err(EncoderError::InsufficientData);
428    }
429    let magic = reader.get_u16_le();
430    if magic != PACK_MAGIC {
431        return Err(EncoderError::Decode(format!(
432            "Invalid pack magic number: expected 0x{:04X}, got 0x{:04X}",
433            PACK_MAGIC, magic
434        )));
435    }
436    T::unpack(reader)
437}