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.
653 lines
20 KiB
Rust
653 lines
20 KiB
Rust
//! 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)
|
|
}
|