Last active
December 21, 2017 14:17
-
-
Save wezm/65e416619f7c4b1e7a510b169426068d to your computer and use it in GitHub Desktop.
An attempt at representing ActivityPub in Rust
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
use std::collections::HashMap; | |
use serde::ser::{Serialize, SerializeStruct}; | |
#[derive(Default)] | |
pub struct Object { | |
pub context: Context, | |
pub id: String, | |
// type to be defined by sub-type? Or have a big enum for it? | |
pub attachment: Option<String>, | |
pub attributed_to: Option<String>, | |
pub audience: Option<String>, | |
pub content: Option<String>, | |
pub name: Option<String>, | |
pub end_time: Option<String>, | |
pub generator: Option<String>, | |
pub icon: Option<String>, | |
pub image: Option<String>, | |
pub in_reply_to: Option<String>, | |
pub location: Option<String>, | |
pub preview: Option<String>, | |
pub published: Option<String>, | |
pub replies: Option<String>, | |
pub start_time: Option<String>, | |
pub summary: Option<String>, | |
pub tag: Option<String>, | |
pub updated: Option<String>, | |
pub url: Option<String>, | |
pub to: Option<String>, | |
pub bto: Option<String>, | |
pub cc: Option<String>, | |
pub bcc: Option<String>, | |
pub media_type: Option<String>, | |
pub duration: Option<String>, | |
} | |
macro_rules! serialize_object { | |
($obj:expr, $n:ident) => ( | |
if $obj.context != Context::None { | |
$n.serialize_field("@context", &$obj.context)?; | |
} | |
$n.serialize_field("id", &$obj.id)?; | |
if let Some(ref attachment) = $obj.attachment { | |
$n.serialize_field("attachment", attachment)?; | |
} | |
if let Some(ref attributed_to) = $obj.attributed_to { | |
$n.serialize_field("attributedTo", attributed_to)?; | |
} | |
if let Some(ref audience) = $obj.audience { | |
$n.serialize_field("audience", audience)?; | |
} | |
if let Some(ref content) = $obj.content { | |
$n.serialize_field("content", content)?; | |
} | |
if let Some(ref name) = $obj.name { | |
$n.serialize_field("name", name)?; | |
} | |
if let Some(ref end_time) = $obj.end_time { | |
$n.serialize_field("endTime", end_time)?; | |
} | |
if let Some(ref generator) = $obj.generator { | |
$n.serialize_field("generator", generator)?; | |
} | |
if let Some(ref icon) = $obj.icon { | |
$n.serialize_field("icon", icon)?; | |
} | |
if let Some(ref image) = $obj.image { | |
$n.serialize_field("image", image)?; | |
} | |
if let Some(ref in_reply_to) = $obj.in_reply_to { | |
$n.serialize_field("inReplyTo", in_reply_to)?; | |
} | |
if let Some(ref location) = $obj.location { | |
$n.serialize_field("location", location)?; | |
} | |
if let Some(ref preview) = $obj.preview { | |
$n.serialize_field("preview", preview)?; | |
} | |
if let Some(ref published) = $obj.published { | |
$n.serialize_field("published", published)?; | |
} | |
if let Some(ref replies) = $obj.replies { | |
$n.serialize_field("replies", replies)?; | |
} | |
if let Some(ref start_time) = $obj.start_time { | |
$n.serialize_field("startTime", start_time)?; | |
} | |
if let Some(ref summary) = $obj.summary { | |
$n.serialize_field("summary", summary)?; | |
} | |
if let Some(ref tag) = $obj.tag { | |
$n.serialize_field("tag", tag)?; | |
} | |
if let Some(ref updated) = $obj.updated { | |
$n.serialize_field("updated", updated)?; | |
} | |
if let Some(ref url) = $obj.url { | |
$n.serialize_field("url", url)?; | |
} | |
if let Some(ref to) = $obj.to { | |
$n.serialize_field("to", to)?; | |
} | |
if let Some(ref bto) = $obj.bto { | |
$n.serialize_field("bto", bto)?; | |
} | |
if let Some(ref cc) = $obj.cc { | |
$n.serialize_field("cc", cc)?; | |
} | |
if let Some(ref bcc) = $obj.bcc { | |
$n.serialize_field("bcc", bcc)?; | |
} | |
if let Some(ref media_type) = $obj.media_type { | |
$n.serialize_field("mediaType", media_type)?; | |
} | |
if let Some(ref duration) = $obj.duration { | |
$n.serialize_field("duration", duration)?; | |
} | |
) | |
} | |
impl Serialize for Object { | |
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> | |
where S: ::serde::Serializer | |
{ | |
let mut state = serializer.serialize_struct("Object", 28)?; | |
serialize_object!(self, state); | |
// state.serialize_field("type", &self.type_)?; | |
state.end() | |
} | |
} | |
#[derive(Serialize)] | |
pub struct Link { | |
// @context | |
// type: Link | |
href: String, | |
rel: String, | |
media_type: String, | |
name: String, | |
hreflang: String, | |
height: String, | |
width: String, | |
preview : String, | |
} | |
pub type Uri = String; | |
pub type Lang = String; // a BCP47 Language tag | |
pub enum NaturalLanguageValue { | |
String(String), | |
Map(HashMap<Lang, String>) | |
} | |
#[derive(Serialize)] | |
#[serde(untagged)] | |
pub enum PartOf { | |
Link(Link), | |
Collection(Collection), | |
} | |
#[derive(Serialize)] | |
#[serde(tag="type")] | |
pub enum CollectionLink { | |
Link(Link), | |
Page(CollectionPage), | |
} | |
pub type OptionalCollectionLink = Option<Box<CollectionLink>>; | |
// CollectionPage extends Collection | |
#[derive(Serialize, Default)] | |
pub struct CollectionPage { | |
pub collection: Box<Collection>, | |
pub next: OptionalCollectionLink, | |
pub prev: OptionalCollectionLink, | |
pub part_of: Option<PartOf>, | |
} | |
#[derive(Default)] | |
pub struct Collection { | |
pub obj: Object, | |
pub ordered: bool, | |
pub items: Vec<Object>, | |
pub total_items: Option<u64>, | |
pub first: OptionalCollectionLink, | |
pub last: OptionalCollectionLink, | |
pub current: OptionalCollectionLink, | |
} | |
impl Serialize for Collection { | |
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> | |
where S: ::serde::Serializer | |
{ | |
let mut state = serializer.serialize_struct("Actor", 15)?; | |
serialize_object!(self.obj, state); | |
// state.serialize_field("type", &self.type_)?; | |
// Required Collection properties | |
if self.ordered { | |
state.serialize_field("orderedItems", &self.items)?; | |
} | |
else { | |
state.serialize_field("items", &self.items)?; | |
} | |
// Optional Collection properties | |
if let Some(total_items) = self.total_items { | |
state.serialize_field("totalItems", &total_items)?; | |
} | |
if let Some(ref first) = self.first { | |
state.serialize_field("first", first)?; | |
} | |
if let Some(ref last) = self.last { | |
state.serialize_field("last", last)?; | |
} | |
if let Some(ref current) = self.current { | |
state.serialize_field("current", current)?; | |
} | |
state.end() | |
} | |
} | |
#[derive(Serialize)] | |
pub enum ActorType { | |
Application, | |
Group, | |
Organization, | |
Person, | |
Service, | |
} | |
// #[derive(Serialize)] | |
// pub enum ObjectType { | |
// Actor(ActorType) | |
// } | |
impl Default for ActorType { | |
fn default() -> ActorType { ActorType::Person } | |
} | |
#[derive(PartialEq)] | |
pub enum Context { | |
None, | |
ActivityStreams, | |
// TODO: Add array and Hash variants | |
} | |
impl Default for Context { | |
fn default() -> Context { Context::ActivityStreams } | |
} | |
impl Serialize for Context { | |
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> | |
where S: ::serde::Serializer | |
{ | |
match *self { | |
Context::None => serializer.serialize_none(), | |
Context::ActivityStreams => { | |
serializer.serialize_unit_variant("ActivityStreams", 1, "https://www.w3.org/ns/activitystreams") | |
} | |
} | |
} | |
} | |
type Should<T> = Option<T>; | |
type May<T> = Option<T>; | |
#[derive(Default)] | |
pub struct Actor { | |
pub obj: Object, | |
pub type_: ActorType, | |
pub inbox: Uri, // must | |
pub outbox: Uri, // must | |
pub following: Should<Uri>, // should | |
pub followers: Should<Uri>, // should | |
pub liked: Should<Uri>, // should | |
pub streams: Should<Vec<Uri>>, // may | |
pub preferred_username: May<String>, // may, at risk | |
// pub endpoints: JSON object or link to JSON-LD document // may | |
pub upload_media: May<Uri>, // may | |
pub proxy_url: May<Uri>, // may | |
pub oauth_client_authorize: May<Uri>, // may | |
pub provide_client_key: May<Uri>, // may | |
pub sign_client_key: May<Uri>, // may | |
pub public_inbox: May<Uri>, // may, at risk | |
} | |
impl Serialize for Actor { | |
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> | |
where S: ::serde::Serializer | |
{ | |
let mut state = serializer.serialize_struct("Actor", 15)?; | |
serialize_object!(self.obj, state); | |
state.serialize_field("type", &self.type_)?; | |
// Required Actor properties | |
state.serialize_field("inbox", &self.inbox)?; | |
state.serialize_field("outbox", &self.outbox)?; | |
// Optional Actor properties | |
if let Some(ref following) = self.following { | |
state.serialize_field("following", following)?; | |
} | |
if let Some(ref followers) = self.followers { | |
state.serialize_field("followers", followers)?; | |
} | |
if let Some(ref liked) = self.liked { | |
state.serialize_field("liked", liked)?; | |
} | |
if let Some(ref preferred_username) = self.preferred_username { | |
state.serialize_field("preferredUsername", preferred_username)?; | |
} | |
state.end() | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
fn example() { | |
let outbox = Collection { | |
obj: Object { | |
id: "https://example.com/@wezm".to_string(), | |
name: Some("Wesley Moore".to_string()), | |
..Default::default() | |
}, | |
ordered: true, | |
current: Some(Box::new(CollectionLink::Page(CollectionPage { | |
collection: Box::new(Collection { | |
ordered: true, | |
items: vec![], | |
..Default::default() | |
}), | |
..Default::default() | |
}))), | |
..Default::default() | |
}; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment