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
}
}