amq-rpc 0.1.0

RabbitMQ RPC library
Documentation
use std::collections::HashMap;
use std::sync::Arc;
use tokio::sync::RwLock;
use futures::StreamExt;

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

pub struct RpcServer {
    connection: AmqpConnection,
    queue: String,
    handlers: Arc<RwLock<HashMap<String, Box<dyn CommandHandler>>>>,
}

impl RpcServer {
    pub fn new(connection: AmqpConnection, queue: String) -> Self {
        Self {
            connection,
            queue,
            handlers: Arc::new(RwLock::new(HashMap::new())),
        }
    }

    pub async fn register_handler<H>(&self, command: &str, handler: H)
    where
        H: CommandHandler + 'static,
    {
        self.handlers.write().await.insert(command.to_string(), Box::new(handler));
    }

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

        let consumer = self.connection.consume(&self.queue, "rpc_server").await?;
        let handlers = self.handlers.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();

                    let reply_to = delivery.properties.reply_to()
                        .as_ref()
                        .map(|s| s.as_str().to_string());

                    if let Some(reply_queue) = reply_to {
                        let response = match serde_json::from_slice::<Command>(&delivery.data) {
                            Ok(cmd) => {
                                let handlers_guard = handlers.read().await;
                                match handlers_guard.get(&cmd.command) {
                                    Some(handler) => {
                                        match handler.handle(&cmd.command, cmd.args).await {
                                            Ok(result) => CommandResponse {
                                                success: true,
                                                result: Some(result),
                                                error: None,
                                            },
                                            Err(e) => CommandResponse {
                                                success: false,
                                                result: None,
                                                error: Some(e.to_string()),
                                            },
                                        }
                                    }
                                    None => CommandResponse {
                                        success: false,
                                        result: None,
                                        error: Some(format!("Unknown command: {}", cmd.command)),
                                    },
                                }
                            }
                            Err(e) => CommandResponse {
                                success: false,
                                result: None,
                                error: Some(format!("Failed to parse command: {}", e)),
                            },
                        };

                        if let Ok(response_payload) = serde_json::to_vec(&response) {
                            let _ = connection.publish(
                                &reply_queue,
                                &response_payload,
                                &correlation_id,
                                None,
                                None,
                            ).await;
                        }
                    }

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

        Ok(())
    }

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