tap_msg/message/basic_message/
mod.rs

1//! Basic Message Protocol Implementation
2//!
3//! Implementation of the DIDComm Basic Message 2.0 protocol as specified at:
4//! https://didcomm.org/basicmessage/2.0/
5//!
6//! The Basic Message protocol provides simple, human-readable messaging
7//! capabilities between DIDComm agents.
8
9use crate::error::{Error, Result};
10use serde::{Deserialize, Serialize};
11use std::collections::HashMap;
12use tap_msg_derive::TapMessage;
13
14pub const BASIC_MESSAGE_TYPE: &str = "https://didcomm.org/basicmessage/2.0/message";
15
16/// Basic Message for simple text communication between agents
17///
18/// The Basic Message protocol allows agents to send simple text messages
19/// to each other. This is useful for human-readable communication and
20/// debugging purposes.
21#[derive(Debug, Clone, Serialize, Deserialize, TapMessage)]
22#[tap(
23    message_type = "https://didcomm.org/basicmessage/2.0/message",
24    custom_validation
25)]
26pub struct BasicMessage {
27    /// The content of the message
28    pub content: String,
29
30    /// Optional locale for the message content (e.g., "en", "es", "fr")
31    #[serde(skip_serializing_if = "Option::is_none")]
32    pub locale: Option<String>,
33
34    /// Optional timestamp when the message was sent
35    #[serde(skip_serializing_if = "Option::is_none")]
36    pub sent_time: Option<u64>,
37
38    /// Additional metadata
39    #[serde(flatten)]
40    pub metadata: HashMap<String, serde_json::Value>,
41}
42
43impl BasicMessage {
44    /// Create a new Basic Message with content
45    pub fn new(content: String) -> Self {
46        Self {
47            content,
48            locale: None,
49            sent_time: Some(chrono::Utc::now().timestamp_millis() as u64),
50            metadata: HashMap::new(),
51        }
52    }
53
54    /// Create a Basic Message with content and locale
55    pub fn with_locale(content: String, locale: String) -> Self {
56        Self {
57            content,
58            locale: Some(locale),
59            sent_time: Some(chrono::Utc::now().timestamp_millis() as u64),
60            metadata: HashMap::new(),
61        }
62    }
63
64    /// Set the sent time
65    pub fn sent_time(mut self, timestamp: u64) -> Self {
66        self.sent_time = Some(timestamp);
67        self
68    }
69
70    /// Add metadata
71    pub fn with_metadata(mut self, key: String, value: serde_json::Value) -> Self {
72        self.metadata.insert(key, value);
73        self
74    }
75
76    /// Get the content of the message
77    pub fn get_content(&self) -> &str {
78        &self.content
79    }
80
81    /// Get the locale of the message
82    pub fn get_locale(&self) -> Option<&str> {
83        self.locale.as_deref()
84    }
85
86    /// Get the sent time
87    pub fn get_sent_time(&self) -> Option<u64> {
88        self.sent_time
89    }
90}
91
92impl BasicMessage {
93    /// Custom validation for Basic Message
94    pub fn validate_basicmessage(&self) -> Result<()> {
95        if self.content.is_empty() {
96            return Err(Error::Validation(
97                "Basic message content cannot be empty".to_string(),
98            ));
99        }
100
101        // Validate comment length if present
102        if self.content.len() > 10000 {
103            return Err(Error::Validation(
104                "Basic message content exceeds maximum length of 10000 characters".to_string(),
105            ));
106        }
107
108        Ok(())
109    }
110}
111
112#[cfg(test)]
113mod tests {
114    use super::*;
115
116    #[test]
117    fn test_basic_message_creation() {
118        let message = BasicMessage::new("Hello, world!".to_string());
119        assert_eq!(message.content, "Hello, world!");
120        assert!(message.locale.is_none());
121        assert!(message.sent_time.is_some());
122        assert!(message.metadata.is_empty());
123    }
124
125    #[test]
126    fn test_basic_message_with_locale() {
127        let message = BasicMessage::with_locale("Hola, mundo!".to_string(), "es".to_string());
128        assert_eq!(message.content, "Hola, mundo!");
129        assert_eq!(message.locale, Some("es".to_string()));
130        assert!(message.sent_time.is_some());
131    }
132
133    #[test]
134    fn test_basic_message_with_metadata() {
135        let message = BasicMessage::new("Test message".to_string())
136            .with_metadata("priority".to_string(), serde_json::json!("high"))
137            .with_metadata("category".to_string(), serde_json::json!("alert"));
138
139        assert_eq!(message.metadata.len(), 2);
140        assert_eq!(
141            message.metadata.get("priority"),
142            Some(&serde_json::json!("high"))
143        );
144        assert_eq!(
145            message.metadata.get("category"),
146            Some(&serde_json::json!("alert"))
147        );
148    }
149
150    #[test]
151    fn test_basic_message_getters() {
152        let timestamp = chrono::Utc::now().timestamp_millis() as u64;
153        let message = BasicMessage::new("Test".to_string()).sent_time(timestamp);
154
155        assert_eq!(message.get_content(), "Test");
156        assert_eq!(message.get_locale(), None);
157        assert_eq!(message.get_sent_time(), Some(timestamp));
158    }
159
160    #[test]
161    fn test_basic_message_serialization() {
162        let message = BasicMessage::with_locale("Test message".to_string(), "en".to_string())
163            .with_metadata("test_key".to_string(), serde_json::json!("test_value"));
164
165        let serialized = serde_json::to_string(&message).unwrap();
166        let deserialized: BasicMessage = serde_json::from_str(&serialized).unwrap();
167
168        assert_eq!(message.content, deserialized.content);
169        assert_eq!(message.locale, deserialized.locale);
170        assert_eq!(message.sent_time, deserialized.sent_time);
171        assert_eq!(message.metadata, deserialized.metadata);
172    }
173
174    #[test]
175    fn test_basic_message_validation() {
176        let message = BasicMessage::new("Test".to_string());
177        assert!(message.validate_basicmessage().is_ok());
178
179        let empty_message = BasicMessage {
180            content: "".to_string(),
181            locale: None,
182            sent_time: None,
183            metadata: HashMap::new(),
184        };
185        assert!(empty_message.validate_basicmessage().is_err());
186
187        let long_message = BasicMessage::new("a".repeat(10001));
188        assert!(long_message.validate_basicmessage().is_err());
189    }
190
191    #[test]
192    fn test_message_type() {
193        use crate::message::tap_message_trait::TapMessageBody;
194        assert_eq!(BasicMessage::message_type(), BASIC_MESSAGE_TYPE);
195    }
196}