arminius_lib_message_derive/lib.rs
1// SPDX-FileCopyrightText: 2025 BWI GmbH
2//
3// SPDX-License-Identifier: Apache-2.0
4
5//! Derive macros for `arminius_lib_message_bus`'s `Event` and `Command` traits.
6//!
7//! The derive is driven by the `#[subject(...)]` attribute; see the macro
8//! below for the supported forms.
9
10mod command;
11mod event;
12mod subject;
13
14use proc_macro::TokenStream;
15use syn::{DeriveInput, Error, parse_macro_input};
16
17/// Derive macro that implements `arminius_lib_message_bus::Event`.
18///
19/// # Attributes
20///
21/// `#[subject(...)]` — the NATS subject the event is published on. Supported
22/// forms:
23///
24/// 1. `#[subject("MyEvent.v1")]`
25/// 2. `#[subject(name = "MyEvent", version = "1")]`
26/// 3. `#[subject(name = "MyEvent", version("1"))]`
27/// 4. `#[subject(name = "MyEvent", version = env!("CARGO_PKG_VERSION_MAJOR"))]`
28/// 5. `#[subject(name = "MyEvent", version(env!("CARGO_PKG_VERSION_MAJOR")))]`
29/// 6. `#[subject(name = "MyEvent", version = "2", compatible = [v0::MyEvent])]`
30/// 7. `#[subject(version = env!("CARGO_PKG_VERSION_MAJOR"))]`
31/// 8. `#[subject(version = "2", compatible = [v1::MyEvent, v0::MyEvent])]`
32///
33/// # Subject rules
34///
35/// - **Struct-name fallback.** When `name` is omitted the struct name is used.
36/// - **`compatible` list.** Names older event types whose subjects the subscriber will also
37/// consume, converting each to `Self` via the required `From<OldType> for Self` implementation.
38///
39/// # Dynamic (per-instance) subjects
40///
41/// The subject template (the string literal, or the `name` value) may embed
42/// `{field}` placeholders naming struct fields. The generated
43/// `Event::SUBJECT` constant replaces each placeholder with a `*` wildcard
44/// (used to subscribe / as the stream filter), while the generated
45/// `subject(&self)` method interpolates each field's `Display` value to build
46/// the concrete subject the instance publishes to. Multiple placeholders map to
47/// one `*` each, in order; with no placeholders `subject(&self)` equals
48/// `SUBJECT`.
49///
50/// ```rust,ignore
51/// // SUBJECT == "rooms.*.events.v1"
52/// // RoomCreated { room_id: 7, .. }.subject() == "rooms.7.events.v1"
53/// #[derive(Event)]
54/// #[subject(name = "rooms.{room_id}.events", version = 1)]
55/// struct RoomCreated {
56/// room_id: u64,
57/// }
58/// ```
59#[proc_macro_derive(Event, attributes(subject))]
60pub fn derive_event(input: TokenStream) -> TokenStream {
61 let input = parse_macro_input!(input as DeriveInput);
62 event::expand(&input).unwrap_or_else(Error::into_compile_error).into()
63}
64
65/// Derive macro that implements `arminius_lib_message_bus::Command` for a
66/// struct.
67///
68/// # Attributes
69///
70/// - `#[subject(...)]` — the NATS subject the command is sent on. Accepts a bare literal
71/// (`#[subject("rooms.create")]`) or the `name = "...", version = ...` form (which appends
72/// `.v{version}`).
73/// - `#[command(reply = Type)]` — the reply type returned by the handler.
74///
75/// # Subject rules (differ from `Event`)
76///
77/// - **No struct-name fallback.** A command must name an explicit subject, so it has a namespace
78/// segment for its stream filter; a namespace-less subject would collapse to the catch-all `>`
79/// filter.
80/// - **No `compatible` list.**
81///
82/// # Payload and dynamic subjects
83///
84/// The whole struct is serialized as the request payload, so the command must
85/// also derive `serde::Serialize` and `serde::Deserialize`. Any number of
86/// fields is allowed.
87///
88/// Fields named in `{…}` placeholders become wildcard segments in the subject:
89/// the derive interpolates them from the struct's own fields, so the subject
90/// and the payload copies cannot drift.
91///
92/// # Example
93///
94/// ```rust,ignore
95/// #[derive(Serialize, Deserialize, Command)]
96/// #[subject("rooms.{room_id}.create")]
97/// #[command(reply = Result<OwnedRoomId, CreateRoomError>)]
98/// pub struct CreateRoomCommand {
99/// pub room_id: OwnedRoomId,
100/// pub name: String,
101/// pub preset: RoomPreset,
102/// }
103/// ```
104///
105/// `#[command(...)]` also takes `delivery = at_least_once | at_most_once`.
106/// For a detailed explanation look at
107/// `arminius_lib_message_bus::command::Delivery`.
108#[proc_macro_derive(Command, attributes(subject, command))]
109pub fn derive_command(input: TokenStream) -> TokenStream {
110 let input = parse_macro_input!(input as DeriveInput);
111 command::expand(&input).unwrap_or_else(Error::into_compile_error).into()
112}