use serde::{Serialize, de::DeserializeOwned};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ValueKind {
Json,
Binary,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum SerializerError {
EncodeFailed(String),
DecodeFailed(String),
FormatMismatch {
expected: &'static str,
actual: &'static str,
},
VersionIncompatible,
}
impl std::fmt::Display for SerializerError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::EncodeFailed(msg) => write!(f, "Serialization encoding failed: {msg}"),
Self::DecodeFailed(msg) => write!(f, "Serialization decoding failed: {msg}"),
Self::FormatMismatch { expected, actual } => {
write!(f, "Format mismatch: expected {expected}, got {actual}")
}
Self::VersionIncompatible => {
write!(f, "Version incompatible: stored data version is too new")
}
}
}
}
impl std::error::Error for SerializerError {}
pub trait SaSerializer: Send + Sync {
fn name(&self) -> &'static str;
fn kind(&self, raw: &str) -> ValueKind;
fn encode<T: Serialize + ?Sized>(&self, value: &T) -> Result<String, SerializerError>;
fn decode<T: DeserializeOwned>(&self, raw: &str) -> Result<T, SerializerError>;
#[inline]
fn encode_bytes<T: Serialize + ?Sized>(&self, value: &T) -> Result<Vec<u8>, SerializerError> {
self.encode(value).map(|s| s.into_bytes())
}
#[inline]
fn decode_bytes<T: DeserializeOwned>(&self, bytes: &[u8]) -> Result<T, SerializerError> {
let s = std::str::from_utf8(bytes)
.map_err(|e| SerializerError::DecodeFailed(format!("Invalid UTF-8: {e}")))?;
self.decode(s)
}
}
pub const BINARY_MAGIC: &str = "\u{0001}STF";
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub struct JsonSerializerConfig {
pub pretty_print: bool,
pub escape_unicode: bool,
}
#[derive(Debug, Clone, Copy, Default)]
pub struct JsonSerializer {
config: JsonSerializerConfig,
}
impl JsonSerializer {
pub fn with_config(config: JsonSerializerConfig) -> Self {
Self { config }
}
}
impl SaSerializer for JsonSerializer {
#[inline]
fn name(&self) -> &'static str {
"json"
}
#[inline]
fn kind(&self, raw: &str) -> ValueKind {
if raw.starts_with(BINARY_MAGIC) {
ValueKind::Binary
} else {
ValueKind::Json
}
}
#[inline]
fn encode<T: Serialize + ?Sized>(&self, value: &T) -> Result<String, SerializerError> {
if self.config.pretty_print {
serde_json::to_string_pretty(value)
.map_err(|e| SerializerError::EncodeFailed(e.to_string()))
} else {
serde_json::to_string(value).map_err(|e| SerializerError::EncodeFailed(e.to_string()))
}
}
#[inline]
fn decode<T: DeserializeOwned>(&self, raw: &str) -> Result<T, SerializerError> {
if raw.starts_with(BINARY_MAGIC) {
return Err(SerializerError::FormatMismatch {
expected: "json",
actual: "binary",
});
}
serde_json::from_str(raw).map_err(|e| SerializerError::DecodeFailed(e.to_string()))
}
#[inline]
fn encode_bytes<T: Serialize + ?Sized>(&self, value: &T) -> Result<Vec<u8>, SerializerError> {
if self.config.pretty_print {
serde_json::to_vec_pretty(value)
.map_err(|e| SerializerError::EncodeFailed(e.to_string()))
} else {
serde_json::to_vec(value).map_err(|e| SerializerError::EncodeFailed(e.to_string()))
}
}
}
#[cfg(feature = "fory")]
mod fory_impl {
use super::*;
use base64::{Engine as _, engine::general_purpose::STANDARD};
use fory::Fory;
use std::sync::OnceLock;
fn fory_runtime() -> &'static Fory {
static RUNTIME: OnceLock<Fory> = OnceLock::new();
RUNTIME.get_or_init(Fory::default)
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct ForySerializerConfig {
pub compression_level: u8,
}
impl Default for ForySerializerConfig {
fn default() -> Self {
Self {
compression_level: 6,
}
}
}
#[derive(Debug, Clone, Copy, Default)]
pub struct ForySerializer {
#[allow(dead_code)]
config: ForySerializerConfig,
}
impl ForySerializer {
pub fn with_config(config: ForySerializerConfig) -> Self {
Self { config }
}
}
impl SaSerializer for ForySerializer {
#[inline]
fn name(&self) -> &'static str {
"fory"
}
#[inline]
fn kind(&self, raw: &str) -> ValueKind {
if raw.starts_with(BINARY_MAGIC) {
ValueKind::Binary
} else {
ValueKind::Json
}
}
fn encode<T: Serialize + ?Sized>(&self, value: &T) -> Result<String, SerializerError> {
let json = serde_json::to_string(value)
.map_err(|e| SerializerError::EncodeFailed(e.to_string()))?;
let bytes = fory_runtime()
.serialize(&json)
.map_err(|e| SerializerError::EncodeFailed(e.to_string()))?;
Ok(format!("{}{}", super::BINARY_MAGIC, STANDARD.encode(bytes)))
}
fn decode<T: DeserializeOwned>(&self, raw: &str) -> Result<T, SerializerError> {
if raw.starts_with(BINARY_MAGIC) {
let b64 = &raw[super::BINARY_MAGIC.len()..];
let bytes = STANDARD
.decode(b64)
.map_err(|e| SerializerError::DecodeFailed(e.to_string()))?;
let json: String = fory_runtime()
.deserialize(&bytes)
.map_err(|e| SerializerError::DecodeFailed(e.to_string()))?;
serde_json::from_str(&json)
.map_err(|e| SerializerError::DecodeFailed(e.to_string()))
} else {
serde_json::from_str(raw).map_err(|e| SerializerError::DecodeFailed(e.to_string()))
}
}
fn encode_bytes<T: Serialize + ?Sized>(
&self,
value: &T,
) -> Result<Vec<u8>, SerializerError> {
let json = serde_json::to_string(value)
.map_err(|e| SerializerError::EncodeFailed(e.to_string()))?;
fory_runtime()
.serialize(&json)
.map_err(|e| SerializerError::EncodeFailed(e.to_string()))
}
fn decode_bytes<T: DeserializeOwned>(&self, bytes: &[u8]) -> Result<T, SerializerError> {
let json: String = fory_runtime()
.deserialize(bytes)
.map_err(|e| SerializerError::DecodeFailed(e.to_string()))?;
serde_json::from_str(&json).map_err(|e| SerializerError::DecodeFailed(e.to_string()))
}
}
}
#[cfg(feature = "fory")]
pub use fory_impl::{ForySerializer, ForySerializerConfig};
#[derive(Clone)]
pub enum SharedSerializer {
Json(JsonSerializer),
#[cfg(feature = "fory")]
Fory(ForySerializer),
}
impl Default for SharedSerializer {
fn default() -> Self {
Self::Json(JsonSerializer::default())
}
}
impl std::fmt::Debug for SharedSerializer {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "SharedSerializer({})", self.name())
}
}
impl SharedSerializer {
#[inline]
pub fn name(&self) -> &'static str {
match self {
Self::Json(s) => s.name(),
#[cfg(feature = "fory")]
Self::Fory(s) => s.name(),
}
}
#[inline]
pub fn kind(&self, raw: &str) -> ValueKind {
match self {
Self::Json(s) => s.kind(raw),
#[cfg(feature = "fory")]
Self::Fory(s) => s.kind(raw),
}
}
#[inline]
pub fn encode<T: Serialize + ?Sized>(&self, value: &T) -> Result<String, SerializerError> {
match self {
Self::Json(s) => s.encode(value),
#[cfg(feature = "fory")]
Self::Fory(s) => s.encode(value),
}
}
#[inline]
pub fn decode<T: DeserializeOwned>(&self, raw: &str) -> Result<T, SerializerError> {
match self {
Self::Json(s) => s.decode(raw),
#[cfg(feature = "fory")]
Self::Fory(s) => s.decode(raw),
}
}
#[inline]
pub fn encode_bytes<T: Serialize + ?Sized>(
&self,
value: &T,
) -> Result<Vec<u8>, SerializerError> {
match self {
Self::Json(s) => s.encode_bytes(value),
#[cfg(feature = "fory")]
Self::Fory(s) => s.encode_bytes(value),
}
}
#[inline]
pub fn decode_bytes<T: DeserializeOwned>(&self, bytes: &[u8]) -> Result<T, SerializerError> {
match self {
Self::Json(s) => s.decode_bytes(bytes),
#[cfg(feature = "fory")]
Self::Fory(s) => s.decode_bytes(bytes),
}
}
#[inline]
pub fn as_json(&self) -> Option<&JsonSerializer> {
match self {
Self::Json(s) => Some(s),
#[cfg(feature = "fory")]
_ => None,
}
}
#[cfg(feature = "fory")]
#[inline]
pub fn as_fory(&self) -> Option<&ForySerializer> {
match self {
Self::Fory(s) => Some(s),
_ => None,
}
}
}
impl From<JsonSerializer> for SharedSerializer {
fn from(value: JsonSerializer) -> Self {
Self::Json(value)
}
}
#[cfg(feature = "fory")]
impl From<ForySerializer> for SharedSerializer {
fn from(value: ForySerializer) -> Self {
Self::Fory(value)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[derive(serde::Serialize, serde::Deserialize, PartialEq, Debug)]
struct Sample {
id: u32,
name: String,
}
#[test]
fn json_roundtrip() {
let ser = SharedSerializer::default();
let sample = Sample {
id: 1,
name: "alice".into(),
};
let raw = ser.encode(&sample).unwrap();
assert_eq!(ser.kind(&raw), ValueKind::Json);
assert_eq!(ser.decode::<Sample>(&raw).unwrap(), sample);
}
#[test]
fn json_rejects_binary_magic() {
let ser = SharedSerializer::default();
let err = ser
.decode::<Sample>(&format!("{BINARY_MAGIC}xxx"))
.unwrap_err();
assert!(matches!(
err,
SerializerError::FormatMismatch {
expected: "json",
actual: "binary"
}
));
}
#[cfg(feature = "fory")]
#[test]
fn fory_roundtrip() {
let ser = SharedSerializer::from(ForySerializer::default());
let sample = Sample {
id: 2,
name: "bob".into(),
};
let raw = ser.encode(&sample).unwrap();
assert_eq!(ser.kind(&raw), ValueKind::Binary);
assert!(raw.starts_with(BINARY_MAGIC));
assert_eq!(ser.decode::<Sample>(&raw).unwrap(), sample);
}
#[cfg(feature = "fory")]
#[test]
fn fory_reads_legacy_json() {
let ser = SharedSerializer::from(ForySerializer::default());
let json = r#"{"id":3,"name":"carol"}"#;
assert_eq!(ser.kind(json), ValueKind::Json);
assert_eq!(
ser.decode::<Sample>(json).unwrap(),
Sample {
id: 3,
name: "carol".into(),
}
);
}
}