use base64::Engine;
use base64::engine::general_purpose::STANDARD;
use serde::Deserialize;
use serde::Deserializer;
use serde::Serializer;
use serde::de;
#[expect(clippy::ref_option)]
pub(crate) fn serialize<S: Serializer>(
bytes: &Option<Vec<u8>>,
serializer: S,
) -> Result<S::Ok, S::Error> {
serializer.serialize_str(&STANDARD.encode(bytes.as_deref().unwrap_or_default()))
}
pub(crate) fn deserialize<'de, D: Deserializer<'de>>(
deserializer: D,
) -> Result<Option<Vec<u8>>, D::Error> {
Option::<&str>::deserialize(deserializer)?
.map(|s| STANDARD.decode(s))
.transpose()
.map_err(de::Error::custom)
}
#[cfg(test)]
mod test {
use serde::Deserialize;
use serde::Serialize;
#[derive(Debug, PartialEq, Eq, Serialize, Deserialize)]
struct Test {
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(default, with = "crate::serde::bytes_as_base64_opt")]
pub(crate) data: Option<Vec<u8>>,
}
#[test]
fn test_serialize() {
assert_eq!(ser(&Test { data: None }), "{}");
assert_eq!(ser(&Test { data: empty() }), r#"{"data":""}"#);
assert_eq!(ser(&Test { data: nonempty() }), r#"{"data":"MQ=="}"#);
}
#[test]
fn test_deserialize() {
assert_eq!(de("{}"), Test { data: None });
assert_eq!(de(r#"{"data":""}"#), Test { data: empty() });
assert_eq!(de(r#"{"data":"MQ=="}"#), Test { data: nonempty() });
}
#[track_caller]
fn ser(value: &Test) -> String {
serde_json::ser::to_string(value).unwrap()
}
#[track_caller]
fn de(value: &str) -> Test {
serde_json::de::from_str::<Test>(value).unwrap()
}
#[track_caller]
fn empty() -> Option<Vec<u8>> {
Some(Vec::default())
}
#[track_caller]
fn nonempty() -> Option<Vec<u8>> {
Some(Vec::from(b"1"))
}
}