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#[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
169impl<'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 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 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
252impl 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}