aleph_types/message/
store.rs

1use crate::cid::Cid;
2use crate::item_hash::{AlephItemHash, ItemHash};
3use crate::memory_size::Bytes;
4use serde::{Deserialize, Serialize};
5use std::collections::HashMap;
6
7#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
8#[serde(tag = "item_type", rename_all = "lowercase")]
9pub enum StorageBackend {
10    Ipfs { item_hash: Cid },
11    Storage { item_hash: AlephItemHash },
12}
13
14#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
15pub struct StoreContent {
16    #[serde(flatten)]
17    /// A combination of the `item_hash` and `item_type` fields, deserialized together to detect
18    /// inconsistencies. Use the `file_hash()` method to access the file hash.
19    file_hash: StorageBackend,
20    #[serde(default)]
21    /// Size of the file. Generated by CCNs upon processing.
22    pub size: Option<Bytes>,
23    /// Generated by CCNs upon processing.
24    pub content_type: Option<String>,
25    #[serde(rename = "ref", default)]
26    pub reference: Option<String>,
27    /// Metadata of the VM.
28    pub metadata: Option<HashMap<String, serde_json::Value>>,
29}
30
31impl StoreContent {
32    pub fn file_hash(&self) -> ItemHash {
33        match &self.file_hash {
34            StorageBackend::Ipfs { item_hash: cid } => ItemHash::Ipfs(cid.clone()),
35            StorageBackend::Storage { item_hash } => ItemHash::Native(*item_hash),
36        }
37    }
38}
39
40#[cfg(test)]
41mod tests {
42    use super::*;
43    use crate::chain::{Address, Chain, Signature};
44    use crate::channel::Channel;
45    use crate::item_hash;
46    use crate::message::base_message::{Message, MessageContentEnum};
47    use crate::message::{ContentSource, MessageType};
48    use crate::timestamp::Timestamp;
49    use assert_matches::assert_matches;
50
51    const STORE_IPFS_FIXTURE: &str = include_str!(concat!(
52        env!("CARGO_MANIFEST_DIR"),
53        "/../../fixtures/messages/store/store-ipfs.json"
54    ));
55
56    #[test]
57    fn test_deserialize_store_message() {
58        let message: Message = serde_json::from_str(STORE_IPFS_FIXTURE).unwrap();
59
60        assert_eq!(
61            message.sender,
62            Address::from("0x238224C744F4b90b4494516e074D2676ECfC6803".to_string())
63        );
64        assert_eq!(message.chain, Chain::Ethereum);
65        assert_eq!(
66                message.signature,
67                Signature::from(
68                    "0x9c87f5d659b9165be7cbd4b9f0bd5df5c66b9bb9a384a0a33b1277428be21244595a0731697035c4b085064cd3fc088bc5b3cddeb22159e7f462e6e5b5e7e8181c".to_string()
69                )
70            );
71        assert_matches!(message.message_type, MessageType::Store);
72        assert_matches!(
73            message.content_source,
74            ContentSource::Inline { item_content: _ }
75        );
76        assert_eq!(
77            &message.item_hash.to_string(),
78            "afe106f1fd70b6b806e0452cc2f9485e518143581ffd046ae19fc64af7b6bbaa"
79        );
80        assert_eq!(message.time, Timestamp::from(1761047957.74837));
81        assert_matches!(message.channel, Some(ref channel) if channel == &Channel::from("ALEPH-CLOUDSOLUTIONS".to_string()));
82
83        // Check content fields
84        assert_eq!(
85            &message.content.address,
86            &Address::from("0x238224C744F4b90b4494516e074D2676ECfC6803".to_string())
87        );
88        assert_eq!(&message.content.time, &Timestamp::from(1761047957.7483068));
89        assert_eq!(message.sent_at(), &message.content.time);
90
91        // Check STORE-specific fields
92        match message.content() {
93            MessageContentEnum::Store(store) => {
94                assert_eq!(
95                    store.file_hash,
96                    StorageBackend::Ipfs {
97                        item_hash: Cid::try_from("QmYULJoNGPDmoRq4WNWTDTUvJGJv1hosox8H6vVd1kCsY8")
98                            .unwrap()
99                    }
100                );
101                assert_eq!(
102                    store.file_hash(),
103                    item_hash!("QmYULJoNGPDmoRq4WNWTDTUvJGJv1hosox8H6vVd1kCsY8")
104                );
105
106                assert!(store.size.is_none());
107                assert!(store.content_type.is_none());
108                assert!(store.reference.is_none());
109                assert!(store.metadata.is_none());
110            }
111            other => {
112                panic!("Expected MessageContentEnum::Store, got {:?}", other)
113            }
114        }
115
116        // Check confirmations
117        assert!(message.confirmed());
118        assert_eq!(message.confirmations.len(), 1);
119
120        let confirmation = &message.confirmations[0];
121        assert_eq!(confirmation.chain, Chain::Ethereum);
122        assert_eq!(confirmation.height, 23626206);
123        assert_eq!(
124            confirmation.hash,
125            "0x7e73ff97d7920fcfc289a899aeac4bc2898d1482a9876bd2ac4584ae876d22be"
126        );
127        assert!(confirmation.time.is_none());
128        assert!(confirmation.publisher.is_none());
129    }
130
131    #[test]
132    fn test_deserialize_serialized_store_message() {
133        let message: Message = serde_json::from_str(STORE_IPFS_FIXTURE).unwrap();
134        let serialized_message = serde_json::to_string(&message).unwrap();
135        let deserialized_message: Message = serde_json::from_str(&serialized_message).unwrap();
136
137        assert_eq!(message, deserialized_message);
138    }
139}