use crate::didcomm::PlainMessage;
use crate::error::{Error, Result};
use crate::message::policy::Policy;
use crate::message::{
AddAgents, Agent, Authorize, Cancel, ConfirmRelationship, Party, Reject, RemoveAgent,
ReplaceAgent, Revert, Settle, UpdateParty, UpdatePolicies,
};
use chrono::Utc;
use serde::de::DeserializeOwned;
use serde::Serialize;
pub trait TapMessageBody: Serialize + DeserializeOwned + Send + Sync {
fn message_type() -> &'static str
where
Self: Sized;
fn validate(&self) -> Result<()>;
fn to_didcomm(&self, from: &str) -> Result<PlainMessage> {
let mut body_json =
serde_json::to_value(self).map_err(|e| Error::SerializationError(e.to_string()))?;
if let Some(body_obj) = body_json.as_object_mut() {
body_obj.insert(
"@type".to_string(),
serde_json::Value::String(Self::message_type().to_string()),
);
}
let id = uuid::Uuid::new_v4().to_string();
let now = Utc::now().timestamp_millis() as u64;
let mut agent_dids = Vec::new();
if let Some(body_obj) = body_json.as_object() {
if let Some(originator) = body_obj.get("originator") {
if let Some(originator_obj) = originator.as_object() {
if let Some(id) = originator_obj.get("id") {
if let Some(id_str) = id.as_str() {
if id_str.starts_with("did:") {
agent_dids.push(id_str.to_string());
}
}
}
}
}
if let Some(beneficiary) = body_obj.get("beneficiary") {
if let Some(beneficiary_obj) = beneficiary.as_object() {
if let Some(id) = beneficiary_obj.get("id") {
if let Some(id_str) = id.as_str() {
if id_str.starts_with("did:") {
agent_dids.push(id_str.to_string());
}
}
}
}
}
if let Some(agents) = body_obj.get("agents") {
if let Some(agents_array) = agents.as_array() {
for agent in agents_array {
if let Some(agent_obj) = agent.as_object() {
if let Some(id) = agent_obj.get("id") {
if let Some(id_str) = id.as_str() {
if id_str.starts_with("did:") {
agent_dids.push(id_str.to_string());
}
}
}
}
}
}
}
}
agent_dids.sort();
agent_dids.dedup();
agent_dids.retain(|did| did != from);
let message = PlainMessage {
id,
typ: "application/didcomm-plain+json".to_string(),
type_: Self::message_type().to_string(),
from: from.to_string(),
to: agent_dids,
thid: None,
pthid: None,
created_time: Some(now),
expires_time: None,
extra_headers: std::collections::HashMap::new(),
from_prior: None,
body: body_json,
attachments: None,
};
Ok(message)
}
fn to_didcomm_with_route<'a, I>(&self, from: &str, to: I) -> Result<PlainMessage>
where
I: IntoIterator<Item = &'a str>,
{
let mut message = self.to_didcomm(from)?;
let to_vec: Vec<String> = to.into_iter().map(String::from).collect();
if !to_vec.is_empty() {
message.to = to_vec;
}
Ok(message)
}
fn from_didcomm(message: &PlainMessage) -> Result<Self>
where
Self: Sized,
{
if message.type_ != Self::message_type() {
return Err(Error::InvalidMessageType(format!(
"Expected message type {}, but found {}",
Self::message_type(),
message.type_
)));
}
let mut body_json = message.body.clone();
if let Some(body_obj) = body_json.as_object_mut() {
body_obj.insert(
"@type".to_string(),
serde_json::Value::String(Self::message_type().to_string()),
);
}
let body = serde_json::from_value(body_json).map_err(|e| {
Error::SerializationError(format!("Failed to deserialize message body: {}", e))
})?;
Ok(body)
}
}
pub trait Connectable {
fn with_connection(&mut self, connection_id: &str) -> &mut Self;
fn has_connection(&self) -> bool;
fn connection_id(&self) -> Option<&str>;
}
pub trait TapMessage {
fn validate(&self) -> Result<()>;
fn is_tap_message(&self) -> bool;
fn get_tap_type(&self) -> Option<String>;
fn body_as<T: TapMessageBody>(&self) -> Result<T>;
fn get_all_participants(&self) -> Vec<String>;
fn create_reply<T: TapMessageBody>(&self, body: &T, creator_did: &str) -> Result<PlainMessage> {
let mut message = body.to_didcomm(creator_did)?;
if let Some(thread_id) = self.thread_id() {
message.thid = Some(thread_id.to_string());
} else {
message.thid = Some(self.message_id().to_string());
}
if let Some(parent_thread_id) = self.parent_thread_id() {
message.pthid = Some(parent_thread_id.to_string());
}
let participant_dids = self.get_all_participants();
let recipients: Vec<String> = participant_dids
.into_iter()
.filter(|did| did != creator_did)
.collect();
if !recipients.is_empty() {
message.to = recipients;
}
Ok(message)
}
fn thread_id(&self) -> Option<&str>;
fn parent_thread_id(&self) -> Option<&str>;
fn message_id(&self) -> &str;
}
impl TapMessage for PlainMessage {
fn validate(&self) -> Result<()> {
if !self.is_tap_message() {
return Err(Error::Validation("Not a TAP message".to_string()));
}
if self.id.is_empty() {
return Err(Error::Validation(
"Message must have a non-empty ID".to_string(),
));
}
if self.created_time.is_none() {
return Err(Error::Validation(
"Message must have a created timestamp".to_string(),
));
}
Ok(())
}
fn is_tap_message(&self) -> bool {
self.type_.starts_with("https://tap.rsvp/schema/1.0#")
}
fn get_tap_type(&self) -> Option<String> {
if self.is_tap_message() {
Some(self.type_.clone())
} else {
None
}
}
fn body_as<T: TapMessageBody>(&self) -> Result<T> {
if self.type_ != T::message_type() {
return Err(Error::Validation(format!(
"Message type mismatch. Expected {}, got {}",
T::message_type(),
self.type_
)));
}
let mut body_json = self.body.clone();
if let Some(body_obj) = body_json.as_object_mut() {
body_obj.insert(
"@type".to_string(),
serde_json::Value::String(T::message_type().to_string()),
);
}
let body = serde_json::from_value(body_json).map_err(|e| {
Error::SerializationError(format!("Failed to deserialize message body: {}", e))
})?;
Ok(body)
}
fn get_all_participants(&self) -> Vec<String> {
let mut participants = Vec::new();
if !self.from.is_empty() {
participants.push(self.from.clone());
}
participants.extend(self.to.clone());
participants
}
fn thread_id(&self) -> Option<&str> {
self.thid.as_deref()
}
fn parent_thread_id(&self) -> Option<&str> {
self.pthid.as_deref()
}
fn message_id(&self) -> &str {
&self.id
}
}
impl Connectable for PlainMessage {
fn with_connection(&mut self, connection_id: &str) -> &mut Self {
self.pthid = Some(connection_id.to_string());
self
}
fn has_connection(&self) -> bool {
self.pthid.is_some()
}
fn connection_id(&self) -> Option<&str> {
self.pthid.as_deref()
}
}
pub fn typed_plain_message<T: TapMessageBody>(reply: PlainMessage, body: T) -> PlainMessage<T> {
PlainMessage {
id: reply.id,
typ: reply.typ,
type_: reply.type_,
body,
from: reply.from,
to: reply.to,
thid: reply.thid,
pthid: reply.pthid,
extra_headers: reply.extra_headers,
created_time: reply.created_time,
expires_time: reply.expires_time,
from_prior: reply.from_prior,
attachments: reply.attachments,
}
}
pub fn create_tap_message<T: TapMessageBody>(
body: &T,
id: Option<String>,
from_did: &str,
to_dids: &[&str],
) -> Result<PlainMessage> {
let mut message = body.to_didcomm(from_did)?;
if let Some(custom_id) = id {
message.id = custom_id;
}
if !to_dids.is_empty() {
message.to = to_dids.iter().map(|&s| s.to_string()).collect();
}
Ok(message)
}
pub trait Authorizable: TapMessage {
fn authorize(
&self,
creator_did: &str,
settlement_address: Option<&str>,
expiry: Option<&str>,
) -> PlainMessage<Authorize>;
fn cancel(&self, creator_did: &str, by: &str, reason: Option<&str>) -> PlainMessage<Cancel>;
fn reject(&self, creator_did: &str, reason: &str) -> PlainMessage<Reject>;
}
pub trait Transaction: TapMessage {
fn settle(
&self,
creator_did: &str,
settlement_id: &str,
amount: Option<&str>,
) -> PlainMessage<Settle>;
fn revert(
&self,
creator_did: &str,
settlement_address: &str,
reason: &str,
) -> PlainMessage<Revert>;
fn add_agents(&self, creator_did: &str, agents: Vec<Agent>) -> PlainMessage<AddAgents>;
fn replace_agent(
&self,
creator_did: &str,
original_agent: &str,
replacement: Agent,
) -> PlainMessage<ReplaceAgent>;
fn remove_agent(&self, creator_did: &str, agent: &str) -> PlainMessage<RemoveAgent>;
fn update_party(
&self,
creator_did: &str,
party_type: &str,
party: Party,
) -> PlainMessage<UpdateParty>;
fn update_policies(
&self,
creator_did: &str,
policies: Vec<Policy>,
) -> PlainMessage<UpdatePolicies>;
fn confirm_relationship(
&self,
creator_did: &str,
agent_did: &str,
for_entity: &str,
) -> PlainMessage<ConfirmRelationship>;
}