ploidy_util/
binary.rs

1use base64::Engine;
2use serde::{Deserialize, Deserializer, Serialize, Serializer, de};
3
4/// A wrapper around a [`Vec<u8>`] that serializes and deserializes
5/// OpenAPI `byte` strings, which encode binary data as Base64.
6#[derive(Clone, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd)]
7pub struct Base64(Vec<u8>);
8
9impl Base64 {
10    #[inline]
11    pub fn into_vec(self) -> Vec<u8> {
12        self.0
13    }
14}
15
16impl AsRef<[u8]> for Base64 {
17    #[inline]
18    fn as_ref(&self) -> &[u8] {
19        &self.0
20    }
21}
22
23impl AsMut<[u8]> for Base64 {
24    #[inline]
25    fn as_mut(&mut self) -> &mut [u8] {
26        &mut self.0
27    }
28}
29
30impl From<Vec<u8>> for Base64 {
31    #[inline]
32    fn from(value: Vec<u8>) -> Self {
33        Self(value)
34    }
35}
36
37impl From<&[u8]> for Base64 {
38    #[inline]
39    fn from(value: &[u8]) -> Self {
40        Self(value.to_vec())
41    }
42}
43
44impl Serialize for Base64 {
45    #[inline]
46    fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
47        let encoded = base64::engine::general_purpose::STANDARD.encode(&self.0);
48        serializer.serialize_str(&encoded)
49    }
50}
51
52impl<'de> Deserialize<'de> for Base64 {
53    #[inline]
54    fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
55        use de::Error;
56        let value: &'de str = Deserialize::deserialize(deserializer)?;
57        let decoded = base64::engine::general_purpose::STANDARD
58            .decode(value)
59            .map_err(|err| D::Error::custom(Base64Error(err)))?;
60        Ok(Base64(decoded))
61    }
62}
63
64#[derive(Debug, thiserror::Error)]
65#[error("byte string contains invalid Base64: {0}")]
66pub struct Base64Error(#[from] base64::DecodeError);
67
68#[cfg(test)]
69mod tests {
70    use super::*;
71
72    #[test]
73    fn test_serialize_empty() {
74        let byte = Base64::default();
75        let json = serde_json::to_string(&byte).unwrap();
76        assert_eq!(json, r#""""#);
77    }
78
79    #[test]
80    fn test_serialize_text_data() {
81        let byte = Base64::from(b"Hello, World!".as_slice());
82        let json = serde_json::to_string(&byte).unwrap();
83        assert_eq!(json, r#""SGVsbG8sIFdvcmxkIQ==""#);
84    }
85
86    #[test]
87    fn test_serialize_binary_data() {
88        let byte = Base64::from(vec![0x00, 0x01, 0x02, 0xff, 0xfe, 0xfd]);
89        let json = serde_json::to_string(&byte).unwrap();
90        assert_eq!(json, r#""AAEC//79""#);
91    }
92
93    #[test]
94    fn test_deserialize_empty() {
95        let byte: Base64 = serde_json::from_str(r#""""#).unwrap();
96        assert_eq!(byte.as_ref(), b"");
97    }
98
99    #[test]
100    fn test_deserialize_text_data() {
101        let byte: Base64 = serde_json::from_str(r#""SGVsbG8sIFdvcmxkIQ==""#).unwrap();
102        assert_eq!(byte.as_ref(), b"Hello, World!");
103    }
104
105    #[test]
106    fn test_deserialize_binary_data() {
107        let byte: Base64 = serde_json::from_str(r#""AAEC//79""#).unwrap();
108        assert_eq!(byte.as_ref(), &[0x00, 0x01, 0x02, 0xff, 0xfe, 0xfd]);
109    }
110
111    #[test]
112    fn test_deserialize_invalid_base64() {
113        let result: Result<Base64, _> = serde_json::from_str(r#""not valid base64!!!""#);
114        assert!(result.is_err());
115    }
116
117    #[test]
118    fn test_roundtrip() {
119        let original = Base64::from(vec![0x00, 0x7f, 0x80, 0xff, 0x42]);
120        let json = serde_json::to_string(&original).unwrap();
121        let restored: Base64 = serde_json::from_str(&json).unwrap();
122        assert_eq!(original, restored);
123    }
124}