summaryrefslogtreecommitdiffstats
path: root/src/types
diff options
context:
space:
mode:
authorMatthias Beyer <mail@beyermatthias.de>2017-12-20 00:01:31 +0100
committerMatthias Beyer <mail@beyermatthias.de>2018-10-07 10:14:33 +0200
commit15bc0ad9c1515e8f326e01ca903c0d88bb787aee (patch)
tree17973661361745da860664e3893b99f70c1af097 /src/types
Initial import of distrox code
Diffstat (limited to 'src/types')
-rw-r--r--src/types/block.rs39
-rw-r--r--src/types/content.rs191
-rw-r--r--src/types/mod.rs4
-rw-r--r--src/types/util.rs160
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)
+ }
+}
+