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}