tap_msg/message/
error.rs

1//! Error message type for the Transaction Authorization Protocol.
2//!
3//! This module defines the ErrorBody message type, which is used
4//! to communicate errors in the TAP protocol.
5
6use chrono::Utc;
7use serde::{Deserialize, Serialize};
8use std::collections::HashMap;
9
10use crate::didcomm::PlainMessage;
11use crate::error::{Error, Result};
12use crate::impl_tap_message;
13use crate::message::tap_message_trait::TapMessageBody;
14
15/// Error message body.
16#[derive(Debug, Clone, Serialize, Deserialize)]
17pub struct ErrorBody {
18    /// Error code.
19    pub code: String,
20
21    /// Error description.
22    pub description: String,
23
24    /// Original message ID (if applicable).
25    #[serde(skip_serializing_if = "Option::is_none")]
26    pub original_message_id: Option<String>,
27
28    /// Additional metadata.
29    #[serde(default, skip_serializing_if = "HashMap::is_empty")]
30    pub metadata: HashMap<String, serde_json::Value>,
31}
32
33impl ErrorBody {
34    /// Creates a new ErrorBody message.
35    pub fn new(code: &str, description: &str) -> Self {
36        Self {
37            code: code.to_string(),
38            description: description.to_string(),
39            original_message_id: None,
40            metadata: HashMap::new(),
41        }
42    }
43
44    /// Creates a new ErrorBody message with a reference to the original message.
45    pub fn with_original_message(code: &str, description: &str, original_message_id: &str) -> Self {
46        Self {
47            code: code.to_string(),
48            description: description.to_string(),
49            original_message_id: Some(original_message_id.to_string()),
50            metadata: HashMap::new(),
51        }
52    }
53
54    /// Adds metadata to the error message.
55    pub fn with_metadata(mut self, key: &str, value: serde_json::Value) -> Self {
56        self.metadata.insert(key.to_string(), value);
57        self
58    }
59}
60
61impl TapMessageBody for ErrorBody {
62    fn message_type() -> &'static str {
63        "https://tap.rsvp/schema/1.0#error"
64    }
65
66    fn validate(&self) -> Result<()> {
67        if self.code.is_empty() {
68            return Err(Error::Validation(
69                "Error code is required in ErrorBody".to_string(),
70            ));
71        }
72
73        if self.description.is_empty() {
74            return Err(Error::Validation(
75                "Error description is required in ErrorBody".to_string(),
76            ));
77        }
78
79        Ok(())
80    }
81
82    fn to_didcomm(&self, from_did: &str) -> Result<PlainMessage> {
83        // Create a JSON representation of self with explicit type field
84        let mut body_json =
85            serde_json::to_value(self).map_err(|e| Error::SerializationError(e.to_string()))?;
86
87        // Ensure the @type field is correctly set in the body
88        if let Some(body_obj) = body_json.as_object_mut() {
89            body_obj.insert(
90                "@type".to_string(),
91                serde_json::Value::String(Self::message_type().to_string()),
92            );
93        }
94
95        // Create a new message with a random ID
96        let id = uuid::Uuid::new_v4().to_string();
97        let created_time = Utc::now().timestamp() as u64;
98
99        // The from field is required in our PlainMessage
100        let from = from_did.to_string();
101
102        // If we have an original message ID, use it as the thread ID
103        let thid = self.original_message_id.clone();
104
105        // Create the message
106        let message = PlainMessage {
107            id,
108            typ: "application/didcomm-plain+json".to_string(),
109            type_: Self::message_type().to_string(),
110            from,
111            to: Vec::new(), // Empty recipients
112            thid,
113            pthid: None,
114            created_time: Some(created_time),
115            expires_time: None,
116            extra_headers: std::collections::HashMap::new(),
117            from_prior: None,
118            body: body_json,
119            attachments: None,
120        };
121
122        Ok(message)
123    }
124}
125
126impl_tap_message!(ErrorBody, generated_id);