Skip to main content

hitbox_backend/format/
mod.rs

1//! Serialization formats for cached values.
2//!
3//! Cache backends need to serialize values to bytes for storage. The [`Format`] trait
4//! provides a dyn-compatible interface that allows backends to select serialization
5//! format at runtime.
6//!
7//! See the [crate-level documentation](crate#serialization-formats) for a comparison
8//! of available formats.
9//!
10//! # Why dyn-compatible?
11//!
12//! The [`Backend`](crate::Backend) trait returns its format via `value_format() -> &dyn Format`.
13//! This design enables:
14//!
15//! - **Runtime format selection**: Choose format based on configuration, not compile-time generics
16//! - **Heterogeneous backends**: Combine backends with different formats in composition layers
17//! - **Format switching**: Change serialization strategy without recompiling
18//!
19//! Making [`Format`] dyn-compatible required a callback-based API ([`Format::with_serializer`],
20//! [`Format::with_deserializer`]) instead of returning serializers directly. This avoids
21//! self-referential lifetime issues that would prevent trait object usage.
22//!
23//! # Extending with Custom Formats
24//!
25//! Implement [`Format`] to add custom serialization. Use [`FormatTypeId::Custom`] with
26//! a unique identifier string to ensure your format can be distinguished from built-in ones.
27
28use hitbox_core::{Cacheable, Raw};
29use thiserror::Error;
30
31use hitbox_core::BoxContext;
32
33use crate::context::Context;
34
35#[cfg(feature = "rkyv_format")]
36use ::rkyv::{api::high::to_bytes_in, from_bytes, rancor, util::AlignedVec};
37
38// Bincode imports for concrete types (use absolute paths to avoid conflict with our bincode module)
39use ::bincode::config::Configuration;
40use ::bincode::de::DecoderImpl;
41use ::bincode::de::read::SliceReader;
42use ::bincode::enc::EncoderImpl;
43use ::bincode::serde::Compat;
44use ::bincode::{Decode, Encode};
45
46// Import the BincodeVecWriter from bincode module
47use self::bincode::BincodeVecWriter;
48
49/// Opaque bincode encoder wrapper.
50///
51/// This type is exposed in [`FormatSerializer::Bincode`] but cannot be
52/// constructed outside this crate. Use [`FormatSerializer::Serde`] for
53/// custom format implementations.
54pub struct BincodeEncoder<'a>(pub(crate) &'a mut EncoderImpl<BincodeVecWriter, Configuration>);
55
56/// Opaque bincode decoder wrapper.
57///
58/// This type is exposed in [`FormatDeserializer::Bincode`] but cannot be
59/// constructed outside this crate. Use [`FormatDeserializer::Serde`] for
60/// custom format implementations.
61pub struct BincodeDecoder<'a>(pub(crate) &'a mut DecoderImpl<SliceReader<'a>, Configuration, ()>);
62
63mod bincode;
64mod json;
65#[cfg(feature = "rkyv_format")]
66mod rkyv;
67mod ron;
68
69pub use bincode::BincodeFormat;
70pub use json::JsonFormat;
71#[cfg(feature = "rkyv_format")]
72#[cfg_attr(docsrs, doc(cfg(feature = "rkyv_format")))]
73pub use rkyv::RkyvFormat;
74pub use ron::RonFormat;
75
76/// Errors from serialization and deserialization operations.
77#[derive(Error, Debug)]
78pub enum FormatError {
79    /// Serialization failed.
80    #[error(transparent)]
81    Serialize(Box<dyn std::error::Error + Send>),
82
83    /// Deserialization failed.
84    #[error(transparent)]
85    Deserialize(Box<dyn std::error::Error + Send>),
86}
87
88/// Error type for rkyv serialization that preserves error information
89/// when the actual error type cannot be directly boxed due to trait object constraints
90#[cfg(feature = "rkyv_format")]
91#[derive(Error, Debug)]
92#[error("rkyv serialization failed: {message}")]
93struct RkyvSerializeError {
94    message: String,
95}
96
97#[cfg(feature = "rkyv_format")]
98impl RkyvSerializeError {
99    fn new(error: impl std::fmt::Debug) -> Self {
100        Self {
101            message: format!("{:?}", error),
102        }
103    }
104}
105
106/// Error type for rkyv validation that preserves error information
107/// The underlying CheckArchiveError contains non-Send trait objects, so we
108/// capture the error message as a Send-safe string
109#[cfg(feature = "rkyv_format")]
110#[derive(Error, Debug)]
111#[error("rkyv validation failed: {message}")]
112struct RkyvValidationError {
113    message: String,
114}
115
116#[cfg(feature = "rkyv_format")]
117impl RkyvValidationError {
118    fn new(error: impl std::fmt::Display) -> Self {
119        Self {
120            message: error.to_string(),
121        }
122    }
123}
124
125/// Identifies a format type for equality comparison.
126///
127/// Since [`Format`] is dyn-compatible, you cannot compare formats with `TypeId`.
128/// This enum provides a stable identifier that works across trait objects.
129#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
130pub enum FormatTypeId {
131    /// JSON format.
132    Json,
133    /// Bincode format.
134    Bincode,
135    /// RON format.
136    Ron,
137    /// Rkyv format.
138    Rkyv,
139    /// User-defined format. The string must be globally unique.
140    Custom(&'static str),
141}
142
143/// Serializer passed to [`Format::with_serializer`] callbacks.
144///
145/// Wraps different serialization backends (serde, rkyv, bincode) in a unified interface.
146/// Call [`serialize`](Self::serialize) with any [`Cacheable`] value.
147pub enum FormatSerializer<'a> {
148    /// Serde-based serializer (used by JSON, RON).
149    Serde(&'a mut dyn erased_serde::Serializer),
150    /// Rkyv serializer (zero-copy).
151    #[cfg(feature = "rkyv_format")]
152    Rkyv(&'a mut AlignedVec),
153    /// Bincode serializer (opaque, cannot be constructed externally).
154    Bincode(BincodeEncoder<'a>),
155}
156
157impl<'a> FormatSerializer<'a> {
158    /// Serializes a value using the underlying format.
159    ///
160    /// Dispatches to the appropriate serialization backend automatically.
161    pub fn serialize<T>(&mut self, value: &T) -> Result<(), FormatError>
162    where
163        T: Cacheable,
164    {
165        match self {
166            FormatSerializer::Serde(ser) => {
167                let erased_value = value as &dyn erased_serde::Serialize;
168                erased_value
169                    .erased_serialize(*ser)
170                    .map_err(|e| FormatError::Serialize(Box::new(e)))
171            }
172            #[cfg(feature = "rkyv_format")]
173            FormatSerializer::Rkyv(buffer) => {
174                // Serialize directly into the pre-allocated buffer (no double allocation)
175                // Take ownership temporarily, serialize, then restore the buffer
176                let mut owned_buffer = std::mem::take(*buffer);
177                owned_buffer.clear();
178                let result_buffer = to_bytes_in::<_, rancor::Error>(value, owned_buffer)
179                    .map_err(|e| FormatError::Serialize(Box::new(RkyvSerializeError::new(e))))?;
180                **buffer = result_buffer;
181                Ok(())
182            }
183            FormatSerializer::Bincode(enc) => {
184                // Use Compat wrapper to bridge serde and bincode
185                let compat = Compat(value);
186                Encode::encode(&compat, enc.0).map_err(|e| FormatError::Serialize(Box::new(e)))
187            }
188        }
189    }
190}
191
192/// Deserializer passed to [`Format::with_deserializer`] callbacks.
193///
194/// Wraps different deserialization backends in a unified interface.
195/// Call [`deserialize`](Self::deserialize) to reconstruct the original value.
196pub enum FormatDeserializer<'a> {
197    /// Serde-based deserializer (used by JSON, RON).
198    Serde(&'a mut dyn erased_serde::Deserializer<'a>),
199    /// Rkyv deserializer (validates and deserializes archived bytes).
200    #[cfg(feature = "rkyv_format")]
201    Rkyv(&'a [u8]),
202    /// Bincode deserializer (opaque, cannot be constructed externally).
203    Bincode(BincodeDecoder<'a>),
204}
205
206impl<'a> FormatDeserializer<'a> {
207    /// Deserializes a value using the underlying format.
208    ///
209    /// Dispatches to the appropriate deserialization backend automatically.
210    pub fn deserialize<T>(&mut self) -> Result<T, FormatError>
211    where
212        T: Cacheable,
213    {
214        match self {
215            FormatDeserializer::Serde(deser) => {
216                erased_serde::deserialize(*deser).map_err(|e| FormatError::Deserialize(Box::new(e)))
217            }
218            #[cfg(feature = "rkyv_format")]
219            FormatDeserializer::Rkyv(data) => {
220                // Use rkyv 0.8's from_bytes API which validates and deserializes
221                let value: T = from_bytes::<T, rancor::Error>(data)
222                    .map_err(|e| FormatError::Deserialize(Box::new(RkyvValidationError::new(e))))?;
223                Ok(value)
224            }
225            FormatDeserializer::Bincode(dec) => {
226                // Use Compat wrapper to decode from bincode
227                let compat: Compat<T> =
228                    Decode::decode(dec.0).map_err(|e| FormatError::Deserialize(Box::new(e)))?;
229                Ok(compat.0)
230            }
231        }
232    }
233}
234
235/// Dyn-compatible serialization format.
236///
237/// Uses a callback-based API to work around lifetime constraints that would
238/// prevent returning serializers directly from trait methods.
239///
240/// # For Implementors
241///
242/// Implement [`with_serializer`](Self::with_serializer) and [`with_deserializer`](Self::with_deserializer)
243/// to provide serialization/deserialization. The callback receives a [`FormatSerializer`] or
244/// [`FormatDeserializer`] that handles the actual conversion.
245///
246/// # For Callers
247///
248/// Use [`FormatExt::serialize`] and [`FormatExt::deserialize`] instead of calling
249/// the callback methods directly. The extension trait provides a cleaner API.
250pub trait Format: std::fmt::Debug + Send + Sync {
251    /// Serializes a value through a callback.
252    ///
253    /// Creates a serializer, passes it to the callback, and returns the serialized bytes.
254    fn with_serializer(
255        &self,
256        f: &mut dyn FnMut(&mut FormatSerializer) -> Result<(), FormatError>,
257        context: &dyn Context,
258    ) -> Result<Raw, FormatError>;
259
260    /// Deserializes a value through a callback.
261    ///
262    /// Creates a deserializer from the data and passes it to the callback.
263    /// The context can be modified during deserialization (e.g., to upgrade schema versions).
264    fn with_deserializer(
265        &self,
266        data: &[u8],
267        f: &mut dyn FnMut(&mut FormatDeserializer) -> Result<(), FormatError>,
268        ctx: &mut BoxContext,
269    ) -> Result<(), FormatError>;
270
271    /// Clones this format into a boxed trait object.
272    fn clone_box(&self) -> Box<dyn Format>;
273
274    /// Returns this format's type identifier.
275    fn format_type_id(&self) -> FormatTypeId;
276}
277
278/// Ergonomic serialization methods for [`Format`].
279///
280/// Provides typed `serialize` and `deserialize` methods. This trait is automatically
281/// implemented for all `Format` types via blanket implementation.
282pub trait FormatExt: Format {
283    /// Serializes a value to raw bytes.
284    fn serialize<T>(&self, value: &T, context: &dyn Context) -> Result<Raw, FormatError>
285    where
286        T: Cacheable,
287    {
288        self.with_serializer(&mut |serializer| serializer.serialize(value), context)
289    }
290
291    /// Deserializes raw bytes into a value.
292    fn deserialize<T>(&self, data: &Raw, ctx: &mut BoxContext) -> Result<T, FormatError>
293    where
294        T: Cacheable,
295    {
296        let mut result: Option<T> = None;
297        self.with_deserializer(
298            data,
299            &mut |deserializer| {
300                let value: T = deserializer.deserialize()?;
301                result = Some(value);
302                Ok(())
303            },
304            ctx,
305        )?;
306
307        result.ok_or_else(|| {
308            FormatError::Deserialize(Box::new(std::io::Error::other(
309                "deserialization produced no result",
310            )))
311        })
312    }
313}
314
315// Blanket implementation: all Formats automatically get generic methods
316impl<T: Format + ?Sized> FormatExt for T {}
317
318// Implement Clone for Box<dyn Format>
319impl Clone for Box<dyn Format> {
320    fn clone(&self) -> Self {
321        self.clone_box()
322    }
323}
324
325// Implement Format for Box<dyn Format>
326impl Format for Box<dyn Format> {
327    fn with_serializer(
328        &self,
329        f: &mut dyn FnMut(&mut FormatSerializer) -> Result<(), FormatError>,
330        context: &dyn Context,
331    ) -> Result<Raw, FormatError> {
332        (**self).with_serializer(f, context)
333    }
334
335    fn with_deserializer(
336        &self,
337        data: &[u8],
338        f: &mut dyn FnMut(&mut FormatDeserializer) -> Result<(), FormatError>,
339        ctx: &mut BoxContext,
340    ) -> Result<(), FormatError> {
341        (**self).with_deserializer(data, f, ctx)
342    }
343
344    fn clone_box(&self) -> Box<dyn Format> {
345        (**self).clone_box()
346    }
347
348    fn format_type_id(&self) -> FormatTypeId {
349        (**self).format_type_id()
350    }
351}
352
353// Implement Format for Arc<dyn Format>
354impl Format for std::sync::Arc<dyn Format> {
355    fn with_serializer(
356        &self,
357        f: &mut dyn FnMut(&mut FormatSerializer) -> Result<(), FormatError>,
358        context: &dyn Context,
359    ) -> Result<Raw, FormatError> {
360        (**self).with_serializer(f, context)
361    }
362
363    fn with_deserializer(
364        &self,
365        data: &[u8],
366        f: &mut dyn FnMut(&mut FormatDeserializer) -> Result<(), FormatError>,
367        ctx: &mut BoxContext,
368    ) -> Result<(), FormatError> {
369        (**self).with_deserializer(data, f, ctx)
370    }
371
372    fn clone_box(&self) -> Box<dyn Format> {
373        (**self).clone_box()
374    }
375
376    fn format_type_id(&self) -> FormatTypeId {
377        (**self).format_type_id()
378    }
379}