apub/apub-core/src/ingest.rs
Aode (lion) a1ad76b485
All checks were successful
continuous-integration/drone/push Build is passing
Add IngestFactory for more dynamic ingest structures
2021-12-05 15:54:12 -06:00

306 lines
8.3 KiB
Rust

//! traits around accepting activities
use crate::{
repo::{Dereference, Repo},
session::Session,
};
use std::{rc::Rc, sync::Arc};
use url::{Host, Url};
/// The Authority that is providing the ingested data
///
/// - An Authority of None means the data is untrustworthy, as no entity can be verified to have
/// provided this data
/// - An Authority of Server means the data to be ingested has been provided on behalf of it's
/// origin server. A URL is provided in the Server variant to describe the specific URL the data
/// originates from.
/// - An Authority of Actor(Url) means the data to be ingested has been provided on behalf of the
/// Actor identified by the associated URL
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
pub enum Authority {
/// No Authority provided
None,
/// The Authority for ingested data is the server the data is hosted on
Server(Url),
/// The Authority for the ingested data is the provided url
Actor(Url),
}
/// Describes accepting a new Object into the system
///
/// This type is implemented by users of `apub` to hook into provied inbox methods
#[async_trait::async_trait(?Send)]
pub trait Ingest<Object> {
/// The actor that is receiving the activity
///
/// e.g.
/// - the community that is receiving a new post
/// - the server actor (in the case of the shared inbox)
type ActorId;
/// The local repository
type Local: Repo;
/// The error produced when ingesting activities
type Error: From<<Self::Local as Repo>::Error>;
/// Get the local repository
fn local_repo(&self) -> &Self::Local;
/// Determine if a given URL is local
fn is_local(&self, url: &Url) -> bool;
/// Accept and process a given activity
///
/// Args:
/// - authority: the source of the information, either the Actor that provided the object, or the URL it was fetched from
/// - actor_id: the ID of the actor accepting the object.
/// - activity: the Object being ingested
/// - remote_repo: a handle to a remote repository (probably an HTTP client) for ingesting further objects
/// - session: the request session associated with the Remote Repo
async fn ingest<Remote: Repo, S: Session>(
&self,
authority: Authority,
actor_id: Self::ActorId,
activity: &Object,
remote_repo: Remote,
session: S,
) -> Result<(), Self::Error>
where
Self::Error: From<Remote::Error>;
/// Dereference an ID from the local or remote repo
///
/// Args:
/// - id: the ID of the object to be fetched
/// - actor_id: the ID of the actor fetching the object.
/// - remote_repo: a handle to a remote repository (probably an HTTP client) for ingesting further objects
/// - session: the request session associated with the Remote Repo
async fn fetch<D: Dereference<Output = Object>, Remote: Repo, S: Session>(
&self,
id: D,
actor_id: Self::ActorId,
remote_repo: Remote,
mut session: S,
) -> Result<Option<D::Output>, Self::Error>
where
Self::ActorId: 'static,
Self::Error: From<Remote::Error>,
{
let opt = self.local_repo().fetch(&id, &mut session).await?;
if self.is_local(id.url()) {
return Ok(opt);
}
let opt = remote_repo.fetch(&id, &mut session).await?;
if let Some(object) = opt.as_ref() {
let authority = Authority::Server(id.url().clone());
self.ingest(authority, actor_id, object, remote_repo, session)
.await?;
}
Ok(opt)
}
}
/// Describes a type that can produce an Ingest
pub trait IngestFactory<A> {
/// The Ingest type
type Ingest: Ingest<A>;
/// Build the ingest type
fn build_ingest(&self) -> Self::Ingest;
}
impl std::fmt::Display for Authority {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Authority::Actor(actor_id) => write!(f, "Actor - '{}'", actor_id),
Authority::Server(url) => write!(f, "Server URL - '{}'", url),
Authority::None => write!(f, "None"),
}
}
}
/// A helper for determining if a given URL matches a Host and Port
pub fn is_local(local_host: &Host<String>, local_port: Option<u16>, url: &Url) -> bool {
Some(borrow_host(local_host)) == url.host() && local_port == url.port()
}
fn borrow_host(host: &Host<String>) -> Host<&str> {
match host {
Host::Ipv4(ip) => Host::Ipv4(*ip),
Host::Ipv6(ip) => Host::Ipv6(*ip),
Host::Domain(ref domain) => Host::Domain(domain),
}
}
#[async_trait::async_trait(?Send)]
impl<'a, Object, T> Ingest<Object> for &'a T
where
T: Ingest<Object>,
Object: 'static,
{
type Local = T::Local;
type ActorId = T::ActorId;
type Error = T::Error;
fn local_repo(&self) -> &Self::Local {
T::local_repo(self)
}
fn is_local(&self, url: &Url) -> bool {
T::is_local(self, url)
}
async fn ingest<Remote: Repo, S: Session>(
&self,
authority: Authority,
actor_id: Self::ActorId,
activity: &Object,
remote_repo: Remote,
session: S,
) -> Result<(), Self::Error>
where
Self::Error: From<Remote::Error>,
{
T::ingest(self, authority, actor_id, activity, remote_repo, session).await
}
}
#[async_trait::async_trait(?Send)]
impl<'a, Object, T> Ingest<Object> for &'a mut T
where
T: Ingest<Object>,
Object: 'static,
{
type Local = T::Local;
type Error = T::Error;
type ActorId = T::ActorId;
fn local_repo(&self) -> &Self::Local {
T::local_repo(self)
}
fn is_local(&self, url: &Url) -> bool {
T::is_local(self, url)
}
async fn ingest<Remote: Repo, S: Session>(
&self,
authority: Authority,
actor_id: Self::ActorId,
activity: &Object,
remote_repo: Remote,
session: S,
) -> Result<(), Self::Error>
where
Self::Error: From<Remote::Error>,
{
T::ingest(self, authority, actor_id, activity, remote_repo, session).await
}
}
#[async_trait::async_trait(?Send)]
impl<Object, T> Ingest<Object> for Box<T>
where
T: Ingest<Object>,
Object: 'static,
{
type Local = T::Local;
type Error = T::Error;
type ActorId = T::ActorId;
fn local_repo(&self) -> &Self::Local {
T::local_repo(self)
}
fn is_local(&self, url: &Url) -> bool {
T::is_local(self, url)
}
async fn ingest<Remote: Repo, S: Session>(
&self,
authority: Authority,
actor_id: Self::ActorId,
activity: &Object,
remote_repo: Remote,
session: S,
) -> Result<(), Self::Error>
where
Self::Error: From<Remote::Error>,
{
T::ingest(self, authority, actor_id, activity, remote_repo, session).await
}
}
#[async_trait::async_trait(?Send)]
impl<Object, T> Ingest<Object> for Rc<T>
where
T: Ingest<Object>,
Object: 'static,
{
type Local = T::Local;
type Error = T::Error;
type ActorId = T::ActorId;
fn local_repo(&self) -> &Self::Local {
T::local_repo(self)
}
fn is_local(&self, url: &Url) -> bool {
T::is_local(self, url)
}
async fn ingest<Remote: Repo, S: Session>(
&self,
authority: Authority,
actor_id: Self::ActorId,
activity: &Object,
remote_repo: Remote,
session: S,
) -> Result<(), Self::Error>
where
Self::Error: From<Remote::Error>,
{
T::ingest(self, authority, actor_id, activity, remote_repo, session).await
}
}
#[async_trait::async_trait(?Send)]
impl<Object, T> Ingest<Object> for Arc<T>
where
T: Ingest<Object>,
Object: 'static,
{
type Local = T::Local;
type Error = T::Error;
type ActorId = T::ActorId;
fn local_repo(&self) -> &Self::Local {
T::local_repo(self)
}
fn is_local(&self, url: &Url) -> bool {
T::is_local(self, url)
}
async fn ingest<Remote: Repo, S: Session>(
&self,
authority: Authority,
actor_id: Self::ActorId,
activity: &Object,
remote_repo: Remote,
session: S,
) -> Result<(), Self::Error>
where
Self::Error: From<Remote::Error>,
{
T::ingest(self, authority, actor_id, activity, remote_repo, session).await
}
}