Skip to content

Instantly share code, notes, and snippets.

@wezm
Last active December 21, 2017 14:17
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save wezm/65e416619f7c4b1e7a510b169426068d to your computer and use it in GitHub Desktop.
Save wezm/65e416619f7c4b1e7a510b169426068d to your computer and use it in GitHub Desktop.
An attempt at representing ActivityPub in Rust
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()
}
}
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