hermit/src/lib.rs

244 lines
5.7 KiB
Rust

//! # The Hermit ActivityPub server
//!
//! This library contains the types and trait impls that make up the ActivityPub
//! support and database interaction for the Hermit ActivityPub server.
// Expose the `Id` type in the crate root
pub use id::Id;
pub use ctx::Context;
// Module imports
pub mod conf;
pub mod sign;
pub mod db;
pub mod ap;
/// The Activity supertype used in abstractions over any kind of activity.
#[derive(Clone)]
pub enum Activity {
/// Create a post.
Create (ap::Create),
/// Request to follow an actor.
Follow (ap::Follow),
/// Accept a follow request.
Accept (ap::Accept),
}
/// A result type that defaults to using [`Error`] as the second type
/// parameter.
pub type Result <T, E = Error> = std::result::Result<T, E>;
/// Errors generated within Hermit.
#[derive(Debug)]
pub enum Error {
/// [`reqwest`] errors.
Http (reqwest::Error),
/// [`serde_json`] errors.
Json (serde_json::Error),
/// [`sqlx`] errors.
Sqlx (sqlx::Error),
/// A cryptography error from [`openssl`].
OpenSSL (openssl::error::ErrorStack),
}
impl From<sqlx::Error> for Error {
fn from (e: sqlx::Error) -> Self { Error::Sqlx (e) }
}
impl From<reqwest::Error> for Error {
fn from (e: reqwest::Error) -> Self { Error::Http (e) }
}
impl From<serde_json::Error> for Error {
fn from (e: serde_json::Error) -> Self { Error::Json (e) }
}
impl From<openssl::error::ErrorStack> for Error {
fn from (e: openssl::error::ErrorStack) -> Self { Error::OpenSSL (e) }
}
/// Trivial conversion function for use in `map_err` functions.
pub (crate) fn err (e: impl Into<Error>) -> Error { e.into() }
mod id {
use std::str::FromStr;
use serde::{ Deserialize, Serialize };
/// An ActivityPub identifier.
#[derive(PartialEq, Eq, Clone, Serialize, Deserialize)]
pub struct Id (reqwest::Url);
impl crate::IntoUrl for Id {
fn into_url (self) -> Option<url::Url> { Some (self.0) }
}
impl FromStr for Id {
type Err = url::ParseError;
fn from_str (s: &str) -> Result<Self, Self::Err> {
s.parse().map(Id)
}
}
}
mod ctx {
use std::sync::Arc;
use futures::prelude::*;
use serde_json::Value;
use crate::{ conf::Config, db, Result, sign::Sign, ap, Activity };
pub struct Context <S> {
pub config: Config,
pub signer: Arc<S>,
pub client: db::Client,
}
impl<S> Clone for Context<S> {
fn clone (&self) -> Context<S> {
Context {
config: self.config.clone(),
signer: self.signer.clone(),
client: self.client.clone(),
}
}
}
impl<S> Context<S> {
/// Attempt an action within the context of the database.
pub async fn with_db <'a, F, O, T> (&'a mut self, f: F) -> Result<T>
where
F: FnOnce (&'a mut db::Client) -> O,
O: Future<Output = Result<T>> + 'a,
{
f(&mut self.client).await
}
/// Get all actors on the instance.
pub fn actors (&self) -> impl Iterator<Item = ap::Actor> + '_ {
None.into_iter()
}
/// Get a dereferencer.
pub fn dereferencer (&self) -> Dereferencer<S>
where
S: Sign
{
Dereferencer {
web: reqwest::Client::new(),
signer: self.signer.clone(),
db: self.client.clone(),
}
}
/// Access the inner [`Sign`] provider.
pub fn signer (&self) -> &S {
&self.signer
}
pub fn config (&self) -> &Config {
&self.config
}
pub fn config_mut (&mut self) -> &mut Config {
&mut self.config
}
/// Conjure an activity "from thin air" as though it were posted through a client.
pub (crate) async fn conjure (&self, act: impl Into<Activity>) -> Result<()> {
let act = act.into();
todo!()
}
}
/// A type that provides dereferencing facilities for [`Activity`] data.
pub struct Dereferencer <S> {
web: reqwest::Client,
db: db::Client,
signer: Arc<S>,
}
impl<S> Dereferencer<S>
where
S: Sign
{
/// Perform the dereferencing.
pub async fn dereference (&self, json: Value) -> Result<Activity> {
match json["type"].as_str() {
Some ("Create") => self.deref_create(json).await.map(Activity::Create),
_ => todo!()
}
}
fn db_client (&self) -> &db::Client {
&self.db
}
fn web_client (&self) -> &reqwest::Client {
&self.web
}
/// Fetch a JSON value.
pub async fn fetch (&self, url: impl crate::IntoUrl) -> Result<Value> {
let client = self.web_client();
let url = match url.into_url() {
Some (url) => url,
None => todo!(),
};
let req = {
let mut r = client.get(url).build()?;
self.signer.sign(&mut r)?;
r
};
let value = client
.execute(req)
.await?
.json()
.await?;
Ok (value)
}
/// Attempt to dereference to a [`Create`](ap::Create) activity.
async fn deref_create (&self, json: Value) -> Result<ap::Create> {
let json = if let Value::String (url) = json {
self.fetch(url).await?
} else { json };
match json["object"]["type"].as_str() {
Some ("Note" | "Article") => todo!(), //Ok (act::Create::Note { id }),
_ => return Err (todo!()),
}
}
}
}
/// Types that can be mapped to a [`Url`](url::Url).
pub trait IntoUrl {
/// Perform the conversion.
fn into_url (self) -> Option<url::Url>;
}
impl<T> IntoUrl for T where T: ToString {
fn into_url (self) -> Option<url::Url> {
self.to_string()
.parse()
.ok()
}
}