Skip to main content

ruststream_lapin/
context.rs

1//! Per-delivery context fields exposed to handlers.
2//!
3//! [`AmqpContext`] carries the AMQP delivery metadata that is not part of the payload or the
4//! headers. Request it in a handler by typing the context parameter as
5//! `Context<'_, AmqpContext>` and read individual fields with the zero-sized keys in [`keys`].
6//! The prelude carries the keys but not the context type, so import that from here. The
7//! [AmqpContext](https://powersemmi.github.io/ruststream-lapin/latest/components/context/) page
8//! shows both forms in a service.
9//!
10//! # Examples
11//!
12//! ```
13//! use ruststream_lapin::context::AmqpContext;
14//! use ruststream_lapin::context::keys::RoutingKey;
15//! use ruststream_lapin::prelude::*;
16//! use serde::Deserialize;
17//!
18//! #[derive(Deserialize)]
19//! struct Order {
20//!     id: u64,
21//! }
22//!
23//! #[subscriber(RabbitQueue::new("orders"))]
24//! async fn audit(order: &Order, ctx: &mut Context<'_, AmqpContext>) -> HandlerOutcome {
25//!     tracing::info!(order.id, routing_key = ctx.context(RoutingKey), "audited");
26//!     HandlerOutcome::ack()
27//! }
28//!
29//! fn app() -> RustStream {
30//!     let broker = LapinBroker::new("amqp://localhost:5672");
31//!     RustStream::new(AppInfo::new("audit", "0.1.0")).with_broker(broker, |b| {
32//!         b.include(audit);
33//!     })
34//! }
35//! ```
36
37use ruststream::{BuildContext, Field};
38
39use crate::message::LapinMessage;
40
41/// Native AMQP delivery metadata, built once per delivery.
42#[derive(Debug, Clone, Default, PartialEq, Eq)]
43pub struct AmqpContext {
44    exchange: String,
45    routing_key: String,
46    redelivered: bool,
47    delivery_tag: u64,
48}
49
50impl AmqpContext {
51    /// The exchange the message was published to (empty for the default exchange).
52    #[must_use]
53    pub fn exchange(&self) -> &str {
54        &self.exchange
55    }
56
57    /// The routing key the message was published with.
58    #[must_use]
59    pub fn routing_key(&self) -> &str {
60        &self.routing_key
61    }
62
63    /// Whether the broker marked the delivery as redelivered.
64    #[must_use]
65    pub fn redelivered(&self) -> bool {
66        self.redelivered
67    }
68
69    /// The channel-local delivery tag.
70    #[must_use]
71    pub fn delivery_tag(&self) -> u64 {
72        self.delivery_tag
73    }
74}
75
76impl BuildContext<LapinMessage> for AmqpContext {
77    fn build(msg: &LapinMessage) -> Self {
78        Self {
79            exchange: msg.exchange().to_owned(),
80            routing_key: msg.routing_key().to_owned(),
81            redelivered: msg.redelivered(),
82            delivery_tag: msg.delivery_tag(),
83        }
84    }
85}
86
87/// Zero-sized [`Field`] keys reading one [`AmqpContext`] field each.
88///
89/// A handler names the fields it needs one key at a time, through `Ctx<..>`; the prelude carries
90/// the four keys.
91///
92/// # Examples
93///
94/// ```
95/// use ruststream_lapin::prelude::*;
96/// use serde::Deserialize;
97///
98/// #[derive(Deserialize)]
99/// struct Order {
100///     id: u64,
101/// }
102///
103/// #[subscriber(RabbitQueue::new("orders-audit"))]
104/// async fn audit(
105///     order: &Order,
106///     Ctx(routing_key): Ctx<RoutingKey>,
107///     Ctx(redelivered): Ctx<Redelivered>,
108/// ) -> HandlerOutcome {
109///     tracing::info!(order.id, routing_key, redelivered, "audited");
110///     HandlerOutcome::ack()
111/// }
112///
113/// fn app() -> RustStream {
114///     let broker = LapinBroker::new("amqp://localhost:5672");
115///     RustStream::new(AppInfo::new("audit", "0.1.0")).with_broker(broker, |b| {
116///         b.include(audit);
117///     })
118/// }
119/// ```
120pub mod keys {
121    use ruststream::ContextField;
122
123    use super::{AmqpContext, Field};
124
125    /// Reads the source exchange name.
126    #[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
127    pub struct Exchange;
128
129    impl Field<AmqpContext> for Exchange {
130        type Value<'a> = &'a str;
131
132        fn get(self, src: &AmqpContext) -> &str {
133            src.exchange()
134        }
135    }
136
137    impl ContextField for Exchange {
138        type Context = AmqpContext;
139        type Value = String;
140        fn read(self, src: &AmqpContext) -> String {
141            src.exchange().to_owned()
142        }
143    }
144
145    /// Reads the routing key.
146    #[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
147    pub struct RoutingKey;
148
149    impl Field<AmqpContext> for RoutingKey {
150        type Value<'a> = &'a str;
151
152        fn get(self, src: &AmqpContext) -> &str {
153            src.routing_key()
154        }
155    }
156
157    impl ContextField for RoutingKey {
158        type Context = AmqpContext;
159        type Value = String;
160        fn read(self, src: &AmqpContext) -> String {
161            src.routing_key().to_owned()
162        }
163    }
164
165    /// Reads the redelivered flag.
166    #[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
167    pub struct Redelivered;
168
169    impl Field<AmqpContext> for Redelivered {
170        type Value<'a> = bool;
171
172        fn get(self, src: &AmqpContext) -> bool {
173            src.redelivered()
174        }
175    }
176
177    impl ContextField for Redelivered {
178        type Context = AmqpContext;
179        type Value = bool;
180        fn read(self, src: &AmqpContext) -> bool {
181            src.redelivered()
182        }
183    }
184
185    /// Reads the channel-local delivery tag.
186    #[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
187    pub struct DeliveryTag;
188
189    impl Field<AmqpContext> for DeliveryTag {
190        type Value<'a> = u64;
191
192        fn get(self, src: &AmqpContext) -> u64 {
193            src.delivery_tag()
194        }
195    }
196
197    impl ContextField for DeliveryTag {
198        type Context = AmqpContext;
199        type Value = u64;
200        fn read(self, src: &AmqpContext) -> u64 {
201            src.delivery_tag()
202        }
203    }
204}