bitwarden_ffi_macro/lib.rs
1//! Proc macros for FFI bindings (WASM, UniFFI).
2//!
3//! Provides:
4//! - `#[wasm_export]` attribute macro for an impl block or free function exported to JavaScript.
5//! - `#[wasm_record]` attribute macro for a type that crosses the ABI through serde.
6//! - `#[wasm_object]` attribute macro for a type that crosses the ABI as a handle.
7//! - `#[wasm_import]` attribute macro for an `extern "C"` block of JavaScript imports.
8//!
9//! Each macro **replaces** the attribute it stands in for rather than decorating it, so an item
10//! carries one of these and no `#[wasm_bindgen]` or `#[tsify]` of its own. That is what lets the
11//! expansion change without touching call sites.
12//!
13//! An expansion names only `bitwarden_ffi`, so a call site imports nothing — but the crate has to
14//! forward its own `wasm` feature to `bitwarden-ffi/wasm`, which is where those names live.
15
16use proc_macro::TokenStream;
17
18mod attrs;
19mod wasm_export;
20mod wasm_import;
21mod wasm_object;
22mod wasm_record;
23
24/// Exports an impl block or a free function to JavaScript, in place of
25/// `#[cfg_attr(feature = "wasm", wasm_bindgen(..))]`.
26///
27/// Arguments are forwarded to `#[wasm_bindgen]`, so `#[wasm_export(js_class = Foo)]` behaves as it
28/// would there. The item must not be generic; wasm_bindgen cannot export generic items.
29///
30/// # `#[wasm_only]`
31///
32/// Marks a method whose only intended caller is JavaScript, because Rust has a better API for the
33/// same thing. The method is renamed with a `__wasm_only_` prefix, hidden from documentation, and
34/// marked `#[deprecated]` so it shows struck through in IDE autocomplete. Takes an optional
35/// `note = "..."` for that deprecation. The JS name is unaffected: the original name is declared as
36/// the method's `js_name`, unless it already declares one.
37///
38/// # Example
39///
40/// ```ignore
41/// #[wasm_export(js_class = IpcClient)]
42/// impl JsIpcClient {
43/// pub async fn send(&self, message: OutgoingMessage) -> Result<(), SendError> { ... }
44///
45/// // Exported to JavaScript, but struck through for Rust — use `IpcClient::start` instead.
46/// #[wasm_only(note = "Use `IpcClient::start`.")]
47/// pub async fn start(&self) -> Result<(), AlreadyRunningError> { ... }
48/// }
49/// ```
50#[proc_macro_attribute]
51pub fn wasm_export(attr: TokenStream, item: TokenStream) -> TokenStream {
52 wasm_export::wasm_export(attr.into(), item.into()).into()
53}
54
55/// Declares a type that crosses the wasm ABI through serde, in place of
56/// `#[cfg_attr(feature = "wasm", derive(Tsify), tsify(into_wasm_abi, from_wasm_abi))]`.
57///
58/// `#[serde(..)]` and `#[tsify(..)]` attributes are left in place for `Tsify`'s derive to read, and
59/// the `#[derive(..)]` for `Serialize` / `Deserialize` / UniFFI stays where it is — this macro only
60/// owns the wasm side.
61///
62/// `#[wasm_record(input)]` declares only the JavaScript-to-Rust direction, for a type that must not
63/// be serialized back out (for example, one holding a password).
64///
65/// ```ignore
66/// #[wasm_record]
67/// #[derive(Serialize, Deserialize)]
68/// #[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
69/// #[serde(rename_all = "camelCase")]
70/// pub struct CipherView { pub id: Option<CipherId> }
71/// ```
72#[proc_macro_attribute]
73pub fn wasm_record(attr: TokenStream, item: TokenStream) -> TokenStream {
74 wasm_record::wasm_record(attr.into(), item.into()).into()
75}
76
77/// Declares a type that crosses the wasm ABI as an opaque handle, in place of
78/// `#[cfg_attr(feature = "wasm", wasm_bindgen(..))]` on a struct or an enum.
79///
80/// Arguments are forwarded to `#[wasm_bindgen]`, so `#[wasm_object(js_name = Ciphers)]` behaves as
81/// it would there.
82///
83/// ```ignore
84/// #[wasm_object]
85/// #[derive(Clone)]
86/// pub struct CiphersClient { client: Client }
87/// ```
88#[proc_macro_attribute]
89pub fn wasm_object(attr: TokenStream, item: TokenStream) -> TokenStream {
90 wasm_object::wasm_object(attr.into(), item.into()).into()
91}
92
93/// Declares a block of JavaScript imports, in place of `#[wasm_bindgen]` on an `extern "C"` block.
94///
95/// Arguments are forwarded to `#[wasm_bindgen]`, and the attributes inside the block are left for
96/// it to read. Each `type` the block declares also gets the wire traits, so it can appear in a
97/// `#[wasm_export]` signature. Unlike the other macros, `#[wasm_bindgen]` is applied
98/// unconditionally: an extern block cannot compile without it, so the block's own `cfg` decides.
99///
100/// ```ignore
101/// #[wasm_import]
102/// extern "C" {
103/// #[wasm_bindgen(js_name = SharedUnlockDriver)]
104/// pub type RawJsSharedUnlockDriver;
105///
106/// #[wasm_bindgen(method, catch)]
107/// async fn lock_user(this: &RawJsSharedUnlockDriver, user_id: String) -> Result<(), JsValue>;
108/// }
109/// ```
110#[proc_macro_attribute]
111pub fn wasm_import(attr: TokenStream, item: TokenStream) -> TokenStream {
112 wasm_import::wasm_import(attr.into(), item.into()).into()
113}