Skip to main content

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//!
8//! Each macro **replaces** the attribute it stands in for rather than decorating it, so an item
9//! carries one of these and no `#[wasm_bindgen]` or `#[tsify]` of its own. That is what lets the
10//! expansion change without touching call sites.
11//!
12//! An expansion names only `bitwarden_ffi`, so a call site imports nothing — but the crate has to
13//! forward its own `wasm` feature to `bitwarden-ffi/wasm`, which is where those names live.
14
15use proc_macro::TokenStream;
16
17mod attrs;
18mod wasm_export;
19mod wasm_object;
20mod wasm_record;
21
22/// Exports an impl block or a free function to JavaScript, in place of
23/// `#[cfg_attr(feature = "wasm", wasm_bindgen(..))]`.
24///
25/// Arguments are forwarded to `#[wasm_bindgen]`, so `#[wasm_export(js_class = Foo)]` behaves as it
26/// would there. The item must not be generic; wasm_bindgen cannot export generic items.
27///
28/// # `#[wasm_only]`
29///
30/// Marks a method whose only intended caller is JavaScript, because Rust has a better API for the
31/// same thing. The method is renamed with a `__wasm_only_` prefix, hidden from documentation, and
32/// marked `#[deprecated]` so it shows struck through in IDE autocomplete. Takes an optional
33/// `note = "..."` for that deprecation. The JS name is unaffected: the original name is declared as
34/// the method's `js_name`, unless it already declares one.
35///
36/// # Example
37///
38/// ```ignore
39/// #[wasm_export(js_class = IpcClient)]
40/// impl JsIpcClient {
41///     pub async fn send(&self, message: OutgoingMessage) -> Result<(), SendError> { ... }
42///
43///     // Exported to JavaScript, but struck through for Rust — use `IpcClient::start` instead.
44///     #[wasm_only(note = "Use `IpcClient::start`.")]
45///     pub async fn start(&self) -> Result<(), AlreadyRunningError> { ... }
46/// }
47/// ```
48#[proc_macro_attribute]
49pub fn wasm_export(attr: TokenStream, item: TokenStream) -> TokenStream {
50    wasm_export::wasm_export(attr.into(), item.into()).into()
51}
52
53/// Declares a type that crosses the wasm ABI through serde, in place of
54/// `#[cfg_attr(feature = "wasm", derive(Tsify), tsify(into_wasm_abi, from_wasm_abi))]`.
55///
56/// `#[serde(..)]` and `#[tsify(..)]` attributes are left in place for `Tsify`'s derive to read, and
57/// the `#[derive(..)]` for `Serialize` / `Deserialize` / UniFFI stays where it is — this macro only
58/// owns the wasm side. Takes no arguments.
59///
60/// ```ignore
61/// #[wasm_record]
62/// #[derive(Serialize, Deserialize)]
63/// #[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
64/// #[serde(rename_all = "camelCase")]
65/// pub struct CipherView { pub id: Option<CipherId> }
66/// ```
67#[proc_macro_attribute]
68pub fn wasm_record(attr: TokenStream, item: TokenStream) -> TokenStream {
69    wasm_record::wasm_record(attr.into(), item.into()).into()
70}
71
72/// Declares a type that crosses the wasm ABI as an opaque handle, in place of
73/// `#[cfg_attr(feature = "wasm", wasm_bindgen(..))]` on a struct or an enum.
74///
75/// Arguments are forwarded to `#[wasm_bindgen]`, so `#[wasm_object(js_name = Ciphers)]` behaves as
76/// it would there.
77///
78/// ```ignore
79/// #[wasm_object]
80/// #[derive(Clone)]
81/// pub struct CiphersClient { client: Client }
82/// ```
83#[proc_macro_attribute]
84pub fn wasm_object(attr: TokenStream, item: TokenStream) -> TokenStream {
85    wasm_object::wasm_object(attr.into(), item.into()).into()
86}