#[macro_export]
macro_rules! impl_main_wasm_traits {
($type_name:ident $(< $($generics:ident),+ >)?) => {
impl$(<$($generics),+>)? $type_name$(<$($generics),+>)?
$(where $($generics: serde::Serialize + for<'de> serde::Deserialize<'de>, )+ )? {
const TYPE_NAME: &'static str = stringify!($type_name);
pub fn try_into_js_value(&self) -> Result<$crate::prelude::JsValue, $crate::prelude::serde_wasm_bindgen::Error> {
$crate::prelude::to_js_value(&self)
}
pub fn try_from_js_value(js: $crate::prelude::JsValue) -> Result<Self, $crate::prelude::serde_wasm_bindgen::Error> {
$crate::prelude::from_js_value(js)
}
}
impl$(<$($generics),+>)? $crate::prelude::wasm_bindgen::describe::WasmDescribe for $type_name$(<$($generics),+>)? {
#[inline]
fn describe() {
<Self as $crate::prelude::Tsify>::JsType::describe()
}
}
impl$(<$($generics),+>)? $crate::prelude::wasm_bindgen::convert::IntoWasmAbi for $type_name$(<$($generics),+>)?
$(where $($generics: serde::Serialize + for<'de> serde::Deserialize<'de>, )+ )? {
type Abi = <<Self as $crate::prelude::Tsify>::JsType as $crate::prelude::wasm_bindgen::convert::IntoWasmAbi>::Abi;
#[inline]
fn into_abi(self) -> Self::Abi {
let mut err = String::new();
err.push_str(Self::TYPE_NAME);
err.push_str(": ");
let result = self.try_into_js_value().map(<<Self as $crate::prelude::Tsify>::JsType as $crate::prelude::JsCast>::unchecked_from_js);
$crate::prelude::UnwrapThrowExt::expect_throw(result.inspect_err(|e| err.push_str(&e.to_string())), &err).into_abi()
}
}
impl$(<$($generics),+>)? $crate::prelude::wasm_bindgen::convert::OptionIntoWasmAbi for $type_name$(<$($generics),+>)?
$(where $($generics: serde::Serialize + for<'de> serde::Deserialize<'de>, )+ )? {
#[inline]
fn none() -> Self::Abi {
<<Self as $crate::prelude::Tsify>::JsType as $crate::prelude::wasm_bindgen::convert::OptionIntoWasmAbi>::none()
}
}
impl$(<$($generics),+>)? $crate::prelude::wasm_bindgen::convert::FromWasmAbi for $type_name$(<$($generics),+>)?
$(where $($generics: serde::Serialize + for<'de> serde::Deserialize<'de>, )+ )? {
type Abi = <<Self as $crate::prelude::Tsify>::JsType as $crate::prelude::wasm_bindgen::convert::FromWasmAbi>::Abi;
#[inline]
unsafe fn from_abi(js: Self::Abi) -> Self {
let mut err = String::new();
err.push_str(Self::TYPE_NAME);
err.push_str(": ");
let result = Self::try_from_js_value(<Self as $crate::prelude::Tsify>::JsType::from_abi(js).into());
$crate::prelude::UnwrapThrowExt::expect_throw(result.inspect_err(|e| err.push_str(&e.to_string())), &err)
}
}
impl$(<$($generics),+>)? $crate::prelude::wasm_bindgen::convert::OptionFromWasmAbi for $type_name$(<$($generics),+>)?
$(where $($generics: serde::Serialize + for<'de> serde::Deserialize<'de>, )+ )? {
#[inline]
fn is_none(js: &Self::Abi) -> bool {
<<Self as $crate::prelude::Tsify>::JsType as $crate::prelude::wasm_bindgen::convert::OptionFromWasmAbi>::is_none(js)
}
}
};
}
#[macro_export]
macro_rules! impl_complementary_wasm_traits {
($type_name:ident $(< $($generics:ident),+ >)?) => {
impl$(<$($generics),+>)? $crate::prelude::wasm_bindgen::convert::RefFromWasmAbi for $type_name$(<$($generics),+>)?
$(where $($generics: serde::Serialize + for<'de> serde::Deserialize<'de>, )+ )? {
type Abi = <<Self as $crate::prelude::Tsify>::JsType as $crate::prelude::wasm_bindgen::convert::RefFromWasmAbi>::Abi;
type Anchor = Box<Self>;
unsafe fn ref_from_abi(js: Self::Abi) -> Self::Anchor {
Box::new(<Self as $crate::prelude::wasm_bindgen::convert::FromWasmAbi>::from_abi(js))
}
}
impl$(<$($generics),+>)? $crate::prelude::wasm_bindgen::convert::LongRefFromWasmAbi for $type_name$(<$($generics),+>)?
$(where $($generics: serde::Serialize + for<'de> serde::Deserialize<'de>, )+ )? {
type Abi = <<Self as $crate::prelude::Tsify>::JsType as $crate::prelude::wasm_bindgen::convert::LongRefFromWasmAbi>::Abi;
type Anchor = Box<Self>;
unsafe fn long_ref_from_abi(js: Self::Abi) -> Self::Anchor {
Box::new(<Self as $crate::prelude::wasm_bindgen::convert::FromWasmAbi>::from_abi(js))
}
}
impl$(<$($generics),+>)? $crate::prelude::wasm_bindgen::convert::VectorIntoWasmAbi for $type_name$(<$($generics),+>)?
$(where $($generics: serde::Serialize + for<'de> serde::Deserialize<'de>, )+ )? {
type Abi = <Box<[<Self as $crate::prelude::Tsify>::JsType]> as $crate::prelude::wasm_bindgen::convert::IntoWasmAbi>::Abi;
fn vector_into_abi(vector: Box<[Self]>) -> Self::Abi {
$crate::prelude::wasm_bindgen::convert::js_value_vector_into_abi(vector)
}
}
impl$(<$($generics),+>)? $crate::prelude::wasm_bindgen::convert::VectorFromWasmAbi for $type_name$(<$($generics),+>)?
$(where $($generics: serde::Serialize + for<'de> serde::Deserialize<'de>, )+ )? {
type Abi = <Box<[<Self as $crate::prelude::Tsify>::JsType]> as $crate::prelude::wasm_bindgen::convert::FromWasmAbi>::Abi;
unsafe fn vector_from_abi(js: Self::Abi) -> Box<[Self]> {
$crate::prelude::wasm_bindgen::convert::js_value_vector_from_abi(js)
}
}
impl$(<$($generics),+>)? $crate::prelude::wasm_bindgen::describe::WasmDescribeVector for $type_name$(<$($generics),+>)? {
fn describe_vector() {
$crate::prelude::wasm_bindgen::describe::inform($crate::prelude::wasm_bindgen::describe::VECTOR);
<Self as $crate::prelude::wasm_bindgen::describe::WasmDescribe>::describe();
}
}
impl$(<$($generics),+>)? From<$type_name$(<$($generics),+>)?> for $crate::prelude::JsValue
$(where $($generics: serde::Serialize + for<'de> serde::Deserialize<'de>, )+ )? {
fn from(value: $type_name$(<$($generics),+>)?) -> Self {
let mut err = String::new();
err.push_str(<$type_name$(<$($generics),+>)?>::TYPE_NAME);
err.push_str(": ");
let result = value.try_into_js_value();
$crate::prelude::UnwrapThrowExt::expect_throw(
result.inspect_err(|e| err.push_str(&e.to_string())),
&err,
)
}
}
impl$(<$($generics),+>)? $crate::prelude::wasm_bindgen::convert::TryFromJsValue for $type_name$(<$($generics),+>)?
$(where $($generics: serde::Serialize + for<'de> serde::Deserialize<'de>, )+ )? {
type Error = $crate::prelude::serde_wasm_bindgen::Error;
fn try_from_js_value(value: $crate::prelude::JsValue) -> Result<Self, Self::Error> {
Self::try_from_js_value(value)
}
}
impl$(<$($generics),+>)? $crate::prelude::wasm_bindgen::__rt::VectorIntoJsValue for $type_name$(<$($generics),+>)?
$(where $($generics: serde::Serialize + for<'de> serde::Deserialize<'de>, )+ )? {
fn vector_into_jsvalue(vector: Box<[Self]>) -> $crate::prelude::JsValue {
$crate::prelude::wasm_bindgen::__rt::js_value_vector_into_jsvalue(vector)
}
}
};
}
#[macro_export]
macro_rules! impl_wasm_traits {
($type_name:ident $(< $($generics:ident),+ >)?) => {
$crate::impl_main_wasm_traits!($type_name$(<$($generics),+>)?);
$crate::impl_complementary_wasm_traits!($type_name$(<$($generics),+>)?);
};
}
#[macro_export]
macro_rules! impl_custom_tsify {
($type_name:ident $(< $($generics:ident),+ >)?, $decl:literal) => {
$crate::prelude::paste::paste! {
#[$crate::prelude::wasm_bindgen]
extern "C" {
#[wasm_bindgen(typescript_type = [<$type_name>])]
pub type [<Js $type_name>];
}
#[$crate::prelude::wasm_bindgen(typescript_custom_section)]
const TYPESCRIPT_CONTENT: &'static str = $decl;
impl$(<$($generics),+>)? $crate::prelude::Tsify for $type_name$(<$($generics),+>)? {
type JsType = [<Js $type_name>];
const DECL: &'static str = $decl;
}
}
};
}
#[macro_export]
macro_rules! add_ts_content {
($decl:literal) => {
$crate::prelude::paste::paste! {
#[$crate::prelude::wasm_bindgen(typescript_custom_section)]
const TYPESCRIPT_CONTENT: &'static str = $decl;
}
};
}
#[cfg(target_family = "wasm")]
#[cfg(test)]
mod tests {
use crate::*;
use wasm_bindgen::JsCast;
use js_sys::{JsString, Reflect};
use wasm_bindgen_test::wasm_bindgen_test;
use std::{collections::HashMap, str::FromStr};
#[derive(serde::Deserialize, serde::Serialize, Default)]
pub struct A {
pub field1: String,
#[serde(serialize_with = "serialize_as_bytes")]
pub field2: Vec<u8>,
#[serde(serialize_with = "serialize_hashmap_as_object")]
pub field3: HashMap<String, u64>,
}
impl_custom_tsify!(
A,
"export interface A {
field1: String;
field2: Uint8Array;
field3: Record<string, bigint>;
};"
);
impl_wasm_traits!(A);
add_ts_content!("export type SomeType = string;");
#[wasm_bindgen_test]
fn test_macros() {
let res = A::default().try_into_js_value().unwrap();
let field1_key = JsString::from_str("field1").unwrap();
let field2_key = JsString::from_str("field2").unwrap();
let field3_key = JsString::from_str("field3").unwrap();
assert!(field1_key.js_in(&res));
assert_eq!(
Reflect::get(&res, &field1_key)
.unwrap()
.as_string()
.unwrap(),
""
);
assert!(field2_key.js_in(&res));
assert!(Reflect::get(&res, &field2_key)
.unwrap()
.is_instance_of::<js_sys::Uint8Array>());
assert!(field3_key.js_in(&res));
assert!(Reflect::get(&res, &field3_key).unwrap().is_object());
assert!(!JsString::from_str("field4").unwrap().js_in(&res));
}
#[derive(serde::Deserialize, serde::Serialize, Default)]
pub struct B<T, E> {
pub field1: T,
pub field2: E,
}
impl_wasm_traits!(B<T, E>);
impl_custom_tsify!(
B<T, E>,
"export interface B<T, E> {
field1: T;
field2: E;
};"
);
#[wasm_bindgen_test]
fn test_macros_generic() {
let res = B::<String, u8>::default().try_into_js_value().unwrap();
let field1_key = JsString::from_str("field1").unwrap();
let field2_key = JsString::from_str("field2").unwrap();
assert!(field1_key.js_in(&res));
assert_eq!(
Reflect::get(&res, &field1_key)
.unwrap()
.as_string()
.unwrap(),
""
);
assert!(field2_key.js_in(&res));
assert_eq!(
Reflect::get(&res, &field2_key).unwrap().as_f64().unwrap(),
0.0
);
assert!(!JsString::from_str("field3").unwrap().js_in(&res));
}
}