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}