You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
429 lines
17 KiB
Rust
429 lines
17 KiB
Rust
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);
|