ap: add ActivityPub class hierarchy
words cannot describe the constant agonizing pain of my miserable existence and why i haven't ended it yet is beyond my comprehensionmain
parent
dad5d07dd6
commit
891c6a9a39
@ -1,357 +0,0 @@
|
|||||||
use serde::de::{MapAccess, SeqAccess};
|
|
||||||
use serde::ser::{SerializeMap, SerializeSeq};
|
|
||||||
use serde::{de, Deserialize, Deserializer, Serialize, Serializer};
|
|
||||||
use std::collections::HashMap;
|
|
||||||
use std::fmt;
|
|
||||||
|
|
||||||
use crate::core::*;
|
|
||||||
|
|
||||||
/// The `@context` field in an ActivityPub document.
|
|
||||||
#[cfg_attr(test, derive(PartialEq))]
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct Context {
|
|
||||||
entries: Vec<Entry>,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub mod uri {
|
|
||||||
pub static ACTIVITY_STREAMS: &'static str = "https://www.w3.org/ns/activitystreams";
|
|
||||||
pub static ACTIVITY_STREAMS_ALT: &'static str = "http://www.w3.org/ns/activitystreams";
|
|
||||||
pub static NYANO: &'static str = "https://nyanoblog.org/ns/activitystreams";
|
|
||||||
pub static TOOT: &'static str = "http://joinmastodon.org/ns#";
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
|
||||||
pub enum Entry {
|
|
||||||
Anon(AnonEntry),
|
|
||||||
Named(NamedEntry),
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq)]
|
|
||||||
pub struct AnonEntry(String);
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq)]
|
|
||||||
pub struct NamedEntry(String, String);
|
|
||||||
|
|
||||||
impl Entry {
|
|
||||||
pub fn anon(uri: &str) -> Entry {
|
|
||||||
Entry::Anon(AnonEntry(String::from(uri)))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn named(name: &str, uri: &str) -> Entry {
|
|
||||||
Entry::Named(NamedEntry(String::from(name), String::from(uri)))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn name(&self) -> Option<&str> {
|
|
||||||
match self {
|
|
||||||
Entry::Anon(_) => None,
|
|
||||||
Entry::Named(named) => Some(&named.0),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn uri(&self) -> &str {
|
|
||||||
match self {
|
|
||||||
Entry::Anon(anon) => &anon.0,
|
|
||||||
Entry::Named(named) => &named.1,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl PartialEq for Entry {
|
|
||||||
fn eq(&self, other: &Self) -> bool {
|
|
||||||
match self {
|
|
||||||
Entry::Anon(self_anon) => self_anon.0.eq(other.uri()),
|
|
||||||
Entry::Named(self_named) => match other {
|
|
||||||
Entry::Anon(other_anon) => self_named.1.eq(&other_anon.0),
|
|
||||||
Entry::Named(other_named) => self_named.eq(other_named),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Context {
|
|
||||||
pub fn new() -> Context {
|
|
||||||
Context {
|
|
||||||
entries: Vec::new(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn has_entry(&self, uri: &str) -> bool {
|
|
||||||
self.entries.iter().any(|e| e.uri().eq(uri))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn add_entry(&mut self, mut entry: Entry) {
|
|
||||||
// As per <https://www.w3.org/TR/activitystreams-core/#jsonld>:
|
|
||||||
//
|
|
||||||
// > Implementations producing ActivityStreams 2.0. documents SHOULD include a
|
|
||||||
// > @context property with a value that includes a reference to the normative
|
|
||||||
// > Activity Streams 2.0 JSON-LD @context definition using the URL
|
|
||||||
// > "https://www.w3.org/ns/activitystreams". Implementations MAY use the
|
|
||||||
// > alternative URL "http://www.w3.org/ns/activitystreams" instead.
|
|
||||||
// > This can be done using a string, object, or array.
|
|
||||||
//
|
|
||||||
// We're just gonna silently rewrite the http:// to https:// and call it a day.
|
|
||||||
if entry.uri().eq(uri::ACTIVITY_STREAMS_ALT) {
|
|
||||||
entry = match entry {
|
|
||||||
Entry::Anon(_) => Entry::anon(uri::ACTIVITY_STREAMS),
|
|
||||||
Entry::Named(named) => Entry::named(&named.0, uri::ACTIVITY_STREAMS),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
if !self.has_entry(entry.uri()) {
|
|
||||||
self.entries.push(entry);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Merge with a contained object's context, consuming that child's context.
|
|
||||||
///
|
|
||||||
/// To be used as:
|
|
||||||
///
|
|
||||||
/// ```
|
|
||||||
/// if let Some(other_context) = other_object.context.take() {
|
|
||||||
/// context.merge(other_context);
|
|
||||||
/// }
|
|
||||||
/// ```
|
|
||||||
pub fn merge(&mut self, other: Context) {
|
|
||||||
for e in other.entries {
|
|
||||||
self.add_entry(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Default for Context {
|
|
||||||
fn default() -> Self {
|
|
||||||
let mut ctx = Context::new();
|
|
||||||
ctx.add_entry(Entry::anon(uri::ACTIVITY_STREAMS));
|
|
||||||
ctx
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Serialize for Context {
|
|
||||||
fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
|
|
||||||
where
|
|
||||||
S: Serializer,
|
|
||||||
{
|
|
||||||
// FIXME: omg this is just horrible
|
|
||||||
|
|
||||||
if self.entries.len() == 1 {
|
|
||||||
serializer.serialize_str(self.entries[0].uri())
|
|
||||||
} else {
|
|
||||||
let mut anons = Vec::new();
|
|
||||||
let mut nameds = Vec::new();
|
|
||||||
for e in &self.entries {
|
|
||||||
match e {
|
|
||||||
Entry::Anon(anon) => anons.push(anon),
|
|
||||||
Entry::Named(named) => nameds.push(named),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
let (anons, nameds) = (anons, nameds);
|
|
||||||
|
|
||||||
let seq_len = anons.len() + if nameds.len() > 0 { 1 } else { 0 };
|
|
||||||
let mut seq = serializer.serialize_seq(Some(seq_len))?;
|
|
||||||
for a in anons {
|
|
||||||
seq.serialize_element(&a.0)?;
|
|
||||||
}
|
|
||||||
if nameds.len() > 0 {
|
|
||||||
seq.serialize_element(&NamedEntriesWrapper(nameds))?;
|
|
||||||
}
|
|
||||||
seq.end()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'de> de::Deserialize<'de> for Context {
|
|
||||||
fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
|
|
||||||
where
|
|
||||||
D: Deserializer<'de>,
|
|
||||||
{
|
|
||||||
deserializer.deserialize_any(ContextVisitor)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
struct ContextVisitor;
|
|
||||||
|
|
||||||
impl<'de> de::Visitor<'de> for ContextVisitor {
|
|
||||||
type Value = Context;
|
|
||||||
|
|
||||||
fn expecting(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
|
||||||
f.write_str("a valid JSON-LD context specification")
|
|
||||||
}
|
|
||||||
|
|
||||||
fn visit_str<E>(self, value: &str) -> std::result::Result<Self::Value, E>
|
|
||||||
where
|
|
||||||
E: de::Error,
|
|
||||||
{
|
|
||||||
Ok(Context {
|
|
||||||
entries: vec![Entry::anon(value)],
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
fn visit_none<E>(self) -> std::result::Result<Self::Value, E>
|
|
||||||
where
|
|
||||||
E: de::Error,
|
|
||||||
{
|
|
||||||
Ok(Context::new())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn visit_seq<S>(self, mut seq: S) -> std::result::Result<Self::Value, S::Error>
|
|
||||||
where
|
|
||||||
S: SeqAccess<'de>,
|
|
||||||
{
|
|
||||||
let mut context = Context::new();
|
|
||||||
while let Some(wrapper) = seq.next_element::<StupidEntryWrapper>()? {
|
|
||||||
match wrapper {
|
|
||||||
StupidEntryWrapper::Single(entry) => context.add_entry(entry),
|
|
||||||
StupidEntryWrapper::Multi(entries) => {
|
|
||||||
for e in entries {
|
|
||||||
context.add_entry(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// As per <https://www.w3.org/TR/activitystreams-core/#jsonld>:
|
|
||||||
//
|
|
||||||
// "When a JSON-LD enabled Activity Streams 2.0 implementation encounters
|
|
||||||
// a JSON document identified using the "application/activity+json" MIME
|
|
||||||
// media type, and that document does not contain a @context property whose
|
|
||||||
// value includes a reference to the normative Activity Streams 2.0 JSON-LD
|
|
||||||
// @context definition, the implementation MUST assume that the normative
|
|
||||||
// @context definition still applies."
|
|
||||||
if !context.has_entry(uri::ACTIVITY_STREAMS) {
|
|
||||||
warn!(target: "ap", "@context does not have canonical ActivityStreams URI");
|
|
||||||
context.add_entry(Entry::anon(uri::ACTIVITY_STREAMS));
|
|
||||||
}
|
|
||||||
if context.entries.len() == 0 {
|
|
||||||
warn!(target: "ap", "Malformed @context: Arrays must always have elements, or be null");
|
|
||||||
context.add_entry(Entry::anon(uri::ACTIVITY_STREAMS));
|
|
||||||
}
|
|
||||||
Ok(context)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn visit_map<A>(self, mut map: A) -> std::result::Result<Self::Value, A::Error>
|
|
||||||
where
|
|
||||||
A: MapAccess<'de>,
|
|
||||||
{
|
|
||||||
let mut entries = Vec::new();
|
|
||||||
|
|
||||||
while let Some((k, v)) = map.next_entry::<&str, &str>()? {
|
|
||||||
entries.push(Entry::named(k, v));
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(Context { entries })
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Stupid wrapper because if @context is an array, its elements are strings and/or maps.
|
|
||||||
enum StupidEntryWrapper {
|
|
||||||
/// Single (anonymous) URI
|
|
||||||
Single(Entry),
|
|
||||||
/// Map of (named) URIs
|
|
||||||
Multi(Vec<Entry>),
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'de> Deserialize<'de> for StupidEntryWrapper {
|
|
||||||
fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
|
|
||||||
where
|
|
||||||
D: Deserializer<'de>,
|
|
||||||
{
|
|
||||||
deserializer.deserialize_any(StupidEntryWrapperVisitor)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
struct StupidEntryWrapperVisitor;
|
|
||||||
|
|
||||||
impl<'de> de::Visitor<'de> for StupidEntryWrapperVisitor {
|
|
||||||
type Value = StupidEntryWrapper;
|
|
||||||
|
|
||||||
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
|
|
||||||
formatter.write_str("a context URI or map of named URIs")
|
|
||||||
}
|
|
||||||
|
|
||||||
fn visit_str<E>(self, s: &str) -> std::result::Result<Self::Value, E>
|
|
||||||
where
|
|
||||||
E: de::Error,
|
|
||||||
{
|
|
||||||
Ok(StupidEntryWrapper::Single(Entry::anon(s)))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn visit_map<A>(self, mut map: A) -> std::result::Result<Self::Value, A::Error>
|
|
||||||
where
|
|
||||||
A: MapAccess<'de>,
|
|
||||||
{
|
|
||||||
let mut entries = Vec::new();
|
|
||||||
while let Some((k, v)) = map.next_entry()? {
|
|
||||||
entries.push(Entry::Named(NamedEntry(k, v)));
|
|
||||||
}
|
|
||||||
Ok(StupidEntryWrapper::Multi(entries))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// We can't use a HashMap to serialize named entries because that doesn't
|
|
||||||
/// produce deterministic results, so we have to use our own serializer
|
|
||||||
struct NamedEntriesWrapper<'a>(Vec<&'a NamedEntry>);
|
|
||||||
|
|
||||||
impl<'a> Serialize for NamedEntriesWrapper<'a> {
|
|
||||||
fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
|
|
||||||
where
|
|
||||||
S: Serializer,
|
|
||||||
{
|
|
||||||
let mut map = serializer.serialize_map(Some(self.0.len()))?;
|
|
||||||
for entry in &self.0 {
|
|
||||||
//let entry = *entry;
|
|
||||||
map.serialize_entry(&entry.0, &entry.1)?;
|
|
||||||
}
|
|
||||||
map.end()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod tests {
|
|
||||||
use super::*;
|
|
||||||
use serde_test::{assert_tokens, Token};
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn default() {
|
|
||||||
let ctx = Context::default();
|
|
||||||
|
|
||||||
assert_tokens(&ctx, &[Token::String(uri::ACTIVITY_STREAMS)]);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn multi_anon() {
|
|
||||||
let mut ctx = Context::default();
|
|
||||||
ctx.add_entry(Entry::anon(uri::NYANO));
|
|
||||||
|
|
||||||
assert_tokens(
|
|
||||||
&ctx,
|
|
||||||
&[
|
|
||||||
Token::Seq { len: Some(2) },
|
|
||||||
Token::String(uri::ACTIVITY_STREAMS),
|
|
||||||
Token::String(uri::NYANO),
|
|
||||||
Token::SeqEnd,
|
|
||||||
],
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn multi_named() {
|
|
||||||
let mut ctx = Context::default();
|
|
||||||
ctx.add_entry(Entry::named("nyano", uri::NYANO));
|
|
||||||
ctx.add_entry(Entry::named("toot", uri::TOOT));
|
|
||||||
|
|
||||||
assert_tokens(
|
|
||||||
&ctx,
|
|
||||||
&[
|
|
||||||
Token::Seq { len: Some(2) },
|
|
||||||
Token::String(uri::ACTIVITY_STREAMS),
|
|
||||||
Token::Map { len: Some(2) },
|
|
||||||
Token::String("nyano"),
|
|
||||||
Token::String(uri::NYANO),
|
|
||||||
Token::String("toot"),
|
|
||||||
Token::String(uri::TOOT),
|
|
||||||
Token::MapEnd,
|
|
||||||
Token::SeqEnd,
|
|
||||||
],
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,3 +1,4 @@
|
|||||||
pub mod loader;
|
pub mod loader;
|
||||||
pub mod processor;
|
pub mod processor;
|
||||||
|
pub mod trans;
|
||||||
pub mod vocab;
|
pub mod vocab;
|
||||||
|
@ -1,617 +0,0 @@
|
|||||||
use serde::de::{self, IgnoredAny, MapAccess, SeqAccess, Visitor};
|
|
||||||
use serde::ser::{SerializeMap, SerializeSeq};
|
|
||||||
use serde::{Deserialize, Deserializer, Serialize, Serializer};
|
|
||||||
use std::fmt;
|
|
||||||
|
|
||||||
use crate::ap::context::{uri, Context};
|
|
||||||
use crate::core::*;
|
|
||||||
|
|
||||||
/// Wrapper for [`BaseObject`] because ActivityPub allows
|
|
||||||
/// any object to be referenced indirectly through a URL.
|
|
||||||
#[cfg_attr(test, derive(PartialEq))]
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub enum Object {
|
|
||||||
Direct(BaseObject),
|
|
||||||
Indirect(String),
|
|
||||||
Multi(Vec<Object>),
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Intermediate representation of actual AP objects.
|
|
||||||
/// Contains all possible fields for any type of object.
|
|
||||||
/// We "simplify" AP processing by treating anything as an object.
|
|
||||||
/// This struct is only used internally as a helper for (de)serializing objects
|
|
||||||
/// because doing that all in one step would, frankly, be insane.
|
|
||||||
#[cfg_attr(test, derive(PartialEq))]
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct BaseObject {
|
|
||||||
pub context: Option<Context>,
|
|
||||||
pub typ: Option<ObjectType>,
|
|
||||||
pub id: Option<ObjectId>,
|
|
||||||
pub name: Option<String>,
|
|
||||||
pub content: Option<String>,
|
|
||||||
pub subject: Option<Box<Object>>,
|
|
||||||
pub actor: Option<Box<Object>>,
|
|
||||||
pub object: Option<Box<Object>>,
|
|
||||||
pub attributed_to: Option<Box<Object>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, PartialEq)]
|
|
||||||
pub enum ObjectType {
|
|
||||||
Unknown(String),
|
|
||||||
// Object
|
|
||||||
Object,
|
|
||||||
Article,
|
|
||||||
Audio,
|
|
||||||
Document,
|
|
||||||
Event,
|
|
||||||
Image,
|
|
||||||
Note,
|
|
||||||
Page,
|
|
||||||
Place,
|
|
||||||
Profile,
|
|
||||||
Relationship,
|
|
||||||
Tombstone,
|
|
||||||
Video,
|
|
||||||
// Link
|
|
||||||
Link,
|
|
||||||
Mention,
|
|
||||||
// Activity
|
|
||||||
Activity,
|
|
||||||
Accept,
|
|
||||||
Add,
|
|
||||||
Announce,
|
|
||||||
Arrive,
|
|
||||||
Block,
|
|
||||||
Create,
|
|
||||||
Delete,
|
|
||||||
Dislike,
|
|
||||||
EmojiReact, // extension
|
|
||||||
Flag,
|
|
||||||
Follow,
|
|
||||||
Ignore,
|
|
||||||
Invite,
|
|
||||||
Join,
|
|
||||||
Leave,
|
|
||||||
Like,
|
|
||||||
Listen,
|
|
||||||
Move,
|
|
||||||
Offer,
|
|
||||||
Question,
|
|
||||||
Reject,
|
|
||||||
Read,
|
|
||||||
Remove,
|
|
||||||
TentativeReject,
|
|
||||||
TentativeAccept,
|
|
||||||
Travel,
|
|
||||||
Undo,
|
|
||||||
Update,
|
|
||||||
View,
|
|
||||||
// Actor
|
|
||||||
Actor,
|
|
||||||
Application,
|
|
||||||
Group,
|
|
||||||
Organization,
|
|
||||||
Person,
|
|
||||||
Service,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Globally unique object identifier.
|
|
||||||
#[derive(Debug, PartialEq)]
|
|
||||||
pub struct ObjectId(String);
|
|
||||||
|
|
||||||
pub trait GetObjectId {
|
|
||||||
/// Get this object's globally unique (ActivityPub) id.
|
|
||||||
/// The `base_url` parameter is for constructing a URI and takes
|
|
||||||
/// the form `https://domain.tld` (without a trailing slash).
|
|
||||||
/// Do not rely on the URL starting with `https` though, a future
|
|
||||||
/// version might support `http` URLs for Tor and such.
|
|
||||||
fn get_object_id(&self, base_url: &str) -> String;
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T> From<T> for ObjectId
|
|
||||||
where
|
|
||||||
T: GetObjectId,
|
|
||||||
{
|
|
||||||
fn from(t: T) -> ObjectId {
|
|
||||||
// TODO: there has to be a more elegant way for this
|
|
||||||
let base_domain = format!("https://{}", std::env::var("LOCAL_DOMAIN").unwrap());
|
|
||||||
ObjectId(t.get_object_id(&base_domain))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Serialize for Object {
|
|
||||||
fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
|
|
||||||
where
|
|
||||||
S: Serializer,
|
|
||||||
{
|
|
||||||
match self {
|
|
||||||
Object::Direct(direct) => direct.serialize(serializer),
|
|
||||||
Object::Indirect(url) => serializer.serialize_str(url),
|
|
||||||
Object::Multi(objs) => {
|
|
||||||
let mut seq = serializer.serialize_seq(Some(objs.len()))?;
|
|
||||||
for obj in objs {
|
|
||||||
seq.serialize_element(obj)?;
|
|
||||||
}
|
|
||||||
seq.end()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Serialize for BaseObject {
|
|
||||||
fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
|
|
||||||
where
|
|
||||||
S: Serializer,
|
|
||||||
{
|
|
||||||
let mut map = serializer.serialize_map(None)?;
|
|
||||||
|
|
||||||
if let Some(context) = &self.context {
|
|
||||||
map.serialize_entry("@context", context)?;
|
|
||||||
}
|
|
||||||
if let Some(typ) = &self.typ {
|
|
||||||
map.serialize_entry("type", typ)?;
|
|
||||||
}
|
|
||||||
if let Some(id) = &self.id {
|
|
||||||
map.serialize_entry("id", &id.0)?;
|
|
||||||
}
|
|
||||||
if let Some(name) = &self.name {
|
|
||||||
map.serialize_entry("name", name)?;
|
|
||||||
}
|
|
||||||
if let Some(content) = &self.content {
|
|
||||||
map.serialize_entry("content", content)?;
|
|
||||||
}
|
|
||||||
if let Some(subject) = &self.subject {
|
|
||||||
map.serialize_entry("subject", subject.as_ref())?;
|
|
||||||
}
|
|
||||||
if let Some(actor) = &self.actor {
|
|
||||||
map.serialize_entry("actor", actor.as_ref())?;
|
|
||||||
}
|
|
||||||
if let Some(object) = &self.object {
|
|
||||||
map.serialize_entry("object", object.as_ref())?;
|
|
||||||
}
|
|
||||||
if let Some(attributed_to) = &self.attributed_to {
|
|
||||||
map.serialize_entry("attributedTo", attributed_to.as_ref())?;
|
|
||||||
}
|
|
||||||
|
|
||||||
map.end()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Serialize for ObjectType {
|
|
||||||
fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
|
|
||||||
where
|
|
||||||
S: Serializer,
|
|
||||||
{
|
|
||||||
serializer.serialize_str(self.as_str())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'de> Deserialize<'de> for Object {
|
|
||||||
fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
|
|
||||||
where
|
|
||||||
D: Deserializer<'de>,
|
|
||||||
{
|
|
||||||
deserializer.deserialize_any(ObjectVisitor)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
struct ObjectVisitor;
|
|
||||||
|
|
||||||
impl<'de> Visitor<'de> for ObjectVisitor {
|
|
||||||
type Value = Object;
|
|
||||||
|
|
||||||
fn expecting(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
|
||||||
f.write_str("an Activity Streams 2.0 compliant JSON-LD object")
|
|
||||||
}
|
|
||||||
|
|
||||||
fn visit_string<E>(self, s: String) -> std::result::Result<Self::Value, E>
|
|
||||||
where
|
|
||||||
E: de::Error,
|
|
||||||
{
|
|
||||||
Ok(Object::Indirect(s))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn visit_seq<A>(self, mut seq: A) -> std::result::Result<Self::Value, A::Error>
|
|
||||||
where
|
|
||||||
A: SeqAccess<'de>,
|
|
||||||
{
|
|
||||||
let mut objs = Vec::new();
|
|
||||||
|
|
||||||
while let Some(obj) = seq.next_element()? {
|
|
||||||
objs.push(obj);
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(Object::Multi(objs))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn visit_map<A>(self, mut map: A) -> std::result::Result<Self::Value, A::Error>
|
|
||||||
where
|
|
||||||
A: MapAccess<'de>,
|
|
||||||
{
|
|
||||||
let mut obj = BaseObject::default();
|
|
||||||
|
|
||||||
while let Some(k) = map.next_key::<String>()? {
|
|
||||||
match k.as_str() {
|
|
||||||
"@context" => obj.context = Some(map.next_value()?),
|
|
||||||
"type" => obj.typ = Some(map.next_value()?),
|
|
||||||
"id" => obj.id = Some(ObjectId(map.next_value()?)),
|
|
||||||
"name" => obj.name = Some(map.next_value()?),
|
|
||||||
"content" => obj.content = Some(map.next_value()?),
|
|
||||||
"subject" => obj.subject = Some(map.next_value()?),
|
|
||||||
"actor" => obj.actor = Some(Box::new(map.next_value()?)),
|
|
||||||
"object" => obj.object = Some(Box::new(map.next_value()?)),
|
|
||||||
"attributedTo" => obj.attributed_to = Some(Box::new(map.next_value()?)),
|
|
||||||
_ => {
|
|
||||||
map.next_value::<IgnoredAny>()?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(Object::Direct(obj))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'de> Deserialize<'de> for ObjectType {
|
|
||||||
fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
|
|
||||||
where
|
|
||||||
D: Deserializer<'de>,
|
|
||||||
{
|
|
||||||
deserializer.deserialize_str(ObjectTypeVisitor)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
struct ObjectTypeVisitor;
|
|
||||||
|
|
||||||
impl<'de> Visitor<'de> for ObjectTypeVisitor {
|
|
||||||
type Value = ObjectType;
|
|
||||||
|
|
||||||
fn expecting(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
|
||||||
f.write_str("an Activity Streams 2.0 compliant object type name")
|
|
||||||
}
|
|
||||||
|
|
||||||
fn visit_str<E>(self, s: &str) -> std::result::Result<Self::Value, E>
|
|
||||||
where
|
|
||||||
E: de::Error,
|
|
||||||
{
|
|
||||||
Ok(ObjectType::from(s))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Default for BaseObject {
|
|
||||||
fn default() -> Self {
|
|
||||||
BaseObject {
|
|
||||||
context: None,
|
|
||||||
typ: None,
|
|
||||||
id: None,
|
|
||||||
name: None,
|
|
||||||
content: None,
|
|
||||||
subject: None,
|
|
||||||
actor: None,
|
|
||||||
object: None,
|
|
||||||
attributed_to: None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
////////// the rest is just boring ObjectType impls and tests //////////
|
|
||||||
|
|
||||||
impl ObjectType {
|
|
||||||
pub fn is_object(&self) -> bool {
|
|
||||||
match self {
|
|
||||||
ObjectType::Object => true,
|
|
||||||
ObjectType::Article => true,
|
|
||||||
ObjectType::Audio => true,
|
|
||||||
ObjectType::Document => true,
|
|
||||||
ObjectType::Event => true,
|
|
||||||
ObjectType::Image => true,
|
|
||||||
ObjectType::Note => true,
|
|
||||||
ObjectType::Page => true,
|
|
||||||
ObjectType::Place => true,
|
|
||||||
ObjectType::Profile => true,
|
|
||||||
ObjectType::Relationship => true,
|
|
||||||
ObjectType::Tombstone => true,
|
|
||||||
ObjectType::Video => true,
|
|
||||||
_ => false,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn is_link(&self) -> bool {
|
|
||||||
match self {
|
|
||||||
ObjectType::Link => true,
|
|
||||||
ObjectType::Mention => true,
|
|
||||||
_ => false,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn is_activity(&self) -> bool {
|
|
||||||
match self {
|
|
||||||
ObjectType::Activity => true,
|
|
||||||
ObjectType::Accept => true,
|
|
||||||
ObjectType::Add => true,
|
|
||||||
ObjectType::Announce => true,
|
|
||||||
ObjectType::Arrive => true,
|
|
||||||
ObjectType::Block => true,
|
|
||||||
ObjectType::Create => true,
|
|
||||||
ObjectType::Delete => true,
|
|
||||||
ObjectType::Dislike => true,
|
|
||||||
ObjectType::EmojiReact => true,
|
|
||||||
ObjectType::Flag => true,
|
|
||||||
ObjectType::Follow => true,
|
|
||||||
ObjectType::Ignore => true,
|
|
||||||
ObjectType::Invite => true,
|
|
||||||
ObjectType::Join => true,
|
|
||||||
ObjectType::Leave => true,
|
|
||||||
ObjectType::Like => true,
|
|
||||||
ObjectType::Listen => true,
|
|
||||||
ObjectType::Move => true,
|
|
||||||
ObjectType::Offer => true,
|
|
||||||
ObjectType::Question => true,
|
|
||||||
ObjectType::Reject => true,
|
|
||||||
ObjectType::Read => true,
|
|
||||||
ObjectType::Remove => true,
|
|
||||||
ObjectType::TentativeReject => true,
|
|
||||||
ObjectType::TentativeAccept => true,
|
|
||||||
ObjectType::Travel => true,
|
|
||||||
ObjectType::Undo => true,
|
|
||||||
ObjectType::Update => true,
|
|
||||||
ObjectType::View => true,
|
|
||||||
_ => false,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn is_actor(&self) -> bool {
|
|
||||||
match self {
|
|
||||||
ObjectType::Actor => true,
|
|
||||||
ObjectType::Application => true,
|
|
||||||
ObjectType::Group => true,
|
|
||||||
ObjectType::Organization => true,
|
|
||||||
ObjectType::Person => true,
|
|
||||||
ObjectType::Service => true,
|
|
||||||
_ => false,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn as_str(&self) -> &str {
|
|
||||||
match self {
|
|
||||||
ObjectType::Unknown(s) => s.as_str(),
|
|
||||||
// Object
|
|
||||||
ObjectType::Object => "Object",
|
|
||||||
ObjectType::Article => "Article",
|
|
||||||
ObjectType::Audio => "Audio",
|
|
||||||
ObjectType::Document => "Document",
|
|
||||||
ObjectType::Event => "Event",
|
|
||||||
ObjectType::Image => "Image",
|
|
||||||
ObjectType::Note => "Note",
|
|
||||||
ObjectType::Page => "Page",
|
|
||||||
ObjectType::Place => "Place",
|
|
||||||
ObjectType::Profile => "Profile",
|
|
||||||
ObjectType::Relationship => "Relationship",
|
|
||||||
ObjectType::Tombstone => "Tombstone",
|
|
||||||
ObjectType::Video => "Video",
|
|
||||||
// Link
|
|
||||||
ObjectType::Link => "Link",
|
|
||||||
ObjectType::Mention => "Mention",
|
|
||||||
// Activity
|
|
||||||
ObjectType::Activity => "Activity",
|
|
||||||
ObjectType::Accept => "Accept",
|
|
||||||
ObjectType::Add => "Add",
|
|
||||||
ObjectType::Announce => "Announce",
|
|
||||||
ObjectType::Arrive => "Arrive",
|
|
||||||
ObjectType::Block => "Block",
|
|
||||||
ObjectType::Create => "Create",
|
|
||||||
ObjectType::Delete => "Delete",
|
|
||||||
ObjectType::Dislike => "Dislike",
|
|
||||||
ObjectType::EmojiReact => "EmojiReact",
|
|
||||||
ObjectType::Flag => "Flag",
|
|
||||||
ObjectType::Follow => "Follow",
|
|
||||||
ObjectType::Ignore => "Ignore",
|
|
||||||
ObjectType::Invite => "Invite",
|
|
||||||
ObjectType::Join => "Join",
|
|
||||||
ObjectType::Leave => "Leave",
|
|
||||||
ObjectType::Like => "Like",
|
|
||||||
ObjectType::Listen => "Listen",
|
|
||||||
ObjectType::Move => "Move",
|
|
||||||
ObjectType::Offer => "Offer",
|
|
||||||
ObjectType::Question => "Question",
|
|
||||||
ObjectType::Reject => "Reject",
|
|
||||||
ObjectType::Read => "Read",
|
|
||||||
ObjectType::Remove => "Remove",
|
|
||||||
ObjectType::TentativeReject => "TentativeReject",
|
|
||||||
ObjectType::TentativeAccept => "TentativeAccept",
|
|
||||||
ObjectType::Travel => "Travel",
|
|
||||||
ObjectType::Undo => "Undo",
|
|
||||||
ObjectType::Update => "Update",
|
|
||||||
ObjectType::View => "View",
|
|
||||||
// Actor
|
|
||||||
ObjectType::Actor => "Actor",
|
|
||||||
ObjectType::Application => "Application",
|
|
||||||
ObjectType::Group => "Group",
|
|
||||||
ObjectType::Organization => "Organization",
|
|
||||||
ObjectType::Person => "Person",
|
|
||||||
ObjectType::Service => "Service",
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<&str> for ObjectType {
|
|
||||||
fn from(s: &str) -> ObjectType {
|
|
||||||
match s {
|
|
||||||
// Object
|
|
||||||
"Object" => ObjectType::Object,
|
|
||||||
"Article" => ObjectType::Article,
|
|
||||||
"Audio" => ObjectType::Audio,
|
|
||||||
"Document" => ObjectType::Document,
|
|
||||||
"Event" => ObjectType::Event,
|
|
||||||
"Image" => ObjectType::Image,
|
|
||||||
"Note" => ObjectType::Note,
|
|
||||||
"Page" => ObjectType::Page,
|
|
||||||
"Place" => ObjectType::Place,
|
|
||||||
"Profile" => ObjectType::Profile,
|
|
||||||
"Relationship" => ObjectType::Relationship,
|
|
||||||
"Tombstone" => ObjectType::Tombstone,
|
|
||||||
"Video" => ObjectType::Video,
|
|
||||||
// Link
|
|
||||||
"Link" => ObjectType::Link,
|
|
||||||
"Mention" => ObjectType::Mention,
|
|
||||||
// Activity
|
|
||||||
"Activity" => ObjectType::Activity,
|
|
||||||
"Accept" => ObjectType::Accept,
|
|
||||||
"Add" => ObjectType::Add,
|
|
||||||
"Announce" => ObjectType::Announce,
|
|
||||||
"Arrive" => ObjectType::Arrive,
|
|
||||||
"Block" => ObjectType::Block,
|
|
||||||
"Create" => ObjectType::Create,
|
|
||||||
"Delete" => ObjectType::Delete,
|
|
||||||
"Dislike" => ObjectType::Dislike,
|
|
||||||
"EmojiReact" => ObjectType::EmojiReact,
|
|
||||||
"Flag" => ObjectType::Flag,
|
|
||||||
"Follow" => ObjectType::Follow,
|
|
||||||
"Ignore" => ObjectType::Ignore,
|
|
||||||
"Invite" => ObjectType::Invite,
|
|
||||||
"Join" => ObjectType::Join,
|
|
||||||
"Leave" => ObjectType::Leave,
|
|
||||||
"Like" => ObjectType::Like,
|
|
||||||
"Listen" => ObjectType::Listen,
|
|
||||||
"Move" => ObjectType::Move,
|
|
||||||
"Offer" => ObjectType::Offer,
|
|
||||||
"Question" => ObjectType::Question,
|
|
||||||
"Reject" => ObjectType::Reject,
|
|
||||||
"Read" => ObjectType::Read,
|
|
||||||
"Remove" => ObjectType::Remove,
|
|
||||||
"TentativeReject" => ObjectType::TentativeReject,
|
|
||||||
"TentativeAccept" => ObjectType::TentativeAccept,
|
|
||||||
"Travel" => ObjectType::Travel,
|
|
||||||
"Undo" => ObjectType::Undo,
|
|
||||||
"Update" => ObjectType::Update,
|
|
||||||
"View" => ObjectType::View,
|
|
||||||
// Actor
|
|
||||||
"Actor" => ObjectType::Actor,
|
|
||||||
"Application" => ObjectType::Application,
|
|
||||||
"Group" => ObjectType::Group,
|
|
||||||
"Organization" => ObjectType::Organization,
|
|
||||||
"Person" => ObjectType::Person,
|
|
||||||
"Service" => ObjectType::Service,
|
|
||||||
// Unknown
|
|
||||||
_ => ObjectType::Unknown(String::from(s)),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod tests {
|
|
||||||
use super::*;
|
|
||||||
use serde_test::{assert_tokens, Token};
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn indirect() {
|
|
||||||
let obj = Object::Indirect(String::from("https://example.org/notes/1"));
|
|
||||||
|
|
||||||
assert_tokens(&obj, &[Token::String("https://example.org/notes/1")]);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn empty() {
|
|
||||||
let obj = Object::Direct(BaseObject::default());
|
|
||||||
|
|
||||||
assert_tokens(&obj, &[Token::Map { len: None }, Token::MapEnd]);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn basic() {
|
|
||||||
let obj = Object::Direct(BaseObject {
|
|
||||||
context: Some(Context::default()),
|
|
||||||
typ: Some(ObjectType::Object),
|
|
||||||
id: Some(ObjectId(String::from("https://www.example.com/object/1"))),
|
|
||||||
name: Some(String::from("A Simple, non-specific object")),
|
|
||||||
..Default::default()
|
|
||||||
});
|
|
||||||
|
|
||||||
assert_tokens(
|
|
||||||
&obj,
|
|
||||||
&[
|
|
||||||
Token::Map { len: None },
|
|
||||||
Token::String("@context"),
|
|
||||||
Token::String(uri::ACTIVITY_STREAMS),
|
|
||||||
Token::String("type"),
|
|
||||||
Token::String("Object"),
|
|
||||||
Token::String("id"),
|
|
||||||
Token::String("https://www.example.com/object/1"),
|
|
||||||
Token::String("name"),
|
|
||||||
Token::String("A Simple, non-specific object"),
|
|
||||||
Token::MapEnd,
|
|
||||||
],
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn full() {
|
|
||||||
let obj = Object::Direct(BaseObject {
|
|
||||||
context: Some(Context::default()),
|
|
||||||
typ: Some(ObjectType::Create),
|
|
||||||
id: Some(ObjectId(String::from("https://www.example.org/object/2"))),
|
|
||||||
name: None,
|
|
||||||
content: None,
|
|
||||||
subject: Some(Box::new(Object::Indirect(String::from(
|
|
||||||
"https://www.example.org/actor/1",
|
|
||||||
)))),
|
|
||||||
actor: Some(Box::new(Object::Direct(BaseObject {
|
|
||||||
typ: Some(ObjectType::Person),
|
|
||||||
name: Some(String::from("Alice")),
|
|
||||||
..Default::default()
|
|
||||||
}))),
|
|
||||||
object: Some(Box::new(Object::Direct(BaseObject {
|
|
||||||
typ: Some(ObjectType::Note),
|
|
||||||
content: Some(String::from("hi i'm gay uwu")),
|
|
||||||
..Default::default()
|
|
||||||
}))),
|
|
||||||
attributed_to: Some(Box::new(Object::Multi(vec![
|
|
||||||
Object::Indirect(String::from("https://www.example.org/actor/2")),
|
|
||||||
Object::Direct(BaseObject {
|
|
||||||
typ: Some(ObjectType::Person),
|
|
||||||
name: Some(String::from("Bob")),
|
|
||||||
..Default::default()
|
|
||||||
}),
|
|
||||||
]))),
|
|
||||||
});
|
|
||||||
|
|
||||||
assert_tokens(
|
|
||||||
&obj,
|
|
||||||
&[
|
|
||||||
Token::Map { len: None },
|
|
||||||
Token::String("@context"),
|
|
||||||
Token::String(uri::ACTIVITY_STREAMS),
|
|
||||||
Token::String("type"),
|
|
||||||
Token::String("Create"),
|
|
||||||
Token::String("id"),
|
|
||||||
Token::String("https://www.example.org/object/2"),
|
|
||||||
Token::String("subject"),
|
|
||||||
Token::String("https://www.example.org/actor/1"),
|
|
||||||
Token::String("actor"),
|
|
||||||
Token::Map { len: None },
|
|
||||||
Token::String("type"),
|
|
||||||
Token::String("Person"),
|
|
||||||
Token::String("name"),
|
|
||||||
Token::String("Alice"),
|
|
||||||
Token::MapEnd,
|
|
||||||
Token::String("object"),
|
|
||||||
Token::Map { len: None },
|
|
||||||
Token::String("type"),
|
|
||||||
Token::String("Note"),
|
|
||||||
Token::String("content"),
|
|
||||||
Token::String("hi i'm gay uwu"),
|
|
||||||
Token::MapEnd,
|
|
||||||
Token::String("attributedTo"),
|
|
||||||
Token::Seq { len: Some(2) },
|
|
||||||
Token::String("https://www.example.org/actor/2"),
|
|
||||||
Token::Map { len: None },
|
|
||||||
Token::String("type"),
|
|
||||||
Token::String("Person"),
|
|
||||||
Token::String("name"),
|
|
||||||
Token::String("Bob"),
|
|
||||||
Token::MapEnd,
|
|
||||||
Token::SeqEnd,
|
|
||||||
Token::MapEnd,
|
|
||||||
],
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
@ -0,0 +1,303 @@
|
|||||||
|
use chrono::NaiveDateTime;
|
||||||
|
use rdf_types::vocabulary::IriIndex;
|
||||||
|
use rdf_types::Vocabulary;
|
||||||
|
use std::fmt;
|
||||||
|
|
||||||
|
use crate::ap::trans::{
|
||||||
|
matches_type, AbstractObject, ApDocument, DebugApub, ParseApub, PropHelper, RawObject,
|
||||||
|
};
|
||||||
|
use crate::ap::vocab::Ids;
|
||||||
|
|
||||||
|
pub struct AbstractActivity {
|
||||||
|
_super: AbstractObject,
|
||||||
|
pub actor: Vec<ApDocument>,
|
||||||
|
pub object: Vec<ApDocument>,
|
||||||
|
pub target: Option<Box<ApDocument>>,
|
||||||
|
pub origin: Option<Box<ApDocument>>,
|
||||||
|
pub instrument: Vec<ApDocument>,
|
||||||
|
}
|
||||||
|
ap_extends!(AbstractActivity, AbstractObject);
|
||||||
|
|
||||||
|
impl<V: Vocabulary<Iri = IriIndex>> ParseApub<V> for AbstractActivity {
|
||||||
|
fn parse_apub(obj: &RawObject, vocab: &V, ids: &Ids) -> Option<Self> {
|
||||||
|
matches_type(obj, &ids.apub.object.activity)?;
|
||||||
|
unsafe { Self::_parse_apub_unchecked(obj, vocab, ids) }
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe fn _parse_apub_unchecked(obj: &RawObject, vocab: &V, ids: &Ids) -> Option<Self> {
|
||||||
|
let ph = PropHelper::new(obj, vocab, ids)?;
|
||||||
|
let prop_ids = &ids.apub.property;
|
||||||
|
|
||||||
|
AbstractObject::_parse_apub_unchecked(obj, vocab, ids).map(|s| Self {
|
||||||
|
_super: s,
|
||||||
|
actor: ph.parse_prop_vec(&prop_ids.actor),
|
||||||
|
object: ph.parse_prop_vec(&prop_ids.object),
|
||||||
|
target: ph.parse_prop_box(&prop_ids.target),
|
||||||
|
origin: ph.parse_prop_box(&prop_ids.origin),
|
||||||
|
instrument: ph.parse_prop_vec(&prop_ids.instrument),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DebugApub for AbstractActivity {
|
||||||
|
fn apub_class_name(&self) -> &str {
|
||||||
|
"Activity"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn debug_apub_members(&self, f: &mut fmt::Formatter, depth: usize) -> fmt::Result {
|
||||||
|
self._super.debug_apub_members(f, depth)?;
|
||||||
|
ap_display_prop_vec!(self, f, actor, depth)?;
|
||||||
|
ap_display_prop_vec!(self, f, object, depth)?;
|
||||||
|
ap_display_prop_box!(self, f, target, depth)?;
|
||||||
|
ap_display_prop_box!(self, f, origin, depth)?;
|
||||||
|
ap_display_prop_vec!(self, f, instrument, depth)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub enum Activity {
|
||||||
|
Accept(Accept),
|
||||||
|
Add(Add),
|
||||||
|
Announce(Announce),
|
||||||
|
Arrive(Arrive),
|
||||||
|
Block(Block),
|
||||||
|
Create(Create),
|
||||||
|
Delete(Delete),
|
||||||
|
Dislike(Dislike),
|
||||||
|
Flag(Flag),
|
||||||
|
Follow(Follow),
|
||||||
|
Ignore(Ignore),
|
||||||
|
Invite(Invite),
|
||||||
|
Join(Join),
|
||||||
|
Leave(Leave),
|
||||||
|
Like(Like),
|
||||||
|
Listen(Listen),
|
||||||
|
Move(Move),
|
||||||
|
Offer(Offer),
|
||||||
|
Question(Question),
|
||||||
|
Reject(Reject),
|
||||||
|
Read(Read),
|
||||||
|
Remove(Remove),
|
||||||
|
TentativeReject(TentativeReject),
|
||||||
|
TentativeAccept(TentativeAccept),
|
||||||
|
Travel(Travel),
|
||||||
|
Undo(Undo),
|
||||||
|
Update(Update),
|
||||||
|
View(View),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<V: Vocabulary<Iri = IriIndex>> ParseApub<V> for Activity {
|
||||||
|
fn parse_apub(obj: &RawObject, vocab: &V, ids: &Ids) -> Option<Self> {
|
||||||
|
Accept::parse_apub(obj, vocab, ids)
|
||||||
|
.map(Self::Accept)
|
||||||
|
.or_else(|| Add::parse_apub(obj, vocab, ids).map(Self::Add))
|
||||||
|
.or_else(|| Announce::parse_apub(obj, vocab, ids).map(Self::Announce))
|
||||||
|
.or_else(|| Arrive::parse_apub(obj, vocab, ids).map(Self::Arrive))
|
||||||
|
.or_else(|| Block::parse_apub(obj, vocab, ids).map(Self::Block))
|
||||||
|
.or_else(|| Create::parse_apub(obj, vocab, ids).map(Self::Create))
|
||||||
|
.or_else(|| Delete::parse_apub(obj, vocab, ids).map(Self::Delete))
|
||||||
|
.or_else(|| Dislike::parse_apub(obj, vocab, ids).map(Self::Dislike))
|
||||||
|
.or_else(|| Flag::parse_apub(obj, vocab, ids).map(Self::Flag))
|
||||||
|
.or_else(|| Follow::parse_apub(obj, vocab, ids).map(Self::Follow))
|
||||||
|
.or_else(|| Ignore::parse_apub(obj, vocab, ids).map(Self::Ignore))
|
||||||
|
.or_else(|| Invite::parse_apub(obj, vocab, ids).map(Self::Invite))
|
||||||
|
.or_else(|| Join::parse_apub(obj, vocab, ids).map(Self::Join))
|
||||||
|
.or_else(|| Leave::parse_apub(obj, vocab, ids).map(Self::Leave))
|
||||||
|
.or_else(|| Like::parse_apub(obj, vocab, ids).map(Self::Like))
|
||||||
|
.or_else(|| Listen::parse_apub(obj, vocab, ids).map(Self::Listen))
|
||||||
|
.or_else(|| Move::parse_apub(obj, vocab, ids).map(Self::Move))
|
||||||
|
.or_else(|| Offer::parse_apub(obj, vocab, ids).map(Self::Offer))
|
||||||
|
.or_else(|| Question::parse_apub(obj, vocab, ids).map(Self::Question))
|
||||||
|
.or_else(|| Reject::parse_apub(obj, vocab, ids).map(Self::Reject))
|
||||||
|
.or_else(|| Read::parse_apub(obj, vocab, ids).map(Self::Read))
|
||||||
|
.or_else(|| Remove::parse_apub(obj, vocab, ids).map(Self::Remove))
|
||||||
|
.or_else(|| TentativeReject::parse_apub(obj, vocab, ids).map(Self::TentativeReject))
|
||||||
|
.or_else(|| TentativeAccept::parse_apub(obj, vocab, ids).map(Self::TentativeAccept))
|
||||||
|
.or_else(|| Travel::parse_apub(obj, vocab, ids).map(Self::Travel))
|
||||||
|
.or_else(|| Undo::parse_apub(obj, vocab, ids).map(Self::Undo))
|
||||||
|
.or_else(|| Update::parse_apub(obj, vocab, ids).map(Self::Update))
|
||||||
|
.or_else(|| View::parse_apub(obj, vocab, ids).map(Self::View))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DebugApub for Activity {
|
||||||
|
fn apub_class_name(&self) -> &str {
|
||||||
|
use Activity::*;
|
||||||
|
|
||||||
|
match self {
|
||||||
|
Accept(a) => a.apub_class_name(),
|
||||||
|
Add(a) => a.apub_class_name(),
|
||||||
|
Announce(a) => a.apub_class_name(),
|
||||||
|
Arrive(a) => a.apub_class_name(),
|
||||||
|
Block(b) => b.apub_class_name(),
|
||||||
|
Create(c) => c.apub_class_name(),
|
||||||
|
Delete(d) => d.apub_class_name(),
|
||||||
|
Dislike(d) => d.apub_class_name(),
|
||||||
|
Flag(f) => f.apub_class_name(),
|
||||||
|
Follow(f) => f.apub_class_name(),
|
||||||
|
Ignore(i) => i.apub_class_name(),
|
||||||
|
Invite(i) => i.apub_class_name(),
|
||||||
|
Join(j) => j.apub_class_name(),
|
||||||
|
Leave(l) => l.apub_class_name(),
|
||||||
|
Like(l) => l.apub_class_name(),
|
||||||
|
Listen(l) => l.apub_class_name(),
|
||||||
|
Move(m) => m.apub_class_name(),
|
||||||
|
Offer(o) => o.apub_class_name(),
|
||||||
|
Question(q) => q.apub_class_name(),
|
||||||
|
Reject(r) => r.apub_class_name(),
|
||||||
|
Read(r) => r.apub_class_name(),
|
||||||
|
Remove(r) => r.apub_class_name(),
|
||||||
|
TentativeReject(t) => t.apub_class_name(),
|
||||||
|
TentativeAccept(t) => t.apub_class_name(),
|
||||||
|
Travel(t) => t.apub_class_name(),
|
||||||
|
Undo(u) => u.apub_class_name(),
|
||||||
|
Update(u) => u.apub_class_name(),
|
||||||
|
View(v) => v.apub_class_name(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn debug_apub(&self, f: &mut fmt::Formatter, depth: usize) -> fmt::Result {
|
||||||
|
use Activity::*;
|
||||||
|
|
||||||
|
match self {
|
||||||
|
Accept(a) => a.debug_apub(f, depth),
|
||||||
|
Add(a) => a.debug_apub(f, depth),
|
||||||
|
Announce(a) => a.debug_apub(f, depth),
|
||||||
|
Arrive(a) => a.debug_apub(f, depth),
|
||||||
|
Block(b) => b.debug_apub(f, depth),
|
||||||
|
Create(c) => c.debug_apub(f, depth),
|
||||||
|
Delete(d) => d.debug_apub(f, depth),
|
||||||
|
Dislike(d) => d.debug_apub(f, depth),
|
||||||
|
Flag(flag) => flag.debug_apub(f, depth),
|
||||||
|
Follow(follow) => follow.debug_apub(f, depth),
|
||||||
|
Ignore(i) => i.debug_apub(f, depth),
|
||||||
|
Invite(i) => i.debug_apub(f, depth),
|
||||||
|
Join(j) => j.debug_apub(f, depth),
|
||||||
|
Leave(l) => l.debug_apub(f, depth),
|
||||||
|
Like(l) => l.debug_apub(f, depth),
|
||||||
|
Listen(l) => l.debug_apub(f, depth),
|
||||||
|
Move(m) => m.debug_apub(f, depth),
|
||||||
|
Offer(o) => o.debug_apub(f, depth),
|
||||||
|
Question(q) => q.debug_apub(f, depth),
|
||||||
|
Reject(r) => r.debug_apub(f, depth),
|
||||||
|
Read(r) => r.debug_apub(f, depth),
|
||||||
|
Remove(r) => r.debug_apub(f, depth),
|
||||||
|
TentativeReject(t) => t.debug_apub(f, depth),
|
||||||
|
TentativeAccept(t) => t.debug_apub(f, depth),
|
||||||
|
Travel(t) => t.debug_apub(f, depth),
|
||||||
|
Undo(u) => u.debug_apub(f, depth),
|
||||||
|
Update(u) => u.debug_apub(f, depth),
|
||||||
|
View(v) => v.debug_apub(f, depth),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn debug_apub_members(&self, f: &mut fmt::Formatter, depth: usize) -> fmt::Result {
|
||||||
|
use Activity::*;
|
||||||
|
|
||||||
|
match self {
|
||||||
|
Accept(a) => a.debug_apub_members(f, depth),
|
||||||
|
Add(a) => a.debug_apub_members(f, depth),
|
||||||
|
Announce(a) => a.debug_apub_members(f, depth),
|
||||||
|
Arrive(a) => a.debug_apub_members(f, depth),
|
||||||
|
Block(b) => b.debug_apub_members(f, depth),
|
||||||
|
Create(c) => c.debug_apub_members(f, depth),
|
||||||
|
Delete(d) => d.debug_apub_members(f, depth),
|
||||||
|
Dislike(d) => d.debug_apub_members(f, depth),
|
||||||
|
Flag(flag) => flag.debug_apub_members(f, depth),
|
||||||
|
Follow(follow) => follow.debug_apub_members(f, depth),
|
||||||
|
Ignore(i) => i.debug_apub_members(f, depth),
|
||||||
|
Invite(i) => i.debug_apub_members(f, depth),
|
||||||
|
Join(j) => j.debug_apub_members(f, depth),
|
||||||
|
Leave(l) => l.debug_apub_members(f, depth),
|
||||||
|
Like(l) => l.debug_apub_members(f, depth),
|
||||||
|
Listen(l) => l.debug_apub_members(f, depth),
|
||||||
|
Move(m) => m.debug_apub_members(f, depth),
|
||||||
|
Offer(o) => o.debug_apub_members(f, depth),
|
||||||
|
Question(q) => q.debug_apub_members(f, depth),
|
||||||
|
Reject(r) => r.debug_apub_members(f, depth),
|
||||||
|
Read(r) => r.debug_apub_members(f, depth),
|
||||||
|
Remove(r) => r.debug_apub_members(f, depth),
|
||||||
|
TentativeReject(t) => t.debug_apub_members(f, depth),
|
||||||
|
TentativeAccept(t) => t.debug_apub_members(f, depth),
|
||||||
|
Travel(t) => t.debug_apub_members(f, depth),
|
||||||
|
Undo(u) => u.debug_apub_members(f, depth),
|
||||||
|
Update(u) => u.debug_apub_members(f, depth),
|
||||||
|
View(v) => v.debug_apub_members(f, depth),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Question {
|
||||||
|
_super: AbstractActivity,
|
||||||
|
pub one_of: Vec<ApDocument>,
|
||||||
|
pub any_of: Vec<ApDocument>,
|
||||||
|
pub closed: Option<NaiveDateTime>,
|
||||||
|
}
|
||||||
|
ap_extends!(Question, AbstractActivity);
|
||||||
|
|
||||||
|
impl<V: Vocabulary<Iri = IriIndex>> ParseApub<V> for Question {
|
||||||
|
fn parse_apub(obj: &RawObject, vocab: &V, ids: &Ids) -> Option<Self> {
|
||||||
|
matches_type(obj, &ids.apub.object.question)?;
|
||||||
|
unsafe { Self::_parse_apub_unchecked(obj, vocab, ids) }
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe fn _parse_apub_unchecked(obj: &RawObject, vocab: &V, ids: &Ids) -> Option<Self> {
|
||||||
|
let ph = PropHelper::new(obj, vocab, ids)?;
|
||||||
|
let prop_ids = &ids.apub.property;
|
||||||
|
|
||||||
|
AbstractActivity::_parse_apub_unchecked(obj, vocab, ids).map(|s| Self {
|
||||||
|
_super: s,
|
||||||
|
one_of: ph.parse_prop_vec(&prop_ids.one_of),
|
||||||
|
any_of: ph.parse_prop_vec(&prop_ids.any_of),
|
||||||
|
closed: ph.parse_prop(&prop_ids.closed),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DebugApub for Question {
|
||||||
|
fn debug_apub_members(&self, f: &mut fmt::Formatter, depth: usize) -> fmt::Result {
|
||||||
|
self._super.debug_apub_members(f, depth)?;
|
||||||
|
ap_display_prop_vec!(self, f, one_of, depth)?;
|
||||||
|
ap_display_prop_vec!(self, f, any_of, depth)?;
|
||||||
|
ap_display_prop!(self, f, closed, depth)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ap_empty_child_impl!(Accept, AbstractActivity, apub, activity, accept);
|
||||||
|
ap_empty_child_impl!(Add, AbstractActivity, apub, activity, add);
|
||||||
|
ap_empty_child_impl!(Announce, AbstractActivity, apub, activity, announce);
|
||||||
|
ap_empty_child_impl!(Arrive, AbstractActivity, apub, activity, arrive);
|
||||||
|
ap_empty_child_impl!(Block, Ignore, apub, activity, block);
|
||||||
|
ap_empty_child_impl!(Create, AbstractActivity, apub, activity, create);
|
||||||
|
ap_empty_child_impl!(Delete, AbstractActivity, apub, activity, delete);
|
||||||
|
ap_empty_child_impl!(Dislike, AbstractActivity, apub, activity, dislike);
|
||||||
|
ap_empty_child_impl!(Flag, AbstractActivity, apub, activity, flag);
|
||||||
|
ap_empty_child_impl!(Follow, AbstractActivity, apub, activity, follow);
|
||||||
|
ap_empty_child_impl!(Ignore, AbstractActivity, apub, activity, ignore);
|
||||||
|
ap_empty_child_impl!(Invite, AbstractActivity, apub, activity, invite);
|
||||||
|
ap_empty_child_impl!(Join, AbstractActivity, apub, activity, join);
|
||||||
|
ap_empty_child_impl!(Leave, AbstractActivity, apub, activity, leave);
|
||||||
|
ap_empty_child_impl!(Like, AbstractActivity, apub, activity, like);
|
||||||
|
ap_empty_child_impl!(Listen, AbstractActivity, apub, activity, listen);
|
||||||
|
ap_empty_child_impl!(Move, AbstractActivity, apub, activity, mov);
|
||||||
|
ap_empty_child_impl!(Offer, AbstractActivity, apub, activity, offer);
|
||||||
|
ap_empty_child_impl!(Reject, AbstractActivity, apub, activity, reject);
|
||||||
|
ap_empty_child_impl!(Read, AbstractActivity, apub, activity, read);
|
||||||
|
ap_empty_child_impl!(Remove, AbstractActivity, apub, activity, remove);
|
||||||
|
ap_empty_child_impl!(
|
||||||
|
TentativeReject,
|
||||||
|
AbstractActivity,
|
||||||
|
apub,
|
||||||
|
activity,
|
||||||
|
tentative_reject
|
||||||
|
);
|
||||||
|
ap_empty_child_impl!(
|
||||||
|
TentativeAccept,
|
||||||
|
AbstractActivity,
|
||||||
|
apub,
|
||||||
|
activity,
|
||||||
|
tentative_accept
|
||||||
|
);
|
||||||
|
ap_empty_child_impl!(Travel, AbstractActivity, apub, activity, travel);
|
||||||
|
ap_empty_child_impl!(Undo, AbstractActivity, apub, activity, undo);
|
||||||
|
ap_empty_child_impl!(Update, AbstractActivity, apub, activity, update);
|
||||||
|
ap_empty_child_impl!(View, AbstractActivity, apub, activity, view);
|
@ -0,0 +1,124 @@
|
|||||||
|
use iref::IriBuf;
|
||||||
|
use rdf_types::vocabulary::IriIndex;
|
||||||
|
use rdf_types::Vocabulary;
|
||||||
|
use std::fmt;
|
||||||
|
|
||||||
|
use crate::ap::trans::{AbstractObject, ApDocument, DebugApub, ParseApub, PropHelper, RawObject};
|
||||||
|
use crate::ap::vocab::Ids;
|
||||||
|
|
||||||
|
pub struct AbstractActor {
|
||||||
|
_super: AbstractObject,
|
||||||
|
inbox: Option<IriBuf>,
|
||||||
|
outbox: Option<IriBuf>,
|
||||||
|
following: Option<IriBuf>,
|
||||||
|
followers: Option<IriBuf>,
|
||||||
|
liked: Option<IriBuf>,
|
||||||
|
preferred_username: Option<String>,
|
||||||
|
// Mastodon extensions
|
||||||
|
featured: Option<Box<ApDocument>>,
|
||||||
|
featured_tags: Option<Box<ApDocument>>,
|
||||||
|
discoverable: Option<bool>,
|
||||||
|
suspended: Option<bool>,
|
||||||
|
}
|
||||||
|
ap_extends!(AbstractActor, AbstractObject);
|
||||||
|
|
||||||
|
impl<V: Vocabulary<Iri = IriIndex>> ParseApub<V> for AbstractActor {
|
||||||
|
fn parse_apub(obj: &RawObject, vocab: &V, ids: &Ids) -> Option<Self> {
|
||||||
|
let ph = PropHelper::new(obj, vocab, ids)?;
|
||||||
|
|
||||||
|
let result = unsafe { AbstractObject::_parse_apub_unchecked(obj, vocab, ids) };
|
||||||
|
result.map(|s| Self {
|
||||||
|
_super: s,
|
||||||
|
inbox: ph.parse_prop(&ids.apub.property.inbox),
|
||||||
|
outbox: ph.parse_prop(&ids.apub.property.outbox),
|
||||||
|
following: ph.parse_prop(&ids.apub.property.following),
|
||||||
|
followers: ph.parse_prop(&ids.apub.property.followers),
|
||||||
|
liked: ph.parse_prop(&ids.apub.property.liked),
|
||||||
|
preferred_username: ph.parse_prop(&ids.apub.property.preferred_username),
|
||||||
|
featured: ph.parse_prop_box(&ids.toot.props.featured),
|
||||||
|
featured_tags: ph.parse_prop_box(&ids.toot.props.featured_tags),
|
||||||
|
discoverable: ph.parse_prop(&ids.toot.props.discoverable),
|
||||||
|
suspended: ph.parse_prop(&ids.toot.props.suspended),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DebugApub for AbstractActor {
|
||||||
|
fn apub_class_name(&self) -> &str {
|
||||||
|
"Actor"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn debug_apub_members(&self, f: &mut fmt::Formatter, depth: usize) -> fmt::Result {
|
||||||
|
self._super.debug_apub_members(f, depth)?;
|
||||||
|
ap_display_prop!(self, f, inbox, depth)?;
|
||||||
|
ap_display_prop!(self, f, outbox, depth)?;
|
||||||
|
ap_display_prop!(self, f, following, depth)?;
|
||||||
|
ap_display_prop!(self, f, followers, depth)?;
|
||||||
|
ap_display_prop!(self, f, liked, depth)?;
|
||||||
|
ap_display_prop!(self, f, preferred_username, depth)?;
|
||||||
|
ap_display_prop_box!(self, f, featured, depth)?;
|
||||||
|
ap_display_prop_box!(self, f, featured_tags, depth)?;
|
||||||
|
ap_display_prop!(self, f, discoverable, depth)?;
|
||||||
|
ap_display_prop!(self, f, suspended, depth)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub enum Actor {
|
||||||
|
Application(Application),
|
||||||
|
Group(Group),
|
||||||
|
Organization(Organization),
|
||||||
|
Person(Person),
|
||||||
|
Service(Service),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<V: Vocabulary<Iri = IriIndex>> ParseApub<V> for Actor {
|
||||||
|
fn parse_apub(obj: &RawObject, vocab: &V, ids: &Ids) -> Option<Self> {
|
||||||
|
Application::parse_apub(obj, vocab, ids)
|
||||||
|
.map(Self::Application)
|
||||||
|
.or_else(|| Group::parse_apub(obj, vocab, ids).map(Self::Group))
|
||||||
|
.or_else(|| Organization::parse_apub(obj, vocab, ids).map(Self::Organization))
|
||||||
|
.or_else(|| Person::parse_apub(obj, vocab, ids).map(Self::Person))
|
||||||
|
.or_else(|| Service::parse_apub(obj, vocab, ids).map(Self::Service))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DebugApub for Actor {
|
||||||
|
fn apub_class_name(&self) -> &str {
|
||||||
|
use Actor::*;
|
||||||
|
match self {
|
||||||
|
Application(a) => a.apub_class_name(),
|
||||||
|
Group(g) => g.apub_class_name(),
|
||||||
|
Organization(o) => o.apub_class_name(),
|
||||||
|
Person(p) => p.apub_class_name(),
|
||||||
|
Service(s) => s.apub_class_name(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn debug_apub(&self, f: &mut fmt::Formatter, depth: usize) -> fmt::Result {
|
||||||
|
use Actor::*;
|
||||||
|
match self {
|
||||||
|
Application(a) => a.debug_apub(f, depth),
|
||||||
|
Group(g) => g.debug_apub(f, depth),
|
||||||
|
Organization(o) => o.debug_apub(f, depth),
|
||||||
|
Person(p) => p.debug_apub(f, depth),
|
||||||
|
Service(s) => s.debug_apub(f, depth),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn debug_apub_members(&self, f: &mut fmt::Formatter, depth: usize) -> fmt::Result {
|
||||||
|
use Actor::*;
|
||||||
|
match self {
|
||||||
|
Application(a) => a.debug_apub_members(f, depth),
|
||||||
|
Group(g) => g.debug_apub_members(f, depth),
|
||||||
|
Organization(o) => o.debug_apub_members(f, depth),
|
||||||
|
Person(p) => p.debug_apub_members(f, depth),
|
||||||
|
Service(s) => s.debug_apub_members(f, depth),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ap_empty_child_impl!(Application, AbstractActor, apub, object, application);
|
||||||
|
ap_empty_child_impl!(Group, AbstractActor, apub, object, group);
|
||||||
|
ap_empty_child_impl!(Organization, AbstractActor, apub, object, organization);
|
||||||
|
ap_empty_child_impl!(Person, AbstractActor, apub, object, person);
|
||||||
|
ap_empty_child_impl!(Service, AbstractActor, apub, object, service);
|
@ -0,0 +1,103 @@
|
|||||||
|
use iref::IriBuf;
|
||||||
|
use mime::Mime;
|
||||||
|
use rdf_types::vocabulary::IriIndex;
|
||||||
|
use rdf_types::Vocabulary;
|
||||||
|
use std::fmt;
|
||||||
|
|
||||||
|
use crate::ap::trans::{matches_type, ApDocument, DebugApub, ParseApub, PropHelper, RawObject};
|
||||||
|
use crate::ap::vocab::Ids;
|
||||||
|
|
||||||
|
pub enum Link {
|
||||||
|
Link(BaseLink),
|
||||||
|
Mention(Mention),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<V: Vocabulary<Iri = IriIndex>> ParseApub<V> for Link {
|
||||||
|
fn parse_apub(obj: &RawObject, vocab: &V, ids: &Ids) -> Option<Self> {
|
||||||
|
BaseLink::parse_apub(obj, vocab, ids)
|
||||||
|
.map(Self::Link)
|
||||||
|
.or_else(|| Mention::parse_apub(obj, vocab, ids).map(Self::Mention))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DebugApub for Link {
|
||||||
|
fn apub_class_name(&self) -> &str {
|
||||||
|
match self {
|
||||||
|
Link::Link(link) => link.apub_class_name(),
|
||||||
|
Link::Mention(mention) => mention.apub_class_name(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn debug_apub(&self, f: &mut fmt::Formatter, depth: usize) -> fmt::Result {
|
||||||
|
match self {
|
||||||
|
Link::Link(link) => link.debug_apub(f, depth),
|
||||||
|
Link::Mention(mention) => mention.debug_apub(f, depth),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn debug_apub_members(&self, f: &mut fmt::Formatter, depth: usize) -> fmt::Result {
|
||||||
|
match self {
|
||||||
|
Link::Link(link) => link.debug_apub_members(f, depth),
|
||||||
|
Link::Mention(mention) => mention.debug_apub_members(f, depth),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct BaseLink {
|
||||||
|
pub id: Option<IriBuf>,
|
||||||
|
pub href: Option<String>,
|
||||||
|
pub rel: Option<String>,
|
||||||
|
pub media_type: Option<Mime>,
|
||||||
|
pub name: Option<String>, // TODO: this could be a langString
|
||||||
|
pub hreflang: Option<String>,
|
||||||
|
pub height: Option<u32>,
|
||||||
|
pub width: Option<u32>,
|
||||||
|
pub preview: Option<Box<ApDocument>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<V: Vocabulary<Iri = IriIndex>> ParseApub<V> for BaseLink {
|
||||||
|
fn parse_apub(obj: &RawObject, vocab: &V, ids: &Ids) -> Option<Self> {
|
||||||
|
matches_type(obj, &ids.apub.link.link)?;
|
||||||
|
unsafe { Self::_parse_apub_unchecked(obj, vocab, ids) }
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe fn _parse_apub_unchecked(obj: &RawObject, vocab: &V, ids: &Ids) -> Option<Self> {
|
||||||
|
let ph = PropHelper::new(obj, vocab, ids)?;
|
||||||
|
let prop_ids = &ids.apub.property;
|
||||||
|
|
||||||
|
Some(Self {
|
||||||
|
id: obj
|
||||||
|
.id()
|
||||||
|
.and_then(|id| id.as_iri())
|
||||||
|
.and_then(|idx| vocab.iri(idx))
|
||||||
|
.map(|iri| iri.to_owned()),
|
||||||
|
href: ph.parse_prop(&prop_ids.href),
|
||||||
|
rel: ph.parse_prop(&prop_ids.rel),
|
||||||
|
media_type: ph.parse_prop(&prop_ids.media_type),
|
||||||
|
name: ph.parse_prop(&prop_ids.name),
|
||||||
|
hreflang: ph.parse_prop(&prop_ids.hreflang),
|
||||||
|
height: ph.parse_prop(&prop_ids.height),
|
||||||
|
width: ph.parse_prop(&prop_ids.width),
|
||||||
|
preview: ph.parse_prop_box(&prop_ids.preview),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DebugApub for BaseLink {
|
||||||
|
fn apub_class_name(&self) -> &str {
|
||||||
|
"Link"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn debug_apub_members(&self, f: &mut fmt::Formatter, depth: usize) -> fmt::Result {
|
||||||
|
ap_display_prop!(self, f, href, depth)?;
|
||||||
|
ap_display_prop!(self, f, rel, depth)?;
|
||||||
|
ap_display_prop!(self, f, media_type, depth)?;
|
||||||
|
ap_display_prop!(self, f, name, depth)?;
|
||||||
|
ap_display_prop!(self, f, hreflang, depth)?;
|
||||||
|
ap_display_prop!(self, f, height, depth)?;
|
||||||
|
ap_display_prop!(self, f, width, depth)?;
|
||||||
|
ap_display_prop_box!(self, f, preview, depth)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ap_empty_child_impl!(Mention, BaseLink, apub, link, mention);
|
@ -0,0 +1,652 @@
|
|||||||
|
//! A (nearly) complete model of ActivityPub's classes, with translators.
|
||||||
|
//! This is used as an intermediate representation between raw JSON-LD objects
|
||||||
|
//! as spit out by the loader, and our own internal representation of the data.
|
||||||
|
|
||||||
|
use chrono::NaiveDateTime;
|
||||||
|
use iref::IriBuf;
|
||||||
|
use json_ld::object::node::Properties;
|
||||||
|
use json_ld::IndexedObject;
|
||||||
|
use locspan::Span;
|
||||||
|
use mime::Mime;
|
||||||
|
use rdf_types::vocabulary::{BlankIdIndex, IriIndex};
|
||||||
|
use rdf_types::Vocabulary;
|
||||||
|
use std::collections::HashMap;
|
||||||
|
use std::fmt;
|
||||||
|
use std::str::FromStr;
|
||||||
|
|
||||||
|
use crate::ap::vocab::{Id, Ids};
|
||||||
|
use crate::core::*;
|
||||||
|
use crate::util::xsd;
|
||||||
|
|
||||||
|
pub type RawProps = Properties<IriIndex, BlankIdIndex, Span>;
|
||||||
|
pub type RawObject = IndexedObject<IriIndex, BlankIdIndex, Span>;
|
||||||
|
|
||||||
|
pub trait ParseApub<V: Vocabulary<Iri = IriIndex>>: Sized {
|
||||||
|
/// Attempt to translate a raw JSON-LD object (as in, from the `json-ld` crate)
|
||||||
|
/// to this type. Returns the parsed object on success, and the input on failure.
|
||||||
|
/// Unsupported properties SHOULD be logged but otherwise ignored.
|
||||||
|
fn parse_apub(obj: &RawObject, vocab: &V, ids: &Ids) -> Option<Self>;
|
||||||
|
|
||||||
|
/// Only for internal use from subclasses, **DO NOT TOUCH**.
|
||||||
|
/// Can cause an infinite recursion loop that ends in a segfault.
|
||||||
|
unsafe fn _parse_apub_unchecked(obj: &RawObject, vocab: &V, ids: &Ids) -> Option<Self> {
|
||||||
|
Self::parse_apub(obj, vocab, ids)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
trait DebugApub {
|
||||||
|
fn apub_class_name(&self) -> &str {
|
||||||
|
std::any::type_name::<Self>().split("::").last().unwrap()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn debug_apub(&self, f: &mut fmt::Formatter, depth: usize) -> fmt::Result {
|
||||||
|
f.write_str(self.apub_class_name())?;
|
||||||
|
f.write_str(" {\n")?;
|
||||||
|
self.debug_apub_members(f, depth + 1)?;
|
||||||
|
writeln!(f, "{}}}", indent(depth))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn debug_apub_members(&self, f: &mut fmt::Formatter, depth: usize) -> fmt::Result;
|
||||||
|
}
|
||||||
|
|
||||||
|
// just ... don't ask questions and be glad this, somehow, works
|
||||||
|
|
||||||
|
macro_rules! ap_extends {
|
||||||
|
($child:ident, $parent:ty) => {
|
||||||
|
impl ::std::ops::Deref for $child {
|
||||||
|
type Target = $parent;
|
||||||
|
|
||||||
|
fn deref(&self) -> &Self::Target {
|
||||||
|
&self._super
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ::std::ops::DerefMut for $child {
|
||||||
|
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||||
|
&mut self._super
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
macro_rules! ap_empty_child {
|
||||||
|
($child:ident, $parent:ty) => {
|
||||||
|
pub struct $child {
|
||||||
|
_super: $parent,
|
||||||
|
}
|
||||||
|
ap_extends!($child, $parent);
|
||||||
|
|
||||||
|
impl $crate::ap::trans::DebugApub for $child {
|
||||||
|
fn debug_apub_members(
|
||||||
|
&self,
|
||||||
|
f: &mut ::std::fmt::Formatter,
|
||||||
|
depth: usize,
|
||||||
|
) -> ::std::fmt::Result {
|
||||||
|
$crate::ap::trans::DebugApub::debug_apub_members(&self._super, f, depth)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
macro_rules! ap_empty_child_impl {
|
||||||
|
($child:ident, $parent:ty, $id1:ident, $id2:ident, $id3:ident) => {
|
||||||
|
ap_empty_child!($child, $parent);
|
||||||
|
|
||||||
|
impl<V> $crate::ap::trans::ParseApub<V> for $child
|
||||||
|
where
|
||||||
|
V: ::rdf_types::Vocabulary<Iri = ::rdf_types::vocabulary::IriIndex>,
|
||||||
|
{
|
||||||
|
fn parse_apub(
|
||||||
|
obj: &$crate::ap::trans::RawObject,
|
||||||
|
vocab: &V,
|
||||||
|
ids: &$crate::ap::vocab::Ids,
|
||||||
|
) -> Option<Self> {
|
||||||
|
$crate::ap::trans::matches_type(obj, &ids.$id1.$id2.$id3)?;
|
||||||
|
unsafe { Self::_parse_apub_unchecked(obj, vocab, ids) }
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe fn _parse_apub_unchecked(
|
||||||
|
obj: &$crate::ap::trans::RawObject,
|
||||||
|
vocab: &V,
|
||||||
|
ids: &$crate::ap::vocab::Ids,
|
||||||
|
) -> Option<Self> {
|
||||||
|
<$parent>::_parse_apub_unchecked(obj, vocab, ids).map(|p| Self { _super: p })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
macro_rules! ap_display_prop {
|
||||||
|
($this:expr, $f:ident, $name:ident, $depth:expr) => {
|
||||||
|
$crate::ap::trans::display_prop($f, stringify!($name), $this.$name.as_ref(), $depth)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
macro_rules! ap_display_prop_box {
|
||||||
|
($this:expr, $f:ident, $name:ident, $depth:expr) => {
|
||||||
|
$crate::ap::trans::display_prop($f, stringify!($name), $this.$name.as_deref(), $depth)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
macro_rules! ap_display_prop_vec {
|
||||||
|
($this:expr, $f:ident, $name:ident, $depth:expr) => {
|
||||||
|
$crate::ap::trans::display_prop_vec($f, stringify!($name), $this.$name.as_slice(), $depth)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
mod activity;
|
||||||
|
pub use activity::*;
|
||||||
|
|
||||||
|
mod actor;
|
||||||
|
pub use actor::*;
|
||||||
|
|
||||||
|
mod link;
|
||||||
|
pub use link::*;
|
||||||
|
|
||||||
|
mod object;
|
||||||
|
pub use object::*;
|
||||||
|
|
||||||
|
/// The top-level type for any kind of object or link
|
||||||
|
pub enum ApDocument {
|
||||||
|
Object(Object),
|
||||||
|
Link(Link),
|
||||||
|
Remote(String),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<V: Vocabulary<Iri = IriIndex>> ParseApub<V> for ApDocument {
|
||||||
|
fn parse_apub(obj: &RawObject, vocab: &V, ids: &Ids) -> Option<Self> {
|
||||||
|
if let Some(s) = obj.as_value().and_then(|v| v.as_str()) {
|
||||||
|
Some(Self::Remote(String::from(s)))
|
||||||
|
} else {
|
||||||
|
Object::parse_apub(obj, vocab, ids)
|
||||||
|
.map(Self::Object)
|
||||||
|
.or_else(|| Link::parse_apub(obj, vocab, ids).map(Self::Link))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DebugApub for ApDocument {
|
||||||
|
fn apub_class_name(&self) -> &str {
|
||||||
|
use ApDocument::*;
|
||||||
|
match self {
|
||||||
|
Object(o) => o.apub_class_name(),
|
||||||
|
Link(l) => l.apub_class_name(),
|
||||||
|
Remote(_) => "<remote>",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn debug_apub(&self, f: &mut fmt::Formatter, depth: usize) -> fmt::Result {
|
||||||
|
use ApDocument::*;
|
||||||
|
match self {
|
||||||
|
Object(o) => o.debug_apub(f, depth),
|
||||||
|
Link(l) => l.debug_apub(f, depth),
|
||||||
|
Remote(url) => writeln!(f, "{}<remote> => {url}", indent(depth)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn debug_apub_members(&self, f: &mut fmt::Formatter, depth: usize) -> fmt::Result {
|
||||||
|
use ApDocument::*;
|
||||||
|
match self {
|
||||||
|
Object(o) => o.debug_apub_members(f, depth),
|
||||||
|
Link(l) => l.debug_apub_members(f, depth),
|
||||||
|
Remote(url) => writeln!(f, "{}<remote> => {url}", indent(depth)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Debug for ApDocument {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
|
self.debug_apub(f, 0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub enum ImageOrLink {
|
||||||
|
Link(Link),
|
||||||
|
Image(Image),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<V: Vocabulary<Iri = IriIndex>> ParseApub<V> for ImageOrLink {
|
||||||
|
fn parse_apub(obj: &RawObject, vocab: &V, ids: &Ids) -> Option<Self> {
|
||||||
|
Image::parse_apub(obj, vocab, ids)
|
||||||
|
.map(Self::Image)
|
||||||
|
.or_else(|| Link::parse_apub(obj, vocab, ids).map(Self::Link))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DebugApub for ImageOrLink {
|
||||||
|
fn apub_class_name(&self) -> &str {
|
||||||
|
match self {
|
||||||
|
ImageOrLink::Link(link) => link.apub_class_name(),
|
||||||
|
ImageOrLink::Image(image) => image.apub_class_name(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn debug_apub_members(&self, f: &mut fmt::Formatter, depth: usize) -> fmt::Result {
|
||||||
|
match self {
|
||||||
|
ImageOrLink::Link(link) => link.debug_apub_members(f, depth),
|
||||||
|
ImageOrLink::Image(image) => image.debug_apub_members(f, depth),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub enum Collection {
|
||||||
|
Base(BaseCollection),
|
||||||
|
Ordered(OrderedCollection),
|
||||||
|
Page(CollectionPage),
|
||||||
|
OrderedPage(OrderedCollectionPage),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<V: Vocabulary<Iri = IriIndex>> ParseApub<V> for Collection {
|
||||||
|
fn parse_apub(obj: &RawObject, vocab: &V, ids: &Ids) -> Option<Self> {
|
||||||
|
BaseCollection::parse_apub(obj, vocab, ids)
|
||||||
|
.map(Self::Base)
|
||||||
|
.or_else(|| OrderedCollection::parse_apub(obj, vocab, ids).map(Self::Ordered))
|
||||||
|
.or_else(|| CollectionPage::parse_apub(obj, vocab, ids).map(Self::Page))
|
||||||
|
.or_else(|| OrderedCollectionPage::parse_apub(obj, vocab, ids).map(Self::OrderedPage))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DebugApub for Collection {
|
||||||
|
fn apub_class_name(&self) -> &str {
|
||||||
|
use Collection::*;
|
||||||
|
match self {
|
||||||
|
Base(b) => b.apub_class_name(),
|
||||||
|
Ordered(o) => o.apub_class_name(),
|
||||||
|
Page(p) => p.apub_class_name(),
|
||||||
|
OrderedPage(o) => o.apub_class_name(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn debug_apub(&self, f: &mut fmt::Formatter, depth: usize) -> fmt::Result {
|
||||||
|
use Collection::*;
|
||||||
|
match self {
|
||||||
|
Base(b) => b.debug_apub(f, depth),
|
||||||
|
Ordered(o) => o.debug_apub(f, depth),
|
||||||
|
Page(p) => p.debug_apub(f, depth),
|
||||||
|
OrderedPage(o) => o.debug_apub(f, depth),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn debug_apub_members(&self, f: &mut fmt::Formatter, depth: usize) -> fmt::Result {
|
||||||
|
use Collection::*;
|
||||||
|
match self {
|
||||||
|
Base(b) => b.debug_apub_members(f, depth),
|
||||||
|
Ordered(o) => o.debug_apub_members(f, depth),
|
||||||
|
Page(p) => p.debug_apub_members(f, depth),
|
||||||
|
OrderedPage(o) => o.debug_apub_members(f, depth),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct BaseCollection {
|
||||||
|
_super: AbstractObject,
|
||||||
|
pub total_items: Option<u32>,
|
||||||
|
pub current: Option<Box<CollectionPageOrLink>>,
|
||||||
|
pub first: Option<Box<CollectionPageOrLink>>,
|
||||||
|
pub last: Option<Box<CollectionPageOrLink>>,
|
||||||
|
pub items: Vec<ApDocument>,
|
||||||
|
}
|
||||||
|
ap_extends!(BaseCollection, AbstractObject);
|
||||||
|
|
||||||
|
impl<V: Vocabulary<Iri = IriIndex>> ParseApub<V> for BaseCollection {
|
||||||
|
fn parse_apub(obj: &RawObject, vocab: &V, ids: &Ids) -> Option<Self> {
|
||||||
|
matches_type(obj, &ids.apub.object.collection)?;
|
||||||
|
unsafe { Self::_parse_apub_unchecked(obj, vocab, ids) }
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe fn _parse_apub_unchecked(obj: &RawObject, vocab: &V, ids: &Ids) -> Option<Self> {
|
||||||
|
let ph = PropHelper::new(obj, vocab, ids)?;
|
||||||
|
let prop_ids = &ids.apub.property;
|
||||||
|
|
||||||
|
AbstractObject::_parse_apub_unchecked(obj, vocab, ids).map(|s| Self {
|
||||||
|
_super: s,
|
||||||
|
total_items: ph.parse_prop(&prop_ids.total_items),
|
||||||
|
current: ph.parse_prop_box(&prop_ids.current),
|
||||||
|
first: ph.parse_prop_box(&prop_ids.first),
|
||||||
|
last: ph.parse_prop_box(&prop_ids.last),
|
||||||
|
items: ph.parse_prop_vec(&prop_ids.items),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DebugApub for BaseCollection {
|
||||||
|
fn apub_class_name(&self) -> &str {
|
||||||
|
"Collection"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn debug_apub_members(&self, f: &mut fmt::Formatter, depth: usize) -> fmt::Result {
|
||||||
|
self._super.debug_apub_members(f, depth)?;
|
||||||
|
ap_display_prop!(self, f, total_items, depth)?;
|
||||||
|
ap_display_prop_box!(self, f, current, depth)?;
|
||||||
|
ap_display_prop_box!(self, f, first, depth)?;
|
||||||
|
ap_display_prop_box!(self, f, last, depth)?;
|
||||||
|
ap_display_prop_vec!(self, f, items, depth)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ap_empty_child_impl!(
|
||||||
|
OrderedCollection,
|
||||||
|
BaseCollection,
|
||||||
|
apub,
|
||||||
|
object,
|
||||||
|
ordered_collection
|
||||||
|
);
|
||||||
|
|
||||||
|
pub struct CollectionPage {
|
||||||
|
_super: BaseCollection,
|
||||||
|
pub part_of: Option<Box<CollectionOrLink>>,
|
||||||
|
pub next: Option<Box<CollectionPageOrLink>>,
|
||||||
|
pub prev: Option<Box<CollectionPageOrLink>>,
|
||||||
|
}
|
||||||
|
ap_extends!(CollectionPage, BaseCollection);
|
||||||
|
|
||||||
|
impl<V: Vocabulary<Iri = IriIndex>> ParseApub<V> for CollectionPage {
|
||||||
|
fn parse_apub(obj: &RawObject, vocab: &V, ids: &Ids) -> Option<Self> {
|
||||||
|
matches_type(obj, &ids.apub.object.collection_page)?;
|
||||||
|
unsafe { Self::_parse_apub_unchecked(obj, vocab, ids) }
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe fn _parse_apub_unchecked(obj: &RawObject, vocab: &V, ids: &Ids) -> Option<Self> {
|
||||||
|
let ph = PropHelper::new(obj, vocab, ids)?;
|
||||||
|
let prop_ids = &ids.apub.property;
|
||||||
|
|
||||||
|
BaseCollection::_parse_apub_unchecked(obj, vocab, ids).map(|s| Self {
|
||||||
|
_super: s,
|
||||||
|
part_of: ph.parse_prop_box(&prop_ids.part_of),
|
||||||
|
next: ph.parse_prop_box(&prop_ids.next),
|
||||||
|
prev: ph.parse_prop_box(&prop_ids.prev),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DebugApub for CollectionPage {
|
||||||
|
fn debug_apub_members(&self, f: &mut fmt::Formatter, depth: usize) -> fmt::Result {
|
||||||
|
self._super.debug_apub_members(f, depth)?;
|
||||||
|
ap_display_prop_box!(self, f, part_of, depth)?;
|
||||||
|
ap_display_prop_box!(self, f, next, depth)?;
|
||||||
|
ap_display_prop_box!(self, f, prev, depth)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct OrderedCollectionPage {
|
||||||
|
_super: CollectionPage,
|
||||||
|
pub start_index: Option<u32>,
|
||||||
|
}
|
||||||
|
ap_extends!(OrderedCollectionPage, CollectionPage);
|
||||||
|
|
||||||
|
impl<V: Vocabulary<Iri = IriIndex>> ParseApub<V> for OrderedCollectionPage {
|
||||||
|
fn parse_apub(obj: &RawObject, vocab: &V, ids: &Ids) -> Option<Self> {
|
||||||
|
matches_type(obj, &ids.apub.object.ordered_collection_page)?;
|
||||||
|
unsafe { Self::_parse_apub_unchecked(obj, vocab, ids) }
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe fn _parse_apub_unchecked(obj: &RawObject, vocab: &V, ids: &Ids) -> Option<Self> {
|
||||||
|
let ph = PropHelper::new(obj, vocab, ids)?;
|
||||||
|
let prop_ids = &ids.apub.property;
|
||||||
|
|
||||||
|
CollectionPage::_parse_apub_unchecked(obj, vocab, ids).map(|s| Self {
|
||||||
|
_super: s,
|
||||||
|
start_index: ph.parse_prop(&prop_ids.start_index),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DebugApub for OrderedCollectionPage {
|
||||||
|
fn debug_apub_members(&self, f: &mut fmt::Formatter, depth: usize) -> fmt::Result {
|
||||||
|
self._super.debug_apub_members(f, depth)?;
|
||||||
|
ap_display_prop!(self, f, start_index, depth)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub enum CollectionOrLink {
|
||||||
|
Collection(Collection),
|
||||||
|
Link(BaseLink),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<V: Vocabulary<Iri = IriIndex>> ParseApub<V> for CollectionOrLink {
|
||||||
|
fn parse_apub(obj: &RawObject, vocab: &V, ids: &Ids) -> Option<Self> {
|
||||||
|
Collection::parse_apub(obj, vocab, ids)
|
||||||
|
.map(Self::Collection)
|
||||||
|
.or_else(|| BaseLink::parse_apub(obj, vocab, ids).map(Self::Link))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DebugApub for CollectionOrLink {
|
||||||
|
fn apub_class_name(&self) -> &str {
|
||||||
|
use CollectionOrLink::*;
|
||||||
|
match self {
|
||||||
|
Collection(c) => c.apub_class_name(),
|
||||||
|
Link(l) => l.apub_class_name(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn debug_apub(&self, f: &mut fmt::Formatter, depth: usize) -> fmt::Result {
|
||||||
|
use CollectionOrLink::*;
|
||||||
|
match self {
|
||||||
|
Collection(c) => c.debug_apub(f, depth),
|
||||||
|
Link(l) => l.debug_apub(f, depth),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn debug_apub_members(&self, f: &mut fmt::Formatter, depth: usize) -> fmt::Result {
|
||||||
|
use CollectionOrLink::*;
|
||||||
|
match self {
|
||||||
|
Collection(c) => c.debug_apub_members(f, depth),
|
||||||
|
Link(l) => l.debug_apub_members(f, depth),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub enum CollectionPageOrLink {
|
||||||
|
CollectionPage(CollectionPage),
|
||||||
|
Link(BaseLink),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<V: Vocabulary<Iri = IriIndex>> ParseApub<V> for CollectionPageOrLink {
|
||||||
|
fn parse_apub(obj: &RawObject, vocab: &V, ids: &Ids) -> Option<Self> {
|
||||||
|
CollectionPage::parse_apub(obj, vocab, ids)
|
||||||
|
.map(Self::CollectionPage)
|
||||||
|
.or_else(|| BaseLink::parse_apub(obj, vocab, ids).map(Self::Link))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DebugApub for CollectionPageOrLink {
|
||||||
|
fn apub_class_name(&self) -> &str {
|
||||||
|
use CollectionPageOrLink::*;
|
||||||
|
match self {
|
||||||
|
CollectionPage(c) => c.apub_class_name(),
|
||||||
|
Link(l) => l.apub_class_name(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn debug_apub(&self, f: &mut fmt::Formatter, depth: usize) -> fmt::Result {
|
||||||
|
use CollectionPageOrLink::*;
|
||||||
|
match self {
|
||||||
|
CollectionPage(c) => c.debug_apub(f, depth),
|
||||||
|
Link(l) => l.debug_apub(f, depth),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn debug_apub_members(&self, f: &mut fmt::Formatter, depth: usize) -> fmt::Result {
|
||||||
|
use CollectionPageOrLink::*;
|
||||||
|
match self {
|
||||||
|
CollectionPage(c) => c.debug_apub_members(f, depth),
|
||||||
|
Link(l) => l.debug_apub_members(f, depth),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<V: Vocabulary<Iri = IriIndex>> ParseApub<V> for IriBuf {
|
||||||
|
fn parse_apub(obj: &RawObject, vocab: &V, _ids: &Ids) -> Option<Self> {
|
||||||
|
vocab.iri(obj.as_iri()?).map(|iri| iri.to_owned())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<V: Vocabulary<Iri = IriIndex>> ParseApub<V> for xsd::Duration {
|
||||||
|
fn parse_apub(obj: &RawObject, _vocab: &V, _ids: &Ids) -> Option<Self> {
|
||||||
|
xsd::Duration::from_str(obj.as_value()?.as_str()?).ok()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<V: Vocabulary<Iri = IriIndex>> ParseApub<V> for NaiveDateTime {
|
||||||
|
fn parse_apub(obj: &RawObject, _vocab: &V, _ids: &Ids) -> Option<Self> {
|
||||||
|
use chrono::{DateTime, Utc};
|
||||||
|
// TODO: close enough for now, but this only supports UTC
|
||||||
|
let dt = DateTime::<Utc>::from_str(obj.as_value()?.as_str()?).ok()?;
|
||||||
|
Some(dt.naive_utc())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<V: Vocabulary<Iri = IriIndex>> ParseApub<V> for Mime {
|
||||||
|
fn parse_apub(obj: &RawObject, _vocab: &V, _ids: &Ids) -> Option<Self> {
|
||||||
|
Mime::from_str(obj.as_value()?.as_str()?).ok()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<V: Vocabulary<Iri = IriIndex>> ParseApub<V> for String {
|
||||||
|
fn parse_apub(obj: &RawObject, _vocab: &V, _ids: &Ids) -> Option<Self> {
|
||||||
|
Some(obj.as_value()?.as_str()?.to_owned())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<V: Vocabulary<Iri = IriIndex>> ParseApub<V> for u32 {
|
||||||
|
fn parse_apub(obj: &RawObject, _vocab: &V, _ids: &Ids) -> Option<Self> {
|
||||||
|
obj.as_value()?.as_number()?.as_u32()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<V: Vocabulary<Iri = IriIndex>> ParseApub<V> for f32 {
|
||||||
|
fn parse_apub(obj: &RawObject, _vocab: &V, _ids: &Ids) -> Option<Self> {
|
||||||
|
Some(obj.as_value()?.as_number()?.as_f32_lossy())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<V: Vocabulary<Iri = IriIndex>> ParseApub<V> for bool {
|
||||||
|
fn parse_apub(obj: &RawObject, _vocab: &V, _ids: &Ids) -> Option<Self> {
|
||||||
|
obj.as_bool()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn matches_type(obj: &RawObject, iri_id: &Id) -> Option<()> {
|
||||||
|
let iri = iri_id.as_iri().expect("IDs should only refer to IRIs");
|
||||||
|
let type_matches = obj
|
||||||
|
.types()
|
||||||
|
.any(|t| t.as_iri().is_some_and(|index| index == iri));
|
||||||
|
if type_matches {
|
||||||
|
Some(())
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct PropHelper<'a, V: Vocabulary<Iri = IriIndex>> {
|
||||||
|
props: &'a RawProps,
|
||||||
|
vocab: &'a V,
|
||||||
|
ids: &'a Ids,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, V: Vocabulary<Iri = IriIndex>> PropHelper<'a, V> {
|
||||||
|
fn new(obj: &'a RawObject, vocab: &'a V, ids: &'a Ids) -> Option<Self> {
|
||||||
|
let props = obj.as_node()?.properties();
|
||||||
|
Some(Self { props, vocab, ids })
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_prop<T: ParseApub<V>>(&self, prop_id: &Id) -> Option<T> {
|
||||||
|
T::parse_apub(self.props.get_any(prop_id)?, self.vocab, self.ids).or_else(|| {
|
||||||
|
let iri = prop_id
|
||||||
|
.as_iri()
|
||||||
|
.and_then(|index| self.vocab.iri(index))
|
||||||
|
.expect("predefined IRIs must always exist");
|
||||||
|
warn!("Ignoring unknown value for property {iri}");
|
||||||
|
None
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_prop_box<T: ParseApub<V>>(&self, prop_id: &Id) -> Option<Box<T>> {
|
||||||
|
self.parse_prop(prop_id).map(Box::new)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_prop_vec<T: ParseApub<V>>(&self, prop_id: &Id) -> Vec<T> {
|
||||||
|
self.props
|
||||||
|
.get(prop_id)
|
||||||
|
.filter_map(|prop| T::parse_apub(prop, self.vocab, self.ids))
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DebugApub for IriBuf {
|
||||||
|
fn debug_apub(&self, f: &mut fmt::Formatter, depth: usize) -> fmt::Result {
|
||||||
|
self.debug_apub_members(f, depth)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn debug_apub_members(&self, f: &mut fmt::Formatter, _depth: usize) -> fmt::Result {
|
||||||
|
writeln!(f, "{}", self.as_str())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DebugApub for Mime {
|
||||||
|
fn debug_apub(&self, f: &mut fmt::Formatter, depth: usize) -> fmt::Result {
|
||||||
|
self.debug_apub_members(f, depth)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn debug_apub_members(&self, f: &mut fmt::Formatter, _depth: usize) -> fmt::Result {
|
||||||
|
writeln!(f, "{self}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
macro_rules! primitive_debug_apub_impl {
|
||||||
|
($t:ty) => {
|
||||||
|
impl DebugApub for $t {
|
||||||
|
fn debug_apub(&self, f: &mut fmt::Formatter, depth: usize) -> fmt::Result {
|
||||||
|
self.debug_apub_members(f, depth)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn debug_apub_members(&self, f: &mut fmt::Formatter, _depth: usize) -> fmt::Result {
|
||||||
|
writeln!(f, "{self:?}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
primitive_debug_apub_impl!(bool);
|
||||||
|
primitive_debug_apub_impl!(u32);
|
||||||
|
primitive_debug_apub_impl!(f32);
|
||||||
|
primitive_debug_apub_impl!(String);
|
||||||
|
primitive_debug_apub_impl!(NaiveDateTime);
|
||||||
|
primitive_debug_apub_impl!(xsd::Duration);
|
||||||
|
primitive_debug_apub_impl!(HashMap<String, String>);
|
||||||
|
|
||||||
|
fn display_prop<T>(f: &mut fmt::Formatter, name: &str, val: Option<&T>, depth: usize) -> fmt::Result
|
||||||
|
where
|
||||||
|
T: DebugApub,
|
||||||
|
{
|
||||||
|
match val {
|
||||||
|
Some(val) => {
|
||||||
|
write!(f, "{}{name}: ", indent(depth))?;
|
||||||
|
val.debug_apub(f, depth)
|
||||||
|
}
|
||||||
|
None => Ok(()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn display_prop_vec<T>(f: &mut fmt::Formatter, name: &str, val: &[T], depth: usize) -> fmt::Result
|
||||||
|
where
|
||||||
|
T: DebugApub,
|
||||||
|
{
|
||||||
|
match val.len() {
|
||||||
|
0 => Ok(()),
|
||||||
|
1 => display_prop(f, name, Some(&val[0]), depth),
|
||||||
|
_ => {
|
||||||
|
writeln!(f, "{}{name}: [", indent(depth))?;
|
||||||
|
for v in val {
|
||||||
|
write!(f, "{}", indent(depth))?;
|
||||||
|
v.debug_apub(f, depth + 1)?;
|
||||||
|
}
|
||||||
|
writeln!(f, "{}]", indent(depth))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn indent(n: usize) -> impl fmt::Display {
|
||||||
|
" ".repeat(n)
|
||||||
|
}
|
@ -0,0 +1,428 @@
|
|||||||
|
use chrono::NaiveDateTime;
|
||||||
|
use iref::IriBuf;
|
||||||
|
use mime::Mime;
|
||||||
|
use rdf_types::vocabulary::IriIndex;
|
||||||
|
use rdf_types::Vocabulary;
|
||||||
|
use std::fmt;
|
||||||
|
|
||||||
|
use crate::ap::trans::{
|
||||||
|
activity, actor, matches_type, ApDocument, BaseCollection, Collection, DebugApub, ImageOrLink,
|
||||||
|
ParseApub, PropHelper, RawObject,
|
||||||
|
};
|
||||||
|
use crate::ap::vocab::Ids;
|
||||||
|
use crate::util::xsd;
|
||||||
|
|
||||||
|
// The ActivityStreams vocabulary actually defines Image, Audio, Video, and Page
|
||||||
|
// as subclasses of Document. However, since Document is a child of Object and
|
||||||
|
// does not introduce any new properties, we simplify the class hierarchy by
|
||||||
|
// making all of the ones mentioned above direct subclasses of Object.
|
||||||
|
|
||||||
|
pub enum Object {
|
||||||
|
Activity(activity::Activity),
|
||||||
|
Actor(actor::Actor),
|
||||||
|
Article(Article),
|
||||||
|
Audio(Audio),
|
||||||
|
Document(Document),
|
||||||
|
Event(Event),
|
||||||
|
Image(Image),
|
||||||
|
Note(Note),
|
||||||
|
Page(Page),
|
||||||
|
Place(Place),
|
||||||
|
Profile(Profile),
|
||||||
|
Relationship(Relationship),
|
||||||
|
Tombstone(Tombstone),
|
||||||
|
Video(Video),
|
||||||
|
Collection(Collection),
|
||||||
|
|
||||||
|
// Mastodon extensions
|
||||||
|
Emoji(Emoji),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<V: Vocabulary<Iri = IriIndex>> ParseApub<V> for Object {
|
||||||
|
fn parse_apub(obj: &RawObject, vocab: &V, ids: &Ids) -> Option<Self> {
|
||||||
|
activity::Activity::parse_apub(obj, vocab, ids)
|
||||||
|
.map(Self::Activity)
|
||||||
|
.or_else(|| actor::Actor::parse_apub(obj, vocab, ids).map(Self::Actor))
|
||||||
|
.or_else(|| Article::parse_apub(obj, vocab, ids).map(Self::Article))
|
||||||
|
.or_else(|| Audio::parse_apub(obj, vocab, ids).map(Self::Audio))
|
||||||
|
.or_else(|| Document::parse_apub(obj, vocab, ids).map(Self::Document))
|
||||||
|
.or_else(|| Event::parse_apub(obj, vocab, ids).map(Self::Event))
|
||||||
|
.or_else(|| Image::parse_apub(obj, vocab, ids).map(Self::Image))
|
||||||
|
.or_else(|| Note::parse_apub(obj, vocab, ids).map(Self::Note))
|
||||||
|
.or_else(|| Page::parse_apub(obj, vocab, ids).map(Self::Page))
|
||||||
|
.or_else(|| Place::parse_apub(obj, vocab, ids).map(Self::Place))
|
||||||
|
.or_else(|| Profile::parse_apub(obj, vocab, ids).map(Self::Profile))
|
||||||
|
.or_else(|| Relationship::parse_apub(obj, vocab, ids).map(Self::Relationship))
|
||||||
|
.or_else(|| Tombstone::parse_apub(obj, vocab, ids).map(Self::Tombstone))
|
||||||
|
.or_else(|| Video::parse_apub(obj, vocab, ids).map(Self::Video))
|
||||||
|
.or_else(|| Collection::parse_apub(obj, vocab, ids).map(Self::Collection))
|
||||||
|
.or_else(|| Emoji::parse_apub(obj, vocab, ids).map(Self::Emoji))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DebugApub for Object {
|
||||||
|
fn apub_class_name(&self) -> &str {
|
||||||
|
use Object::*;
|
||||||
|
match self {
|
||||||
|
Activity(a) => a.apub_class_name(),
|
||||||
|
Actor(a) => a.apub_class_name(),
|
||||||
|
Article(a) => a.apub_class_name(),
|
||||||
|
Audio(a) => a.apub_class_name(),
|
||||||
|
Document(d) => d.apub_class_name(),
|
||||||
|
Event(e) => e.apub_class_name(),
|
||||||
|
Image(i) => i.apub_class_name(),
|
||||||
|
Note(n) => n.apub_class_name(),
|
||||||
|
Page(p) => p.apub_class_name(),
|
||||||
|
Place(p) => p.apub_class_name(),
|
||||||
|
Profile(p) => p.apub_class_name(),
|
||||||
|
Relationship(r) => r.apub_class_name(),
|
||||||
|
Tombstone(t) => t.apub_class_name(),
|
||||||
|
Video(v) => v.apub_class_name(),
|
||||||
|
Collection(c) => c.apub_class_name(),
|
||||||
|
Emoji(e) => e.apub_class_name(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn debug_apub(&self, f: &mut fmt::Formatter, depth: usize) -> fmt::Result {
|
||||||
|
use Object::*;
|
||||||
|
match self {
|
||||||
|
Activity(a) => a.debug_apub(f, depth),
|
||||||
|
Actor(a) => a.debug_apub(f, depth),
|
||||||
|
Article(a) => a.debug_apub(f, depth),
|
||||||
|
Audio(a) => a.debug_apub(f, depth),
|
||||||
|
Document(d) => d.debug_apub(f, depth),
|
||||||
|
Event(e) => e.debug_apub(f, depth),
|
||||||
|
Image(i) => i.debug_apub(f, depth),
|
||||||
|
Note(n) => n.debug_apub(f, depth),
|
||||||
|
Page(p) => p.debug_apub(f, depth),
|
||||||
|
Place(p) => p.debug_apub(f, depth),
|
||||||
|
Profile(p) => p.debug_apub(f, depth),
|
||||||
|
Relationship(r) => r.debug_apub(f, depth),
|
||||||
|
Tombstone(t) => t.debug_apub(f, depth),
|
||||||
|
Video(v) => v.debug_apub(f, depth),
|
||||||
|
Collection(c) => c.debug_apub(f, depth),
|
||||||
|
Emoji(e) => e.debug_apub(f, depth),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn debug_apub_members(&self, f: &mut fmt::Formatter, depth: usize) -> fmt::Result {
|
||||||
|
use Object::*;
|
||||||
|
match self {
|
||||||
|
Activity(a) => a.debug_apub_members(f, depth),
|
||||||
|
Actor(a) => a.debug_apub_members(f, depth),
|
||||||
|
Article(a) => a.debug_apub_members(f, depth),
|
||||||
|
Audio(a) => a.debug_apub_members(f, depth),
|
||||||
|
Document(d) => d.debug_apub_members(f, depth),
|
||||||
|
Event(e) => e.debug_apub_members(f, depth),
|
||||||
|
Image(i) => i.debug_apub_members(f, depth),
|
||||||
|
Note(n) => n.debug_apub_members(f, depth),
|
||||||
|
Page(p) => p.debug_apub_members(f, depth),
|
||||||
|
Place(p) => p.debug_apub_members(f, depth),
|
||||||
|
Profile(p) => p.debug_apub_members(f, depth),
|
||||||
|
Relationship(r) => r.debug_apub_members(f, depth),
|
||||||
|
Tombstone(t) => t.debug_apub_members(f, depth),
|
||||||
|
Video(v) => v.debug_apub_members(f, depth),
|
||||||
|
Collection(c) => c.debug_apub_members(f, depth),
|
||||||
|
Emoji(e) => e.debug_apub_members(f, depth),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct AbstractObject {
|
||||||
|
pub id: Option<IriBuf>,
|
||||||
|
pub attachment: Vec<ApDocument>,
|
||||||
|
pub attributed_to: Vec<ApDocument>,
|
||||||
|
pub audience: Vec<ApDocument>,
|
||||||
|
pub content: Option<String>, // TODO: this could be a langString
|
||||||
|
pub context: Option<Box<ApDocument>>,
|
||||||
|
pub name: Option<String>, // TODO: this could be a langString
|
||||||
|
pub end_time: Option<NaiveDateTime>,
|
||||||
|
pub generator: Option<Box<ApDocument>>,
|
||||||
|
pub icon: Option<Box<ImageOrLink>>,
|
||||||
|
pub image: Option<Box<ImageOrLink>>,
|
||||||
|
pub in_reply_to: Vec<ApDocument>,
|
||||||
|
pub location: Option<Box<ApDocument>>,
|
||||||
|
pub preview: Option<Box<ApDocument>>,
|
||||||
|
pub published: Option<NaiveDateTime>,
|
||||||
|
pub replies: Option<Box<BaseCollection>>,
|
||||||
|
pub start_time: Option<NaiveDateTime>,
|
||||||
|
pub summary: Option<String>, // TODO: this could be a langString
|
||||||
|
pub tag: Option<Box<ApDocument>>,
|
||||||
|
pub updated: Option<NaiveDateTime>,
|
||||||
|
pub url: Option<IriBuf>,
|
||||||
|
pub to: Option<Box<ApDocument>>,
|
||||||
|
pub bto: Option<Box<ApDocument>>,
|
||||||
|
pub cc: Option<Box<ApDocument>>,
|
||||||
|
pub bcc: Option<Box<ApDocument>>,
|
||||||
|
pub media_type: Option<Mime>,
|
||||||
|
pub duration: Option<xsd::Duration>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<V: Vocabulary<Iri = IriIndex>> ParseApub<V> for AbstractObject {
|
||||||
|
fn parse_apub(obj: &RawObject, vocab: &V, ids: &Ids) -> Option<Self> {
|
||||||
|
let ph = PropHelper::new(obj, vocab, ids)?;
|
||||||
|
let prop_ids = &ids.apub.property;
|
||||||
|
|
||||||
|
Some(Self {
|
||||||
|
id: obj
|
||||||
|
.id()
|
||||||
|
.and_then(|id| id.as_iri())
|
||||||
|
.and_then(|idx| vocab.iri(idx))
|
||||||
|
.map(|iri| iri.to_owned()),
|
||||||
|
attachment: ph.parse_prop_vec(&prop_ids.attachment),
|
||||||
|
attributed_to: ph.parse_prop_vec(&prop_ids.attributed_to),
|
||||||
|
audience: ph.parse_prop_vec(&prop_ids.audience),
|
||||||
|
content: ph.parse_prop(&prop_ids.content),
|
||||||
|
context: ph.parse_prop_box(&prop_ids.context),
|
||||||
|
name: ph.parse_prop(&prop_ids.name),
|
||||||
|
end_time: ph.parse_prop(&prop_ids.end_time),
|
||||||
|
generator: ph.parse_prop_box(&prop_ids.generator),
|
||||||
|
icon: ph.parse_prop_box(&prop_ids.icon),
|
||||||
|
image: ph.parse_prop_box(&prop_ids.image),
|
||||||
|
in_reply_to: ph.parse_prop_vec(&prop_ids.in_reply_to),
|
||||||
|
location: ph.parse_prop_box(&prop_ids.location),
|
||||||
|
preview: ph.parse_prop_box(&prop_ids.preview),
|
||||||
|
published: ph.parse_prop(&prop_ids.published),
|
||||||
|
replies: ph.parse_prop_box(&prop_ids.replies),
|
||||||
|
start_time: ph.parse_prop(&prop_ids.start_time),
|
||||||
|
summary: ph.parse_prop(&prop_ids.summary),
|
||||||
|
tag: ph.parse_prop_box(&prop_ids.tag),
|
||||||
|
updated: ph.parse_prop(&prop_ids.updated),
|
||||||
|
url: ph.parse_prop(&prop_ids.url),
|
||||||
|
to: ph.parse_prop_box(&prop_ids.to),
|
||||||
|
bto: ph.parse_prop_box(&prop_ids.bto),
|
||||||
|
cc: ph.parse_prop_box(&prop_ids.cc),
|
||||||
|
bcc: ph.parse_prop_box(&prop_ids.bcc),
|
||||||
|
media_type: ph.parse_prop(&prop_ids.media_type),
|
||||||
|
duration: ph.parse_prop(&prop_ids.duration),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DebugApub for AbstractObject {
|
||||||
|
fn debug_apub_members(&self, f: &mut fmt::Formatter, depth: usize) -> fmt::Result {
|
||||||
|
ap_display_prop!(self, f, id, depth)?;
|
||||||
|
ap_display_prop_vec!(self, f, attachment, depth)?;
|
||||||
|
ap_display_prop_vec!(self, f, attributed_to, depth)?;
|
||||||
|
ap_display_prop_vec!(self, f, audience, depth)?;
|
||||||
|
ap_display_prop!(self, f, content, depth)?;
|
||||||
|
ap_display_prop_box!(self, f, context, depth)?;
|
||||||
|
ap_display_prop!(self, f, name, depth)?;
|
||||||
|
ap_display_prop!(self, f, end_time, depth)?;
|
||||||
|
ap_display_prop_box!(self, f, generator, depth)?;
|
||||||
|
ap_display_prop_box!(self, f, icon, depth)?;
|
||||||
|
ap_display_prop_box!(self, f, image, depth)?;
|
||||||
|
ap_display_prop_vec!(self, f, in_reply_to, depth)?;
|
||||||
|
ap_display_prop_box!(self, f, location, depth)?;
|
||||||
|
ap_display_prop_box!(self, f, preview, depth)?;
|
||||||
|
ap_display_prop!(self, f, published, depth)?;
|
||||||
|
ap_display_prop_box!(self, f, replies, depth)?;
|
||||||
|
ap_display_prop!(self, f, start_time, depth)?;
|
||||||
|
ap_display_prop!(self, f, summary, depth)?;
|
||||||
|
ap_display_prop_box!(self, f, tag, depth)?;
|
||||||
|
ap_display_prop!(self, f, updated, depth)?;
|
||||||
|
ap_display_prop!(self, f, url, depth)?;
|
||||||
|
ap_display_prop_box!(self, f, to, depth)?;
|
||||||
|
ap_display_prop_box!(self, f, bto, depth)?;
|
||||||
|
ap_display_prop_box!(self, f, cc, depth)?;
|
||||||
|
ap_display_prop_box!(self, f, bcc, depth)?;
|
||||||
|
ap_display_prop!(self, f, media_type, depth)?;
|
||||||
|
ap_display_prop!(self, f, duration, depth)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Relationship {
|
||||||
|
_super: AbstractObject,
|
||||||
|
pub subject: Option<Box<ApDocument>>,
|
||||||
|
pub object: Option<Box<ApDocument>>,
|
||||||
|
pub relationship: Option<Box<Object>>,
|
||||||
|
}
|
||||||
|
ap_extends!(Relationship, AbstractObject);
|
||||||
|
|
||||||
|
impl<V: Vocabulary<Iri = IriIndex>> ParseApub<V> for Relationship {
|
||||||
|
fn parse_apub(obj: &RawObject, vocab: &V, ids: &Ids) -> Option<Self> {
|
||||||
|
matches_type(obj, &ids.apub.object.relationship)?;
|
||||||
|
unsafe { Self::_parse_apub_unchecked(obj, vocab, ids) }
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe fn _parse_apub_unchecked(obj: &RawObject, vocab: &V, ids: &Ids) -> Option<Self> {
|
||||||
|
let ph = PropHelper::new(obj, vocab, ids)?;
|
||||||
|
let prop_ids = &ids.apub.property;
|
||||||
|
|
||||||
|
AbstractObject::_parse_apub_unchecked(obj, vocab, ids).map(|s| Self {
|
||||||
|
_super: s,
|
||||||
|
subject: ph.parse_prop_box(&prop_ids.subject),
|
||||||
|
object: ph.parse_prop_box(&prop_ids.object),
|
||||||
|
relationship: ph.parse_prop_box(&prop_ids.relationship),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DebugApub for Relationship {
|
||||||
|
fn debug_apub_members(&self, f: &mut fmt::Formatter, depth: usize) -> fmt::Result {
|
||||||
|
self._super.debug_apub_members(f, depth)?;
|
||||||
|
ap_display_prop_box!(self, f, subject, depth)?;
|
||||||
|
ap_display_prop_box!(self, f, object, depth)?;
|
||||||
|
ap_display_prop_box!(self, f, relationship, depth)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Place {
|
||||||
|
_super: AbstractObject,
|
||||||
|
pub accuracy: Option<f32>,
|
||||||
|
pub altitude: Option<f32>,
|
||||||
|
pub latitude: Option<f32>,
|
||||||
|
pub longitude: Option<f32>,
|
||||||
|
pub radius: Option<f32>,
|
||||||
|
pub units: Option<String>,
|
||||||
|
}
|
||||||
|
ap_extends!(Place, AbstractObject);
|
||||||
|
|
||||||
|
impl<V: Vocabulary<Iri = IriIndex>> ParseApub<V> for Place {
|
||||||
|
fn parse_apub(obj: &RawObject, vocab: &V, ids: &Ids) -> Option<Self> {
|
||||||
|
matches_type(obj, &ids.apub.object.place)?;
|
||||||
|
unsafe { Self::_parse_apub_unchecked(obj, vocab, ids) }
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe fn _parse_apub_unchecked(obj: &RawObject, vocab: &V, ids: &Ids) -> Option<Self> {
|
||||||
|
let ph = PropHelper::new(obj, vocab, ids)?;
|
||||||
|
let prop_ids = &ids.apub.property;
|
||||||
|
|
||||||
|
AbstractObject::_parse_apub_unchecked(obj, vocab, ids).map(|s| Self {
|
||||||
|
_super: s,
|
||||||
|
accuracy: ph.parse_prop(&prop_ids.accuracy),
|
||||||
|
altitude: ph.parse_prop(&prop_ids.altitude),
|
||||||
|
latitude: ph.parse_prop(&prop_ids.latitude),
|
||||||
|
longitude: ph.parse_prop(&prop_ids.longitude),
|
||||||
|
radius: ph.parse_prop(&prop_ids.radius),
|
||||||
|
units: ph.parse_prop(&prop_ids.units),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DebugApub for Place {
|
||||||
|
fn debug_apub_members(&self, f: &mut fmt::Formatter, depth: usize) -> fmt::Result {
|
||||||
|
self._super.debug_apub_members(f, depth)?;
|
||||||
|
ap_display_prop!(self, f, accuracy, depth)?;
|
||||||
|
ap_display_prop!(self, f, altitude, depth)?;
|
||||||
|
ap_display_prop!(self, f, latitude, depth)?;
|
||||||
|
ap_display_prop!(self, f, longitude, depth)?;
|
||||||
|
ap_display_prop!(self, f, radius, depth)?;
|
||||||
|
ap_display_prop!(self, f, units, depth)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Profile {
|
||||||
|
_super: AbstractObject,
|
||||||
|
pub describes: Option<Box<Object>>,
|
||||||
|
}
|
||||||
|
ap_extends!(Profile, AbstractObject);
|
||||||
|
|
||||||
|
impl<V: Vocabulary<Iri = IriIndex>> ParseApub<V> for Profile {
|
||||||
|
fn parse_apub(obj: &RawObject, vocab: &V, ids: &Ids) -> Option<Self> {
|
||||||
|
matches_type(obj, &ids.apub.object.profile)?;
|
||||||
|
unsafe { Self::_parse_apub_unchecked(obj, vocab, ids) }
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe fn _parse_apub_unchecked(obj: &RawObject, vocab: &V, ids: &Ids) -> Option<Self> {
|
||||||
|
let ph = PropHelper::new(obj, vocab, ids)?;
|
||||||
|
let prop_ids = &ids.apub.property;
|
||||||
|
|
||||||
|
AbstractObject::_parse_apub_unchecked(obj, vocab, ids).map(|s| Self {
|
||||||
|
_super: s,
|
||||||
|
describes: ph.parse_prop_box(&prop_ids.describes),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DebugApub for Profile {
|
||||||
|
fn debug_apub_members(&self, f: &mut fmt::Formatter, depth: usize) -> fmt::Result {
|
||||||
|
self._super.debug_apub_members(f, depth)?;
|
||||||
|
ap_display_prop_box!(self, f, describes, depth)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Tombstone {
|
||||||
|
_super: AbstractObject,
|
||||||
|
pub former_type: Option<Mime>,
|
||||||
|
pub deleted: Option<NaiveDateTime>,
|
||||||
|
}
|
||||||
|
ap_extends!(Tombstone, AbstractObject);
|
||||||
|
|
||||||
|
impl<V: Vocabulary<Iri = IriIndex>> ParseApub<V> for Tombstone {
|
||||||
|
fn parse_apub(obj: &RawObject, vocab: &V, ids: &Ids) -> Option<Self> {
|
||||||
|
matches_type(obj, &ids.apub.object.tombstone)?;
|
||||||
|
unsafe { Self::_parse_apub_unchecked(obj, vocab, ids) }
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe fn _parse_apub_unchecked(obj: &RawObject, vocab: &V, ids: &Ids) -> Option<Self> {
|
||||||
|
let ph = PropHelper::new(obj, vocab, ids)?;
|
||||||
|
let prop_ids = &ids.apub.property;
|
||||||
|
|
||||||
|
AbstractObject::_parse_apub_unchecked(obj, vocab, ids).map(|s| Self {
|
||||||
|
_super: s,
|
||||||
|
former_type: ph.parse_prop(&prop_ids.former_type),
|
||||||
|
deleted: ph.parse_prop(&prop_ids.deleted),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DebugApub for Tombstone {
|
||||||
|
fn debug_apub_members(&self, f: &mut fmt::Formatter, depth: usize) -> fmt::Result {
|
||||||
|
self._super.debug_apub_members(f, depth)?;
|
||||||
|
ap_display_prop!(self, f, former_type, depth)?;
|
||||||
|
ap_display_prop!(self, f, deleted, depth)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Image {
|
||||||
|
_super: AbstractObject,
|
||||||
|
// Mastodon extensions
|
||||||
|
pub focal_point: Option<[f32; 2]>,
|
||||||
|
pub blurhash: Option<String>,
|
||||||
|
}
|
||||||
|
ap_extends!(Image, AbstractObject);
|
||||||
|
|
||||||
|
impl<V: Vocabulary<Iri = IriIndex>> ParseApub<V> for Image {
|
||||||
|
fn parse_apub(obj: &RawObject, vocab: &V, ids: &Ids) -> Option<Self> {
|
||||||
|
matches_type(obj, &ids.apub.object.image)?;
|
||||||
|
unsafe { Self::_parse_apub_unchecked(obj, vocab, ids) }
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe fn _parse_apub_unchecked(obj: &RawObject, vocab: &V, ids: &Ids) -> Option<Self> {
|
||||||
|
let ph = PropHelper::new(obj, vocab, ids)?;
|
||||||
|
|
||||||
|
let focal_point: Vec<f32> = ph.parse_prop_vec(&ids.toot.props.focal_point);
|
||||||
|
AbstractObject::_parse_apub_unchecked(obj, vocab, ids).map(|s| Self {
|
||||||
|
_super: s,
|
||||||
|
focal_point: (focal_point.len() >= 2).then(|| [focal_point[0], focal_point[1]]),
|
||||||
|
blurhash: ph.parse_prop(&ids.toot.props.blurhash),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DebugApub for Image {
|
||||||
|
fn debug_apub_members(&self, f: &mut fmt::Formatter, depth: usize) -> fmt::Result {
|
||||||
|
self._super.debug_apub_members(f, depth)?;
|
||||||
|
if let Some(focal_point) = self.focal_point {
|
||||||
|
writeln!(
|
||||||
|
f,
|
||||||
|
"{:>3$}: [{:.3}, {:.3}]",
|
||||||
|
"focal_point",
|
||||||
|
focal_point[0],
|
||||||
|
focal_point[1],
|
||||||
|
depth * 4
|
||||||
|
)?;
|
||||||
|
}
|
||||||
|
ap_display_prop!(self, f, blurhash, depth)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ap_empty_child_impl!(Article, AbstractObject, apub, object, article);
|
||||||
|
ap_empty_child_impl!(Document, AbstractObject, apub, object, document);
|
||||||
|
ap_empty_child_impl!(Audio, AbstractObject, apub, object, audio);
|
||||||
|
ap_empty_child_impl!(Video, AbstractObject, apub, object, video);
|
||||||
|
ap_empty_child_impl!(Note, AbstractObject, apub, object, note);
|
||||||
|
ap_empty_child_impl!(Page, AbstractObject, apub, object, page);
|
||||||
|
ap_empty_child_impl!(Event, AbstractObject, apub, object, event);
|
||||||
|
ap_empty_child_impl!(Emoji, AbstractObject, toot, class, emoji);
|
Loading…
Reference in New Issue