saa_common/types/
bin.rs

1use core::fmt;
2use core::ops::Deref;
3use base64::engine::{Engine, GeneralPurpose};
4use serde::{de::{self, DeserializeOwned}, ser, Deserialize, Deserializer, Serialize};
5
6use crate::AuthError;
7
8#[derive(Clone, Default, PartialEq, Eq, Hash, PartialOrd, Ord)]
9#[cfg_attr(feature = "wasm", derive(
10    saa_schema::schemars::JsonSchema
11))]
12#[cfg_attr(feature = "substrate", derive(
13    saa_schema::scale::Encode, 
14    saa_schema::scale::Decode
15))]
16#[cfg_attr(feature = "solana", derive(
17    saa_schema::borsh::BorshSerialize, 
18    saa_schema::borsh::BorshDeserialize
19))]
20#[cfg_attr(all(feature = "std", feature="substrate"), derive(
21    saa_schema::scale_info::TypeInfo)
22)]
23pub struct Binary(
24    #[cfg_attr(feature = "wasm", schemars(with = "String"))]
25    Vec<u8>
26);
27
28impl Binary {
29    /// Creates a new `Binary` containing the given data.
30    pub const fn new(data: Vec<u8>) -> Self {
31        Self(data)
32    }
33
34    /// Base64 encoding engine used in conversion to/from base64.
35    ///
36    /// The engine adds padding when encoding and accepts strings with or
37    /// without padding when decoding.
38    const B64_ENGINE: GeneralPurpose = GeneralPurpose::new(
39        &base64::alphabet::STANDARD,
40        base64::engine::GeneralPurposeConfig::new()
41            .with_decode_padding_mode(base64::engine::DecodePaddingMode::Indifferent),
42    );
43
44    /// take an (untrusted) string and decode it into bytes.
45    /// fails if it is not valid base64
46    pub fn from_base64(encoded: &str) -> Result<Self, AuthError> {
47        Self::B64_ENGINE
48            .decode(encoded.as_bytes())
49            .map(Binary::from)
50            .map_err(|_| AuthError::generic("invalid base64"))
51    }
52
53    /// encode to base64 string (guaranteed to be success as we control the data inside).
54    /// this returns normalized form (with trailing = if needed)
55    pub fn to_base64(&self) -> String {
56        Self::B64_ENGINE.encode(self.0.as_slice())
57    }
58
59    pub fn as_slice(&self) -> &[u8] {
60        self.0.as_slice()
61    }
62
63    pub fn to_array<const LENGTH: usize>(&self) -> Result<[u8; LENGTH], AuthError> {
64        if self.len() != LENGTH {
65            return Err(AuthError::InvalidLength("Binary".to_string(), LENGTH as u16, self.len() as u16));
66        }
67
68        let mut out: [u8; LENGTH] = [0; LENGTH];
69        out.copy_from_slice(&self.0);
70        Ok(out)
71    }
72}
73
74impl fmt::Display for Binary {
75    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
76        write!(f, "{}", self.to_base64())
77    }
78}
79
80impl fmt::Debug for Binary {
81    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
82        // Use an output inspired by tuples (https://doc.rust-lang.org/std/fmt/struct.Formatter.html#method.debug_tuple)
83        // but with a custom implementation to avoid the need for an intemediate hex string.
84        write!(f, "Binary(")?;
85        for byte in self.0.iter() {
86            write!(f, "{byte:02x}")?;
87        }
88        write!(f, ")")?;
89        Ok(())
90    }
91}
92
93/// Just like Vec<u8>, Binary is a smart pointer to [u8].
94/// This implements `*binary` for us and allows us to
95/// do `&*binary`, returning a `&[u8]` from a `&Binary`.
96/// With [deref coercions](https://doc.rust-lang.org/1.22.1/book/first-edition/deref-coercions.html#deref-coercions),
97/// this allows us to use `&binary` whenever a `&[u8]` is required.
98impl Deref for Binary {
99    type Target = [u8];
100
101    fn deref(&self) -> &Self::Target {
102        self.as_slice()
103    }
104}
105
106impl AsRef<[u8]> for Binary {
107    fn as_ref(&self) -> &[u8] {
108        self.as_slice()
109    }
110}
111
112// Slice
113impl From<&[u8]> for Binary {
114    fn from(binary: &[u8]) -> Self {
115        Self(binary.to_vec())
116    }
117}
118
119// Array reference
120impl<const LENGTH: usize> From<&[u8; LENGTH]> for Binary {
121    fn from(source: &[u8; LENGTH]) -> Self {
122        Self(source.to_vec())
123    }
124}
125
126// Owned array
127impl<const LENGTH: usize> From<[u8; LENGTH]> for Binary {
128    fn from(source: [u8; LENGTH]) -> Self {
129        Self(source.into())
130    }
131}
132
133impl From<Vec<u8>> for Binary {
134    fn from(vec: Vec<u8>) -> Self {
135        Self(vec)
136    }
137}
138
139impl From<Binary> for Vec<u8> {
140    fn from(original: Binary) -> Vec<u8> {
141        original.0
142    }
143}
144
145/// Implement `encoding::Binary == alloc::vec::Vec<u8>`
146impl PartialEq<Vec<u8>> for Binary {
147    fn eq(&self, rhs: &Vec<u8>) -> bool {
148        // Use Vec<u8> == Vec<u8>
149        self.0 == *rhs
150    }
151}
152
153/// Implement `alloc::vec::Vec<u8> == encoding::Binary`
154impl PartialEq<Binary> for Vec<u8> {
155    fn eq(&self, rhs: &Binary) -> bool {
156        // Use Vec<u8> == Vec<u8>
157        *self == rhs.0
158    }
159}
160
161/// Implement `Binary == &[u8]`
162impl PartialEq<&[u8]> for Binary {
163    fn eq(&self, rhs: &&[u8]) -> bool {
164        // Use &[u8] == &[u8]
165        self.as_slice() == *rhs
166    }
167}
168
169/// Implement `&[u8] == Binary`
170impl PartialEq<Binary> for &[u8] {
171    fn eq(&self, rhs: &Binary) -> bool {
172        // Use &[u8] == &[u8]
173        *self == rhs.as_slice()
174    }
175}
176
177/// Implement `Binary == &[u8; LENGTH]`
178impl<const LENGTH: usize> PartialEq<&[u8; LENGTH]> for Binary {
179    fn eq(&self, rhs: &&[u8; LENGTH]) -> bool {
180        self.as_slice() == rhs.as_slice()
181    }
182}
183
184/// Implement `&[u8; LENGTH] == Binary`
185impl<const LENGTH: usize> PartialEq<Binary> for &[u8; LENGTH] {
186    fn eq(&self, rhs: &Binary) -> bool {
187        self.as_slice() == rhs.as_slice()
188    }
189}
190
191/// Implement `Binary == [u8; LENGTH]`
192impl<const LENGTH: usize> PartialEq<[u8; LENGTH]> for Binary {
193    fn eq(&self, rhs: &[u8; LENGTH]) -> bool {
194        self.as_slice() == rhs.as_slice()
195    }
196}
197
198/// Implement `[u8; LENGTH] == Binary`
199impl<const LENGTH: usize> PartialEq<Binary> for [u8; LENGTH] {
200    fn eq(&self, rhs: &Binary) -> bool {
201        self.as_slice() == rhs.as_slice()
202    }
203}
204
205/// Serializes as a base64 string
206impl Serialize for Binary {
207    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
208    where
209        S: ser::Serializer,
210    {
211        if serializer.is_human_readable() {
212            serializer.serialize_str(&self.to_base64())
213        } else {
214            panic!("Binary is only intended to be used with JSON serialization for now. If you are hitting this panic please open an issue at https://github.com/CosmWasm/cosmwasm describing your use case.")
215        }
216    }
217}
218
219/// Deserializes as a base64 string
220impl<'de> Deserialize<'de> for Binary {
221    fn deserialize<D>(deserializer: D) -> Result<Binary, D::Error>
222    where
223        D: Deserializer<'de>,
224    {
225        if deserializer.is_human_readable() {
226            deserializer.deserialize_str(Base64Visitor)
227        } else {
228            panic!("Binary is only intended to be used with JSON serialization for now. If you are hitting this panic please open an issue at https://github.com/CosmWasm/cosmwasm describing your use case.")
229        }
230    }
231}
232
233
234
235struct Base64Visitor;
236
237impl<'de> de::Visitor<'de> for Base64Visitor {
238    type Value = Binary;
239
240    fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
241        formatter.write_str("valid base64 encoded string")
242    }
243
244    fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
245    where
246        E: de::Error,
247    {
248        match Binary::from_base64(v) {
249            Ok(binary) => Ok(binary),
250            Err(_) => Err(E::custom(format!("invalid base64: {v}"))),
251        }
252    }
253}
254
255
256
257pub fn to_json_binary<T>(data: &T) -> Result<Binary, AuthError>
258where
259    T: Serialize + ?Sized,
260{   
261    serde_json_wasm::to_vec(data).map_err(|e| AuthError::generic(e.to_string())).map(Binary)
262}
263
264
265
266pub fn from_json<T: DeserializeOwned>(value: impl AsRef<[u8]>) -> Result<T, AuthError> {
267    serde_json_wasm::from_slice(value.as_ref())
268        .map_err(|e| AuthError::generic(e.to_string()))
269}
270
271
272pub fn to_json_string<T>(data: &T) -> Result<String, AuthError>
273where T: Serialize + ?Sized,{
274    serde_json_wasm::to_string(data).map_err(|e| AuthError::generic(e.to_string()))
275}