pipa-lang 1.0.0-alpha.1

A tiny template language
Documentation
// SPDX-FileCopyrightText: Copyright 2026 olav@occy.org
// SPDX-License-Identifier: MPL-2.0

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"))
    }
}