From 28da169259d0426e5506a28df8de971c06b4d173 Mon Sep 17 00:00:00 2001 From: Felix Ableitner Date: Wed, 15 May 2024 12:33:15 +0200 Subject: [PATCH] allow loading multiple plugins --- crates/apub/src/objects/post.rs | 21 ++------- crates/apub/src/plugins.rs | 79 +++++++++++++++++++++------------ src/plugin_middleware.rs | 39 ++++++++-------- 3 files changed, 72 insertions(+), 67 deletions(-) diff --git a/crates/apub/src/objects/post.rs b/crates/apub/src/objects/post.rs index 9650ef54e..3f84fe3ed 100644 --- a/crates/apub/src/objects/post.rs +++ b/crates/apub/src/objects/post.rs @@ -3,7 +3,7 @@ use crate::{ check_apub_id_valid_with_strictness, local_site_data_cached, objects::{read_from_string_or_source_opt, verify_is_remote_object}, - plugins::{call_plugin, load_plugins}, + plugins::Plugins, protocol::{ objects::{ page::{Attachment, AttributedTo, Hashtag, HashtagType, Page, PageType}, @@ -51,7 +51,6 @@ use lemmy_utils::{ }; use std::ops::Deref; use stringreader::StringReader; -use tracing::info; use url::Url; const MAX_TITLE_LENGTH: usize = 200; @@ -277,25 +276,13 @@ impl Object for ApubPost { .build() }; - // TODO: move this all into helper function - let before_plugin_hook = "federation_before_receive_post"; - info!("Calling plugin hook {}", &before_plugin_hook); - if let Some(mut plugins) = load_plugins()? { - if plugins.function_exists(&before_plugin_hook) { - call_plugin(plugins, &before_plugin_hook, &mut form)?; - } - } + let mut plugins = Plugins::load()?; + plugins.call("federation_before_receive_post", &mut form)?; let timestamp = page.updated.or(page.published).unwrap_or_else(naive_now); let mut post = Post::insert_apub(&mut context.pool(), timestamp, &form).await?; - let after_plugin_hook = "federation_after_receive_post"; - info!("Calling plugin hook {}", &after_plugin_hook); - if let Some(mut plugins) = load_plugins()? { - if plugins.function_exists(&after_plugin_hook) { - call_plugin(plugins, &after_plugin_hook, &mut post)?; - } - } + plugins.call("federation_after_receive_post", &mut post)?; generate_post_link_metadata( post.clone(), diff --git a/crates/apub/src/plugins.rs b/crates/apub/src/plugins.rs index 98a666aac..8c72509b1 100644 --- a/crates/apub/src/plugins.rs +++ b/crates/apub/src/plugins.rs @@ -2,38 +2,59 @@ use extism::{Manifest, Plugin}; use lemmy_utils::{error::LemmyResult, LemmyErrorType}; use serde::{Deserialize, Serialize}; use std::{ffi::OsStr, fs::read_dir}; +use tracing::info; -pub fn load_plugins() -> LemmyResult> { - // TODO: make dir configurable via env var - // TODO: should only read fs once at startup for performance - let plugin_paths = read_dir("plugins")?; +pub struct Plugins { + plugins: Vec, +} - let mut wasm_files = vec![]; - for path in plugin_paths { - let path = path?.path(); - if path.extension() == Some(OsStr::new("wasm")) { - wasm_files.push(path); +impl Plugins { + pub fn load() -> LemmyResult { + // TODO: make dir configurable via env var + // TODO: should only read fs once at startup for performance + let plugin_paths = read_dir("plugins")?; + + let mut wasm_files = vec![]; + for path in plugin_paths { + let path = path?.path(); + if path.extension() == Some(OsStr::new("wasm")) { + wasm_files.push(path); + } } + let plugins = wasm_files + .into_iter() + .map(|w| { + let manifest = Manifest::new(vec![w]); + Plugin::new(manifest, [], true).unwrap() + }) + .collect(); + Ok(Self { plugins }) } - if !wasm_files.is_empty() { - // TODO: what if theres more than one plugin for the same hook? - let manifest = Manifest::new(wasm_files); - let plugin = Plugin::new(manifest, [], true)?; - Ok(Some(plugin)) - } else { - Ok(None) - } -} -pub fn call_plugin Deserialize<'de> + Clone>( - mut plugins: Plugin, - name: &str, - data: &mut T, -) -> LemmyResult<()> { - *data = plugins - .call::, extism_convert::Json>(name, (*data).clone().into()) - .map_err(|e| LemmyErrorType::PluginError(e.to_string()))? - .0 - .into(); - Ok(()) + pub fn exists(&mut self, name: &str) -> bool { + for p in &mut self.plugins { + if p.function_exists(name) { + return true; + } + } + false + } + + pub fn call Deserialize<'de> + Clone>( + &mut self, + name: &str, + data: &mut T, + ) -> LemmyResult<()> { + info!("Calling plugin hook {name}"); + for p in &mut self.plugins { + if p.function_exists(name) { + *data = p + .call::, extism_convert::Json>(name, (*data).clone().into()) + .map_err(|e| LemmyErrorType::PluginError(e.to_string()))? + .0 + .into(); + } + } + Ok(()) + } } diff --git a/src/plugin_middleware.rs b/src/plugin_middleware.rs index f2ba4356f..d650772df 100644 --- a/src/plugin_middleware.rs +++ b/src/plugin_middleware.rs @@ -7,7 +7,7 @@ use actix_web::{ }; use core::future::Ready; use futures_util::future::LocalBoxFuture; -use lemmy_apub::plugins::{call_plugin, load_plugins}; +use lemmy_apub::plugins::Plugins; use serde_json::Value; use std::{future::ready, rc::Rc}; use tracing::info; @@ -61,33 +61,30 @@ where let path = service_req.path().replace("/api/v3/", "").replace("/", "_"); // TODO: naming can be a bit silly, `POST /api/v3/post` becomes `api_before_post_post` let before_plugin_hook = format!("api_before_{method}_{path}").to_lowercase(); + let mut plugins = Plugins::load()?; info!("Calling plugin hook {}", &before_plugin_hook); - if let Some(mut plugins) = load_plugins()? { - if plugins.function_exists(&before_plugin_hook) { - let payload = service_req.extract::().await?; - - let mut json: Value = serde_json::from_slice(&payload.to_vec())?; - call_plugin(plugins, &before_plugin_hook, &mut json)?; - - let (_, mut new_payload) = Payload::create(true); - new_payload.unread_data(Bytes::from(serde_json::to_vec(&json)?)); - service_req.set_payload(new_payload.into()); - } + if plugins.exists(&before_plugin_hook) { + let payload = service_req.extract::().await?; + let mut json: Value = serde_json::from_slice(&payload.to_vec()).unwrap_or(Value::Null); + plugins.call(&before_plugin_hook, &mut json)?; + let (_, mut new_payload) = Payload::create(true); + new_payload.unread_data(Bytes::from(serde_json::to_vec(&json)?)); + service_req.set_payload(new_payload.into()); } + let mut res = svc.call(service_req).await?; let after_plugin_hook = format!("api_after_{method}_{path}").to_lowercase(); + info!("Calling plugin hook {}", &after_plugin_hook); - if let Some(mut plugins) = load_plugins()? { - if plugins.function_exists(&before_plugin_hook) { - res = res.map_body(|_, body| { - let mut json: Value = - serde_json::from_slice(&body.try_into_bytes().unwrap().to_vec()).unwrap(); - call_plugin(plugins, &after_plugin_hook, &mut json).unwrap(); - BoxBody::new(Bytes::from(serde_json::to_vec(&json).unwrap())) - }); - } + if plugins.exists(&after_plugin_hook) { + res = res.map_body(|_, body| { + let mut json: Value = + serde_json::from_slice(&body.try_into_bytes().unwrap().to_vec()).unwrap(); + plugins.call(&after_plugin_hook, &mut json).unwrap(); + BoxBody::new(Bytes::from(serde_json::to_vec(&json).unwrap())) + }); } Ok(res)