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}