| Crates.io | tap-msg-derive |
| lib.rs | tap-msg-derive |
| version | 0.4.0 |
| created_at | 2025-06-04 06:30:03.580724+00 |
| updated_at | 2025-06-17 07:33:16.677333+00 |
| description | Derive macros for TAP message types |
| homepage | |
| repository | https://github.com/TransactionAuthorizationProtocol/tap-rs |
| max_upload_size | |
| id | 1699841 |
| size | 56,550 |
Procedural derive macro for automatically implementing TAP message traits.
This crate provides the #[derive(TapMessage)] procedural macro that automatically implements both TapMessage and MessageContext traits for TAP protocol message types. It reduces boilerplate by generating implementations based on struct field attributes.
Add this to your Cargo.toml:
[dependencies]
tap-msg = "0.2.0"
tap-msg-derive = "0.2.0"
serde = { version = "1.0", features = ["derive"] }
use tap_msg::TapMessage;
use tap_msg::message::{Participant, TapMessageBody};
use tap_msg::didcomm::PlainMessage;
use tap_msg::error::Result;
use serde::{Serialize, Deserialize};
#[derive(Debug, Clone, Serialize, Deserialize, TapMessage)]
pub struct Transfer {
/// Originator participant - automatically extracted
#[tap(participant)]
pub originator: Participant,
/// Optional beneficiary - automatically handled
#[tap(participant)]
pub beneficiary: Option<Participant>,
/// List of agents - automatically extracted
#[tap(participant_list)]
pub agents: Vec<Participant>,
/// Transaction ID for message threading
#[tap(transaction_id)]
pub transaction_id: String,
// Regular fields don't need attributes
pub amount: String,
pub asset_id: String,
}
// You still need to implement TapMessageBody for message-specific logic
impl TapMessageBody for Transfer {
fn message_type() -> &'static str {
"https://tap.rsvp/schema/1.0#transfer"
}
fn validate(&self) -> Result<()> {
if self.amount.is_empty() {
return Err(tap_msg::error::Error::Validation("Amount required".to_string()));
}
Ok(())
}
fn to_didcomm(&self, from: &str) -> Result<PlainMessage> {
// Convert to DIDComm message format
// Implementation details...
}
}
For automatic generation of TapMessageBody implementation (including to_didcomm() with automatic participant routing), use the separate TapMessageBody derive macro:
use tap_msg::{TapMessage, TapMessageBody};
use tap_msg::message::Participant;
use serde::{Serialize, Deserialize};
#[derive(Debug, Clone, Serialize, Deserialize, TapMessage, TapMessageBody)]
#[tap(message_type = "https://tap.rsvp/schema/1.0#enhanced-transfer")]
pub struct EnhancedTransfer {
#[tap(participant)]
pub originator: Participant,
#[tap(participant)]
pub beneficiary: Option<Participant>,
#[tap(participant_list)]
pub agents: Vec<Participant>,
#[tap(transaction_id)]
pub transaction_id: String,
pub amount: String,
pub asset_id: String,
}
// No need to manually implement TapMessageBody - it's automatically generated!
// The generated implementation includes:
// - message_type() returning the specified type
// - validate() with basic validation (can be customized by implementing manually)
// - to_didcomm() with automatic participant extraction and routing
This approach eliminates boilerplate and automatically generates the to_didcomm() implementation with proper participant routing based on field attributes.
#[tap(message_type = "url")] - TAP message type URL (required for TapMessageBody derive)#[tap(participant)] - Marks a field as a single participant (type: Participant or Option<Participant>)#[tap(participant_list)] - Marks a field as a list of participants (type: Vec<Participant>)#[tap(transaction_id)] - Marks the transaction ID field (type: String)#[tap(optional_transaction_id)] - Marks an optional transaction ID field (type: Option<String>)#[tap(thread_id)] - Marks a thread ID field for thread-based messages (type: Option<String>)The derive macro generates implementations for two traits:
pub trait TapMessage {
fn validate(&self) -> Result<()>;
fn is_tap_message(&self) -> bool;
fn get_tap_type(&self) -> Option<String>;
fn get_all_participants(&self) -> Vec<String>;
fn create_reply<T: TapMessageBody>(&self, body: &T, creator_did: &str) -> Result<PlainMessage>;
fn thread_id(&self) -> Option<&str>;
fn parent_thread_id(&self) -> Option<&str>;
fn message_id(&self) -> &str;
}
The generated implementation:
TapMessageBody implementationpub trait MessageContext {
fn participants(&self) -> Vec<&Participant>;
fn participant_dids(&self) -> Vec<String>;
fn transaction_context(&self) -> Option<TransactionContext>;
}
The generated implementation:
Participant objects#[derive(Debug, Clone, Serialize, Deserialize, TapMessage)]
pub struct Presentation {
#[tap(participant)]
pub presenter: Participant,
#[tap(optional_transaction_id)]
pub transaction_id: Option<String>,
pub credentials: Vec<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize, TapMessage)]
pub struct DIDCommPresentation {
#[tap(thread_id)]
pub thid: Option<String>,
pub formats: Vec<String>,
pub attachments: Vec<Attachment>,
}
#[derive(Debug, Clone, Serialize, Deserialize, TapMessage)]
pub struct MultiPartyTransfer {
#[tap(participant)]
pub initiator: Participant,
#[tap(participant_list)]
pub senders: Vec<Participant>,
#[tap(participant_list)]
pub receivers: Vec<Participant>,
#[tap(participant_list)]
pub validators: Vec<Participant>,
#[tap(transaction_id)]
pub transaction_id: String,
}
This derive macro is designed to work seamlessly with the tap-msg crate. When both are used together:
TapMessageBody for message-specific behaviorSerialize and Deserialize to be derivedTapMessageBody trait must still be implemented manuallyParticipant, not a type alias)This project is licensed under the MIT License - see the LICENSE file for details.