use base64::{Engine as _, engine::general_purpose};
use serde::{Deserialize, Deserializer, Serializer};
pub fn serialize<S: Serializer>(bytes: &[u8], ser: S) -> Result<S::Ok, S::Error> {
ser.serialize_str(&general_purpose::STANDARD.encode(bytes))
}
pub fn deserialize<'de, D: Deserializer<'de>>(de: D) -> Result<Vec<u8>, D::Error> {
let s = String::deserialize(de)?;
general_purpose::STANDARD
.decode(s.as_bytes())
.map_err(serde::de::Error::custom)
}
pub mod string {
use base64::{Engine as _, engine::general_purpose};
use serde::{Deserialize, Deserializer, Serializer};
pub fn serialize<S: Serializer>(value: &str, ser: S) -> Result<S::Ok, S::Error> {
ser.serialize_str(&general_purpose::STANDARD.encode(value.as_bytes()))
}
pub fn deserialize<'de, D: Deserializer<'de>>(de: D) -> Result<String, D::Error> {
let encoded = String::deserialize(de)?;
let bytes = general_purpose::STANDARD
.decode(encoded.as_bytes())
.map_err(serde::de::Error::custom)?;
String::from_utf8(bytes).map_err(serde::de::Error::custom)
}
}
#[cfg(test)]
mod tests {
use serde::{Deserialize, Serialize};
#[derive(Serialize, Deserialize, PartialEq, Debug)]
struct Blob {
#[serde(with = "super")]
data: Vec<u8>,
}
#[test]
fn round_trips_bytes_as_base64_string() {
let blob = Blob {
data: vec![0, 1, 2, 250, 255],
};
let json = serde_json::to_string(&blob).unwrap();
assert_eq!(json, r#"{"data":"AAEC+v8="}"#);
let back: Blob = serde_json::from_str(&json).unwrap();
assert_eq!(back, blob);
}
#[test]
fn rejects_invalid_base64() {
let err = serde_json::from_str::<Blob>(r#"{"data":"not base64!!"}"#);
assert!(err.is_err());
}
#[test]
fn opaque_string_round_trips_and_rejects_non_utf8() {
#[derive(Serialize, Deserialize, PartialEq, Debug)]
struct Opaque {
#[serde(with = "super::string")]
value: String,
}
let value = Opaque {
value: "eyJopaque.payload.signature".to_string(),
};
let json = serde_json::to_string(&value).unwrap();
assert!(!json.contains("eyJopaque.payload.signature"));
assert_eq!(serde_json::from_str::<Opaque>(&json).unwrap(), value);
assert!(serde_json::from_str::<Opaque>(r#"{"value":"/w=="}"#).is_err());
}
}