# TAP Message Derive Macro
Procedural derive macro for automatically implementing TAP message traits.
## Overview
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.
## Usage
Add this to your `Cargo.toml`:
```toml
[dependencies]
tap-msg = "0.2.0"
tap-msg-derive = "0.2.0"
serde = { version = "1.0", features = ["derive"] }
```
## Basic Example
```rust
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...
}
}
```
## Enhanced Usage with TapMessageBody Derive
For automatic generation of TapMessageBody implementation (including `to_didcomm()` with automatic participant routing), use the separate `TapMessageBody` derive macro:
```rust
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.
## Supported Attributes
### Struct-level Attributes
- `#[tap(message_type = "url")]` - TAP message type URL (required for TapMessageBody derive)
### Field-level Attributes
- `#[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>`)
## What Gets Generated
The derive macro generates implementations for two traits:
### 1. TapMessage Trait
```rust
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:
- Extracts participant DIDs from all marked fields
- Returns the appropriate thread/transaction ID
- Creates properly threaded reply messages
- Delegates validation to your `TapMessageBody` implementation
### 2. MessageContext Trait
```rust
pub trait MessageContext {
fn participants(&self) -> Vec<&Participant>;
fn participant_dids(&self) -> Vec<String>;
fn transaction_context(&self) -> Option<TransactionContext>;
}
```
The generated implementation:
- Collects references to all `Participant` objects
- Extracts DIDs from all participants
- Creates transaction context with ID and message type
## Advanced Examples
### Message with Optional Transaction ID
```rust
#[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>,
}
```
### Thread-based Message
```rust
#[derive(Debug, Clone, Serialize, Deserialize, TapMessage)]
pub struct DIDCommPresentation {
#[tap(thread_id)]
pub thid: Option<String>,
pub formats: Vec<String>,
pub attachments: Vec<Attachment>,
}
```
### Message with Multiple Participant Lists
```rust
#[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,
}
```
## Integration with tap-msg
This derive macro is designed to work seamlessly with the tap-msg crate. When both are used together:
1. Define your message struct with the derive macro
2. Implement `TapMessageBody` for message-specific behavior
3. The macro handles the boilerplate trait implementations
4. Your message type is ready to use with TAP agents and protocols
## Limitations
- Only works with structs that have named fields
- Requires serde `Serialize` and `Deserialize` to be derived
- The `TapMessageBody` trait must still be implemented manually
- Field types must match exactly (e.g., `Participant`, not a type alias)
## License
This project is licensed under the MIT License - see the [LICENSE](../LICENSE-MIT) file for details.