hermit_toolkit_serialization/
base64.rs

1use std::fmt;
2use std::marker::PhantomData;
3
4use serde::{de, ser};
5
6use cosmwasm_std::Binary;
7
8use crate::Serde;
9
10/// Alias of `cosmwasm_std::Binary` for better naming
11pub type Base64 = Binary;
12
13/// A wrapper that automatically deserializes base64 strings to `T` using
14/// one of the `Serde` types.
15/// Use it as a field of your Handle messages (input and output), for
16/// example in the `msg` field of the `Receive` interface, to remove the
17/// boilerplate of serializing or deserializing the `Binary` to the relevant
18/// type `T`.
19pub struct Base64Of<S: Serde, T> {
20    // This is pub so that users can easily unwrap this if needed,
21    // or just swap the entire instance.
22    pub inner: T,
23    ser: PhantomData<S>,
24}
25
26#[cfg(feature = "json")]
27pub type Base64JsonOf<T> = Base64Of<crate::Json, T>;
28
29#[cfg(feature = "bincode2")]
30pub type Base64Bincode2Of<T> = Base64Of<crate::Bincode2, T>;
31
32impl<S: Serde, T> From<T> for Base64Of<S, T> {
33    fn from(other: T) -> Self {
34        Self {
35            inner: other,
36            ser: PhantomData,
37        }
38    }
39}
40
41impl<S: Serde, T> std::ops::Deref for Base64Of<S, T> {
42    type Target = T;
43    fn deref(&self) -> &Self::Target {
44        &self.inner
45    }
46}
47
48impl<S: Serde, T> std::ops::DerefMut for Base64Of<S, T> {
49    fn deref_mut(&mut self) -> &mut Self::Target {
50        &mut self.inner
51    }
52}
53
54impl<Ser: Serde, T: ser::Serialize> ser::Serialize for Base64Of<Ser, T> {
55    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
56        where
57            S: ser::Serializer,
58    {
59        let string = match Ser::serialize(&self.inner) {
60            Ok(b) => Binary(b).to_base64(),
61            Err(err) => return Err(<S::Error as ser::Error>::custom(err)),
62        };
63        serializer.serialize_str(&string)
64    }
65}
66
67impl<'de, S: Serde, T: for<'des> de::Deserialize<'des>> de::Deserialize<'de> for Base64Of<S, T> {
68    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
69        where
70            D: de::Deserializer<'de>,
71    {
72        deserializer.deserialize_str(Base64TVisitor::<S, T>::new())
73    }
74}
75
76struct Base64TVisitor<S: Serde, T> {
77    inner: PhantomData<T>,
78    ser: PhantomData<S>,
79}
80
81impl<S: Serde, T> Base64TVisitor<S, T> {
82    fn new() -> Self {
83        Self {
84            inner: PhantomData,
85            ser: PhantomData,
86        }
87    }
88}
89
90impl<'de, S: Serde, T: for<'des> de::Deserialize<'des>> de::Visitor<'de> for Base64TVisitor<S, T> {
91    type Value = Base64Of<S, T>;
92
93    fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
94        formatter.write_str("valid base64 encoded string")
95    }
96
97    fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
98        where
99            E: de::Error,
100    {
101        let binary = Base64::from_base64(v).map_err(|_| {
102            //
103            E::custom(format!("invalid base64: {}", v))
104        })?;
105        match S::deserialize::<T>(binary.as_slice()) {
106            Ok(val) => Ok(Base64Of::from(val)),
107            Err(err) => Err(E::custom(err)),
108        }
109    }
110}
111
112/// These traits are conditionally implemented for Base64Of<S, T>
113/// if T implements the trait being implemented.
114mod passthrough_impls {
115    use std::cmp::Ordering;
116    use std::fmt::{Debug, Display, Formatter};
117    use std::hash::{Hash, Hasher};
118    use std::marker::PhantomData;
119
120    use schemars::gen::SchemaGenerator;
121    use schemars::schema::Schema;
122    use schemars::JsonSchema;
123
124    use cosmwasm_std::Binary;
125
126    use crate::Serde;
127
128    use super::Base64Of;
129
130    // Clone
131    impl<S: Serde, T: Clone> Clone for Base64Of<S, T> {
132        fn clone(&self) -> Self {
133            Self {
134                inner: self.inner.clone(),
135                ser: self.ser,
136            }
137        }
138    }
139
140    // Copy
141    impl<S: Serde, T: Copy> Copy for Base64Of<S, T> {}
142
143    // Debug
144    impl<S: Serde, T: Debug> Debug for Base64Of<S, T> {
145        fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
146            self.inner.fmt(f)
147        }
148    }
149
150    // Display
151    impl<S: Serde, T: Display> Display for Base64Of<S, T> {
152        fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
153            self.inner.fmt(f)
154        }
155    }
156
157    // PartialEq
158    impl<S: Serde, S2: Serde, T: PartialEq> PartialEq<Base64Of<S2, T>> for Base64Of<S, T> {
159        fn eq(&self, other: &Base64Of<S2, T>) -> bool {
160            self.inner.eq(&other.inner)
161        }
162    }
163
164    impl<S: Serde, T: PartialEq> PartialEq<T> for Base64Of<S, T> {
165        fn eq(&self, other: &T) -> bool {
166            self.inner.eq(other)
167        }
168    }
169
170    // Eq
171    // This implementation is not possible because the `S: Ser` type parameter
172    // shouldn't matter in the `PartialEq` implementation, but `Eq` demands
173    // that Rhs is Self, and Rust doesn't recognize that the `PartialEq` impl
174    // covers that case already. Basically it doesn't understand that S1 and S2
175    // _can_ be the same type.
176    //
177    // impl<S: Serde, T: Eq> Eq for Base64Of<S, T> {}
178
179    // PartialOrd
180    impl<S: Serde, S2: Serde, T: PartialOrd> PartialOrd<Base64Of<S2, T>> for Base64Of<S, T> {
181        fn partial_cmp(&self, other: &Base64Of<S2, T>) -> Option<Ordering> {
182            self.inner.partial_cmp(&other.inner)
183        }
184    }
185
186    impl<S: Serde, T: PartialOrd> PartialOrd<T> for Base64Of<S, T> {
187        fn partial_cmp(&self, other: &T) -> Option<Ordering> {
188            self.inner.partial_cmp(other)
189        }
190    }
191
192    // Ord
193    // This can not be implemented for the same reason that `Eq` can't be implemented.
194
195    // Hash
196    impl<S: Serde, T: Hash> Hash for Base64Of<S, T> {
197        fn hash<H: Hasher>(&self, state: &mut H) {
198            self.inner.hash(state)
199        }
200    }
201
202    // Default
203    impl<S: Serde, T: Default> Default for Base64Of<S, T> {
204        fn default() -> Self {
205            Self {
206                inner: T::default(),
207                ser: PhantomData,
208            }
209        }
210    }
211
212    // JsonSchema
213    impl<S: Serde, T: JsonSchema> JsonSchema for Base64Of<S, T> {
214        fn schema_name() -> String {
215            Binary::schema_name()
216        }
217
218        fn json_schema(gen: &mut SchemaGenerator) -> Schema {
219            Binary::json_schema(gen)
220        }
221    }
222}
223
224#[cfg(test)]
225mod test {
226    use schemars::JsonSchema;
227    use serde::{Deserialize, Serialize};
228
229    use cosmwasm_schema::schema_for;
230    use cosmwasm_std::{Binary, StdResult};
231
232    use crate::base64::Base64JsonOf;
233
234    #[derive(Deserialize, Serialize, PartialEq, Debug, JsonSchema)]
235    struct Foo {
236        bar: String,
237        baz: u32,
238    }
239
240    impl Foo {
241        fn new() -> Self {
242            Self {
243                bar: String::from("some stuff"),
244                baz: 234,
245            }
246        }
247    }
248
249    #[derive(Deserialize, Serialize, PartialEq, Debug, JsonSchema)]
250    struct Wrapper {
251        inner: Base64JsonOf<Foo>,
252    }
253
254    impl Wrapper {
255        fn new() -> Self {
256            Self {
257                inner: Base64JsonOf::from(Foo::new()),
258            }
259        }
260    }
261
262    #[test]
263    fn test_serialize() -> StdResult<()> {
264        let serialized = cosmwasm_std::to_vec(&Base64JsonOf::from(Foo::new()))?;
265        let serialized2 =
266            cosmwasm_std::to_vec(&Binary::from(b"{\"bar\":\"some stuff\",\"baz\":234}"))?;
267        assert_eq!(
268            br#""eyJiYXIiOiJzb21lIHN0dWZmIiwiYmF6IjoyMzR9""#[..],
269            serialized
270        );
271        assert_eq!(serialized, serialized2);
272
273        let serialized3 = cosmwasm_std::to_vec(&Wrapper::new())?;
274        assert_eq!(
275            br#"{"inner":"eyJiYXIiOiJzb21lIHN0dWZmIiwiYmF6IjoyMzR9"}"#[..],
276            serialized3
277        );
278
279        Ok(())
280    }
281
282    #[test]
283    fn test_deserialize() -> StdResult<()> {
284        let obj: Base64JsonOf<Foo> =
285            cosmwasm_std::from_slice(&br#""eyJiYXIiOiJzb21lIHN0dWZmIiwiYmF6IjoyMzR9""#[..])?;
286        assert_eq!(obj, Foo::new());
287
288        let obj2: Wrapper = cosmwasm_std::from_slice(
289            &br#"{"inner":"eyJiYXIiOiJzb21lIHN0dWZmIiwiYmF6IjoyMzR9"}"#[..],
290        )?;
291        assert_eq!(obj2, Wrapper::new());
292        assert_eq!(obj2.inner, Foo::new());
293
294        Ok(())
295    }
296
297    #[test]
298    fn test_schema() {
299        let schema = schema_for!(Wrapper);
300        let pretty = serde_json::to_string_pretty(&schema).unwrap();
301        println!("{}", pretty);
302        println!("{:#?}", schema);
303
304        assert_eq!(schema.schema.metadata.unwrap().title.unwrap(), "Wrapper");
305        let object = schema.schema.object.unwrap();
306        let required = object.required;
307        assert_eq!(required.len(), 1);
308        assert!(required.contains("inner"));
309
310        // This checks that the schema sees the Base64Of field as a Binary object
311        if let schemars::schema::Schema::Object(ref obj) = object.properties["inner"] {
312            assert_eq!(obj.reference.as_ref().unwrap(), "#/definitions/Binary")
313        } else {
314            panic!("unexpected schema");
315        }
316    }
317}