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