tap_msg/message/
agent_management.rs

1//! Agent management message types for the Transaction Authorization Protocol.
2//!
3//! This module defines the message types for managing agents in the TAP protocol,
4//! including adding, replacing, and removing agents from transactions.
5
6use chrono::Utc;
7use serde::{Deserialize, Serialize};
8
9use crate::didcomm::PlainMessage;
10use crate::error::{Error, Result};
11use crate::impl_tap_message;
12use crate::message::tap_message_trait::TapMessageBody;
13use crate::message::Participant;
14
15/// Add agents message body (TAIP-5).
16#[derive(Debug, Clone, Serialize, Deserialize)]
17pub struct AddAgents {
18    /// ID of the transaction to add agents to.
19    #[serde(rename = "transfer_id")]
20    pub transaction_id: String,
21
22    /// Agents to add.
23    pub agents: Vec<Participant>,
24}
25
26impl AddAgents {
27    /// Create a new AddAgents message
28    pub fn new(transaction_id: &str, agents: Vec<Participant>) -> Self {
29        Self {
30            transaction_id: transaction_id.to_string(),
31            agents,
32        }
33    }
34
35    /// Add a single agent to this message
36    pub fn add_agent(mut self, agent: Participant) -> Self {
37        self.agents.push(agent);
38        self
39    }
40}
41
42impl TapMessageBody for AddAgents {
43    fn message_type() -> &'static str {
44        "https://tap.rsvp/schema/1.0#add-agents"
45    }
46
47    fn validate(&self) -> Result<()> {
48        if self.transaction_id.is_empty() {
49            return Err(Error::Validation(
50                "Transaction ID is required in AddAgents".to_string(),
51            ));
52        }
53
54        if self.agents.is_empty() {
55            return Err(Error::Validation(
56                "At least one agent must be specified in AddAgents".to_string(),
57            ));
58        }
59
60        // Validate each agent
61        for agent in &self.agents {
62            if agent.id.is_empty() {
63                return Err(Error::Validation("Agent ID cannot be empty".to_string()));
64            }
65        }
66
67        Ok(())
68    }
69
70    fn to_didcomm(&self, from_did: &str) -> Result<PlainMessage> {
71        // Create a JSON representation of self with explicit type field
72        let mut body_json =
73            serde_json::to_value(self).map_err(|e| Error::SerializationError(e.to_string()))?;
74
75        // Ensure the @type field is correctly set in the body
76        if let Some(body_obj) = body_json.as_object_mut() {
77            body_obj.insert(
78                "@type".to_string(),
79                serde_json::Value::String(Self::message_type().to_string()),
80            );
81        }
82
83        // Create a new message with a random ID
84        let id = uuid::Uuid::new_v4().to_string();
85        let created_time = Utc::now().timestamp() as u64;
86
87        // Create the message
88        let message = PlainMessage {
89            id,
90            typ: "application/didcomm-plain+json".to_string(),
91            type_: Self::message_type().to_string(),
92            from: from_did.to_string(),
93            to: Vec::new(), // Empty recipients, will be determined by the framework later
94            thid: Some(self.transaction_id.clone()),
95            pthid: None,
96            created_time: Some(created_time),
97            expires_time: None,
98            extra_headers: std::collections::HashMap::new(),
99            from_prior: None,
100            body: body_json,
101            attachments: None,
102        };
103
104        Ok(message)
105    }
106}
107
108impl_tap_message!(AddAgents);
109
110/// Replace agent message body (TAIP-5).
111///
112/// This message type allows replacing an agent with another agent in a transaction.
113#[derive(Debug, Clone, Serialize, Deserialize)]
114pub struct ReplaceAgent {
115    /// ID of the transaction to replace agent in.
116    #[serde(rename = "transfer_id")]
117    pub transaction_id: String,
118
119    /// DID of the original agent to replace.
120    pub original: String,
121
122    /// Replacement agent.
123    pub replacement: Participant,
124}
125
126impl ReplaceAgent {
127    /// Create a new ReplaceAgent message
128    pub fn new(transaction_id: &str, original: &str, replacement: Participant) -> Self {
129        Self {
130            transaction_id: transaction_id.to_string(),
131            original: original.to_string(),
132            replacement,
133        }
134    }
135}
136
137impl TapMessageBody for ReplaceAgent {
138    fn message_type() -> &'static str {
139        "https://tap.rsvp/schema/1.0#replace-agent"
140    }
141
142    fn validate(&self) -> Result<()> {
143        if self.transaction_id.is_empty() {
144            return Err(Error::Validation(
145                "Transaction ID is required in ReplaceAgent".to_string(),
146            ));
147        }
148
149        if self.original.is_empty() {
150            return Err(Error::Validation(
151                "Original agent ID is required in ReplaceAgent".to_string(),
152            ));
153        }
154
155        if self.replacement.id.is_empty() {
156            return Err(Error::Validation(
157                "Replacement agent ID is required in ReplaceAgent".to_string(),
158            ));
159        }
160
161        Ok(())
162    }
163
164    fn to_didcomm(&self, from_did: &str) -> Result<PlainMessage> {
165        // Create a JSON representation of self with explicit type field
166        let mut body_json =
167            serde_json::to_value(self).map_err(|e| Error::SerializationError(e.to_string()))?;
168
169        // Ensure the @type field is correctly set in the body
170        if let Some(body_obj) = body_json.as_object_mut() {
171            body_obj.insert(
172                "@type".to_string(),
173                serde_json::Value::String(Self::message_type().to_string()),
174            );
175        }
176
177        // Create a new message with a random ID
178        let id = uuid::Uuid::new_v4().to_string();
179        let created_time = Utc::now().timestamp() as u64;
180
181        // Create the message
182        let message = PlainMessage {
183            id,
184            typ: "application/didcomm-plain+json".to_string(),
185            type_: Self::message_type().to_string(),
186            from: from_did.to_string(),
187            to: Vec::new(), // Empty recipients, will be determined by the framework later
188            thid: Some(self.transaction_id.clone()),
189            pthid: None,
190            created_time: Some(created_time),
191            expires_time: None,
192            extra_headers: std::collections::HashMap::new(),
193            from_prior: None,
194            body: body_json,
195            attachments: None,
196        };
197
198        Ok(message)
199    }
200}
201
202impl_tap_message!(ReplaceAgent);
203
204/// Remove agent message body (TAIP-5).
205///
206/// This message type allows removing an agent from a transaction.
207#[derive(Debug, Clone, Serialize, Deserialize)]
208pub struct RemoveAgent {
209    /// ID of the transaction to remove agent from.
210    #[serde(rename = "transfer_id")]
211    pub transaction_id: String,
212
213    /// DID of the agent to remove.
214    pub agent: String,
215}
216
217impl RemoveAgent {
218    /// Create a new RemoveAgent message
219    pub fn new(transaction_id: &str, agent: &str) -> Self {
220        Self {
221            transaction_id: transaction_id.to_string(),
222            agent: agent.to_string(),
223        }
224    }
225}
226
227impl TapMessageBody for RemoveAgent {
228    fn message_type() -> &'static str {
229        "https://tap.rsvp/schema/1.0#remove-agent"
230    }
231
232    fn validate(&self) -> Result<()> {
233        if self.transaction_id.is_empty() {
234            return Err(Error::Validation(
235                "Transaction ID is required in RemoveAgent".to_string(),
236            ));
237        }
238
239        if self.agent.is_empty() {
240            return Err(Error::Validation(
241                "Agent ID is required in RemoveAgent".to_string(),
242            ));
243        }
244
245        Ok(())
246    }
247
248    fn to_didcomm(&self, from_did: &str) -> Result<PlainMessage> {
249        // Create a JSON representation of self with explicit type field
250        let mut body_json =
251            serde_json::to_value(self).map_err(|e| Error::SerializationError(e.to_string()))?;
252
253        // Ensure the @type field is correctly set in the body
254        if let Some(body_obj) = body_json.as_object_mut() {
255            body_obj.insert(
256                "@type".to_string(),
257                serde_json::Value::String(Self::message_type().to_string()),
258            );
259        }
260
261        // Create a new message with a random ID
262        let id = uuid::Uuid::new_v4().to_string();
263        let created_time = Utc::now().timestamp() as u64;
264
265        // Create the message
266        let message = PlainMessage {
267            id,
268            typ: "application/didcomm-plain+json".to_string(),
269            type_: Self::message_type().to_string(),
270            from: from_did.to_string(),
271            to: Vec::new(), // Empty recipients, will be determined by the framework later
272            thid: Some(self.transaction_id.clone()),
273            pthid: None,
274            created_time: Some(created_time),
275            expires_time: None,
276            extra_headers: std::collections::HashMap::new(),
277            from_prior: None,
278            body: body_json,
279            attachments: None,
280        };
281
282        Ok(message)
283    }
284}
285
286impl_tap_message!(RemoveAgent);