From c4ac8c322562a3c2b6a46afd5c9fcd2e0a6b3777 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Le=C3=B3n=20Orell=20Valerian=20Liehr?= Date: Mon, 15 Apr 2024 19:13:42 +0200 Subject: [PATCH] Allow a single notify-zulip notification to send multiple Zulip messages --- src/config.rs | 53 ++++++++++++++++++++++++++++++--- src/handlers/notify_zulip.rs | 57 ++++++++++++++++++++---------------- src/zulip.rs | 2 +- 3 files changed, 82 insertions(+), 30 deletions(-) diff --git a/src/config.rs b/src/config.rs index e1ecce2d..a5b9132d 100644 --- a/src/config.rs +++ b/src/config.rs @@ -228,10 +228,26 @@ pub(crate) struct NotifyZulipConfig { pub(crate) struct NotifyZulipLabelConfig { pub(crate) zulip_stream: u64, pub(crate) topic: String, - pub(crate) message_on_add: Option, - pub(crate) message_on_remove: Option, - pub(crate) message_on_close: Option, - pub(crate) message_on_reopen: Option, + #[serde(rename = "message_on_add", default, deserialize_with = "string_or_seq")] + pub(crate) messages_on_add: Vec, + #[serde( + rename = "message_on_remove", + default, + deserialize_with = "string_or_seq" + )] + pub(crate) messages_on_remove: Vec, + #[serde( + rename = "message_on_close", + default, + deserialize_with = "string_or_seq" + )] + pub(crate) messages_on_close: Vec, + #[serde( + rename = "message_on_reopen", + default, + deserialize_with = "string_or_seq" + )] + pub(crate) messages_on_reopen: Vec, #[serde(default)] pub(crate) required_labels: Vec, } @@ -384,6 +400,35 @@ impl fmt::Display for ConfigurationError { } } +fn string_or_seq<'de, D>(deserializer: D) -> Result, D::Error> +where + D: serde::Deserializer<'de>, +{ + struct Visitor; + + impl<'de> serde::de::Visitor<'de> for Visitor { + type Value = Vec; + + fn expecting(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str("string or sequence of strings") + } + + fn visit_unit(self) -> Result { + Ok(Vec::new()) + } + + fn visit_str(self, value: &str) -> Result { + Ok(vec![value.to_owned()]) + } + + fn visit_seq>(self, seq: A) -> Result { + serde::Deserialize::deserialize(serde::de::value::SeqAccessDeserializer::new(seq)) + } + } + + deserializer.deserialize_any(Visitor) +} + #[cfg(test)] mod tests { use super::*; diff --git a/src/handlers/notify_zulip.rs b/src/handlers/notify_zulip.rs index 87d0a018..79a24462 100644 --- a/src/handlers/notify_zulip.rs +++ b/src/handlers/notify_zulip.rs @@ -60,11 +60,13 @@ fn parse_label_change_input( } match event.action { - IssuesAction::Labeled { .. } if config.message_on_add.is_some() => Some(NotifyZulipInput { - notification_type: NotificationType::Labeled, - label, - }), - IssuesAction::Unlabeled { .. } if config.message_on_remove.is_some() => { + IssuesAction::Labeled { .. } if !config.messages_on_add.is_empty() => { + Some(NotifyZulipInput { + notification_type: NotificationType::Labeled, + label, + }) + } + IssuesAction::Unlabeled { .. } if !config.messages_on_remove.is_empty() => { Some(NotifyZulipInput { notification_type: NotificationType::Unlabeled, label, @@ -96,13 +98,13 @@ fn parse_close_reopen_input( } match event.action { - IssuesAction::Closed if config.message_on_close.is_some() => { + IssuesAction::Closed if !config.messages_on_close.is_empty() => { Some(NotifyZulipInput { notification_type: NotificationType::Closed, label, }) } - IssuesAction::Reopened if config.message_on_reopen.is_some() => { + IssuesAction::Reopened if !config.messages_on_reopen.is_empty() => { Some(NotifyZulipInput { notification_type: NotificationType::Reopened, label, @@ -140,9 +142,9 @@ pub(super) async fn handle_input<'a>( for input in inputs { let config = &config.labels[&input.label.name]; - let mut topic = config.topic.clone(); - topic = topic.replace("{number}", &event.issue.number.to_string()); - topic = topic.replace("{title}", &event.issue.title); + let topic = &config.topic; + let topic = topic.replace("{number}", &event.issue.number.to_string()); + let mut topic = topic.replace("{title}", &event.issue.title); // Truncate to 60 chars (a Zulip limitation) let mut chars = topic.char_indices().skip(59); if let (Some((len, _)), Some(_)) = (chars.next(), chars.next()) { @@ -150,24 +152,29 @@ pub(super) async fn handle_input<'a>( topic.push('…'); } - let mut msg = match input.notification_type { - NotificationType::Labeled => config.message_on_add.as_ref().unwrap().clone(), - NotificationType::Unlabeled => config.message_on_remove.as_ref().unwrap().clone(), - NotificationType::Closed => config.message_on_close.as_ref().unwrap().clone(), - NotificationType::Reopened => config.message_on_reopen.as_ref().unwrap().clone(), + let msgs = match input.notification_type { + NotificationType::Labeled => &config.messages_on_add, + NotificationType::Unlabeled => &config.messages_on_remove, + NotificationType::Closed => &config.messages_on_close, + NotificationType::Reopened => &config.messages_on_reopen, }; - msg = msg.replace("{number}", &event.issue.number.to_string()); - msg = msg.replace("{title}", &event.issue.title); - - let zulip_req = crate::zulip::MessageApiRequest { - recipient: crate::zulip::Recipient::Stream { - id: config.zulip_stream, - topic: &topic, - }, - content: &msg, + let recipient = crate::zulip::Recipient::Stream { + id: config.zulip_stream, + topic: &topic, }; - zulip_req.send(&ctx.github.raw()).await?; + + for msg in msgs { + let msg = msg.replace("{number}", &event.issue.number.to_string()); + let msg = msg.replace("{title}", &event.issue.title); + + crate::zulip::MessageApiRequest { + recipient, + content: &msg, + } + .send(&ctx.github.raw()) + .await?; + } } Ok(()) diff --git a/src/zulip.rs b/src/zulip.rs index 29bcd2aa..65bcf198 100644 --- a/src/zulip.rs +++ b/src/zulip.rs @@ -337,7 +337,7 @@ pub struct Member { pub user_id: u64, } -#[derive(serde::Serialize)] +#[derive(Copy, Clone, serde::Serialize)] #[serde(tag = "type")] #[serde(rename_all = "snake_case")] pub enum Recipient<'a> {