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