diff options
author | Matthias Beyer <mail@beyermatthias.de> | 2017-12-20 00:01:31 +0100 |
---|---|---|
committer | Matthias Beyer <mail@beyermatthias.de> | 2018-10-07 10:14:33 +0200 |
commit | 15bc0ad9c1515e8f326e01ca903c0d88bb787aee (patch) | |
tree | 17973661361745da860664e3893b99f70c1af097 /src/types |
Initial import of distrox code
Diffstat (limited to 'src/types')
-rw-r--r-- | src/types/block.rs | 39 | ||||
-rw-r--r-- | src/types/content.rs | 191 | ||||
-rw-r--r-- | src/types/mod.rs | 4 | ||||
-rw-r--r-- | src/types/util.rs | 160 |
4 files changed, 394 insertions, 0 deletions
diff --git a/src/types/block.rs b/src/types/block.rs new file mode 100644 index 0000000..75c1885 --- /dev/null +++ b/src/types/block.rs @@ -0,0 +1,39 @@ +use types::util::IPFSHash; +use types::util::Version; + +#[derive(Serialize, Deserialize)] +pub struct Block { + /// The version of the API in use + #[serde(rename = "v")] + version: Version, + + /// The parents of this Profile instance + /// + /// Multiple are required for merging. + #[serde(rename = "parents")] + #[serde(skip_serializing_if = "Vec::is_empty")] + parents: Vec<IPFSHash>, + + /// The content of this block, pointed to by IPFS hash. + #[serde(rename = "content")] + content: IPFSHash, +} + +impl Block { + pub fn new(version: Version, parents: Vec<IPFSHash>, content: IPFSHash) -> Self { + Block { version, parents, content } + } + + pub fn version(&self) -> &Version { + &self.version + } + + pub fn parents(&self) -> &Vec<IPFSHash> { + &self.parents + } + + pub fn content(&self) -> &IPFSHash { + &self.content + } +} + diff --git a/src/types/content.rs b/src/types/content.rs new file mode 100644 index 0000000..bec0ce1 --- /dev/null +++ b/src/types/content.rs @@ -0,0 +1,191 @@ +use std::collections::BTreeMap; + +use types::util::IPFSHash; +use types::util::IPNSHash; +use types::util::MimeType; +use types::util::Timestamp; + +#[derive(Serialize, Deserialize)] +pub struct Content { + + // + // + // Metadata about the content + // -------------------------- + // + // This metadata should be added to each block version. It is a small amount of bytes, but it + // makes the aggregation much simpler. + // + // In v2 of the API, we might change this and put all this meta-information into variants of + // `Payload`, if we find that aggregation is fast enough. + // + + /// A list of IPNS hashes which are posting to this chain (so if a client has one profile + /// node, it can find the latest profile nodes from all devices a user posts from) + #[serde(rename = "devices")] + devices: Vec<IPNSHash>, + + /// Timestamp (UNIX timestamp) when this was created. Can be left out. + #[serde(rename = "timestamp")] + #[serde(skip_serializing_if = "Option::is_none")] + timestamp: Option<Timestamp>, + + /// The payload of the content block + #[serde(rename = "payload")] + payload: Payload, + +} + +impl Content { + + pub fn new(devices: Vec<IPNSHash>, timestamp: Option<Timestamp>, payload: Payload) -> Content { + Content { devices, timestamp, payload } + } + + pub fn devices(&self) -> &Vec<IPNSHash> { + &self.devices + } + + pub fn timestamp(&self) -> Option<&Timestamp> { + self.timestamp.as_ref() + } + + pub fn payload(&self) -> &Payload { + &self.payload + } + +} + +/// The Payload type represents the Payload of a Content object +/// +/// The Payload type contains several variants, as an update (new block) may be either just a +/// metadata update (like a merge) or something more meaningful. +/// +/// For example, the payload might be a `Payload::Post`, Alice has posted new kitten pictures! +/// Or a `Payload::ProfileUpdate` which contains information about Alice herself +/// +#[derive(Serialize, Deserialize)] +#[serde(tag = "payload_type")] +pub enum Payload { + + /// A variant that represents no payload + /// + /// This normally happens when merging two chains. + None, + + /// A post + /// + /// A post can be anything which contains data. The data itself is put behind an IPFS hash, the + /// Payload::Post only contains meta-information like payload size, content format (MIME) and + /// optionally a list of IPFS hashes which are related to the post itself. + Post { + /// Format of the actual content + #[serde(rename = "content-format")] + content_format: MimeType, + + /// IPFS hash pointing to the actual content + #[serde(rename = "content")] + content: IPFSHash, + + /// If this post is a reply to another post, this field can be used to point to the + /// replied-to post. + #[serde(rename = "reply-to")] + #[serde(skip_serializing_if = "Option::is_none")] + reply_to: Option<IPFSHash>, + + // + // + // Metadata for the post which should be visible to others + // + // + + /// A flag whether comments to this post will be propagated + /// + /// From a technical POV, comments can be done anyways, but the client which published the + /// comment has to "accept" comments (via `PostComments`) to make them visible to others. + /// This flag indicates whether the client will do that (either manually or automatically, + /// which is not indicated here). + /// + /// # Available values + /// + /// A value of `Some(false)` means that comments will not be propagated, so others will not + /// see them. A UI may not show "add comment"-buttons if this is set to `Some(false)`. + /// + /// A value of `Some(true)` means that comments will eventually be propagated. This means + /// that the author might accept them by hand or tell their client to automatically accept + /// all comments. This distinction is client-side implementation specific. + /// + /// A value of `None` indicates no setting here, which means that the client might or might + /// not propagate any comments. + #[serde(rename = "comments-will-be-propagated")] + #[serde(skip_serializing_if = "Option::is_none")] + comments_will_be_propagated: Option<bool>, + + /// A value which describes until what date/time comments will be propagated + /// + /// This is a hint for other users whether comments will be propagated or not. + /// A UI might not show a "Reply" button after that date. + #[serde(rename = "comments-propagated-until")] + #[serde(skip_serializing_if = "Option::is_none")] + comments_propagated_until: Option<Timestamp>, + }, + + /// Comments for a post + /// + /// Propagating answers to a post must be done by the author of the post itself. + /// This variant is for publishing a message "These are the comments on my post <hash>". + /// + /// Always all comments should be published, not just the new ones! + AttachedPostComments { + /// The Hash of the Block for the Post which the comments are for + #[serde(rename = "comments-for")] + comments_for: IPFSHash, + + /// Hashes of direct answers to the post pointed to by "comments_for" + /// This list always represents a full list of all answers. As comments are added, old + /// versions of this object should be ignored by clients if newer variants for the same `comments_for`-object are published. + #[serde(rename = "refs")] + #[serde(skip_serializing_if = "Vec::is_empty")] + refs: Vec<IPFSHash>, + }, + + /// A variant describing a profile + /// + /// A profile contains the whole set of data which is considered "current" for the + /// profile. Older versions of this shall then be ignored by clients. + Profile { + + /// The self-assigned names of a user. + #[serde(rename = "names")] + names: Vec<String>, + + /// An optional user profile picture + /// + /// The picture itself is located behind a IPFS hash. If the hash does not resolve to a + /// picture, clients should ignore it. + /// + /// A profile may only contain _one_ profile picture in the current version of the + /// protocol. + #[serde(rename = "picture")] + #[serde(skip_serializing_if = "Option::is_none")] + picture: Option<IPFSHash>, + + /// A "more" field where arbitrary data can be stored. Like "Biography", "Hobbies", + /// "Political opinion" or even pictures, ... + /// + /// The stored data can be of any type. + #[serde(rename = "user-defined")] + #[serde(skip_serializing_if = "BTreeMap::is_empty")] + more: BTreeMap<String, Userdata>, + }, +} + +#[derive(Serialize, Deserialize)] +pub struct Userdata { + #[serde(rename = "mimetype")] + mimetype: MimeType, + + #[serde(rename = "data")] + data: IPFSHash, +} + diff --git a/src/types/mod.rs b/src/types/mod.rs new file mode 100644 index 0000000..6e28092 --- /dev/null +++ b/src/types/mod.rs @@ -0,0 +1,4 @@ +pub mod block; +pub mod content; +pub mod util; + diff --git a/src/types/util.rs b/src/types/util.rs new file mode 100644 index 0000000..321bf7a --- /dev/null +++ b/src/types/util.rs @@ -0,0 +1,160 @@ +use std::ops::Deref; + +use chrono::NaiveDateTime; +use mime::Mime; + +/// A simple version. +/// +/// Like "1" for example +#[derive(Serialize, Deserialize, Debug)] +pub struct Version(usize); + +impl From<usize> for Version { + fn from(u: usize) -> Self { + Version(u) + } +} + +/// A Timestamp which can be parsed into a NaiveDateTime object +/// Format: 2014-11-28T21:45:59.324310806+09:00 +/// (RFC3999) +#[derive(Serialize, Deserialize, Debug)] +pub struct Timestamp(NaiveDateTime); + +impl From<NaiveDateTime> for Timestamp { + fn from(ndt: NaiveDateTime) -> Self { + Timestamp(ndt) + } +} + +impl ::std::fmt::Display for Timestamp { + fn fmt(&self, f: &mut ::std::fmt::Formatter) -> Result<(), ::std::fmt::Error> { + self.0.fmt(f) + } +} + +#[derive(Serialize, Deserialize, Debug, Hash, PartialOrd, PartialEq, Ord, Eq)] +pub struct IPFSHash(String); + +impl From<String> for IPFSHash { + fn from(s: String) -> Self { + IPFSHash(s) + } +} + +impl<'a> From<&'a str> for IPFSHash { + fn from(s: &str) -> Self { + String::from(s).into() + } +} + +impl Deref for IPFSHash { + type Target = String; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl ::std::fmt::Display for IPFSHash { + fn fmt(&self, f: &mut ::std::fmt::Formatter) -> Result<(), ::std::fmt::Error> { + self.0.fmt(f) + } +} + +#[derive(Serialize, Deserialize, Debug, Hash, PartialOrd, PartialEq, Ord, Eq)] +pub struct IPNSHash(String); + +impl From<String> for IPNSHash { + fn from(s: String) -> Self { + IPNSHash(s) + } +} + +impl<'a> From<&'a str> for IPNSHash { + fn from(s: &str) -> Self { + String::from(s).into() + } +} + +impl Deref for IPNSHash { + type Target = String; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl ::std::fmt::Display for IPNSHash { + fn fmt(&self, f: &mut ::std::fmt::Formatter) -> Result<(), ::std::fmt::Error> { + self.0.fmt(f) + } +} + +/// TODO: A String as mimetype... we can do this better! +#[derive(Debug, Hash, PartialEq, Eq)] +pub struct MimeType(Mime); + +impl From<Mime> for MimeType { + fn from(m: Mime) -> Self { + MimeType(m) + } +} + +impl Deref for MimeType { + type Target = Mime; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl ::std::fmt::Display for MimeType { + fn fmt(&self, f: &mut ::std::fmt::Formatter) -> Result<(), ::std::fmt::Error> { + self.0.fmt(f) + } +} + + +// +// TODO: Remove code below as soon as "mime" has serde support +// + +use std::fmt; +use std::str::FromStr; +use serde::de::{self, Deserialize, Deserializer}; +use serde::ser::{Serialize, Serializer}; + +impl Serialize for MimeType { + fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> + where S: Serializer, + { + serializer.serialize_str(self.0.as_ref()) + } +} + +impl<'de> Deserialize<'de> for MimeType { + fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> + where D: Deserializer<'de>, + { + struct Visitor; + + impl<'de> de::Visitor<'de> for Visitor { + type Value = MimeType; + + fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + formatter.write_str("a valid MIME type") + } + + fn visit_str<E>(self, value: &str) -> Result<MimeType, E> + where E: de::Error, + { + Mime::from_str(value) + .map(MimeType) + .map_err(E::custom) + } + } + deserializer.deserialize_str(Visitor) + } +} + |