Skip to main content

nest_rs_queue/
producer.rs

1//! Backend + producer seams.
2//!
3//! Every queue backend implements [`QueueBackend`] (the boot identity used by
4//! diagnostics) and exposes a [`JobProducer`] surface so any feature can
5//! enqueue without naming the backend. The first-class backend is `apalis-redis`
6//! (shipped as `nest-rs-queue`); third-party backends provide their own
7//! `*Module` that registers a `JobProducer` in the container the same way.
8
9use async_trait::async_trait;
10use serde::Serialize;
11
12use crate::error::QueueError;
13use crate::processor::Job;
14use crate::queue_name::QueueName;
15
16/// A queue backend, identified by name for boot diagnostics. The actual
17/// runtime work happens through [`JobProducer`] (enqueue) and
18/// [`JobConsumer`](crate::consumer::JobConsumer) (drain `ProcessMethod` and
19/// dispatch). A backend's module typically:
20///
21/// 1. seeds an `Arc<dyn JobProducer>` in the container (so any service can
22///    inject it generically), and
23/// 2. contributes a `Transport` whose `serve` runs the backend's
24///    [`JobConsumer`] driver.
25pub trait QueueBackend: Send + Sync + 'static {
26    /// Stable display name (e.g. `"apalis-redis"`, `"sqs"`, `"in-memory"`).
27    /// Logged at boot when the consumer attaches.
28    fn name(&self) -> &'static str;
29}
30
31/// Backend-agnostic producer: push a JSON-serialized job onto a named queue.
32/// Concrete backends implement this; typed pushes are a convenience built on
33/// top via [`JobProducerExt`].
34#[async_trait]
35pub trait JobProducer: Send + Sync + 'static {
36    /// Push a JSON-encoded job onto `queue`. The wire format is always JSON;
37    /// a backend may re-encode internally but must round-trip the payload. A
38    /// backend failure surfaces as [`QueueError::Backend`].
39    async fn push_json(&self, queue: &str, payload: serde_json::Value) -> Result<(), QueueError>;
40}
41
42/// Typed-push convenience over any [`JobProducer`]. Lives as an extension trait
43/// so the producer trait stays object-safe (`Arc<dyn JobProducer>`).
44#[async_trait]
45pub trait JobProducerExt: JobProducer {
46    /// Push a job onto a **typed** queue handle — the default enqueue path. The
47    /// queue name and the payload type are both taken from `Q`
48    /// ([`QueueName::NAME`] and [`QueueName::Job`]), so the compiler rejects an
49    /// enqueue onto the wrong queue or with the wrong payload before it ever
50    /// runs. Declare `Q` once at the feature port with the
51    /// [`queue`](crate::queue) macro; both the producer here and the consumer's
52    /// `#[process(queue = Q)]` name the same type.
53    ///
54    /// Fails with [`QueueError::Serialize`] if the job won't serialize, else
55    /// with whatever [`push_json`](JobProducer::push_json) returns.
56    ///
57    /// Passing a job of the wrong type is a compile error, not a runtime
58    /// surprise:
59    ///
60    /// ```compile_fail
61    /// use nest_rs_queue::{queue, JobProducer, JobProducerExt};
62    ///
63    /// #[queue(name = "transcode", job = String)]
64    /// struct TranscodeQueue;
65    ///
66    /// async fn demo<P: JobProducer>(producer: &P) {
67    ///     // `TranscodeQueue::Job` is `String`; a `u32` does not compile.
68    ///     producer.push_to::<TranscodeQueue>(42u32).await.unwrap();
69    /// }
70    /// ```
71    async fn push_to<Q: QueueName>(&self, job: Q::Job) -> Result<(), QueueError> {
72        let value = serde_json::to_value(&job)?;
73        self.push_json(Q::NAME, value).await
74    }
75
76    /// Push a job onto a queue named by a raw string — the dynamic-name escape
77    /// hatch. Prefer [`push_to`](JobProducerExt::push_to): a typed handle
78    /// compile-checks both the name and the payload type. Reach for this only
79    /// when the queue name genuinely isn't known until runtime. Fails with
80    /// [`QueueError::Serialize`] if the job won't serialize, else with whatever
81    /// [`push_json`](JobProducer::push_json) returns.
82    async fn push<J: Job + Serialize>(&self, queue: &str, job: J) -> Result<(), QueueError> {
83        let value = serde_json::to_value(&job)?;
84        self.push_json(queue, value).await
85    }
86}
87
88impl<T: JobProducer + ?Sized> JobProducerExt for T {}