amq-rpc 0.1.0

RabbitMQ RPC library
Documentation
use lapin::{
    options::*, types::FieldTable, BasicProperties,
    Connection, ConnectionProperties, Consumer, Channel, Queue
};
use std::sync::Arc;
use tokio::sync::RwLock;
use crate::error::{RpcError, RpcResult};

#[derive(Clone)]
pub struct AmqpConnection {
    connection: Arc<RwLock<Option<Connection>>>,
    channel: Arc<RwLock<Option<Channel>>>,
    url: String,
}

impl AmqpConnection {
    pub fn new(url: impl Into<String>) -> Self {
        Self {
            connection: Arc::new(RwLock::new(None)),
            channel: Arc::new(RwLock::new(None)),
            url: url.into(),
        }
    }

    pub async fn connect(&self) -> RpcResult<()> {
        let conn = Connection::connect(&self.url, ConnectionProperties::default()).await?;
        let channel = conn.create_channel().await?;

        *self.connection.write().await = Some(conn);
        *self.channel.write().await = Some(channel);

        Ok(())
    }

    pub async fn get_channel(&self) -> RpcResult<Channel> {
        let channel_guard = self.channel.read().await;
        match channel_guard.as_ref() {
            Some(channel) => Ok(channel.clone()),
            None => Err(RpcError::InvalidCommand { message: "No connection available".to_string() })
        }
    }

    pub async fn declare_queue(&self, queue_name: &str, exclusive: bool) -> RpcResult<Queue> {
        let channel = self.get_channel().await?;
        let queue = channel
            .queue_declare(
                queue_name,
                QueueDeclareOptions {
                    exclusive,
                    ..Default::default()
                },
                FieldTable::default(),
            )
            .await?;
        Ok(queue)
    }

    pub async fn publish(
        &self,
        queue_name: &str,
        payload: &[u8],
        correlation_id: &str,
        reply_to: Option<&str>,
        expiration: Option<u64>,
    ) -> RpcResult<()> {
        let channel = self.get_channel().await?;

        let mut properties = BasicProperties::default()
            .with_correlation_id(correlation_id.into());

        if let Some(reply_queue) = reply_to {
            properties = properties.with_reply_to(reply_queue.into());
        }

        if let Some(exp) = expiration {
            properties = properties.with_expiration(exp.to_string().into());
        }

        channel
            .basic_publish(
                "",
                queue_name,
                BasicPublishOptions {
                    mandatory: true,
                    ..Default::default()
                },
                payload,
                properties,
            )
            .await?
            .await?;

        Ok(())
    }

    pub async fn consume(&self, queue_name: &str, consumer_tag: &str) -> RpcResult<Consumer> {
        let channel = self.get_channel().await?;
        let consumer = channel
            .basic_consume(
                queue_name,
                consumer_tag,
                BasicConsumeOptions::default(),
                FieldTable::default(),
            )
            .await?;
        Ok(consumer)
    }

    pub async fn ack(&self, delivery_tag: u64) -> RpcResult<()> {
        let channel = self.get_channel().await?;
        channel.basic_ack(delivery_tag, BasicAckOptions::default()).await?;
        Ok(())
    }

    pub async fn close(&self) -> RpcResult<()> {
        if let Some(connection) = self.connection.write().await.take() {
            connection.close(200, "Normal shutdown").await?;
        }
        Ok(())
    }
}