wasm_bindgen_utils/
macros.rs1#[macro_export]
28macro_rules! impl_main_wasm_traits {
29 ($type_name:ident $(< $($generics:ident),+ >)?) => {
30 impl$(<$($generics),+>)? $type_name$(<$($generics),+>)?
31 $(where $($generics: serde::Serialize + for<'de> serde::Deserialize<'de>, )+ )? {
32 const TYPE_NAME: &'static str = stringify!($type_name);
33 pub fn try_into_js_value(&self) -> Result<$crate::prelude::JsValue, $crate::prelude::serde_wasm_bindgen::Error> {
36 $crate::prelude::to_js_value(&self)
37 }
38 pub fn try_from_js_value(js: $crate::prelude::JsValue) -> Result<Self, $crate::prelude::serde_wasm_bindgen::Error> {
41 $crate::prelude::from_js_value(js)
42 }
43 }
44 impl$(<$($generics),+>)? $crate::prelude::wasm_bindgen::describe::WasmDescribe for $type_name$(<$($generics),+>)? {
45 #[inline]
46 fn describe() {
47 <Self as $crate::prelude::Tsify>::JsType::describe()
48 }
49 }
50 impl$(<$($generics),+>)? $crate::prelude::wasm_bindgen::convert::IntoWasmAbi for $type_name$(<$($generics),+>)?
51 $(where $($generics: serde::Serialize + for<'de> serde::Deserialize<'de>, )+ )? {
52 type Abi = <<Self as $crate::prelude::Tsify>::JsType as $crate::prelude::wasm_bindgen::convert::IntoWasmAbi>::Abi;
53
54 #[inline]
55 fn into_abi(self) -> Self::Abi {
56 let mut err = String::new();
57 err.push_str(Self::TYPE_NAME);
58 err.push_str(": ");
59 let result = self.try_into_js_value().map(<<Self as $crate::prelude::Tsify>::JsType as $crate::prelude::JsCast>::unchecked_from_js);
60 $crate::prelude::UnwrapThrowExt::expect_throw(result.inspect_err(|e| err.push_str(&e.to_string())), &err).into_abi()
61 }
62 }
63 impl$(<$($generics),+>)? $crate::prelude::wasm_bindgen::convert::OptionIntoWasmAbi for $type_name$(<$($generics),+>)?
64 $(where $($generics: serde::Serialize + for<'de> serde::Deserialize<'de>, )+ )? {
65 #[inline]
66 fn none() -> Self::Abi {
67 <<Self as $crate::prelude::Tsify>::JsType as $crate::prelude::wasm_bindgen::convert::OptionIntoWasmAbi>::none()
68 }
69 }
70 impl$(<$($generics),+>)? $crate::prelude::wasm_bindgen::convert::FromWasmAbi for $type_name$(<$($generics),+>)?
71 $(where $($generics: serde::Serialize + for<'de> serde::Deserialize<'de>, )+ )? {
72 type Abi = <<Self as $crate::prelude::Tsify>::JsType as $crate::prelude::wasm_bindgen::convert::FromWasmAbi>::Abi;
73
74 #[inline]
75 unsafe fn from_abi(js: Self::Abi) -> Self {
76 let mut err = String::new();
77 err.push_str(Self::TYPE_NAME);
78 err.push_str(": ");
79 let result = Self::try_from_js_value(<Self as $crate::prelude::Tsify>::JsType::from_abi(js).into());
80 $crate::prelude::UnwrapThrowExt::expect_throw(result.inspect_err(|e| err.push_str(&e.to_string())), &err)
81 }
82 }
83 impl$(<$($generics),+>)? $crate::prelude::wasm_bindgen::convert::OptionFromWasmAbi for $type_name$(<$($generics),+>)?
84 $(where $($generics: serde::Serialize + for<'de> serde::Deserialize<'de>, )+ )? {
85 #[inline]
86 fn is_none(js: &Self::Abi) -> bool {
87 <<Self as $crate::prelude::Tsify>::JsType as $crate::prelude::wasm_bindgen::convert::OptionFromWasmAbi>::is_none(js)
88 }
89 }
90 };
91}
92
93#[macro_export]
117macro_rules! impl_complementary_wasm_traits {
118 ($type_name:ident $(< $($generics:ident),+ >)?) => {
119 impl$(<$($generics),+>)? $crate::prelude::wasm_bindgen::convert::RefFromWasmAbi for $type_name$(<$($generics),+>)?
120 $(where $($generics: serde::Serialize + for<'de> serde::Deserialize<'de>, )+ )? {
121 type Abi = <<Self as $crate::prelude::Tsify>::JsType as $crate::prelude::wasm_bindgen::convert::RefFromWasmAbi>::Abi;
122 type Anchor = Box<Self>;
123 unsafe fn ref_from_abi(js: Self::Abi) -> Self::Anchor {
124 Box::new(<Self as $crate::prelude::wasm_bindgen::convert::FromWasmAbi>::from_abi(js))
125 }
126 }
127 impl$(<$($generics),+>)? $crate::prelude::wasm_bindgen::convert::LongRefFromWasmAbi for $type_name$(<$($generics),+>)?
128 $(where $($generics: serde::Serialize + for<'de> serde::Deserialize<'de>, )+ )? {
129 type Abi = <<Self as $crate::prelude::Tsify>::JsType as $crate::prelude::wasm_bindgen::convert::LongRefFromWasmAbi>::Abi;
130 type Anchor = Box<Self>;
131 unsafe fn long_ref_from_abi(js: Self::Abi) -> Self::Anchor {
132 Box::new(<Self as $crate::prelude::wasm_bindgen::convert::FromWasmAbi>::from_abi(js))
133 }
134 }
135 impl$(<$($generics),+>)? $crate::prelude::wasm_bindgen::convert::VectorIntoWasmAbi for $type_name$(<$($generics),+>)?
136 $(where $($generics: serde::Serialize + for<'de> serde::Deserialize<'de>, )+ )? {
137 type Abi = <Box<[<Self as $crate::prelude::Tsify>::JsType]> as $crate::prelude::wasm_bindgen::convert::IntoWasmAbi>::Abi;
138 fn vector_into_abi(vector: Box<[Self]>) -> Self::Abi {
139 $crate::prelude::wasm_bindgen::convert::js_value_vector_into_abi(vector)
140 }
141 }
142 impl$(<$($generics),+>)? $crate::prelude::wasm_bindgen::convert::VectorFromWasmAbi for $type_name$(<$($generics),+>)?
143 $(where $($generics: serde::Serialize + for<'de> serde::Deserialize<'de>, )+ )? {
144 type Abi = <Box<[<Self as $crate::prelude::Tsify>::JsType]> as $crate::prelude::wasm_bindgen::convert::FromWasmAbi>::Abi;
145 unsafe fn vector_from_abi(js: Self::Abi) -> Box<[Self]> {
146 $crate::prelude::wasm_bindgen::convert::js_value_vector_from_abi(js)
147 }
148 }
149 impl$(<$($generics),+>)? $crate::prelude::wasm_bindgen::describe::WasmDescribeVector for $type_name$(<$($generics),+>)? {
150 fn describe_vector() {
151 $crate::prelude::wasm_bindgen::describe::inform($crate::prelude::wasm_bindgen::describe::VECTOR);
152 <Self as $crate::prelude::wasm_bindgen::describe::WasmDescribe>::describe();
153 }
154 }
155 impl$(<$($generics),+>)? From<$type_name$(<$($generics),+>)?> for $crate::prelude::JsValue
156 $(where $($generics: serde::Serialize + for<'de> serde::Deserialize<'de>, )+ )? {
157 fn from(value: $type_name$(<$($generics),+>)?) -> Self {
158 let mut err = String::new();
159 err.push_str(<$type_name$(<$($generics),+>)?>::TYPE_NAME);
160 err.push_str(": ");
161 let result = value.try_into_js_value();
162 $crate::prelude::UnwrapThrowExt::expect_throw(
163 result.inspect_err(|e| err.push_str(&e.to_string())),
164 &err,
165 )
166 }
167 }
168 impl$(<$($generics),+>)? $crate::prelude::wasm_bindgen::convert::TryFromJsValue for $type_name$(<$($generics),+>)?
169 $(where $($generics: serde::Serialize + for<'de> serde::Deserialize<'de>, )+ )? {
170 fn try_from_js_value_ref(value: &$crate::prelude::JsValue) -> Option<Self> {
171 Self::try_from_js_value(value.clone()).ok()
172 }
173 }
174 };
175}
176
177#[macro_export]
198macro_rules! impl_wasm_traits {
199 ($type_name:ident $(< $($generics:ident),+ >)?) => {
200 $crate::impl_main_wasm_traits!($type_name$(<$($generics),+>)?);
201 $crate::impl_complementary_wasm_traits!($type_name$(<$($generics),+>)?);
202 };
203}
204
205#[macro_export]
258macro_rules! impl_custom_tsify {
259 ($type_name:ident $(< $($generics:ident),+ >)?, $decl:literal) => {
260 $crate::prelude::paste::paste! {
261 #[$crate::prelude::wasm_bindgen]
262 extern "C" {
263 #[wasm_bindgen(typescript_type = [<$type_name>])]
264 pub type [<Js $type_name>];
265 }
266
267 #[$crate::prelude::wasm_bindgen(typescript_custom_section)]
268 const TYPESCRIPT_CONTENT: &'static str = $decl;
269
270 impl$(<$($generics),+>)? $crate::prelude::Tsify for $type_name$(<$($generics),+>)? {
271 type JsType = [<Js $type_name>];
272 const DECL: &'static str = $decl;
273 }
274 }
275 };
276}
277
278#[macro_export]
292macro_rules! add_ts_content {
293 ($decl:literal) => {
294 $crate::prelude::paste::paste! {
295 #[$crate::prelude::wasm_bindgen(typescript_custom_section)]
296 const TYPESCRIPT_CONTENT: &'static str = $decl;
297 }
298 };
299}
300
301#[cfg(target_family = "wasm")]
302#[cfg(test)]
303mod tests {
304 use crate::*;
305 use wasm_bindgen::JsCast;
306 use js_sys::{JsString, Reflect};
307 use wasm_bindgen_test::wasm_bindgen_test;
308 use std::{collections::HashMap, str::FromStr};
309
310 #[derive(serde::Deserialize, serde::Serialize, Default)]
311 pub struct A {
312 pub field1: String,
313 #[serde(serialize_with = "serialize_as_bytes")]
314 pub field2: Vec<u8>,
315 #[serde(serialize_with = "serialize_hashmap_as_object")]
316 pub field3: HashMap<String, u64>,
317 }
318
319 impl_custom_tsify!(
323 A,
324 "export interface A {
325 field1: String;
326 field2: Uint8Array;
327 field3: Record<string, bigint>;
328 };"
329 );
330 impl_wasm_traits!(A);
331 add_ts_content!("export type SomeType = string;");
332
333 #[wasm_bindgen_test]
334 fn test_macros() {
335 let res = A::default().try_into_js_value().unwrap();
336 let field1_key = JsString::from_str("field1").unwrap();
337 let field2_key = JsString::from_str("field2").unwrap();
338 let field3_key = JsString::from_str("field3").unwrap();
339
340 assert!(field1_key.js_in(&res));
342 assert_eq!(
343 Reflect::get(&res, &field1_key)
344 .unwrap()
345 .as_string()
346 .unwrap(),
347 ""
348 );
349 assert!(field2_key.js_in(&res));
350 assert!(Reflect::get(&res, &field2_key)
351 .unwrap()
352 .is_instance_of::<js_sys::Uint8Array>());
353 assert!(field3_key.js_in(&res));
354 assert!(Reflect::get(&res, &field3_key).unwrap().is_object());
355
356 assert!(!JsString::from_str("field4").unwrap().js_in(&res));
358 }
359
360 #[derive(serde::Deserialize, serde::Serialize, Default)]
361 pub struct B<T, E> {
362 pub field1: T,
363 pub field2: E,
364 }
365 impl_wasm_traits!(B<T, E>);
366 impl_custom_tsify!(
367 B<T, E>,
368 "export interface B<T, E> {
369 field1: T;
370 field2: E;
371 };"
372 );
373
374 #[wasm_bindgen_test]
375 fn test_macros_generic() {
376 let res = B::<String, u8>::default().try_into_js_value().unwrap();
377 let field1_key = JsString::from_str("field1").unwrap();
378 let field2_key = JsString::from_str("field2").unwrap();
379
380 assert!(field1_key.js_in(&res));
382 assert_eq!(
383 Reflect::get(&res, &field1_key)
384 .unwrap()
385 .as_string()
386 .unwrap(),
387 ""
388 );
389 assert!(field2_key.js_in(&res));
390 assert_eq!(
391 Reflect::get(&res, &field2_key).unwrap().as_f64().unwrap(),
392 0.0
393 );
394
395 assert!(!JsString::from_str("field3").unwrap().js_in(&res));
397 }
398}