Skip to main content

ploidy_util/
binary.rs

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