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}