diff options
Diffstat (limited to 'lib/src/types')
-rw-r--r-- | lib/src/types/datetime.rs | 35 | ||||
-rw-r--r-- | lib/src/types/mod.rs | 8 | ||||
-rw-r--r-- | lib/src/types/node.rs | 88 | ||||
-rw-r--r-- | lib/src/types/payload.rs | 72 |
4 files changed, 203 insertions, 0 deletions
diff --git a/lib/src/types/datetime.rs b/lib/src/types/datetime.rs new file mode 100644 index 0000000..00d739a --- /dev/null +++ b/lib/src/types/datetime.rs @@ -0,0 +1,35 @@ +use std::convert::TryFrom; +use anyhow::Error; +use anyhow::Result; + +#[derive(Clone, Debug, Eq, PartialEq, serde::Serialize, serde::Deserialize)] +#[serde(transparent)] +pub struct DateTime(chrono::DateTime<chrono::Utc>); + +impl Into<ipfs::Ipld> for DateTime { + fn into(self) -> ipfs::Ipld { + ipfs::Ipld::String(self.0.to_rfc3339()) + } +} + +impl TryFrom<ipfs::Ipld> for DateTime { + type Error = Error; + + fn try_from(ipld: ipfs::Ipld) -> Result<DateTime> { + match ipld { + ipfs::Ipld::String(s) => chrono::DateTime::parse_from_rfc3339(&s) + .map(|dt| dt.with_timezone(&chrono::Utc)) + .map(DateTime) + .map_err(Error::from), + _ => anyhow::bail!("Expected string for timestamp"), + } + } +} + + +impl From<chrono::DateTime<chrono::Utc>> for DateTime { + fn from(dt: chrono::DateTime<chrono::Utc>) -> Self { + DateTime(dt) + } +} + diff --git a/lib/src/types/mod.rs b/lib/src/types/mod.rs new file mode 100644 index 0000000..7382f16 --- /dev/null +++ b/lib/src/types/mod.rs @@ -0,0 +1,8 @@ +mod node; +pub use node::*; + +mod datetime; +pub use datetime::*; + +mod payload; +pub use payload::*; diff --git a/lib/src/types/node.rs b/lib/src/types/node.rs new file mode 100644 index 0000000..eb5679b --- /dev/null +++ b/lib/src/types/node.rs @@ -0,0 +1,88 @@ +use anyhow::Result; + +use std::convert::TryFrom; + +#[derive(Debug, Eq, PartialEq, getset::Getters)] +pub struct Node { + /// Version + #[getset(get = "pub")] + version: String, + + /// Parent Nodes, identified by cid + parents: Vec<ipfs::Cid>, + + /// The actual payload of the node, which is stored in another document identified by this cid + payload: ipfs::Cid, +} + +impl Into<ipfs::Ipld> for Node { + fn into(self) -> ipfs::Ipld { + let mut map = std::collections::BTreeMap::new(); + map.insert(String::from("version"), ipfs::Ipld::String(self.version)); + map.insert(String::from("parents"), ipfs::Ipld::List(self.parents.into_iter().map(ipfs::Ipld::Link).collect())); + map.insert(String::from("payload"), ipfs::Ipld::Link(self.payload)); + ipfs::Ipld::Map(map) + } +} + +impl TryFrom<ipfs::Ipld> for Node { + type Error = anyhow::Error; + + fn try_from(ipld: ipfs::Ipld) -> Result<Self> { + let missing_field = |name: &'static str| move || anyhow::anyhow!("Missing field {}", name); + let field_wrong_type = |name: &str, expty: &str| anyhow::bail!("Field {} has wrong type, expected {}", name, expty); + match ipld { + ipfs::Ipld::Map(map) => { + let version = match map.get("version").ok_or_else(missing_field("version"))? { + ipfs::Ipld::String(s) => s.to_string(), + _ => return field_wrong_type("version", "String") + }; + + let parents = match map.get("parents").ok_or_else(missing_field("parents"))? { + ipfs::Ipld::List(s) => { + s.into_iter() + .map(|parent| -> Result<ipfs::Cid> { + match parent { + ipfs::Ipld::Link(cid) => Ok(cid.clone()), + _ => anyhow::bail!("Field in parents has wrong type, expected Link"), + } + }) + .collect::<Result<Vec<ipfs::Cid>>>()? + }, + _ => return field_wrong_type("parents", "Vec<Link>") + }; + + let payload = match map.get("payload").ok_or_else(missing_field("payload"))? { + ipfs::Ipld::Link(cid) => cid.clone(), + _ => return field_wrong_type("payload", "Link") + }; + + Ok(Node { + version, + parents, + payload + }) + } + + _ => anyhow::bail!("Unexpected type, expected map") + } + } +} + +impl Node { + pub fn new(version: String, parents: Vec<ipfs::Cid>, payload: ipfs::Cid) -> Self { + Self { + version, + parents, + payload, + } + } + + pub fn parents(&self) -> Vec<ipfs::Cid> { + self.parents.clone() + } + + pub fn payload(&self) -> ipfs::Cid { + self.payload.clone() + } +} diff --git a/lib/src/types/payload.rs b/lib/src/types/payload.rs new file mode 100644 index 0000000..a11b215 --- /dev/null +++ b/lib/src/types/payload.rs @@ -0,0 +1,72 @@ +use std::convert::TryFrom; + +use anyhow::Result; + +use crate::types::DateTime; + +#[derive(Debug, Eq, PartialEq, getset::Getters)] +pub struct Payload { + // TODO: Make this a mime::Mime, but as this type does not impl Serialize/Deserialize, we + // cannot do this trivially yet + #[getset(get = "pub")] + mime: String, + + #[getset(get = "pub")] + timestamp: DateTime, + + content: ipfs::Cid, +} + +impl Into<ipfs::Ipld> for Payload { + fn into(self) -> ipfs::Ipld { + let mut map = std::collections::BTreeMap::new(); + map.insert(String::from("mime"), ipfs::Ipld::String(self.mime)); + map.insert(String::from("timestamp"), self.timestamp.into()); + map.insert(String::from("content"), ipfs::Ipld::Link(self.content)); + ipfs::Ipld::Map(map) + } +} + +impl TryFrom<ipfs::Ipld> for Payload { + type Error = anyhow::Error; + + fn try_from(ipld: ipfs::Ipld) -> Result<Self> { + let missing_field = |name: &'static str| move || anyhow::anyhow!("Missing field {}", name); + let field_wrong_type = |name: &str, expty: &str| anyhow::bail!("Field {} has wrong type, expected {}", name, expty); + match ipld { + ipfs::Ipld::Map(map) => { + let mime = match map.get("mime").ok_or_else(missing_field("mime"))? { + ipfs::Ipld::String(s) => s.to_owned(), + _ => return field_wrong_type("mime", "String") + }; + + let timestamp = map.get("timestamp") + .ok_or_else(missing_field("timestamp"))?; + let timestamp = DateTime::try_from(timestamp.clone())?; // TODO dont clone + + let content = match map.get("content").ok_or_else(missing_field("content"))? { + ipfs::Ipld::Link(cid) => cid.clone(), + _ => return field_wrong_type("content", "Link") + }; + + Ok(Payload { + mime, + timestamp, + content + }) + }, + + _ => anyhow::bail!("Unexpected type, expected map"), + } + } +} + +impl Payload { + pub fn new(mime: String, timestamp: DateTime, content: ipfs::Cid) -> Self { + Self { mime, timestamp, content: content.into() } + } + + pub fn content(&self) -> ipfs::Cid { + self.content.clone() + } +} |