aleph_types/message/
store.rs1use crate::chain::Address;
2use crate::cid::Cid;
3use crate::item_hash::{AlephItemHash, ItemHash};
4use crate::memory_size::Bytes;
5use serde::{Deserialize, Serialize};
6use std::collections::HashMap;
7use std::fmt::{Display, Formatter};
8
9#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
10#[serde(tag = "item_type", rename_all = "lowercase")]
11pub enum StorageBackend {
12 Ipfs { item_hash: Cid },
13 Storage { item_hash: AlephItemHash },
14}
15
16#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
17#[serde(untagged)]
18pub enum RawFileRef {
21 ItemHash(ItemHash),
22 UserDefined(String),
23}
24
25impl Display for RawFileRef {
26 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
27 match self {
28 RawFileRef::ItemHash(hash) => write!(f, "{}", hash),
29 RawFileRef::UserDefined(name) => write!(f, "{}", name),
30 }
31 }
32}
33
34#[derive(Debug, Clone, PartialEq)]
35pub enum FileRef {
39 ItemHash(ItemHash),
40 UserDefined { owner: Address, reference: String },
41}
42
43impl Display for FileRef {
44 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
45 match self {
46 FileRef::ItemHash(item_hash) => write!(f, "{}", item_hash),
47 FileRef::UserDefined { owner, reference } => write!(f, "{}/{}", owner, reference),
48 }
49 }
50}
51
52#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
53pub struct StoreContent {
54 #[serde(flatten)]
55 file_hash: StorageBackend,
58 #[serde(default)]
59 pub size: Option<Bytes>,
61 pub content_type: Option<String>,
63 #[serde(rename = "ref", default)]
64 pub reference: Option<RawFileRef>,
65 pub metadata: Option<HashMap<String, serde_json::Value>>,
67}
68
69impl StoreContent {
70 pub fn file_hash(&self) -> ItemHash {
71 match &self.file_hash {
72 StorageBackend::Ipfs { item_hash: cid } => ItemHash::Ipfs(cid.clone()),
73 StorageBackend::Storage { item_hash } => ItemHash::Native(*item_hash),
74 }
75 }
76}
77
78#[cfg(test)]
79mod tests {
80 use super::*;
81 use crate::chain::{Address, Chain, Signature};
82 use crate::channel::Channel;
83 use crate::item_hash;
84 use crate::message::base_message::{Message, MessageContentEnum};
85 use crate::message::{ContentSource, MessageType};
86 use crate::timestamp::Timestamp;
87 use assert_matches::assert_matches;
88
89 const STORE_IPFS_FIXTURE: &str = include_str!(concat!(
90 env!("CARGO_MANIFEST_DIR"),
91 "/../../fixtures/messages/store/store-ipfs.json"
92 ));
93
94 #[test]
95 fn test_deserialize_store_message() {
96 let message: Message = serde_json::from_str(STORE_IPFS_FIXTURE).unwrap();
97
98 assert_eq!(
99 message.sender,
100 Address::from("0x238224C744F4b90b4494516e074D2676ECfC6803".to_string())
101 );
102 assert_eq!(message.chain, Chain::Ethereum);
103 assert_eq!(
104 message.signature,
105 Signature::from(
106 "0x9c87f5d659b9165be7cbd4b9f0bd5df5c66b9bb9a384a0a33b1277428be21244595a0731697035c4b085064cd3fc088bc5b3cddeb22159e7f462e6e5b5e7e8181c".to_string()
107 )
108 );
109 assert_matches!(message.message_type, MessageType::Store);
110 assert_matches!(
111 message.content_source,
112 ContentSource::Inline { item_content: _ }
113 );
114 assert_eq!(
115 &message.item_hash.to_string(),
116 "afe106f1fd70b6b806e0452cc2f9485e518143581ffd046ae19fc64af7b6bbaa"
117 );
118 assert_eq!(message.time, Timestamp::from(1761047957.74837));
119 assert_matches!(message.channel, Some(ref channel) if channel == &Channel::from("ALEPH-CLOUDSOLUTIONS".to_string()));
120
121 assert_eq!(
123 &message.content.address,
124 &Address::from("0x238224C744F4b90b4494516e074D2676ECfC6803".to_string())
125 );
126 assert_eq!(&message.content.time, &Timestamp::from(1761047957.7483068));
127 assert_eq!(message.sent_at(), &message.content.time);
128
129 match message.content() {
131 MessageContentEnum::Store(store) => {
132 assert_eq!(
133 store.file_hash,
134 StorageBackend::Ipfs {
135 item_hash: Cid::try_from("QmYULJoNGPDmoRq4WNWTDTUvJGJv1hosox8H6vVd1kCsY8")
136 .unwrap()
137 }
138 );
139 assert_eq!(
140 store.file_hash(),
141 item_hash!("QmYULJoNGPDmoRq4WNWTDTUvJGJv1hosox8H6vVd1kCsY8")
142 );
143
144 assert!(store.size.is_none());
145 assert!(store.content_type.is_none());
146 assert!(store.reference.is_none());
147 assert!(store.metadata.is_none());
148 }
149 other => {
150 panic!("Expected MessageContentEnum::Store, got {:?}", other)
151 }
152 }
153
154 assert!(message.confirmed());
156 assert_eq!(message.confirmations.len(), 1);
157
158 let confirmation = &message.confirmations[0];
159 assert_eq!(confirmation.chain, Chain::Ethereum);
160 assert_eq!(confirmation.height, 23626206);
161 assert_eq!(
162 confirmation.hash,
163 "0x7e73ff97d7920fcfc289a899aeac4bc2898d1482a9876bd2ac4584ae876d22be"
164 );
165 assert!(confirmation.time.is_none());
166 assert!(confirmation.publisher.is_none());
167 }
168
169 #[test]
170 fn test_deserialize_serialized_store_message() {
171 let message: Message = serde_json::from_str(STORE_IPFS_FIXTURE).unwrap();
172 let serialized_message = serde_json::to_string(&message).unwrap();
173 let deserialized_message: Message = serde_json::from_str(&serialized_message).unwrap();
174
175 assert_eq!(message, deserialized_message);
176 }
177}