Skip to main content

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