Skip to content

Commit

Permalink
introduce fetcher and resolver traits
Browse files Browse the repository at this point in the history
  • Loading branch information
aumetra committed Nov 12, 2023
1 parent 6ff91d3 commit 27ebb84
Show file tree
Hide file tree
Showing 24 changed files with 120 additions and 60 deletions.
3 changes: 2 additions & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions crates/kitsune-activitypub/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ http = "0.2.10"
iso8601-timestamp = "0.2.12"
kitsune-cache = { path = "../kitsune-cache" }
kitsune-consts = { path = "../kitsune-consts" }
kitsune-core = { path = "../kitsune-core" }
kitsune-db = { path = "../kitsune-db" }
kitsune-embed = { path = "../kitsune-embed" }
kitsune-federation-filter = { path = "../kitsune-federation-filter" }
Expand Down
7 changes: 4 additions & 3 deletions crates/kitsune-activitypub/src/fetcher/actor.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use super::{FetchOptions, Fetcher};
use super::Fetcher;
use crate::{
error::{Error, Result},
process_attachments,
Expand All @@ -7,6 +7,7 @@ use autometrics::autometrics;
use diesel::{ExpressionMethods, OptionalExtension, QueryDsl, SelectableHelper};
use diesel_async::RunQueryDsl;
use kitsune_cache::CacheBackend;
use kitsune_core::traits::{AccountFetchOptions, Resolver};
use kitsune_db::{
model::account::{Account, AccountConflictChangeset, NewAccount, UpdateAccountMedia},
schema::accounts,
Expand All @@ -25,7 +26,7 @@ impl Fetcher {
/// - Panics if the URL doesn't contain a host section
#[instrument(skip(self))]
#[autometrics(track_concurrency)]
pub async fn fetch_actor(&self, opts: FetchOptions<'_>) -> Result<Account> {
pub async fn fetch_actor(&self, opts: AccountFetchOptions<'_>) -> Result<Account> {
// Obviously we can't hit the cache nor the database if we wanna refetch the actor
if !opts.refetch {
if let Some(user) = self.user_cache.get(opts.url).await? {
Expand Down Expand Up @@ -64,7 +65,7 @@ impl Fetcher {
let used_webfinger = if fetch_webfinger {
match self
.webfinger
.resolve_actor(&actor.preferred_username, domain)
.resolve_account(&actor.preferred_username, domain)
.await?
{
Some(resource) if resource.uri == actor.id => {
Expand Down
44 changes: 18 additions & 26 deletions crates/kitsune-activitypub/src/fetcher/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,9 @@ use headers::{ContentType, HeaderMapExt};
use http::HeaderValue;
use kitsune_cache::ArcCache;
use kitsune_consts::USER_AGENT;
use kitsune_core::traits::{AccountFetchOptions, Fetcher as FetcherTrait};
use kitsune_db::{
model::{account::Account, post::Post},
model::{account::Account, custom_emoji::CustomEmoji, post::Post},
PgPool,
};
use kitsune_embed::Client as EmbedClient;
Expand All @@ -23,31 +24,6 @@ mod actor;
mod emoji;
mod object;

#[derive(Clone, Debug, TypedBuilder)]
/// Options passed to the fetcher
pub struct FetchOptions<'a> {
/// Prefetched WebFinger `acct` URI
#[builder(default, setter(strip_option))]
acct: Option<(&'a str, &'a str)>,

/// Refetch the ActivityPub entity
///
/// This is mainly used to refresh possibly stale actors
///
/// Default: false
#[builder(default = false)]
refetch: bool,

/// URL of the ActivityPub entity
url: &'a str,
}

impl<'a> From<&'a str> for FetchOptions<'a> {
fn from(value: &'a str) -> Self {
Self::builder().url(value).build()
}
}

#[derive(Clone, TypedBuilder)]
pub struct Fetcher {
#[builder(default =
Expand Down Expand Up @@ -124,3 +100,19 @@ impl Fetcher {
Ok(response.jsonld().await?)
}
}

impl FetcherTrait for Fetcher {
type Error = Error;

async fn fetch_account(&self, opts: AccountFetchOptions<'_>) -> Result<Account, Self::Error> {
self.fetch_actor(opts).await
}

async fn fetch_emoji(&self, url: &str) -> Result<CustomEmoji, Self::Error> {
self.fetch_emoji(url).await
}

async fn fetch_post(&self, url: &str) -> Result<Post, Self::Error> {
self.fetch_object(url).await
}
}
1 change: 0 additions & 1 deletion crates/kitsune-core/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@ garde = { version = "0.16.2", default-features = false, features = [
"regex",
"serde",
] }
globset = "0.4.13"
hex-simd = { version = "0.8.0", features = ["unstable"] }
http = "0.2.10"
img-parts = "0.3.0"
Expand Down
17 changes: 10 additions & 7 deletions crates/kitsune-core/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,14 @@ extern crate tracing;

pub mod error;
pub mod event;
pub mod job;
pub mod mapping;
pub mod resolve;
pub mod service;
pub mod state;
pub mod util;

//pub mod mapping;
//pub mod resolve;
//pub mod service;
//pub mod state;
pub mod traits;
//pub mod util;

/*
use self::{
activitypub::Fetcher,
job::KitsuneContextRepo,
Expand Down Expand Up @@ -364,3 +365,5 @@ pub async fn prepare_state(
webfinger,
})
}
*/
66 changes: 66 additions & 0 deletions crates/kitsune-core/src/traits.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
use kitsune_db::model::{account::Account, custom_emoji::CustomEmoji, post::Post};
use serde::{Deserialize, Serialize};
use std::future::Future;
use typed_builder::TypedBuilder;

#[derive(Clone, Debug, TypedBuilder)]
/// Options passed to the fetcher
pub struct AccountFetchOptions<'a> {
/// Prefetched WebFinger `acct` URI
#[builder(default, setter(strip_option))]
pub acct: Option<(&'a str, &'a str)>,

/// Refetch the ActivityPub entity
///
/// This is mainly used to refresh possibly stale actors
///
/// Default: false
#[builder(default = false)]
pub refetch: bool,

/// URL of the ActivityPub entity
pub url: &'a str,
}

impl<'a> From<&'a str> for AccountFetchOptions<'a> {
fn from(value: &'a str) -> Self {
Self::builder().url(value).build()
}
}

/// Description of a resolved account
#[derive(Clone, Debug, Deserialize, Serialize)]
pub struct AccountResource {
/// The `self` link (the account's URI)
pub uri: String,
/// The username part of the canonical `acct:` URI
pub username: String,
/// The host component of the canonical `acct:` URI
pub domain: String,
}

pub trait Fetcher {
type Error;

fn fetch_account(
&self,
opts: AccountFetchOptions<'_>,
) -> impl Future<Output = Result<Account, Self::Error>> + Send;

fn fetch_emoji(
&self,
url: &str,
) -> impl Future<Output = Result<CustomEmoji, Self::Error>> + Send;

fn fetch_post(&self, url: &str) -> impl Future<Output = Result<Post, Self::Error>> + Send;
}

pub trait Resolver {
type Error;

fn resolve_account(
&self,
username: &str,
domain: &str,
) -> impl Future<Output = Result<Option<AccountResource>, Self::Error>> + Send;
}
1 change: 1 addition & 0 deletions crates/kitsune-webfinger/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ futures-util = "0.3.29"
http = "0.2.10"
kitsune-cache = { path = "../kitsune-cache" }
kitsune-consts = { path = "../kitsune-consts" }
kitsune-core = { path = "../kitsune-core" }
kitsune-http-client = { path = "../kitsune-http-client" }
kitsune-type = { path = "../kitsune-type" }
kitsune-util = { path = "../kitsune-util" }
Expand Down
32 changes: 12 additions & 20 deletions crates/kitsune-webfinger/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
#[macro_use]
extern crate tracing;

use crate::error::Result;
use crate::error::{Error, Result};
use autometrics::autometrics;
use futures_util::future::{FutureExt, OptionFuture};
use http::{HeaderValue, StatusCode};
use kitsune_cache::{ArcCache, CacheBackend, RedisCache};
use kitsune_consts::USER_AGENT;
use kitsune_core::traits::{AccountResource, Resolver};
use kitsune_http_client::Client;
use kitsune_type::webfinger::Resource;
use kitsune_util::try_join;
use serde::{Deserialize, Serialize};
use std::{ptr, sync::Arc, time::Duration};

pub mod error;
Expand All @@ -24,22 +24,10 @@ pub const MAX_JRD_REDIRECTS: u32 = 3;

#[derive(Clone)]
pub struct Webfinger {
cache: ArcCache<str, ActorResource>,
cache: ArcCache<str, AccountResource>,
client: Client,
}

#[allow(clippy::doc_markdown)] // "WebFinger" here isn't referring to the item name
/// Description of an ActivityPub actor resolved via WebFinger
#[derive(Clone, Debug, Deserialize, Serialize)]
pub struct ActorResource {
/// The `self` link (the actor's URI)
pub uri: String,
/// The username part of the canonical `acct:` URI
pub username: String,
/// The host component of the canonical `acct:` URI
pub domain: String,
}

impl Webfinger {
#[must_use]
pub fn with_defaults(redis_conn: deadpool_redis::Pool) -> Self {
Expand All @@ -52,7 +40,7 @@ impl Webfinger {
impl Webfinger {
#[allow(clippy::missing_panics_doc)] // The invariants are covered. It won't panic.
#[must_use]
pub fn new(cache: ArcCache<str, ActorResource>) -> Self {
pub fn new(cache: ArcCache<str, AccountResource>) -> Self {
let client = Client::builder()
.default_header("Accept", HeaderValue::from_static("application/jrd+json"))
.unwrap()
Expand All @@ -64,9 +52,13 @@ impl Webfinger {
}

#[must_use]
pub fn with_client(client: Client, cache: ArcCache<str, ActorResource>) -> Self {
pub fn with_client(client: Client, cache: ArcCache<str, AccountResource>) -> Self {
Self { cache, client }
}
}

impl Resolver for Webfinger {
type Error = Error;

/// Resolves the `acct:{username}@{domain}` URI via WebFinger to get the object ID and the
/// canonical `acct:` URI of an ActivityPub actor
Expand All @@ -76,11 +68,11 @@ impl Webfinger {
/// which the caller should check by themselves before trusting the result.
#[instrument(skip(self))]
#[autometrics(track_concurrency)]
pub async fn resolve_actor(
async fn resolve_account(
&self,
username: &str,
domain: &str,
) -> Result<Option<ActorResource>> {
) -> Result<Option<AccountResource>, Self::Error> {
// XXX: Assigning the arguments to local bindings because the `#[instrument]` attribute
// desugars to an `async move {}` block, inside which mutating the function arguments would
// upset the borrowck
Expand Down Expand Up @@ -143,7 +135,7 @@ impl Webfinger {
return Ok(None);
};

let ret = ActorResource {
let ret = AccountResource {
username: username.to_owned(),
domain: domain.to_owned(),
uri,
Expand Down
3 changes: 2 additions & 1 deletion crates/kitsune-webfinger/tests/basic.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
use hyper::{Body, Request, Response};
use kitsune_cache::NoopCache;
use kitsune_core::traits::Resolver;
use kitsune_http_client::Client;
use kitsune_webfinger::Webfinger;
use pretty_assertions::assert_eq;
Expand All @@ -20,7 +21,7 @@ async fn basic() {

let webfinger = Webfinger::with_client(client, Arc::new(NoopCache.into()));
let resource = webfinger
.resolve_actor("0x0", "corteximplant.com")
.resolve_account("0x0", "corteximplant.com")
.await
.expect("Failed to fetch resource")
.unwrap();
Expand Down
5 changes: 4 additions & 1 deletion crates/kitsune-webfinger/tests/redirects.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
use hyper::{Body, Request, Response, StatusCode};
use kitsune_cache::NoopCache;
use kitsune_core::traits::Resolver;
use kitsune_http_client::Client;
use kitsune_type::webfinger::Resource;
use kitsune_webfinger::{Webfinger, MAX_JRD_REDIRECTS};
Expand All @@ -15,6 +16,7 @@ async fn follow_jrd_redirect() {
..simd_json::from_slice(&mut base).unwrap()
})
.unwrap();

let client = service_fn(move |req: Request<_>| {
let body = body.clone();
async move {
Expand All @@ -33,11 +35,12 @@ async fn follow_jrd_redirect() {
}
}
});

let client = Client::builder().service(client);

let webfinger = Webfinger::with_client(client, Arc::new(NoopCache.into()));
let resource = webfinger
.resolve_actor("0x0", "corteximplant.com")
.resolve_account("0x0", "corteximplant.com")
.await
.expect("Failed to fetch resource")
.unwrap();
Expand Down
File renamed without changes.
File renamed without changes.
File renamed without changes.

0 comments on commit 27ebb84

Please sign in to comment.