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, Copy, 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#[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#[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 pub chain: Chain,
138 pub sender: Address,
140 pub signature: Signature,
142 pub content_source: ContentSource,
145 pub item_hash: ItemHash,
147 pub confirmations: Vec<MessageConfirmation>,
149 pub time: Timestamp,
151 pub channel: Option<Channel>,
153 pub message_type: MessageType,
155 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 pub fn sender(&self) -> &Address {
172 &self.sender
173 }
174
175 pub fn owner(&self) -> &Address {
177 &self.content.address
178 }
179
180 pub fn sent_at(&self) -> &Timestamp {
185 &self.content.time
186 }
187
188 pub fn confirmed_at(&self) -> Option<&Timestamp> {
190 self.confirmations.first().and_then(|c| c.time.as_ref())
191 }
192}
193
194impl<'de> Deserialize<'de> for Message {
196 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
197 where
198 D: Deserializer<'de>,
199 {
200 #[derive(Deserialize)]
201 struct MessageRaw {
202 chain: Chain,
203 sender: Address,
204 signature: Signature,
205 #[serde(flatten)]
206 content_source: ContentSource,
207 item_hash: ItemHash,
208 #[serde(default)]
209 confirmations: Option<Vec<MessageConfirmation>>,
210 time: Timestamp,
211 #[serde(default)]
212 channel: Option<Channel>,
213 #[serde(rename = "type")]
214 message_type: MessageType,
215 content: serde_json::Value,
216 }
217
218 let raw = MessageRaw::deserialize(deserializer)?;
219
220 let content_obj = raw
222 .content
223 .as_object()
224 .ok_or_else(|| de::Error::custom("content must be an object"))?;
225
226 let address = content_obj
227 .get("address")
228 .ok_or_else(|| de::Error::missing_field("content.address"))
229 .and_then(|v| Address::deserialize(v).map_err(de::Error::custom))?;
230
231 let time = content_obj
232 .get("time")
233 .ok_or_else(|| de::Error::missing_field("content.time"))
234 .and_then(|v| Timestamp::deserialize(v).map_err(de::Error::custom))?;
235
236 let variant = match raw.message_type {
238 MessageType::Aggregate => MessageContentEnum::Aggregate(
239 AggregateContent::deserialize(&raw.content).map_err(de::Error::custom)?,
240 ),
241 MessageType::Forget => MessageContentEnum::Forget(
242 ForgetContent::deserialize(&raw.content).map_err(de::Error::custom)?,
243 ),
244 MessageType::Instance => MessageContentEnum::Instance(
245 InstanceContent::deserialize(&raw.content).map_err(de::Error::custom)?,
246 ),
247 MessageType::Post => MessageContentEnum::Post(
248 PostContent::deserialize(&raw.content).map_err(de::Error::custom)?,
249 ),
250 MessageType::Program => MessageContentEnum::Program(
251 ProgramContent::deserialize(&raw.content).map_err(de::Error::custom)?,
252 ),
253 MessageType::Store => MessageContentEnum::Store(
254 StoreContent::deserialize(&raw.content).map_err(de::Error::custom)?,
255 ),
256 };
257
258 Ok(Message {
259 chain: raw.chain,
260 sender: raw.sender,
261 signature: raw.signature,
262 content_source: raw.content_source,
263 item_hash: raw.item_hash,
264 confirmations: raw.confirmations.unwrap_or_default(),
265 time: raw.time,
266 channel: raw.channel,
267 message_type: raw.message_type,
268 content: MessageContent {
269 address,
270 time,
271 content: variant,
272 },
273 })
274 }
275}
276
277impl Serialize for Message {
279 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
280 where
281 S: serde::Serializer,
282 {
283 use serde::ser::SerializeStruct;
284
285 let mut state = serializer.serialize_struct("Message", 9)?;
286 state.serialize_field("chain", &self.chain)?;
287 state.serialize_field("sender", &self.sender)?;
288 match &self.content_source {
289 ContentSource::Inline { item_content } => {
290 state.serialize_field("item_type", "inline")?;
291 state.serialize_field("item_content", item_content)?;
292 }
293 ContentSource::Storage => {
294 state.serialize_field("item_type", "storage")?;
295 state.serialize_field("item_content", &None::<String>)?;
296 }
297 ContentSource::Ipfs => {
298 state.serialize_field("item_type", "ipfs")?;
299 state.serialize_field("item_content", &None::<String>)?;
300 }
301 }
302 state.serialize_field("signature", &self.signature)?;
303 state.serialize_field("item_hash", &self.item_hash)?;
304 if self.confirmed() {
305 state.serialize_field("confirmed", &true)?;
306 state.serialize_field("confirmations", &self.confirmations)?;
307 }
308 state.serialize_field("time", &self.time)?;
309 if self.channel.is_some() {
310 state.serialize_field("channel", &self.channel)?;
311 }
312 state.serialize_field("type", &self.message_type)?;
313 state.serialize_field("content", &self.content)?;
314 state.end()
315 }
316}
317
318#[cfg(test)]
319mod tests {
320 use super::*;
321 use assert_matches::assert_matches;
322
323 #[test]
324 fn test_deserialize_item_type_inline() {
325 let item_content_str = "test".to_string();
326 let content_source_str =
327 format!("{{\"item_type\":\"inline\",\"item_content\":\"{item_content_str}\"}}");
328 let content_source: ContentSource = serde_json::from_str(&content_source_str).unwrap();
329
330 assert_matches!(
331 content_source,
332 ContentSource::Inline {
333 item_content
334 } if item_content == item_content_str
335 );
336 }
337
338 #[test]
339 fn test_deserialize_item_type_storage() {
340 let content_source_str = r#"{"item_type":"storage"}"#;
341 let content_source: ContentSource = serde_json::from_str(content_source_str).unwrap();
342 assert_matches!(content_source, ContentSource::Storage);
343 }
344
345 #[test]
346 fn test_deserialize_item_type_ipfs() {
347 let content_source_str = r#"{"item_type":"ipfs"}"#;
348 let content_source: ContentSource = serde_json::from_str(content_source_str).unwrap();
349 assert_matches!(content_source, ContentSource::Ipfs);
350 }
351
352 #[test]
353 fn test_deserialize_item_type_invalid_type() {
354 let content_source_str = r#"{"item_type":"invalid"}"#;
355 let result = serde_json::from_str::<ContentSource>(content_source_str);
356 assert!(result.is_err());
357 }
358
359 #[test]
360 fn test_deserialize_item_type_invalid_format() {
361 let content_source_str = r#"{"type":"inline"}"#;
362 let result = serde_json::from_str::<ContentSource>(content_source_str);
363 assert!(result.is_err());
364 }
365}