From 659a6a3cff374f58959a6cadb265933618003cdf Mon Sep 17 00:00:00 2001 From: Nutomic Date: Wed, 18 Jun 2025 09:38:34 +0000 Subject: [PATCH] Add hook for incoming activities (#146) * Add hook for incoming activities * sync version working * async working * remove generic * separate methods * testing * use trait to allow references --- src/actix_web/inbox.rs | 102 +++++++++++++++++++++++++++++++++++++++-- 1 file changed, 98 insertions(+), 4 deletions(-) diff --git a/src/actix_web/inbox.rs b/src/actix_web/inbox.rs index 0912e72..b441055 100644 --- a/src/actix_web/inbox.rs +++ b/src/actix_web/inbox.rs @@ -20,6 +20,66 @@ pub async fn receive_activity( body: Bytes, data: &Data, ) -> Result::Error> +where + Activity: ActivityHandler + DeserializeOwned + Send + 'static, + ActorT: Object + Actor + Send + 'static, + for<'de2> ::Kind: serde::Deserialize<'de2>, + ::Error: From + From<::Error>, + ::Error: From, + Datatype: Clone, +{ + let (activity, _) = do_stuff::(request, body, data).await?; + + do_more_stuff(activity, data).await +} + +/// Workaround required so we can use references for the hook, instead of cloning data. +pub trait ReceiveActivityHook +where + Activity: ActivityHandler + DeserializeOwned + Send + Clone + 'static, + ActorT: Object + Actor + Send + Clone + 'static, + for<'de2> ::Kind: serde::Deserialize<'de2>, + ::Error: From + From<::Error>, + ::Error: From, + Datatype: Clone, +{ + /// Called when a new activity is recived + fn hook( + self, + activity: &Activity, + actor: &ActorT, + data: &Data, + ) -> impl std::future::Future::Error>>; +} + +/// Same as [receive_activity], only that it calls the provided hook function before +/// calling activity verify and receive functions. +pub async fn receive_activity_with_hook( + request: HttpRequest, + body: Bytes, + hook: impl ReceiveActivityHook, + data: &Data, +) -> Result::Error> +where + Activity: ActivityHandler + DeserializeOwned + Send + Clone + 'static, + ActorT: Object + Actor + Send + Clone + 'static, + for<'de2> ::Kind: serde::Deserialize<'de2>, + ::Error: From + From<::Error>, + ::Error: From, + Datatype: Clone, +{ + let (activity, actor) = do_stuff::(request, body, data).await?; + + hook.hook(&activity, &actor, data).await?; + + do_more_stuff(activity, data).await +} + +async fn do_stuff( + request: HttpRequest, + body: Bytes, + data: &Data, +) -> Result<(Activity, ActorT), ::Error> where Activity: ActivityHandler + DeserializeOwned + Send + 'static, ActorT: Object + Actor + Send + 'static, @@ -41,6 +101,17 @@ where let uri = http_compat::uri(request.uri()); verify_signature(&headers, &method, &uri, actor.public_key_pem())?; + Ok((activity, actor)) +} + +async fn do_more_stuff( + activity: Activity, + data: &Data, +) -> Result::Error> +where + Activity: ActivityHandler + DeserializeOwned + Send + 'static, + Datatype: Clone, +{ debug!("Receiving activity {}", activity.id().to_string()); activity.verify(data).await?; activity.receive(data).await?; @@ -75,15 +146,38 @@ mod test { } #[tokio::test] - async fn test_receive_activity() { + async fn test_receive_activity_hook() { let (body, incoming_request, config) = setup_receive_test().await; - receive_activity::( + let res = receive_activity_with_hook::( incoming_request.to_http_request(), body, + Dummy, &config.to_request_data(), ) - .await - .unwrap(); + .await; + assert_eq!(res.err(), Some(Error::Other("test-error".to_string()))); + } + + struct Dummy; + + impl ReceiveActivityHook for Dummy + where + Activity: ActivityHandler + DeserializeOwned + Send + Clone + 'static, + ActorT: Object + Actor + Send + Clone + 'static, + for<'de2> ::Kind: serde::Deserialize<'de2>, + ::Error: From + From<::Error>, + ::Error: From, + Datatype: Clone, + { + async fn hook( + self, + _activity: &Activity, + _actor: &ActorT, + _data: &Data, + ) -> Result<(), ::Error> { + // ensure that hook gets called by returning this value + Err(Error::Other("test-error".to_string()).into()) + } } #[tokio::test]