use crate::chain::Address;
use crate::cid::Cid;
use crate::item_hash::{AlephItemHash, ItemHash};
use memsizes::Bytes;
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::fmt::{Display, Formatter};
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(tag = "item_type", rename_all = "lowercase")]
pub enum StorageBackend {
Ipfs { item_hash: Cid },
Storage { item_hash: AlephItemHash },
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum StorageEngine {
Storage,
Ipfs,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(untagged)]
pub enum RawFileRef {
ItemHash(ItemHash),
UserDefined(String),
}
impl Display for RawFileRef {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
RawFileRef::ItemHash(hash) => write!(f, "{}", hash),
RawFileRef::UserDefined(name) => write!(f, "{}", name),
}
}
}
#[derive(Debug, Clone, PartialEq)]
pub enum FileRef {
ItemHash(ItemHash),
UserDefined { owner: Address, reference: String },
}
impl Display for FileRef {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
match self {
FileRef::ItemHash(item_hash) => write!(f, "{}", item_hash),
FileRef::UserDefined { owner, reference } => write!(f, "{}/{}", owner, reference),
}
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct StoreContent {
#[serde(flatten)]
file_hash: StorageBackend,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub size: Option<Bytes>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub content_type: Option<String>,
#[serde(rename = "ref", default, skip_serializing_if = "Option::is_none")]
pub reference: Option<RawFileRef>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub metadata: Option<HashMap<String, serde_json::Value>>,
}
impl StoreContent {
pub fn new(
file_hash: StorageBackend,
reference: Option<RawFileRef>,
metadata: Option<HashMap<String, serde_json::Value>>,
) -> Self {
Self {
file_hash,
size: None,
content_type: None,
reference,
metadata,
}
}
pub fn file_hash(&self) -> ItemHash {
match &self.file_hash {
StorageBackend::Ipfs { item_hash: cid } => ItemHash::Ipfs(cid.clone()),
StorageBackend::Storage { item_hash } => ItemHash::Native(*item_hash),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::chain::{Address, Chain, Signature};
use crate::channel::Channel;
use crate::item_hash;
use crate::message::base_message::{Message, MessageContentEnum};
use crate::message::{ContentSource, MessageType};
use crate::timestamp::Timestamp;
use assert_matches::assert_matches;
const STORE_IPFS_FIXTURE: &str = include_str!(concat!(
env!("CARGO_MANIFEST_DIR"),
"/../../fixtures/messages/store/store-ipfs.json"
));
#[test]
fn test_deserialize_store_message() {
let message: Message = serde_json::from_str(STORE_IPFS_FIXTURE).unwrap();
assert_eq!(
message.sender,
Address::from("0x238224C744F4b90b4494516e074D2676ECfC6803".to_string())
);
assert_eq!(message.chain, Chain::Ethereum);
assert_eq!(
message.signature,
Signature::from(
"0x9c87f5d659b9165be7cbd4b9f0bd5df5c66b9bb9a384a0a33b1277428be21244595a0731697035c4b085064cd3fc088bc5b3cddeb22159e7f462e6e5b5e7e8181c".to_string()
)
);
assert_matches!(message.message_type, MessageType::Store);
assert_matches!(
message.content_source,
ContentSource::Inline { item_content: _ }
);
assert_eq!(
&message.item_hash.to_string(),
"afe106f1fd70b6b806e0452cc2f9485e518143581ffd046ae19fc64af7b6bbaa"
);
assert_eq!(message.time, Timestamp::from(1761047957.74837));
assert_matches!(message.channel, Some(ref channel) if channel == &Channel::from("ALEPH-CLOUDSOLUTIONS".to_string()));
assert_eq!(
&message.content.address,
&Address::from("0x238224C744F4b90b4494516e074D2676ECfC6803".to_string())
);
assert_eq!(&message.content.time, &Timestamp::from(1761047957.7483068));
assert_eq!(message.sent_at(), &message.content.time);
match message.content() {
MessageContentEnum::Store(store) => {
assert_eq!(
store.file_hash,
StorageBackend::Ipfs {
item_hash: Cid::try_from("QmYULJoNGPDmoRq4WNWTDTUvJGJv1hosox8H6vVd1kCsY8")
.unwrap()
}
);
assert_eq!(
store.file_hash(),
item_hash!("QmYULJoNGPDmoRq4WNWTDTUvJGJv1hosox8H6vVd1kCsY8")
);
assert!(store.size.is_none());
assert!(store.content_type.is_none());
assert!(store.reference.is_none());
assert!(store.metadata.is_none());
}
other => {
panic!("Expected MessageContentEnum::Store, got {:?}", other)
}
}
assert!(message.confirmed());
assert_eq!(message.confirmations.len(), 1);
let confirmation = &message.confirmations[0];
assert_eq!(confirmation.chain, Chain::Ethereum);
assert_eq!(confirmation.height, 23626206);
assert_eq!(
confirmation.hash,
"0x7e73ff97d7920fcfc289a899aeac4bc2898d1482a9876bd2ac4584ae876d22be"
);
assert!(confirmation.time.is_none());
assert!(confirmation.publisher.is_none());
message.verify_item_hash().unwrap();
}
#[test]
fn test_deserialize_serialized_store_message() {
let message: Message = serde_json::from_str(STORE_IPFS_FIXTURE).unwrap();
message.verify_item_hash().unwrap();
let serialized_message = serde_json::to_string(&message).unwrap();
let deserialized_message: Message = serde_json::from_str(&serialized_message).unwrap();
deserialized_message.verify_item_hash().unwrap();
assert_eq!(message, deserialized_message);
}
}