Skip to main content

nest_rs_queue_macros/
lib.rs

1//! The `#[processor]` decorator, re-exported by `nest-rs-queue` (the
2//! backend-agnostic abstractions crate) so the call site keeps writing
3//! `use nest_rs_queue::processor;` regardless of which backend integration
4//! (nest-rs-redis, …) is wired in.
5#![warn(missing_docs)]
6
7use proc_macro::TokenStream;
8
9mod processor;
10mod queue;
11
12/// Orchestrator on an `#[injectable]` provider's `impl` block. Each method
13/// tagged with `#[process(queue = "...", concurrency, retries)]` becomes a
14/// queue consumer the `QueueWorker` spawns at boot.
15///
16/// A single provider may carry several `#[process]` methods (different
17/// queues, different concurrencies) sharing the same `#[inject]`
18/// dependencies — pooling related queue handlers on one service keeps
19/// shared state (clients, repositories) in one place.
20///
21/// The `queue` is named either by a raw string literal (legacy form) or by a
22/// `QueueName` **type** — the preferred form,
23/// declared with [`queue`](macro@crate::queue) at the feature port:
24/// `#[process(queue = AudioQueue)]`. The type form reads
25/// `<AudioQueue as QueueName>::NAME` into the inventory entry **and** asserts,
26/// at compile time, that this method's job argument is
27/// `<AudioQueue as QueueName>::Job` — a mismatch is a build error naming both
28/// types, not a job that silently never drains.
29///
30/// Per-method attributes (exactly one `#[process]` per method):
31///
32/// - `#[process(queue = "audio")]` — minimal, defaults `concurrency = 1`,
33///   `retries = 0`.
34/// - `#[process(queue = "audio", concurrency = 5)]` — bound the in-flight jobs
35///   per worker.
36/// - `#[process(queue = "audio", concurrency = 5, retries = 3)]` — apalis
37///   retries before the job lands on the queue's failed list.
38///
39/// The method signature is `async fn(&self, job: T) -> anyhow::Result<()>`,
40/// where `T: Job`. The macro extracts the job type from the second
41/// parameter, generates a typed handler, and submits a per-method
42/// inventory entry the worker drains.
43///
44/// # Expands to
45///
46/// The impl unchanged, plus per `#[process]` method: a hidden type-erased
47/// handler `fn` (unwraps the wire envelope, deserializes the job, resolves the
48/// provider, dispatches inside the `JobContext`) and a `ProcessMethod`
49/// submitted to the link-time inventory. No `Discoverable` — the host's own
50/// `#[injectable]` owns it.
51///
52/// ```text
53/// impl AudioProcessor { /* unchanged */ }
54/// fn __nestrs_process_handler_audio_processor_transcode(payload, container) -> Pin<Box<dyn Future<…>>> { /* … */ }
55/// ::nest_rs_core::inventory::submit! {
56///     ::nest_rs_queue::ProcessMethod {
57///         name: "AudioProcessor::transcode", queue: "audio",
58///         concurrency: 5, retries: 3,
59///         provider_type_id: || TypeId::of::<AudioProcessor>(),
60///         handler: __nestrs_process_handler_audio_processor_transcode,
61///     }
62/// }
63/// ```
64#[proc_macro_attribute]
65pub fn processor(args: TokenStream, input: TokenStream) -> TokenStream {
66    processor::processor(args, input)
67}
68
69/// Stamp a unit struct with a compile-time queue identity — its wire name and
70/// the `Job` payload it carries — by implementing
71/// `QueueName`. Lives beside the payload at the feature port; both the producer
72/// (`push_to::<Q>`) and the consumer (`#[process(queue = Q)]`) name the type,
73/// so a typo'd name or a mismatched payload is a compile error, not a job that
74/// silently never drains.
75///
76/// ```ignore
77/// #[queue(name = "audio", job = TranscodeCommand)]
78/// pub struct AudioQueue;
79/// ```
80///
81/// # Expands to
82///
83/// ```ignore
84/// pub struct AudioQueue;
85/// impl ::nest_rs_queue::QueueName for AudioQueue {
86///     const NAME: &'static str = "audio";
87///     type Job = TranscodeCommand;
88/// }
89/// ```
90#[proc_macro_attribute]
91pub fn queue(args: TokenStream, input: TokenStream) -> TokenStream {
92    queue::queue(args, input)
93}