amq-rpc 0.1.0

RabbitMQ RPC library
Documentation
use std::collections::HashMap;
use std::sync::Arc;
use std::time::Duration;
use tokio::sync::{RwLock, oneshot};
use tokio::time::timeout;
use uuid::Uuid;
use futures::StreamExt;
use serde_json::Value;

use crate::connection::AmqpConnection;
use crate::error::{RpcError, RpcResult};
use crate::types::{Command, CommandResponse};

pub struct RpcClient {
    connection: AmqpConnection,
    reply_queue: String,
    pending_commands: Arc<RwLock<HashMap<String, oneshot::Sender<CommandResponse>>>>,
    timeout_ms: u64,
}

impl RpcClient {
    pub fn new(connection: AmqpConnection, timeout_ms: Option<u64>) -> Self {
        let reply_queue = format!("rpc_reply_{}", Uuid::new_v4());

        Self {
            connection,
            reply_queue,
            pending_commands: Arc::new(RwLock::new(HashMap::new())),
            timeout_ms: timeout_ms.unwrap_or(60000),
        }
    }

    pub async fn start(&self) -> RpcResult<()> {
        self.connection.connect().await?;
        self.connection.declare_queue(&self.reply_queue, true).await?;

        let consumer = self.connection.consume(&self.reply_queue, "rpc_client").await?;
        let pending_commands = self.pending_commands.clone();
        let connection = self.connection.clone();

        tokio::spawn(async move {
            let mut consumer = consumer;
            while let Some(delivery) = consumer.next().await {
                if let Ok(delivery) = delivery {
                    let correlation_id = delivery.properties.correlation_id()
                        .as_ref()
                        .map(|s| s.as_str().to_string())
                        .unwrap_or_default();

                    if let Ok(response) = serde_json::from_slice::<CommandResponse>(&delivery.data) {
                        if let Some(sender) = pending_commands.write().await.remove(&correlation_id) {
                            let _ = sender.send(response);
                        }
                    }

                    let _ = connection.ack(delivery.delivery_tag).await;
                }
            }
        });

        Ok(())
    }

    pub async fn send_command(&self, queue: &str, command: &str, args: Vec<Value>) -> RpcResult<Value> {
        let correlation_id = Uuid::new_v4().to_string();
        let (tx, rx) = oneshot::channel();

        self.pending_commands.write().await.insert(correlation_id.clone(), tx);

        let cmd = Command {
            command: command.to_string(),
            args,
        };

        let payload = serde_json::to_vec(&cmd)?;

        self.connection.publish(
            queue,
            &payload,
            &correlation_id,
            Some(&self.reply_queue),
            Some(self.timeout_ms),
        ).await?;

        match timeout(Duration::from_millis(self.timeout_ms), rx).await {
            Ok(Ok(response)) => {
                if response.success {
                    Ok(response.result.unwrap_or(Value::Null))
                } else {
                    Err(RpcError::ServerError {
                        message: response.error.unwrap_or("Unknown server error".to_string())
                    })
                }
            }
            Ok(Err(_)) => Err(RpcError::Cancelled),
            Err(_) => {
                self.pending_commands.write().await.remove(&correlation_id);
                Err(RpcError::Timeout)
            }
        }
    }

    pub async fn close(&self) -> RpcResult<()> {
        self.pending_commands.write().await.clear();
        self.connection.close().await
    }
}