pub mod base64_bytes {
use base64::engine::general_purpose::STANDARD;
use base64::Engine as _;
use serde::{Deserialize, Deserializer, Serialize, Serializer};
pub fn serialize<S>(bytes: &[u8], serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
STANDARD.encode(bytes).serialize(serializer)
}
pub fn deserialize<'de, D>(deserializer: D) -> Result<Vec<u8>, D::Error>
where
D: Deserializer<'de>,
{
let encoded = String::deserialize(deserializer)?;
STANDARD.decode(encoded.as_bytes()).map_err(serde::de::Error::custom)
}
}
#[cfg(test)]
mod tests {
use super::base64_bytes;
#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
struct Wrapper {
#[serde(with = "base64_bytes")]
data: Vec<u8>,
}
#[test]
fn round_trips_through_json() {
let original = Wrapper {
data: vec![0, 1, 2, 3, 254, 255],
};
let json = serde_json::to_string(&original).unwrap();
let decoded: Wrapper = serde_json::from_str(&json).unwrap();
assert_eq!(decoded, original);
}
#[test]
fn serializes_as_compact_base64_string_not_number_array() {
let wrapper = Wrapper {
data: vec![0, 1, 2, 255],
};
let json = serde_json::to_string(&wrapper).unwrap();
assert_eq!(json, r#"{"data":"AAEC/w=="}"#);
assert!(!json.contains('['));
}
#[test]
fn rejects_invalid_base64() {
let bad_json = r#"{"data":"not valid base64!!"}"#;
let result: Result<Wrapper, _> = serde_json::from_str(bad_json);
assert!(result.is_err());
}
}