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