pub use bytes::Bytes;
pub mod serde_helpers {
use bytes::Bytes;
use serde::{Deserialize, Deserializer, Serializer};
pub mod uuid_string {
use super::*;
use serde::Serialize;
pub fn serialize<S>(bytes: &Bytes, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
if bytes.len() == 16 {
let mut uuid_bytes = [0u8; 16];
uuid_bytes.copy_from_slice(&bytes[..]);
let uuid_str = format!(
"{:02x}{:02x}{:02x}{:02x}-{:02x}{:02x}-{:02x}{:02x}-{:02x}{:02x}-{:02x}{:02x}{:02x}{:02x}{:02x}{:02x}",
uuid_bytes[0],
uuid_bytes[1],
uuid_bytes[2],
uuid_bytes[3],
uuid_bytes[4],
uuid_bytes[5],
uuid_bytes[6],
uuid_bytes[7],
uuid_bytes[8],
uuid_bytes[9],
uuid_bytes[10],
uuid_bytes[11],
uuid_bytes[12],
uuid_bytes[13],
uuid_bytes[14],
uuid_bytes[15]
);
uuid_str.serialize(serializer)
} else {
base64::Engine::encode(&base64::engine::general_purpose::STANDARD, bytes)
.serialize(serializer)
}
}
pub fn deserialize<'de, D>(deserializer: D) -> Result<Bytes, D::Error>
where
D: Deserializer<'de>,
{
use serde::de::Error;
let s = String::deserialize(deserializer)?;
let cleaned = s.replace("-", "");
if cleaned.len() == 32 {
let mut bytes = Vec::with_capacity(16);
for i in 0..16 {
let byte_str = &cleaned[i * 2..i * 2 + 2];
if let Ok(byte) = u8::from_str_radix(byte_str, 16) {
bytes.push(byte);
} else {
return Err(D::Error::custom("Invalid UUID hex"));
}
}
Ok(Bytes::from(bytes))
} else {
base64::Engine::decode(&base64::engine::general_purpose::STANDARD, s.as_bytes())
.map(Bytes::from)
.map_err(D::Error::custom)
}
}
}
pub mod payload_flexible {
use super::*;
use serde::Serialize;
pub fn serialize<S>(bytes: &Bytes, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
if let Ok(s) = std::str::from_utf8(bytes) {
s.serialize(serializer)
} else {
base64::Engine::encode(&base64::engine::general_purpose::STANDARD, bytes)
.serialize(serializer)
}
}
pub fn deserialize<'de, D>(deserializer: D) -> Result<Bytes, D::Error>
where
D: Deserializer<'de>,
{
let s = String::deserialize(deserializer)?;
if let Ok(decoded) =
base64::Engine::decode(&base64::engine::general_purpose::STANDARD, s.as_bytes())
{
if decoded != s.as_bytes() {
return Ok(Bytes::from(decoded));
}
}
Ok(Bytes::from(s.into_bytes()))
}
}
pub mod bytes_base64 {
use super::*;
pub fn serialize<S>(bytes: &Bytes, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
use serde::Serialize;
base64::Engine::encode(&base64::engine::general_purpose::STANDARD, bytes)
.serialize(serializer)
}
pub fn deserialize<'de, D>(deserializer: D) -> Result<Bytes, D::Error>
where
D: Deserializer<'de>,
{
use serde::de::Error;
let s = String::deserialize(deserializer)?;
base64::Engine::decode(&base64::engine::general_purpose::STANDARD, s.as_bytes())
.map(Bytes::from)
.map_err(D::Error::custom)
}
}
}
include!(concat!(env!("OUT_DIR"), "/synapse.rs"));
#[cfg(test)]
mod tests {
use super::serde_helpers::*;
use bytes::Bytes;
#[derive(serde::Serialize, serde::Deserialize, Debug)]
struct UuidWrapper {
#[serde(with = "uuid_string")]
id: Bytes,
}
#[derive(serde::Serialize, serde::Deserialize, Debug)]
struct PayloadWrapper {
#[serde(with = "payload_flexible")]
data: Bytes,
}
#[derive(serde::Serialize, serde::Deserialize, Debug)]
struct Base64Wrapper {
#[serde(with = "bytes_base64")]
data: Bytes,
}
#[test]
fn test_uuid_serialize_16_bytes() {
let bytes = Bytes::from(vec![
0x55, 0x0e, 0x84, 0x00, 0xe2, 0x9b, 0x41, 0xd4, 0xa7, 0x16, 0x44, 0x66, 0x55, 0x44,
0x00, 0x00,
]);
let wrapper = UuidWrapper { id: bytes };
let json = serde_json::to_string(&wrapper).unwrap();
assert!(json.contains("550e8400-e29b-41d4-a716-446655440000"));
}
#[test]
fn test_uuid_roundtrip() {
let original = Bytes::from(vec![
0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e,
0x0f, 0x10,
]);
let wrapper = UuidWrapper {
id: original.clone(),
};
let json = serde_json::to_string(&wrapper).unwrap();
let decoded: UuidWrapper = serde_json::from_str(&json).unwrap();
assert_eq!(original, decoded.id);
}
#[test]
fn test_uuid_non_16_bytes_falls_back_to_base64() {
let bytes = Bytes::from(vec![0x01, 0x02, 0x03]); let wrapper = UuidWrapper { id: bytes };
let json = serde_json::to_string(&wrapper).unwrap();
assert!(!json.contains('-'));
}
#[test]
fn test_uuid_deserialize_with_hyphens() {
let json = r#"{"id":"550e8400-e29b-41d4-a716-446655440000"}"#;
let wrapper: UuidWrapper = serde_json::from_str(json).unwrap();
assert_eq!(wrapper.id.len(), 16);
assert_eq!(wrapper.id[0], 0x55);
assert_eq!(wrapper.id[15], 0x00);
}
#[test]
fn test_uuid_deserialize_without_hyphens() {
let json = r#"{"id":"550e8400e29b41d4a716446655440000"}"#;
let wrapper: UuidWrapper = serde_json::from_str(json).unwrap();
assert_eq!(wrapper.id.len(), 16);
assert_eq!(wrapper.id[0], 0x55);
}
#[test]
fn test_payload_utf8_serializes_as_string() {
let wrapper = PayloadWrapper {
data: Bytes::from("hello world"),
};
let json = serde_json::to_string(&wrapper).unwrap();
assert!(json.contains("hello world"));
}
#[test]
fn test_payload_binary_serializes_as_base64() {
let wrapper = PayloadWrapper {
data: Bytes::from(vec![0xFF, 0xFE, 0x00, 0x01]),
};
let json = serde_json::to_string(&wrapper).unwrap();
assert!(!json.contains('\u{ffff}'));
}
#[test]
fn test_payload_plain_text_roundtrip() {
let wrapper = PayloadWrapper {
data: Bytes::from("{\"key\":\"value\"}"),
};
let json = serde_json::to_string(&wrapper).unwrap();
let decoded: PayloadWrapper = serde_json::from_str(&json).unwrap();
assert_eq!(decoded.data, Bytes::from("{\"key\":\"value\"}"));
}
#[test]
fn test_base64_roundtrip() {
let original = Bytes::from(vec![0x00, 0xFF, 0x42, 0x13, 0x37]);
let wrapper = Base64Wrapper {
data: original.clone(),
};
let json = serde_json::to_string(&wrapper).unwrap();
let decoded: Base64Wrapper = serde_json::from_str(&json).unwrap();
assert_eq!(original, decoded.data);
}
#[test]
fn test_base64_invalid_input() {
let json = r#"{"data":"not valid base64!!@@"}"#;
let result: Result<Base64Wrapper, _> = serde_json::from_str(json);
assert!(result.is_err());
}
}