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

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);