use crate::didcomm::PlainMessage;
use crate::error::Result;
use crate::message::{MessageContext, TapMessageBody, TransactionContext};
use crate::TapMessage;
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Participant {
pub id: String,
pub role: Option<String>,
pub policies: Option<Vec<String>>,
#[serde(rename = "leiCode")]
pub leiCode: Option<String>,
pub name: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize, TapMessage)]
pub struct ExampleTransfer {
#[tap(participant)]
pub originator: Participant,
#[serde(skip_serializing_if = "Option::is_none")]
#[tap(participant)]
pub beneficiary: Option<Participant>,
#[serde(default)]
#[tap(participant_list)]
pub agents: Vec<Participant>,
#[tap(transaction_id)]
pub transaction_id: String,
pub amount: String,
#[serde(default, skip_serializing_if = "HashMap::is_empty")]
pub metadata: HashMap<String, serde_json::Value>,
}
impl TapMessageBody for ExampleTransfer {
fn message_type() -> &'static str {
"https://tap.rsvp/schema/1.0#example-transfer"
}
fn validate(&self) -> Result<()> {
if self.originator.id.is_empty() {
return Err(crate::error::Error::Validation(
"Originator ID is required".to_string(),
));
}
if self.amount.is_empty() {
return Err(crate::error::Error::Validation(
"Amount is required".to_string(),
));
}
Ok(())
}
fn to_didcomm(&self, from: &str) -> Result<PlainMessage> {
let participant_dids = self.participant_dids();
let recipients: Vec<String> = participant_dids
.into_iter()
.filter(|did| did != from)
.collect();
let body_json = serde_json::to_value(self)?;
Ok(PlainMessage {
id: uuid::Uuid::new_v4().to_string(),
typ: "application/didcomm-plain+json".to_string(),
type_: Self::message_type().to_string(),
body: body_json,
from: from.to_string(),
to: recipients,
thid: None,
pthid: None,
created_time: Some(chrono::Utc::now().timestamp() as u64),
expires_time: None,
from_prior: None,
attachments: None,
extra_headers: HashMap::new(),
})
}
}
pub fn example_usage() -> Result<()> {
let originator = Participant {
id: "did:example:alice".to_string(),
role: Some("originator".to_string()),
policies: None,
leiCode: None,
name: Some("Alice".to_string()),
};
let beneficiary = Participant {
id: "did:example:bob".to_string(),
role: Some("beneficiary".to_string()),
policies: None,
leiCode: None,
name: Some("Bob".to_string()),
};
let agent = Participant {
id: "did:example:agent".to_string(),
role: Some("agent".to_string()),
policies: None,
leiCode: None,
name: Some("TAP Agent".to_string()),
};
let transfer = ExampleTransfer {
originator,
beneficiary: Some(beneficiary),
agents: vec![agent],
transaction_id: "tx-12345".to_string(),
amount: "100.00".to_string(),
metadata: HashMap::new(),
};
let participants = transfer.participants();
println!("Participants: {:?}", participants.len());
let participant_dids = transfer.participant_dids();
println!("Participant DIDs: {:?}", participant_dids);
if let Some(tx_context) = transfer.transaction_context() {
println!("Transaction ID: {}", tx_context.transaction_id);
println!("Transaction Type: {}", tx_context.transaction_type);
}
let didcomm_msg = transfer.to_didcomm("did:example:sender")?;
println!("Recipients: {:?}", didcomm_msg.to);
let typed_message = PlainMessage::new_typed(transfer, "did:example:sender");
let extracted_participants = typed_message.extract_participants();
println!("Extracted participants: {:?}", extracted_participants);
Ok(())
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ExamplePresentation {
pub id: String,
pub presenter: Participant,
#[serde(skip_serializing_if = "Option::is_none")]
pub verifier: Option<Participant>,
#[serde(skip_serializing_if = "Option::is_none")]
pub transaction_id: Option<String>,
pub presentation: serde_json::Value,
}
impl TapMessageBody for ExamplePresentation {
fn message_type() -> &'static str {
"https://tap.rsvp/schema/1.0#example-presentation"
}
fn validate(&self) -> Result<()> {
if self.presenter.id.is_empty() {
return Err(crate::error::Error::Validation(
"Presenter ID is required".to_string(),
));
}
Ok(())
}
fn to_didcomm(&self, from: &str) -> Result<PlainMessage> {
let participant_dids = self.participant_dids();
let recipients: Vec<String> = participant_dids
.into_iter()
.filter(|did| did != from)
.collect();
let body_json = serde_json::to_value(self)?;
Ok(PlainMessage {
id: self
.transaction_id
.clone()
.unwrap_or_else(|| uuid::Uuid::new_v4().to_string()),
typ: "application/didcomm-plain+json".to_string(),
type_: Self::message_type().to_string(),
body: body_json,
from: from.to_string(),
to: recipients,
thid: None,
pthid: None,
created_time: Some(chrono::Utc::now().timestamp() as u64),
expires_time: None,
from_prior: None,
attachments: None,
extra_headers: HashMap::new(),
})
}
}
impl crate::message::tap_message_trait::TapMessage for ExamplePresentation {
fn validate(&self) -> crate::error::Result<()> {
<Self as crate::message::tap_message_trait::TapMessageBody>::validate(self)
}
fn is_tap_message(&self) -> bool {
false
}
fn get_tap_type(&self) -> Option<String> {
Some(
<Self as crate::message::tap_message_trait::TapMessageBody>::message_type().to_string(),
)
}
fn body_as<T: crate::message::tap_message_trait::TapMessageBody>(
&self,
) -> crate::error::Result<T> {
unimplemented!()
}
fn get_all_participants(&self) -> Vec<String> {
self.participant_dids()
}
fn create_reply<T: crate::message::tap_message_trait::TapMessageBody>(
&self,
body: &T,
creator_did: &str,
) -> crate::error::Result<crate::didcomm::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());
}
Ok(message)
}
fn thread_id(&self) -> Option<&str> {
self.transaction_id.as_deref()
}
fn parent_thread_id(&self) -> Option<&str> {
None
}
fn message_id(&self) -> &str {
if let Some(ref id) = self.transaction_id {
id
} else {
&self.id
}
}
}
impl MessageContext for ExamplePresentation {
fn participants(&self) -> Vec<&Participant> {
let mut participants = vec![&self.presenter];
if let Some(ref verifier) = self.verifier {
participants.push(verifier);
}
participants
}
fn transaction_context(&self) -> Option<TransactionContext> {
self.transaction_id
.as_ref()
.map(|tx_id| TransactionContext::new(tx_id.clone(), Self::message_type().to_string()))
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_example_transfer_context() {
let originator = Participant {
id: "did:example:alice".to_string(),
role: Some("originator".to_string()),
policies: None,
leiCode: None,
name: None,
};
let beneficiary = Participant {
id: "did:example:bob".to_string(),
role: Some("beneficiary".to_string()),
policies: None,
leiCode: None,
name: None,
};
let transfer = ExampleTransfer {
originator,
beneficiary: Some(beneficiary),
agents: vec![],
transaction_id: "tx-123".to_string(),
amount: "100.00".to_string(),
metadata: HashMap::new(),
};
let participants = transfer.participants();
assert_eq!(participants.len(), 2);
let participant_dids = transfer.participant_dids();
assert_eq!(participant_dids.len(), 2);
assert!(participant_dids.contains(&"did:example:alice".to_string()));
assert!(participant_dids.contains(&"did:example:bob".to_string()));
let tx_context = transfer.transaction_context().unwrap();
assert_eq!(tx_context.transaction_id, "tx-123");
assert_eq!(
tx_context.transaction_type,
"https://tap.rsvp/schema/1.0#example-transfer"
);
}
#[test]
fn test_example_presentation_optional_context() {
let presenter = Participant {
id: "did:example:presenter".to_string(),
role: Some("presenter".to_string()),
policies: None,
leiCode: None,
name: None,
};
let presentation = ExamplePresentation {
id: "pres-123".to_string(),
presenter: presenter.clone(),
verifier: None,
transaction_id: None,
presentation: serde_json::json!({"test": "data"}),
};
assert_eq!(presentation.participants().len(), 1);
assert!(presentation.transaction_context().is_none());
let presentation_with_tx = ExamplePresentation {
id: "pres-456".to_string(),
presenter,
verifier: None,
transaction_id: Some("tx-456".to_string()),
presentation: serde_json::json!({"test": "data"}),
};
assert!(presentation_with_tx.transaction_context().is_some());
assert_eq!(
presentation_with_tx
.transaction_context()
.unwrap()
.transaction_id,
"tx-456"
);
}
}