Skip to main content

xmtp_macro/
lib.rs

1extern crate proc_macro;
2
3mod async_trait;
4mod builder;
5mod builders;
6mod error_code;
7mod log_macros;
8mod logging;
9mod span_macro;
10mod test_macro;
11mod timeout_macro;
12
13#[cfg(test)]
14mod builder_test;
15#[cfg(test)]
16mod timeout_macro_test;
17
18/// A proc macro attribute that wraps the input in an `async_trait` implementation,
19/// delegating to the appropriate `async_trait` implementation based on the target architecture.
20///
21/// On wasm32 architecture, it delegates to `async_trait::async_trait(?Send)`.
22/// On all other architectures, it delegates to `async_trait::async_trait`.
23#[proc_macro_attribute]
24pub fn async_trait(
25    attr: proc_macro::TokenStream,
26    input: proc_macro::TokenStream,
27) -> proc_macro::TokenStream {
28    async_trait::async_trait(attr, input)
29}
30
31/// Attribute macro that generates a NAPI-annotated builder pattern for a struct.
32///
33/// Each field must be annotated with one of:
34/// - `#[builder(required)]` — passed in the constructor; no setter generated
35/// - `#[builder(optional)]` — field type must be `Option<T>`; setter takes `T`, wraps in `Some`
36/// - `#[builder(default = "expr")]` — has a default value; setter takes the full type
37/// - `#[builder(skip)]` — no setter; initialized via `Default::default()`
38///
39/// The macro generates a `new()` constructor (with all required fields as parameters)
40/// and fluent setters for optional/default fields. The `build()` method is NOT generated;
41/// implement it manually.
42///
43/// # Example
44///
45/// ```ignore
46/// #[napi_builder]
47/// pub struct FooBuilder {
48///     #[builder(required)]
49///     name: String,
50///     #[builder(optional)]
51///     desc: Option<String>,
52///     #[builder(default = "42")]
53///     count: u32,
54///     #[builder(skip)]
55///     internal: Vec<u8>,
56/// }
57/// ```
58#[proc_macro_attribute]
59pub fn napi_builder(
60    attr: proc_macro::TokenStream,
61    input: proc_macro::TokenStream,
62) -> proc_macro::TokenStream {
63    builders::napi_builder(attr, input)
64}
65
66/// Attribute macro that generates a wasm_bindgen-annotated builder pattern for a struct.
67///
68/// Behaves identically to [`napi_builder`] but emits `#[wasm_bindgen]` annotations
69/// instead of `#[napi]`, and generates `js_name = camelCase` attributes on setters.
70///
71/// See [`napi_builder`] for field attribute documentation.
72#[proc_macro_attribute]
73pub fn wasm_builder(
74    attr: proc_macro::TokenStream,
75    input: proc_macro::TokenStream,
76) -> proc_macro::TokenStream {
77    builders::wasm_builder(attr, input)
78}
79
80/// Attribute macro that generates a UniFFI-annotated builder pattern for a struct.
81///
82/// Emits `#[derive(uniffi::Object)]` on the struct and `#[uniffi::export]` on the
83/// impl block. UniFFI annotates the impl block as a whole rather than individual
84/// methods, so `constructor_ann` and `setter_ann` are empty.
85///
86/// See [`napi_builder`] for field attribute documentation.
87#[proc_macro_attribute]
88pub fn uniffi_builder(
89    attr: proc_macro::TokenStream,
90    input: proc_macro::TokenStream,
91) -> proc_macro::TokenStream {
92    builders::uniffi_builder(attr, input)
93}
94
95/// A test macro that delegates to the appropriate test framework based on the target architecture.
96///
97/// On wasm32 architecture, it delegates to `wasm_bindgen_test::wasm_bindgen_test`.
98/// On all other architectures, it delegates to `tokio::test`.
99///
100/// When using with 'rstest', ensure any other test invocations come after rstest invocation.
101/// # Example
102///
103/// ```ignore
104/// #[test]
105/// async fn test_something() {
106///     assert_eq!(2 + 2, 4);
107/// }
108/// ```
109#[proc_macro_attribute]
110pub fn test(
111    attr: proc_macro::TokenStream,
112    body: proc_macro::TokenStream,
113) -> proc_macro::TokenStream {
114    test_macro::test(attr, body)
115}
116
117#[proc_macro_attribute]
118pub fn build_logging_metadata(
119    attr: proc_macro::TokenStream,
120    item: proc_macro::TokenStream,
121) -> proc_macro::TokenStream {
122    log_macros::build_logging_metadata(attr, item)
123}
124
125#[proc_macro]
126pub fn log_event(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
127    log_macros::log_event(input)
128}
129
130/// Derive macro for the `ErrorCode` trait.
131///
132/// Automatically generates an `error_code()` implementation that returns
133/// `"TypeName::VariantName"` for each enum variant, or `"TypeName"` for structs.
134///
135/// # Example
136///
137/// ```ignore
138/// use xmtp_common::ErrorCode;
139///
140/// #[derive(Debug, thiserror::Error, ErrorCode)]
141/// pub enum GroupError {
142///     #[error("Group not found")]
143///     NotFound,  // Returns "GroupError::NotFound"
144///
145///     #[error("Storage error: {0}")]
146///     #[error_code(inherit)]  // Delegates to StorageError::error_code()
147///     Storage(#[from] StorageError),
148/// }
149/// ```
150///
151/// # Attributes
152///
153/// - `#[error_code(inherit)]` - Delegate to the inner error's `error_code()` method.
154///   Use this for single-field variants that wrap another error implementing `ErrorCode`.
155///
156/// - `#[error_code(remote = "path::Type")]` - Implement `ErrorCode` for a remote type.
157///   The derived item should mirror the remote type's shape. Default codes use the derived
158///   item's type name, so keep it aligned with the remote type's name unless overridden.
159///
160/// - `#[error_code("CustomCode")]` - Override the generated code with a custom value.
161///   Use this to maintain backwards compatibility when renaming variants.
162///
163/// # Example: Custom Code for Backwards Compatibility
164///
165/// ```ignore
166/// #[derive(Debug, thiserror::Error, ErrorCode)]
167/// pub enum MyError {
168///     // Renamed from "OldName" but keeps the old error code
169///     #[error("new name")]
170///     #[error_code("MyError::OldName")]
171///     NewName,
172/// }
173/// ```
174#[proc_macro_derive(ErrorCode, attributes(error_code))]
175pub fn derive_error_code(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
176    error_code::derive_error_code(input)
177}
178
179/// Attribute macro that wraps an async test body with a WASM-compatible timeout.
180///
181/// This is a drop-in replacement for rstest's `#[timeout]` that works on
182/// `wasm32-unknown-unknown` by using `xmtp_common::time::timeout` internally.
183///
184/// # Example
185///
186/// ```ignore
187/// #[xmtp_common::test]
188/// #[xmtp_common::timeout(std::time::Duration::from_secs(60))]
189/// async fn test_something() { ... }
190/// ```
191#[proc_macro_attribute]
192pub fn timeout(
193    attr: proc_macro::TokenStream,
194    body: proc_macro::TokenStream,
195) -> proc_macro::TokenStream {
196    timeout_macro::timeout(attr, body)
197}
198
199/// Instrument an `ApiClientWrapper` RPC method as `operation = "rpc.<fn_name>"`
200/// in libxmtp's canonical, OTEL-safe span form (`err, skip_all`). Surfaces as
201/// `xmtp.api.*` Collector metrics. See [`span`] for the shared rationale.
202///
203/// ```ignore
204/// #[xmtp_macro::rpc_span]
205/// pub async fn upload_key_package(&self, ..) -> Result<()> { .. }
206/// // → #[tracing::instrument(err, skip_all, fields(operation = "rpc.upload_key_package"))]
207/// ```
208#[proc_macro_attribute]
209pub fn rpc_span(
210    attr: proc_macro::TokenStream,
211    body: proc_macro::TokenStream,
212) -> proc_macro::TokenStream {
213    span_macro::rpc_span(attr, body)
214}
215
216/// Instrument an `xmtp_db` query method as `operation = "db.<fn_name>"` in
217/// libxmtp's canonical, OTEL-safe span form (`err, skip_all`). Surfaces as
218/// `xmtp.db.*` Collector metrics. See [`span`] for the shared rationale.
219#[proc_macro_attribute]
220pub fn db_span(
221    attr: proc_macro::TokenStream,
222    body: proc_macro::TokenStream,
223) -> proc_macro::TokenStream {
224    span_macro::db_span(attr, body)
225}
226
227/// Instrument a high-level MLS operation as `operation = "mls.<fn_name>"` in
228/// libxmtp's canonical, OTEL-safe span form (`err, skip_all`). Surfaces as
229/// `xmtp.mls.*` Collector metrics. See [`span`] for the shared rationale.
230#[proc_macro_attribute]
231pub fn mls_span(
232    attr: proc_macro::TokenStream,
233    body: proc_macro::TokenStream,
234) -> proc_macro::TokenStream {
235    span_macro::mls_span(attr, body)
236}
237
238/// Instrument a method as a telemetry operation span in libxmtp's single
239/// canonical, OTEL-safe form: `#[tracing::instrument(err, skip_all,
240/// fields(operation = "<prefix>.<fn_name>"))]`.
241///
242/// `err` records span status=error on an `Err` return; `skip_all` keeps every
243/// argument off the span so a per-call id can never leak in and explode
244/// trace-attribute cardinality. `operation` is the single dimension the
245/// Collector's `span_metrics` connector buckets on. Making this the only
246/// writable form guarantees those invariants at compile time — no runtime test.
247///
248/// This is the escape hatch for a namespace without a dedicated attribute;
249/// prefer [`rpc_span`] / [`db_span`] / [`mls_span`] where they apply.
250///
251/// ```ignore
252/// #[xmtp_macro::span(prefix = "stream")]
253/// pub async fn subscribe(&self, ..) -> Result<..> { .. }
254/// // → operation = "stream.subscribe"
255/// ```
256#[proc_macro_attribute]
257pub fn span(
258    attr: proc_macro::TokenStream,
259    body: proc_macro::TokenStream,
260) -> proc_macro::TokenStream {
261    span_macro::span(attr, body)
262}
263
264/// Error-case-only tracing for an FFI-exported fn:
265/// `#[tracing::instrument(level = "trace", skip_all, err)]`.
266///
267/// The span is `trace`-level, so under normal filters the success path emits
268/// nothing; `err` fires an ERROR event only when the fn returns `Err`, and
269/// `skip_all` keeps arguments (keys, pins, paths) off the span.
270///
271/// Unlike [`span`], this is napi-safe: napi-rs clones every method attribute
272/// onto the `extern "C"` wrapper it generates (which returns a raw
273/// `napi_value`, not `Result`), so a bare `#[tracing::instrument(err)]` on an
274/// exported method fails to compile. This macro detects the wrapper by its
275/// `extern` ABI and passes it through untouched, instrumenting only the real
276/// method.
277///
278/// ```ignore
279/// #[napi]
280/// #[xmtp_common::err_span]
281/// pub async fn sync(&self) -> Result<()> { .. }
282/// ```
283#[proc_macro_attribute]
284pub fn err_span(
285    attr: proc_macro::TokenStream,
286    body: proc_macro::TokenStream,
287) -> proc_macro::TokenStream {
288    span_macro::err_span(attr, body)
289}