aleph_types/message/
base_message.rs

1use crate::chain::{Address, Chain, Signature};
2use crate::channel::Channel;
3use crate::item_hash::ItemHash;
4use crate::message::aggregate::AggregateContent;
5use crate::message::forget::ForgetContent;
6use crate::message::instance::InstanceContent;
7use crate::message::post::PostContent;
8use crate::message::program::ProgramContent;
9use crate::message::store::StoreContent;
10use crate::timestamp::Timestamp;
11use serde::de::{self, Deserializer};
12use serde::{Deserialize, Serialize};
13use std::fmt::Formatter;
14
15#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
16#[serde(rename_all = "UPPERCASE")]
17pub enum MessageType {
18    Aggregate,
19    Forget,
20    Instance,
21    Post,
22    Program,
23    Store,
24}
25
26impl std::fmt::Display for MessageType {
27    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
28        let s = match self {
29            MessageType::Aggregate => "AGGREGATE",
30            MessageType::Forget => "FORGET",
31            MessageType::Instance => "INSTANCE",
32            MessageType::Post => "POST",
33            MessageType::Program => "PROGRAM",
34            MessageType::Store => "STORE",
35        };
36
37        f.write_str(s)
38    }
39}
40
41#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
42#[serde(rename_all = "lowercase")]
43pub enum MessageStatus {
44    Pending,
45    Processed,
46    Removing,
47    Removed,
48    Forgotten,
49}
50
51impl std::fmt::Display for MessageStatus {
52    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
53        let s = match self {
54            MessageStatus::Pending => "pending",
55            MessageStatus::Processed => "processed",
56            MessageStatus::Removing => "removing",
57            MessageStatus::Removed => "removed",
58            MessageStatus::Forgotten => "forgotten",
59        };
60
61        f.write_str(s)
62    }
63}
64
65/// Content variants for different message types.
66#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
67#[serde(untagged)]
68pub enum MessageContentEnum {
69    Aggregate(AggregateContent),
70    Forget(ForgetContent),
71    Instance(InstanceContent),
72    Post(PostContent),
73    Program(ProgramContent),
74    Store(StoreContent),
75}
76
77#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
78pub struct MessageContent {
79    pub address: Address,
80    pub time: Timestamp,
81    #[serde(flatten)]
82    content: MessageContentEnum,
83}
84
85#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
86pub struct MessageConfirmation {
87    pub chain: Chain,
88    pub height: u64,
89    pub hash: String,
90    pub time: Option<Timestamp>,
91    pub publisher: Option<Address>,
92}
93
94/// Where to find the content of the message. Note that this is a mix of ItemType / ItemContent
95/// if you are used to the Python SDK.
96#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize)]
97#[serde(rename_all = "lowercase")]
98pub enum ContentSource {
99    Inline { item_content: String },
100    Storage,
101    Ipfs,
102}
103
104impl<'de> Deserialize<'de> for ContentSource {
105    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
106    where
107        D: Deserializer<'de>,
108    {
109        #[derive(Deserialize)]
110        struct ContentSourceRaw {
111            item_type: String,
112            item_content: Option<String>,
113        }
114
115        let raw = ContentSourceRaw::deserialize(deserializer)?;
116
117        match raw.item_type.as_str() {
118            "inline" => {
119                let item_content = raw
120                    .item_content
121                    .ok_or_else(|| de::Error::missing_field("item_content"))?;
122                Ok(ContentSource::Inline { item_content })
123            }
124            "storage" => Ok(ContentSource::Storage),
125            "ipfs" => Ok(ContentSource::Ipfs),
126            other => Err(de::Error::unknown_variant(
127                other,
128                &["inline", "storage", "ipfs"],
129            )),
130        }
131    }
132}
133
134#[derive(PartialEq, Debug, Clone)]
135pub struct Message {
136    /// Blockchain used for this message.
137    pub chain: Chain,
138    /// Sender address.
139    pub sender: Address,
140    /// Cryptographic signature of the message by the sender.
141    pub signature: Signature,
142    /// Content of the message as created by the sender. Can either be inline or stored
143    /// on Aleph Cloud.
144    pub content_source: ContentSource,
145    /// Hash of the content (SHA2-256).
146    pub item_hash: ItemHash,
147    /// List of confirmations for the message.
148    pub confirmations: Vec<MessageConfirmation>,
149    /// Unix timestamp or datetime when the message was published.
150    pub time: Timestamp,
151    /// Channel of the message, one application ideally has one channel.
152    pub channel: Option<Channel>,
153    /// Message type. (aggregate, forget, instance, post, program, store).
154    pub message_type: MessageType,
155    /// Message content.
156    pub content: MessageContent,
157}
158
159impl Message {
160    pub fn content(&self) -> &MessageContentEnum {
161        &self.content.content
162    }
163
164    pub fn confirmed(&self) -> bool {
165        !self.confirmations.is_empty()
166    }
167}
168
169// Custom deserializer that uses message_type to efficiently deserialize content
170impl<'de> Deserialize<'de> for Message {
171    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
172    where
173        D: Deserializer<'de>,
174    {
175        #[derive(Deserialize)]
176        struct MessageRaw {
177            chain: Chain,
178            sender: Address,
179            signature: Signature,
180            #[serde(flatten)]
181            content_source: ContentSource,
182            item_hash: ItemHash,
183            #[serde(default)]
184            confirmations: Option<Vec<MessageConfirmation>>,
185            time: Timestamp,
186            #[serde(default)]
187            channel: Option<Channel>,
188            #[serde(rename = "type")]
189            message_type: MessageType,
190            content: serde_json::Value,
191        }
192
193        let raw = MessageRaw::deserialize(deserializer)?;
194
195        // Deserialize content based on message_type for efficiency
196        let content_obj = raw
197            .content
198            .as_object()
199            .ok_or_else(|| de::Error::custom("content must be an object"))?;
200
201        let address = content_obj
202            .get("address")
203            .ok_or_else(|| de::Error::missing_field("content.address"))
204            .and_then(|v| Address::deserialize(v).map_err(de::Error::custom))?;
205
206        let time = content_obj
207            .get("time")
208            .ok_or_else(|| de::Error::missing_field("content.time"))
209            .and_then(|v| Timestamp::deserialize(v).map_err(de::Error::custom))?;
210
211        // Deserialize the specific variant based on message_type
212        let variant = match raw.message_type {
213            MessageType::Aggregate => MessageContentEnum::Aggregate(
214                AggregateContent::deserialize(&raw.content).map_err(de::Error::custom)?,
215            ),
216            MessageType::Forget => MessageContentEnum::Forget(
217                ForgetContent::deserialize(&raw.content).map_err(de::Error::custom)?,
218            ),
219            MessageType::Instance => MessageContentEnum::Instance(
220                InstanceContent::deserialize(&raw.content).map_err(de::Error::custom)?,
221            ),
222            MessageType::Post => MessageContentEnum::Post(
223                PostContent::deserialize(&raw.content).map_err(de::Error::custom)?,
224            ),
225            MessageType::Program => MessageContentEnum::Program(
226                ProgramContent::deserialize(&raw.content).map_err(de::Error::custom)?,
227            ),
228            MessageType::Store => MessageContentEnum::Store(
229                StoreContent::deserialize(&raw.content).map_err(de::Error::custom)?,
230            ),
231        };
232
233        Ok(Message {
234            chain: raw.chain,
235            sender: raw.sender,
236            signature: raw.signature,
237            content_source: raw.content_source,
238            item_hash: raw.item_hash,
239            confirmations: raw.confirmations.unwrap_or_default(),
240            time: raw.time,
241            channel: raw.channel,
242            message_type: raw.message_type,
243            content: MessageContent {
244                address,
245                time,
246                content: variant,
247            },
248        })
249    }
250}
251
252// Manual Serialize for Message
253impl Serialize for Message {
254    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
255    where
256        S: serde::Serializer,
257    {
258        use serde::ser::SerializeStruct;
259
260        let mut state = serializer.serialize_struct("Message", 9)?;
261        state.serialize_field("chain", &self.chain)?;
262        state.serialize_field("sender", &self.sender)?;
263        match &self.content_source {
264            ContentSource::Inline { item_content } => {
265                state.serialize_field("item_type", "inline")?;
266                state.serialize_field("item_content", item_content)?;
267            }
268            ContentSource::Storage => {
269                state.serialize_field("item_type", "storage")?;
270                state.serialize_field("item_content", &None::<String>)?;
271            }
272            ContentSource::Ipfs => {
273                state.serialize_field("item_type", "ipfs")?;
274                state.serialize_field("item_content", &None::<String>)?;
275            }
276        }
277        state.serialize_field("signature", &self.signature)?;
278        state.serialize_field("item_hash", &self.item_hash)?;
279        if self.confirmed() {
280            state.serialize_field("confirmed", &true)?;
281            state.serialize_field("confirmations", &self.confirmations)?;
282        }
283        state.serialize_field("time", &self.time)?;
284        if self.channel.is_some() {
285            state.serialize_field("channel", &self.channel)?;
286        }
287        state.serialize_field("type", &self.message_type)?;
288        state.serialize_field("content", &self.content)?;
289        state.end()
290    }
291}
292
293#[cfg(test)]
294mod tests {
295    use super::*;
296    use assert_matches::assert_matches;
297
298    #[test]
299    fn test_deserialize_item_type_inline() {
300        let item_content_str = "test".to_string();
301        let content_source_str =
302            format!("{{\"item_type\":\"inline\",\"item_content\":\"{item_content_str}\"}}");
303        let content_source: ContentSource = serde_json::from_str(&content_source_str).unwrap();
304
305        assert_matches!(
306            content_source,
307            ContentSource::Inline {
308                item_content
309            } if item_content == item_content_str
310        );
311    }
312
313    #[test]
314    fn test_deserialize_item_type_storage() {
315        let content_source_str = r#"{"item_type":"storage"}"#;
316        let content_source: ContentSource = serde_json::from_str(content_source_str).unwrap();
317        assert_matches!(content_source, ContentSource::Storage);
318    }
319
320    #[test]
321    fn test_deserialize_item_type_ipfs() {
322        let content_source_str = r#"{"item_type":"ipfs"}"#;
323        let content_source: ContentSource = serde_json::from_str(content_source_str).unwrap();
324        assert_matches!(content_source, ContentSource::Ipfs);
325    }
326
327    #[test]
328    fn test_deserialize_item_type_invalid_type() {
329        let content_source_str = r#"{"item_type":"invalid"}"#;
330        let result = serde_json::from_str::<ContentSource>(content_source_str);
331        assert!(result.is_err());
332    }
333
334    #[test]
335    fn test_deserialize_item_type_invalid_format() {
336        let content_source_str = r#"{"type":"inline"}"#;
337        let result = serde_json::from_str::<ContentSource>(content_source_str);
338        assert!(result.is_err());
339    }
340}