wasm_bindgen_utils_macros/lib.rs
1use proc_macro::TokenStream;
2
3mod wasm_export;
4
5/// A proc macro that generates the wasm function bindings with `WasmEncodedResult`
6/// return type from rust functions that natively return [Result<T, E>], this makes
7/// it possible to avoid throwing on js when return value is [Result::Err] variant,
8/// instead it will return `WasmEncodedResult<T>` normally on js where either of
9/// [Result::Ok] or [Result::Err] variants are included within the `WasmEncodedResult`
10/// properties.
11///
12/// All of the `wasm_bindgen` attributes are available for this macro and are forwarded
13/// directly to `wasm_bindgen` macro on expansion.
14///
15/// Example:
16/// ```ignore
17/// use wasm_bindgen_utils::prelude::*;
18///
19/// struct TestStruct;
20///
21/// #[wasm_export]
22/// impl TestStruct {
23/// #[wasm_export(js_name = "someStaticMethod", unchecked_return_type = "string")]
24/// pub async fn some_static_method((arg1, arg2): (String, u8)) -> Result<String, Error> {
25/// Ok(String::new())
26/// }
27/// #[wasm_export(skip)]
28/// pub async fn some_skip_fn() -> Result<String, Error> {
29/// Ok(String::new())
30/// }
31/// #[some_external_macro]
32/// #[wasm_export(some_other_wbg_attrs)]
33/// pub fn some_self_method(&self, arg: String) -> Result<TestStruct, Error> {
34/// Ok(TestStruct)
35/// }
36/// }
37/// ```
38/// above will basically translate to the following:
39/// ```ignore
40/// impl TestStruct {
41/// pub async fn some_static_method((arg1, arg2): (String, u8)) -> Result<String, Error> {
42/// Ok(String::new())
43/// }
44/// pub async fn some_skip_fn() -> Result<String, Error> {
45/// Ok(String::new())
46/// }
47/// #[some_external_macro]
48/// pub fn some_self_method(&self, arg: String) -> Result<TestStruct, Error> {
49/// Ok(TestStruct)
50/// }
51/// }
52/// #[wasm_bindgen]
53/// impl TestStruct {
54/// #[wasm_bindgen(js_name = "someStaticMethod", unchecked_return_type = "WasmEncodedResult<string>")]
55/// pub async fn some_static_method__wasm_export((arg1, arg2): (String, u8)) -> WasmEncodedResult<String> {
56/// Self::some_static_method((arg1, arg2)).await.into()
57/// }
58/// #[wasm_bindgen(some_other_wbg_attrs, unchecked_return_type = "WasmEncodedResult<TestStruct>")]
59/// pub fn some_self_method__wasm_export(&self, arg: String) -> WasmEncodedResult<TestStruct> {
60/// self.some_self_method(arg).into()
61/// }
62/// }
63/// ```
64///
65/// ### Preserving JS Class In WasmEncodedResult
66/// By default, `WasmEncodedResult` is serialized to JS/TS using `serde_wasm_bindgen`
67/// due to wasm_bindgen's limitations with generic types, this is fine when the Ok variant
68/// is not a JS/TS class instance, however, this approach does not preserve JS/TS class
69/// instances if a Rust struct corresponds to one, but rather `serde_wasm_bindgen` serialization
70/// will convert it to a plain object, similar to JSON stringification.
71///
72/// To address this, you can use the `preserve_js_class` attribute on a method or function;
73/// when enabled, the macro (using `js_sys` lib helpers) bypasses `serde_wasm_bindgen`
74/// serialization and manually populates the `value` and `error` fields of an empty JS
75/// object to resemble `WasmEncodedResult`, ensuring that class instances are preserved
76/// in JS/TS as expected. As a result, the macro generated exporting function in Rust has
77/// to return a `JsValue` rather than `WasmEncodedResult<T>`, but it is typed with
78/// `unchecked_return_type = "WasmEncodedResult<T>"` (this is just a technicality of it,
79/// it doesn't affect the users of the macro)
80///
81/// example:
82/// in rust we will use it like:
83/// ```ignore
84/// #[wasm_bindgen]
85/// struct TestStruct;
86///
87/// #[wasm_export]
88/// impl TestStruct {
89/// #[wasm_export(js_name = "new", preserve_js_class)]
90/// pub fn new() -> Result<TestStruct, Error> {
91/// Ok(TestStruct)
92/// }
93/// }
94/// ```
95///
96/// and we will get the following on JS/TS:
97/// ```ts
98/// const result = TestStruct.new();
99/// if (result.error) {
100/// // handle error
101/// } else {
102/// const testStruct = result.value;
103/// assert(
104/// testStruct instanceof TestStruct,
105/// "Expected to be instance of TestStruct, but it is not"
106/// );
107/// // do stuff
108/// }
109/// ```
110///
111#[proc_macro_attribute]
112pub fn wasm_export(attr: TokenStream, item: TokenStream) -> TokenStream {
113 match wasm_export::expand(attr.into(), item.into()) {
114 Ok(tokens) => tokens.into(),
115 Err(e) => e.into_compile_error().into(),
116 }
117}