hermit_toolkit_serialization/
base64.rs1use std::fmt;
2use std::marker::PhantomData;
3
4use serde::{de, ser};
5
6use cosmwasm_std::Binary;
7
8use crate::Serde;
9
10pub type Base64 = Binary;
12
13pub struct Base64Of<S: Serde, T> {
20 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 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
112mod 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 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 impl<S: Serde, T: Copy> Copy for Base64Of<S, T> {}
142
143 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 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 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 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 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 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 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 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}