use crate::error::{Error, Result};
use crate::event::EventBus;
use crate::message::processor::PlainMessageProcessor;
use async_trait::async_trait;
use serde_json;
use std::collections::HashMap;
use std::sync::Arc;
use tap_msg::didcomm::PlainMessage;
use tap_msg::message::tap_message_trait::TapMessageBody;
use tap_msg::message::{TrustPing, TrustPingResponse};
pub struct TrustPingProcessor {
event_bus: Option<Arc<EventBus>>,
}
impl std::fmt::Debug for TrustPingProcessor {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("TrustPingProcessor")
.field("event_bus", &self.event_bus.is_some())
.finish()
}
}
impl Clone for TrustPingProcessor {
fn clone(&self) -> Self {
Self {
event_bus: self.event_bus.clone(),
}
}
}
impl Default for TrustPingProcessor {
fn default() -> Self {
Self::new()
}
}
impl TrustPingProcessor {
pub fn new() -> Self {
Self { event_bus: None }
}
pub fn with_event_bus(event_bus: Arc<EventBus>) -> Self {
Self {
event_bus: Some(event_bus),
}
}
fn generate_ping_response(ping_message: &PlainMessage) -> Result<PlainMessage> {
let _ping: TrustPing = serde_json::from_value(ping_message.body.clone())
.map_err(|e| Error::Serialization(format!("Failed to parse TrustPing: {}", e)))?;
let response =
TrustPingResponse::with_comment(ping_message.id.clone(), "Pong!".to_string());
let response_message = PlainMessage {
id: uuid::Uuid::new_v4().to_string(),
typ: "application/didcomm-plain+json".to_string(),
type_: TrustPingResponse::message_type().to_string(),
body: serde_json::to_value(&response).map_err(|e| {
Error::Serialization(format!("Failed to serialize response: {}", e))
})?,
from: ping_message.to[0].clone(), to: vec![ping_message.from.clone()], thid: Some(ping_message.id.clone()), pthid: None,
extra_headers: HashMap::new(),
attachments: None,
created_time: Some(chrono::Utc::now().timestamp_millis() as u64),
expires_time: None,
from_prior: None,
};
Ok(response_message)
}
fn should_respond_to_ping(message: &PlainMessage) -> bool {
if message.type_ != TrustPing::message_type() {
return false;
}
if let Ok(ping) = serde_json::from_value::<TrustPing>(message.body.clone()) {
ping.response_requested
} else {
false
}
}
}
#[async_trait]
impl PlainMessageProcessor for TrustPingProcessor {
async fn process_incoming(&self, message: PlainMessage) -> Result<Option<PlainMessage>> {
if Self::should_respond_to_ping(&message) {
log::debug!(
"Received Trust Ping from {}, generating response",
message.from
);
match Self::generate_ping_response(&message) {
Ok(response) => {
log::debug!("Generated Trust Ping response for message {}", message.id);
if let Some(ref event_bus) = self.event_bus {
let from = response.from.clone();
let to = response.to.first().cloned().unwrap_or_default();
event_bus.publish_message_sent(response, from, to).await;
log::info!("Successfully published Trust Ping response via event bus");
} else {
log::info!("Trust Ping response generated (no event bus configured): id={}, to={:?}", response.id, response.to);
}
}
Err(e) => {
log::warn!("Failed to generate Trust Ping response: {}", e);
}
}
}
Ok(Some(message))
}
async fn process_outgoing(&self, message: PlainMessage) -> Result<Option<PlainMessage>> {
Ok(Some(message))
}
}
#[cfg(test)]
mod tests {
use super::*;
use tap_msg::message::TrustPing;
#[test]
fn test_should_respond_to_ping() {
let ping = TrustPing::new().response_requested(true);
let ping_message = PlainMessage {
id: "ping-123".to_string(),
typ: "application/didcomm-plain+json".to_string(),
type_: TrustPing::message_type().to_string(),
body: serde_json::to_value(&ping).unwrap(),
from: "did:example:sender".to_string(),
to: vec!["did:example:recipient".to_string()],
thid: None,
pthid: None,
extra_headers: HashMap::new(),
attachments: None,
created_time: Some(chrono::Utc::now().timestamp_millis() as u64),
expires_time: None,
from_prior: None,
};
assert!(TrustPingProcessor::should_respond_to_ping(&ping_message));
let ping_no_response = TrustPing::new().response_requested(false);
let ping_message_no_response = PlainMessage {
id: "ping-456".to_string(),
typ: "application/didcomm-plain+json".to_string(),
type_: TrustPing::message_type().to_string(),
body: serde_json::to_value(&ping_no_response).unwrap(),
from: "did:example:sender".to_string(),
to: vec!["did:example:recipient".to_string()],
thid: None,
pthid: None,
extra_headers: HashMap::new(),
attachments: None,
created_time: Some(chrono::Utc::now().timestamp_millis() as u64),
expires_time: None,
from_prior: None,
};
assert!(!TrustPingProcessor::should_respond_to_ping(
&ping_message_no_response
));
}
#[test]
fn test_generate_ping_response() {
let ping = TrustPing::with_comment("Hello!".to_string()).response_requested(true);
let ping_message = PlainMessage {
id: "ping-123".to_string(),
typ: "application/didcomm-plain+json".to_string(),
type_: TrustPing::message_type().to_string(),
body: serde_json::to_value(&ping).unwrap(),
from: "did:example:sender".to_string(),
to: vec!["did:example:recipient".to_string()],
thid: None,
pthid: None,
extra_headers: HashMap::new(),
attachments: None,
created_time: Some(chrono::Utc::now().timestamp_millis() as u64),
expires_time: None,
from_prior: None,
};
let response = TrustPingProcessor::generate_ping_response(&ping_message).unwrap();
assert_eq!(response.type_, TrustPingResponse::message_type());
assert_eq!(response.from, "did:example:recipient");
assert_eq!(response.to, vec!["did:example:sender"]);
assert_eq!(response.thid, Some("ping-123".to_string()));
let response_body: TrustPingResponse = serde_json::from_value(response.body).unwrap();
assert_eq!(response_body.thread_id, "ping-123");
assert_eq!(response_body.comment, Some("Pong!".to_string()));
}
}