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 Rejected,
50}
51
52impl std::fmt::Display for MessageStatus {
53 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
54 let s = match self {
55 MessageStatus::Pending => "pending",
56 MessageStatus::Processed => "processed",
57 MessageStatus::Removing => "removing",
58 MessageStatus::Removed => "removed",
59 MessageStatus::Forgotten => "forgotten",
60 MessageStatus::Rejected => "rejected",
61 };
62
63 f.write_str(s)
64 }
65}
66
67#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
69#[serde(untagged)]
70pub enum MessageContentEnum {
71 Aggregate(AggregateContent),
72 Forget(ForgetContent),
73 Instance(InstanceContent),
74 Post(PostContent),
75 Program(ProgramContent),
76 Store(StoreContent),
77}
78
79#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
80pub struct MessageContent {
81 pub address: Address,
82 pub time: Timestamp,
83 #[serde(flatten)]
84 content: MessageContentEnum,
85}
86
87#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
88pub struct MessageConfirmation {
89 pub chain: Chain,
90 pub height: u64,
91 pub hash: String,
92 pub time: Option<Timestamp>,
93 pub publisher: Option<Address>,
94}
95
96#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize)]
99#[serde(rename_all = "lowercase")]
100pub enum ContentSource {
101 Inline { item_content: String },
102 Storage,
103 Ipfs,
104}
105
106impl<'de> Deserialize<'de> for ContentSource {
107 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
108 where
109 D: Deserializer<'de>,
110 {
111 #[derive(Deserialize)]
112 struct ContentSourceRaw {
113 item_type: String,
114 item_content: Option<String>,
115 }
116
117 let raw = ContentSourceRaw::deserialize(deserializer)?;
118
119 match raw.item_type.as_str() {
120 "inline" => {
121 let item_content = raw
122 .item_content
123 .ok_or_else(|| de::Error::missing_field("item_content"))?;
124 Ok(ContentSource::Inline { item_content })
125 }
126 "storage" => Ok(ContentSource::Storage),
127 "ipfs" => Ok(ContentSource::Ipfs),
128 other => Err(de::Error::unknown_variant(
129 other,
130 &["inline", "storage", "ipfs"],
131 )),
132 }
133 }
134}
135
136#[derive(PartialEq, Debug, Clone)]
137pub struct Message {
138 pub chain: Chain,
140 pub sender: Address,
142 pub signature: Signature,
144 pub content_source: ContentSource,
147 pub item_hash: ItemHash,
149 pub confirmations: Vec<MessageConfirmation>,
151 pub time: Timestamp,
153 pub channel: Option<Channel>,
155 pub message_type: MessageType,
157 pub content: MessageContent,
159}
160
161impl Message {
162 pub fn content(&self) -> &MessageContentEnum {
163 &self.content.content
164 }
165
166 pub fn confirmed(&self) -> bool {
167 !self.confirmations.is_empty()
168 }
169
170 pub fn sender(&self) -> &Address {
174 &self.sender
175 }
176
177 pub fn owner(&self) -> &Address {
179 &self.content.address
180 }
181
182 pub fn sent_at(&self) -> &Timestamp {
187 &self.content.time
188 }
189
190 pub fn confirmed_at(&self) -> Option<&Timestamp> {
192 self.confirmations.first().and_then(|c| c.time.as_ref())
193 }
194}
195
196impl<'de> Deserialize<'de> for Message {
198 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
199 where
200 D: Deserializer<'de>,
201 {
202 #[derive(Deserialize)]
203 struct MessageRaw {
204 chain: Chain,
205 sender: Address,
206 signature: Signature,
207 #[serde(flatten)]
208 content_source: ContentSource,
209 item_hash: ItemHash,
210 #[serde(default)]
211 confirmations: Option<Vec<MessageConfirmation>>,
212 time: Timestamp,
213 #[serde(default)]
214 channel: Option<Channel>,
215 #[serde(rename = "type")]
216 message_type: MessageType,
217 content: serde_json::Value,
218 }
219
220 let raw = MessageRaw::deserialize(deserializer)?;
221
222 let content_obj = raw
224 .content
225 .as_object()
226 .ok_or_else(|| de::Error::custom("content must be an object"))?;
227
228 let address = content_obj
229 .get("address")
230 .ok_or_else(|| de::Error::missing_field("content.address"))
231 .and_then(|v| Address::deserialize(v).map_err(de::Error::custom))?;
232
233 let time = content_obj
234 .get("time")
235 .ok_or_else(|| de::Error::missing_field("content.time"))
236 .and_then(|v| Timestamp::deserialize(v).map_err(de::Error::custom))?;
237
238 let variant = match raw.message_type {
240 MessageType::Aggregate => MessageContentEnum::Aggregate(
241 AggregateContent::deserialize(&raw.content).map_err(de::Error::custom)?,
242 ),
243 MessageType::Forget => MessageContentEnum::Forget(
244 ForgetContent::deserialize(&raw.content).map_err(de::Error::custom)?,
245 ),
246 MessageType::Instance => MessageContentEnum::Instance(
247 InstanceContent::deserialize(&raw.content).map_err(de::Error::custom)?,
248 ),
249 MessageType::Post => MessageContentEnum::Post(
250 PostContent::deserialize(&raw.content).map_err(de::Error::custom)?,
251 ),
252 MessageType::Program => MessageContentEnum::Program(
253 ProgramContent::deserialize(&raw.content).map_err(de::Error::custom)?,
254 ),
255 MessageType::Store => MessageContentEnum::Store(
256 StoreContent::deserialize(&raw.content).map_err(de::Error::custom)?,
257 ),
258 };
259
260 Ok(Message {
261 chain: raw.chain,
262 sender: raw.sender,
263 signature: raw.signature,
264 content_source: raw.content_source,
265 item_hash: raw.item_hash,
266 confirmations: raw.confirmations.unwrap_or_default(),
267 time: raw.time,
268 channel: raw.channel,
269 message_type: raw.message_type,
270 content: MessageContent {
271 address,
272 time,
273 content: variant,
274 },
275 })
276 }
277}
278
279impl Serialize for Message {
281 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
282 where
283 S: serde::Serializer,
284 {
285 use serde::ser::SerializeStruct;
286
287 let mut state = serializer.serialize_struct("Message", 9)?;
288 state.serialize_field("chain", &self.chain)?;
289 state.serialize_field("sender", &self.sender)?;
290 match &self.content_source {
291 ContentSource::Inline { item_content } => {
292 state.serialize_field("item_type", "inline")?;
293 state.serialize_field("item_content", item_content)?;
294 }
295 ContentSource::Storage => {
296 state.serialize_field("item_type", "storage")?;
297 state.serialize_field("item_content", &None::<String>)?;
298 }
299 ContentSource::Ipfs => {
300 state.serialize_field("item_type", "ipfs")?;
301 state.serialize_field("item_content", &None::<String>)?;
302 }
303 }
304 state.serialize_field("signature", &self.signature)?;
305 state.serialize_field("item_hash", &self.item_hash)?;
306 if self.confirmed() {
307 state.serialize_field("confirmed", &true)?;
308 state.serialize_field("confirmations", &self.confirmations)?;
309 }
310 state.serialize_field("time", &self.time)?;
311 if self.channel.is_some() {
312 state.serialize_field("channel", &self.channel)?;
313 }
314 state.serialize_field("type", &self.message_type)?;
315 state.serialize_field("content", &self.content)?;
316 state.end()
317 }
318}
319
320#[cfg(test)]
321mod tests {
322 use super::*;
323 use assert_matches::assert_matches;
324
325 #[test]
326 fn test_deserialize_item_type_inline() {
327 let item_content_str = "test".to_string();
328 let content_source_str =
329 format!("{{\"item_type\":\"inline\",\"item_content\":\"{item_content_str}\"}}");
330 let content_source: ContentSource = serde_json::from_str(&content_source_str).unwrap();
331
332 assert_matches!(
333 content_source,
334 ContentSource::Inline {
335 item_content
336 } if item_content == item_content_str
337 );
338 }
339
340 #[test]
341 fn test_deserialize_item_type_storage() {
342 let content_source_str = r#"{"item_type":"storage"}"#;
343 let content_source: ContentSource = serde_json::from_str(content_source_str).unwrap();
344 assert_matches!(content_source, ContentSource::Storage);
345 }
346
347 #[test]
348 fn test_deserialize_item_type_ipfs() {
349 let content_source_str = r#"{"item_type":"ipfs"}"#;
350 let content_source: ContentSource = serde_json::from_str(content_source_str).unwrap();
351 assert_matches!(content_source, ContentSource::Ipfs);
352 }
353
354 #[test]
355 fn test_deserialize_item_type_invalid_type() {
356 let content_source_str = r#"{"item_type":"invalid"}"#;
357 let result = serde_json::from_str::<ContentSource>(content_source_str);
358 assert!(result.is_err());
359 }
360
361 #[test]
362 fn test_deserialize_item_type_invalid_format() {
363 let content_source_str = r#"{"type":"inline"}"#;
364 let result = serde_json::from_str::<ContentSource>(content_source_str);
365 assert!(result.is_err());
366 }
367}