1use std::sync::Arc;
4
5use lapin::options::{
6 BasicConsumeOptions, BasicQosOptions, ExchangeDeclareOptions, QueueBindOptions,
7 QueueDeclareOptions,
8};
9use lapin::types::{AMQPValue, FieldTable, ShortString};
10use lapin::{Channel, Connection, ConnectionProperties};
11use ruststream::{Broker, DescribeServer, ServerSpec, Subscribe};
12use tokio::sync::OnceCell;
13
14use crate::convert;
15use crate::delay::{Delay, DelayContext, DelayTarget};
16use crate::error::AmqpError;
17use crate::publisher::LapinPublisher;
18use crate::queue::{QueueType, RabbitQueue};
19use crate::requester::LapinRequester;
20use crate::subscriber::LapinSubscriber;
21
22#[derive(Debug)]
24pub(crate) struct ConnState {
25 connection: Connection,
26 publish_channel: Channel,
27}
28
29impl ConnState {
30 pub(crate) fn connection(&self) -> &Connection {
31 &self.connection
32 }
33
34 pub(crate) fn publish_channel(&self) -> &Channel {
35 &self.publish_channel
36 }
37}
38
39pub(crate) type SharedConn = Arc<OnceCell<ConnState>>;
42
43#[derive(Debug, Clone)]
65pub struct LapinBroker {
66 conn: SharedConn,
67 uri: String,
68 connection_name: Option<String>,
69 prefetch: Option<u16>,
70 declare: bool,
71 default_queue_type: Option<QueueType>,
72}
73
74impl LapinBroker {
75 #[must_use]
80 pub fn new(uri: impl Into<String>) -> Self {
81 Self {
82 conn: Arc::new(OnceCell::new()),
83 uri: uri.into(),
84 connection_name: None,
85 prefetch: None,
86 declare: false,
87 default_queue_type: None,
88 }
89 }
90
91 pub async fn connect(uri: impl Into<String>) -> Result<Self, AmqpError> {
97 let broker = Self::new(uri);
98 Broker::connect(&broker).await?;
99 Ok(broker)
100 }
101
102 #[must_use]
104 pub fn connection_name(mut self, name: impl Into<String>) -> Self {
105 self.connection_name = Some(name.into());
106 self
107 }
108
109 #[must_use]
114 pub fn prefetch(mut self, prefetch: u16) -> Self {
115 self.prefetch = Some(prefetch);
116 self
117 }
118
119 #[must_use]
125 pub fn declare_topology(mut self, declare: bool) -> Self {
126 self.declare = declare;
127 self
128 }
129
130 #[must_use]
136 pub fn default_queue_type(mut self, queue_type: QueueType) -> Self {
137 self.default_queue_type = Some(queue_type);
138 self
139 }
140
141 fn connected(&self) -> Result<&ConnState, AmqpError> {
142 self.conn.get().ok_or(AmqpError::NotConnected)
143 }
144
145 pub async fn subscribe(&self, def: RabbitQueue) -> Result<LapinSubscriber, AmqpError> {
154 let state = self.connected()?;
155 let channel = state
156 .connection
157 .create_channel()
158 .await
159 .map_err(AmqpError::subscribe)?;
160
161 if self.declare {
162 declare_topology(&channel, &def, self.default_queue_type).await?;
163 }
164 if let Some(prefetch) = def.prefetch_or(self.prefetch) {
165 channel
166 .basic_qos(prefetch, BasicQosOptions::default())
167 .await
168 .map_err(AmqpError::subscribe)?;
169 }
170
171 let queue = def.name().to_owned();
172 let delay = def
176 .delay_config()
177 .map(|delay| DelayContext::new(channel.clone(), delay.target_for(&queue)));
178
179 let consumer = channel
180 .basic_consume(
181 convert::short(&queue, "queue name")?,
182 ShortString::default(),
183 BasicConsumeOptions::default(),
184 FieldTable::default(),
185 )
186 .await
187 .map_err(AmqpError::subscribe)?;
188
189 Ok(LapinSubscriber::new(channel, consumer, queue, delay))
190 }
191
192 #[must_use]
197 pub fn publisher(&self) -> LapinPublisher {
198 LapinPublisher::new(Arc::clone(&self.conn))
199 }
200
201 #[must_use]
203 pub fn requester(&self) -> LapinRequester {
204 LapinRequester::new(Arc::clone(&self.conn))
205 }
206}
207
208impl Broker for LapinBroker {
209 type Error = AmqpError;
210
211 async fn connect(&self) -> Result<(), Self::Error> {
217 self.conn
218 .get_or_try_init(|| async {
219 let mut properties = ConnectionProperties::default();
220 if let Some(name) = &self.connection_name {
221 properties = properties.with_connection_name(name.as_str().into());
222 }
223 let connection = Connection::connect(&self.uri, properties)
224 .await
225 .map_err(AmqpError::connect)?;
226 let publish_channel = connection
227 .create_channel()
228 .await
229 .map_err(AmqpError::connect)?;
230 Ok(ConnState {
231 connection,
232 publish_channel,
233 })
234 })
235 .await?;
236 Ok(())
237 }
238
239 async fn shutdown(&self) -> Result<(), Self::Error> {
246 if let Some(state) = self.conn.get()
247 && state.connection.status().connected()
248 {
249 state
250 .connection
251 .close(200, ShortString::from("OK"))
252 .await
253 .map_err(AmqpError::connect)?;
254 }
255 Ok(())
256 }
257}
258
259#[allow(clippy::use_self)]
262impl Subscribe for LapinBroker {
263 type Subscriber = LapinSubscriber;
264
265 async fn subscribe(&self, name: &str) -> Result<Self::Subscriber, Self::Error> {
267 LapinBroker::subscribe(self, RabbitQueue::new(name)).await
268 }
269}
270
271impl DescribeServer for LapinBroker {
272 fn describe_server(&self) -> ServerSpec {
273 ServerSpec::new(host_of(&self.uri), "amqp")
274 }
275}
276
277fn host_of(uri: &str) -> String {
280 let after_scheme = uri.split_once("://").map_or(uri, |(_, rest)| rest);
281 let after_auth = after_scheme
282 .rsplit_once('@')
283 .map_or(after_scheme, |(_, rest)| rest);
284 let host = after_auth.split(['/', '?']).next().unwrap_or(after_auth);
285 host.to_owned()
286}
287
288async fn declare_topology(
289 channel: &Channel,
290 def: &RabbitQueue,
291 broker_default: Option<QueueType>,
292) -> Result<(), AmqpError> {
293 for (exchange, _) in def.bindings() {
294 if exchange.name().is_empty() || exchange.name().starts_with("amq.") {
297 continue;
298 }
299 channel
300 .exchange_declare(
301 convert::short(exchange.name(), "exchange name")?,
302 exchange.kind().clone(),
303 ExchangeDeclareOptions {
304 durable: exchange.is_durable(),
305 auto_delete: exchange.is_auto_delete(),
306 ..ExchangeDeclareOptions::default()
307 },
308 FieldTable::default(),
309 )
310 .await
311 .map_err(AmqpError::declare)?;
312 }
313
314 let queue_type = def.queue_type_or(broker_default);
315 if queue_type == Some(QueueType::Quorum) && !def.is_durable() {
316 return Err(AmqpError::InvalidOptions(format!(
317 "queue {:?} is a quorum queue and must stay durable; drop `.durable(false)` or pick \
318 `QueueType::Classic`",
319 def.name(),
320 )));
321 }
322
323 let mut arguments = def.declare_arguments().clone();
324 if let Some(queue_type) = queue_type {
325 arguments.insert(
326 ShortString::from("x-queue-type"),
327 AMQPValue::LongString(queue_type.as_str().into()),
328 );
329 }
330 channel
331 .queue_declare(
332 convert::short(def.name(), "queue name")?,
333 QueueDeclareOptions {
334 durable: def.is_durable(),
335 exclusive: def.is_exclusive(),
336 auto_delete: def.is_auto_delete(),
337 ..QueueDeclareOptions::default()
338 },
339 arguments,
340 )
341 .await
342 .map_err(AmqpError::declare)?;
343
344 for (exchange, routing_key) in def.bindings() {
345 channel
346 .queue_bind(
347 convert::short(def.name(), "queue name")?,
348 convert::short(exchange.name(), "exchange name")?,
349 convert::short(routing_key, "routing key")?,
350 QueueBindOptions::default(),
351 FieldTable::default(),
352 )
353 .await
354 .map_err(AmqpError::declare)?;
355 }
356
357 if let Some(delay) = def.delay_config() {
358 declare_delay_backend(channel, delay, def.name()).await?;
359 }
360
361 Ok(())
362}
363
364async fn declare_delay_backend(
366 channel: &Channel,
367 delay: &Delay,
368 origin: &str,
369) -> Result<(), AmqpError> {
370 match delay.target_for(origin) {
371 DelayTarget::WaitingQueue { waiting_queue } => {
372 declare_delay_queue(channel, &waiting_queue, origin).await
373 }
374 #[cfg(feature = "plugin-dme")]
375 DelayTarget::DelayedExchange {
376 exchange,
377 routing_key,
378 } => declare_delayed_exchange(channel, &exchange, origin, &routing_key).await,
379 }
380}
381
382async fn declare_delay_queue(
386 channel: &Channel,
387 waiting_queue: &str,
388 origin: &str,
389) -> Result<(), AmqpError> {
390 let mut arguments = FieldTable::default();
391 arguments.insert(
392 ShortString::from("x-dead-letter-exchange"),
393 AMQPValue::LongString(String::new().into()),
394 );
395 arguments.insert(
396 ShortString::from("x-dead-letter-routing-key"),
397 AMQPValue::LongString(origin.into()),
398 );
399 channel
400 .queue_declare(
401 convert::short(waiting_queue, "waiting queue name")?,
402 QueueDeclareOptions {
403 durable: true,
404 ..QueueDeclareOptions::default()
405 },
406 arguments,
407 )
408 .await
409 .map_err(AmqpError::declare)?;
410 Ok(())
411}
412
413#[cfg(feature = "plugin-dme")]
416async fn declare_delayed_exchange(
417 channel: &Channel,
418 exchange: &str,
419 origin: &str,
420 routing_key: &str,
421) -> Result<(), AmqpError> {
422 let mut arguments = FieldTable::default();
423 arguments.insert(
425 ShortString::from("x-delayed-type"),
426 AMQPValue::LongString("direct".into()),
427 );
428 channel
429 .exchange_declare(
430 convert::short(exchange, "delayed exchange name")?,
431 lapin::ExchangeKind::Custom("x-delayed-message".to_owned()),
432 ExchangeDeclareOptions {
433 durable: true,
434 ..ExchangeDeclareOptions::default()
435 },
436 arguments,
437 )
438 .await
439 .map_err(AmqpError::declare)?;
440 channel
441 .queue_bind(
442 convert::short(origin, "queue name")?,
443 convert::short(exchange, "delayed exchange name")?,
444 convert::short(routing_key, "routing key")?,
445 QueueBindOptions::default(),
446 FieldTable::default(),
447 )
448 .await
449 .map_err(AmqpError::declare)?;
450 Ok(())
451}
452
453#[cfg(test)]
454mod tests {
455 use super::host_of;
456
457 #[test]
458 fn host_extraction_handles_auth_vhost_and_bare_forms() {
459 assert_eq!(host_of("amqp://localhost:5672"), "localhost:5672");
460 assert_eq!(host_of("amqp://user:pass@rabbit:5672/prod"), "rabbit:5672");
461 assert_eq!(host_of("amqps://rabbit/vhost"), "rabbit");
462 assert_eq!(host_of("rabbit:5672"), "rabbit:5672");
463 }
464}