1#[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 type Error = $crate::prelude::serde_wasm_bindgen::Error;
171 fn try_from_js_value(value: $crate::prelude::JsValue) -> Result<Self, Self::Error> {
172 Self::try_from_js_value(value)
173 }
174 }
175 impl$(<$($generics),+>)? $crate::prelude::wasm_bindgen::__rt::VectorIntoJsValue for $type_name$(<$($generics),+>)?
176 $(where $($generics: serde::Serialize + for<'de> serde::Deserialize<'de>, )+ )? {
177 fn vector_into_jsvalue(vector: Box<[Self]>) -> $crate::prelude::JsValue {
178 $crate::prelude::wasm_bindgen::__rt::js_value_vector_into_jsvalue(vector)
179 }
180 }
181 };
182}
183
184#[macro_export]
205macro_rules! impl_wasm_traits {
206 ($type_name:ident $(< $($generics:ident),+ >)?) => {
207 $crate::impl_main_wasm_traits!($type_name$(<$($generics),+>)?);
208 $crate::impl_complementary_wasm_traits!($type_name$(<$($generics),+>)?);
209 };
210}
211
212#[macro_export]
265macro_rules! impl_custom_tsify {
266 ($type_name:ident $(< $($generics:ident),+ >)?, $decl:literal) => {
267 $crate::prelude::paste::paste! {
268 #[$crate::prelude::wasm_bindgen]
269 extern "C" {
270 #[wasm_bindgen(typescript_type = [<$type_name>])]
271 pub type [<Js $type_name>];
272 }
273
274 #[$crate::prelude::wasm_bindgen(typescript_custom_section)]
275 const TYPESCRIPT_CONTENT: &'static str = $decl;
276
277 impl$(<$($generics),+>)? $crate::prelude::Tsify for $type_name$(<$($generics),+>)? {
278 type JsType = [<Js $type_name>];
279 const DECL: &'static str = $decl;
280 }
281 }
282 };
283}
284
285#[macro_export]
299macro_rules! add_ts_content {
300 ($decl:literal) => {
301 $crate::prelude::paste::paste! {
302 #[$crate::prelude::wasm_bindgen(typescript_custom_section)]
303 const TYPESCRIPT_CONTENT: &'static str = $decl;
304 }
305 };
306}
307
308#[cfg(target_family = "wasm")]
309#[cfg(test)]
310mod tests {
311 use crate::*;
312 use wasm_bindgen::JsCast;
313 use js_sys::{JsString, Reflect};
314 use wasm_bindgen_test::wasm_bindgen_test;
315 use std::{collections::HashMap, str::FromStr};
316
317 #[derive(serde::Deserialize, serde::Serialize, Default)]
318 pub struct A {
319 pub field1: String,
320 #[serde(serialize_with = "serialize_as_bytes")]
321 pub field2: Vec<u8>,
322 #[serde(serialize_with = "serialize_hashmap_as_object")]
323 pub field3: HashMap<String, u64>,
324 }
325
326 impl_custom_tsify!(
330 A,
331 "export interface A {
332 field1: String;
333 field2: Uint8Array;
334 field3: Record<string, bigint>;
335 };"
336 );
337 impl_wasm_traits!(A);
338 add_ts_content!("export type SomeType = string;");
339
340 #[wasm_bindgen_test]
341 fn test_macros() {
342 let res = A::default().try_into_js_value().unwrap();
343 let field1_key = JsString::from_str("field1").unwrap();
344 let field2_key = JsString::from_str("field2").unwrap();
345 let field3_key = JsString::from_str("field3").unwrap();
346
347 assert!(field1_key.js_in(&res));
349 assert_eq!(
350 Reflect::get(&res, &field1_key)
351 .unwrap()
352 .as_string()
353 .unwrap(),
354 ""
355 );
356 assert!(field2_key.js_in(&res));
357 assert!(Reflect::get(&res, &field2_key)
358 .unwrap()
359 .is_instance_of::<js_sys::Uint8Array>());
360 assert!(field3_key.js_in(&res));
361 assert!(Reflect::get(&res, &field3_key).unwrap().is_object());
362
363 assert!(!JsString::from_str("field4").unwrap().js_in(&res));
365 }
366
367 #[derive(serde::Deserialize, serde::Serialize, Default)]
368 pub struct B<T, E> {
369 pub field1: T,
370 pub field2: E,
371 }
372 impl_wasm_traits!(B<T, E>);
373 impl_custom_tsify!(
374 B<T, E>,
375 "export interface B<T, E> {
376 field1: T;
377 field2: E;
378 };"
379 );
380
381 #[wasm_bindgen_test]
382 fn test_macros_generic() {
383 let res = B::<String, u8>::default().try_into_js_value().unwrap();
384 let field1_key = JsString::from_str("field1").unwrap();
385 let field2_key = JsString::from_str("field2").unwrap();
386
387 assert!(field1_key.js_in(&res));
389 assert_eq!(
390 Reflect::get(&res, &field1_key)
391 .unwrap()
392 .as_string()
393 .unwrap(),
394 ""
395 );
396 assert!(field2_key.js_in(&res));
397 assert_eq!(
398 Reflect::get(&res, &field2_key).unwrap().as_f64().unwrap(),
399 0.0
400 );
401
402 assert!(!JsString::from_str("field3").unwrap().js_in(&res));
404 }
405}