use crate::error::{OxCacheError, OxCacheResult};
use serde::{Serialize, de::DeserializeOwned};
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash)]
pub enum SerializationFormat {
#[default]
Json,
#[cfg(feature = "serde-bincode")]
Bincode,
#[cfg(feature = "postcard")]
Postcard,
}
impl SerializationFormat {
pub fn available() -> &'static [SerializationFormat] {
#[cfg(not(any(feature = "serde-bincode", feature = "postcard")))]
{
&[SerializationFormat::Json]
}
#[cfg(any(feature = "serde-bincode", feature = "postcard"))]
{
&[
SerializationFormat::Json,
#[cfg(feature = "serde-bincode")]
SerializationFormat::Bincode,
#[cfg(feature = "postcard")]
SerializationFormat::Postcard,
]
}
}
pub fn name(&self) -> &'static str {
match self {
SerializationFormat::Json => "json",
#[cfg(feature = "serde-bincode")]
SerializationFormat::Bincode => "bincode",
#[cfg(feature = "postcard")]
SerializationFormat::Postcard => "postcard",
}
}
pub fn by_name(name: &str) -> Option<SerializationFormat> {
SerializationFormat::available()
.iter()
.copied()
.find(|f| f.name() == name)
}
}
pub fn serialize_with_format<T: Serialize>(
format: SerializationFormat,
value: &T,
) -> OxCacheResult<Vec<u8>> {
let bytes = match format {
SerializationFormat::Json => {
serde_json::to_vec(value).map_err(|e| OxCacheError::Serialization(e.to_string()))?
}
#[cfg(feature = "serde-bincode")]
SerializationFormat::Bincode => {
bincode::serde::encode_to_vec(value, bincode::config::standard())
.map_err(|e| OxCacheError::Serialization(e.to_string()))?
}
#[cfg(feature = "postcard")]
SerializationFormat::Postcard => {
postcard::to_allocvec(value).map_err(|e| OxCacheError::Serialization(e.to_string()))?
}
};
crate::infra::serialization::utils::check_data_size(
&bytes,
crate::core::constants::MAX_JSON_SIZE,
format.name(),
)?;
Ok(bytes)
}
pub fn deserialize_with_format<T: DeserializeOwned>(
format: SerializationFormat,
data: &[u8],
) -> OxCacheResult<T> {
crate::infra::serialization::utils::check_data_size(
data,
crate::core::constants::MAX_JSON_SIZE,
format.name(),
)?;
match format {
SerializationFormat::Json => crate::infra::serialization::depth_limited::deserialize_safe(
data,
crate::core::constants::MAX_JSON_DEPTH,
)
.map_err(|e| OxCacheError::Serialization(e.to_string())),
#[cfg(feature = "serde-bincode")]
SerializationFormat::Bincode => {
bincode::serde::decode_from_slice(data, bincode::config::standard())
.map(|(value, _consumed): (T, usize)| value)
.map_err(|e| OxCacheError::Serialization(e.to_string()))
}
#[cfg(feature = "postcard")]
SerializationFormat::Postcard => {
postcard::from_bytes(data).map_err(|e| OxCacheError::Serialization(e.to_string()))
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use serde::{Deserialize, Serialize};
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
struct Sample {
id: u64,
name: String,
tags: Vec<String>,
score: f64,
}
fn sample() -> Sample {
Sample {
id: 42,
name: "cache-entry".to_string(),
tags: vec!["l1".to_string(), "l2".to_string(), "ttl".to_string()],
score: 0.987,
}
}
fn all_expected_formats() -> Vec<SerializationFormat> {
SerializationFormat::available().to_vec()
}
#[test]
fn every_available_format_roundtrips() {
for format in all_expected_formats() {
let bytes = serialize_with_format(format, &sample()).unwrap();
let decoded: Sample = deserialize_with_format(format, &bytes).unwrap();
assert_eq!(
decoded,
sample(),
"format {} roundtrip failed",
format.name()
);
}
}
#[cfg(all(feature = "serde-bincode", feature = "postcard"))]
#[test]
fn formats_are_interoperable_on_the_same_value() {
let value = sample();
let json = serialize_with_format(SerializationFormat::Json, &value).unwrap();
let bin = serialize_with_format(SerializationFormat::Bincode, &value).unwrap();
let card = serialize_with_format(SerializationFormat::Postcard, &value).unwrap();
assert_ne!(json, bin);
assert_ne!(json, card);
let a: Sample = deserialize_with_format(SerializationFormat::Json, &json).unwrap();
let b: Sample = deserialize_with_format(SerializationFormat::Bincode, &bin).unwrap();
let c: Sample = deserialize_with_format(SerializationFormat::Postcard, &card).unwrap();
assert_eq!(a, b);
assert_eq!(b, c);
println!(
"serialization size: json={} bincode={} postcard={}",
json.len(),
bin.len(),
card.len()
);
assert!(
card.len() < json.len(),
"postcard 应比 JSON 更紧凑 (card={}, json={})",
card.len(),
json.len()
);
#[derive(Serialize, Deserialize, PartialEq)]
struct NumericHeavy {
a: u64,
b: i64,
c: f64,
d: u64,
e: i64,
f: f64,
}
let heavy = NumericHeavy {
a: 1700000000000,
b: -42,
c: 3.15,
d: u64::MAX,
e: 987654321,
f: 2.71,
};
let h_json = serialize_with_format(SerializationFormat::Json, &heavy).unwrap();
let h_bin = serialize_with_format(SerializationFormat::Bincode, &heavy).unwrap();
println!(
"numeric-heavy size: json={} bincode={}",
h_json.len(),
h_bin.len()
);
assert!(
h_bin.len() < h_json.len(),
"数值密集场景 bincode 应更紧凑 (bin={}, json={})",
h_bin.len(),
h_json.len()
);
}
#[cfg(feature = "serde-bincode")]
#[test]
fn bincode_bytes_are_not_json() {
let bin = serialize_with_format(SerializationFormat::Bincode, &sample()).unwrap();
assert_ne!(bin[0], b'{');
}
#[test]
fn format_by_name_lookup() {
assert_eq!(
SerializationFormat::by_name("json"),
Some(SerializationFormat::Json)
);
#[cfg(feature = "serde-bincode")]
assert_eq!(
SerializationFormat::by_name("bincode"),
Some(SerializationFormat::Bincode)
);
assert_eq!(SerializationFormat::by_name("msgpack"), None);
}
#[test]
fn oversized_payload_rejected() {
let big: Vec<u8> = Vec::new();
let _: Vec<u8> = deserialize_with_format(
SerializationFormat::Json,
&serialize_with_format(SerializationFormat::Json, &big).unwrap(),
)
.unwrap();
let oversized = vec![0u8; crate::core::constants::MAX_JSON_SIZE + 1];
let err = serialize_with_format(SerializationFormat::Json, &oversized)
.expect_err("超限序列化必须报错");
assert!(matches!(err, OxCacheError::Serialization(_)));
}
}