refactor(notify): extract types to separate module and redesign

NotificationRule

- Move NotifyEvent, NotifyPluginMap, and related types to
  notify/types.rs
- Introduce NotificationRule struct replacing NotificationMethod fields
- Add NotificationRulesMap for priority-based notification configuration
- Comment out main event loop and add todo!() placeholders for WIP
This commit is contained in:
Catty Steve
2026-04-24 23:01:40 +08:00
parent 2c9d32e954
commit 5ce8b72900
3 changed files with 151 additions and 121 deletions
-12
View File
@@ -99,18 +99,6 @@ pub struct PluginRuntimeConfig {
/// Notification method configuration
#[derive(Debug, Deserialize, Serialize, Clone)]
pub struct NotificationMethod {
/// Whether notification failure should not trigger next priority group
#[serde(default)]
pub fallible: bool,
/// Number of retries on failure (0 = no retry, -1 = infinite retries)
#[serde(default)]
pub retry: i8,
/// Timeout for notification execution in milliseconds (0 = no timeout)
#[serde(default)]
pub timeout_ms: u64,
/// Template definition
#[serde(default)]
pub template: NotificationTemplate,
+55 -109
View File
@@ -1,23 +1,24 @@
use crate::constant::FINALIZE_STANDALONE_PLUGIN_PATH;
use crate::finalize::FinalizeConfig;
use crate::finalize::NotificationConfig;
use crate::finalize::parse;
use crate::finalize::plugin;
use crate::finalize::plugin::FinalizePlugin;
use crate::finalize::plugin::fetch;
use crate::types::buildstatus::StagePhase;
use crate::types::buildstatus::StageResult;
use crate::notify::types::BundledNotifyEvent;
use crate::notify::types::NOTIFY_TEMPORARY_DELAY_FACTOR;
use crate::notify::types::NOTIFY_TEMPORARY_DELAY_TIME;
use crate::notify::types::NOTIFY_TEMPORARY_MAX_TIME;
use crate::notify::types::NotificationRulesMap;
use crate::notify::types::NotifyEvent;
use crate::notify::types::NotifyEventReceiver;
use crate::notify::types::NotifyEventSender;
use crate::notify::types::NotifyPluginMap;
use crate::{EventSender, ExecutionContext};
use chrono::{DateTime, Utc};
use serde::Serialize;
use std::collections::HashMap;
use std::collections::HashSet;
use std::hash::Hash;
use std::path::Path;
use std::path::PathBuf;
use uuid::Uuid;
mod error;
mod types;
use error::NotifyError;
fn validate(finalize: &FinalizeConfig) -> anyhow::Result<()> {
@@ -31,8 +32,6 @@ fn validate(finalize: &FinalizeConfig) -> anyhow::Result<()> {
Ok(())
}
pub type NotifyPluginMap = HashMap<String, FinalizePlugin>;
fn extract_plugin(finalize: &FinalizeConfig) -> HashSet<String> {
finalize
.notification
@@ -43,60 +42,6 @@ fn extract_plugin(finalize: &FinalizeConfig) -> HashSet<String> {
.collect()
}
#[derive(Debug, Clone, Serialize)]
pub struct NotifyEvent {
id: Uuid,
stage: StagePhase,
substage: String,
result: StageResult,
not_before: DateTime<Utc>,
priority: u32,
effective_priority: u32,
lives: u32,
max_lives: u32,
target: HashSet<String>, // Target audience
}
impl Hash for NotifyEvent {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
self.id.hash(state);
}
}
impl PartialEq for NotifyEvent {
fn eq(&self, other: &Self) -> bool {
self.id == other.id
}
}
impl Eq for NotifyEvent {}
impl PartialOrd for NotifyEvent {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
Some(
self.effective_priority
.cmp(&other.effective_priority)
.then(self.not_before.cmp(&other.not_before))
.reverse(),
)
}
}
impl Ord for NotifyEvent {
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
self.partial_cmp(other).unwrap()
}
}
type BundledNotifyEvent = Vec<NotifyEvent>;
type NotifyEventReceiver = tokio::sync::mpsc::Receiver<BundledNotifyEvent>;
pub type NotifyEventSender = tokio::sync::mpsc::Sender<NotifyEvent>;
const NOTIFY_NOTREADY_DELAY: std::time::Duration = std::time::Duration::from_secs(5);
const NOTIFY_TEMPORARY_DELAY_FACTOR: f64 = 2.0;
const NOTIFY_TEMPORARY_DELAY_TIME: f64 = 5.0;
const NOTIFY_TEMPORARY_MAX_TIME: f64 = f64::MAX;
pub async fn notify(
finalize_path: &Path,
ctx: &mut ExecutionContext,
@@ -124,53 +69,54 @@ pub async fn notify(
}
let notify_plugins = plugin::register(finalize.plugin, Some(&plugin_list))?;
loop {
tokio::select! {
Some(events) = notifyevent_rx.recv() => {
match notify_handler(&notify_plugins, &finalize.notification, &events){
Ok(_) => {}, // Notification succeeded / failed with fallible
Err(e) => {
log::debug!("Failed to send notification: {:?}", e);
match e {
NotifyError::PluginNotAvailable(_) => {
// Should keep lives, add static delay and give less priority
for mut event in events {
event.not_before = Utc::now() + NOTIFY_NOTREADY_DELAY;
event.effective_priority = event.effective_priority.saturating_add(1);
notifyevent_tx.send(event).await?;
}
},
NotifyError::TemporaryError(_) => {
// Should cost 1 life, add dynamic (exponential) delay
for mut event in events {
event.lives -= 1;
event.not_before = Utc::now() + notify_delay(&event);
notifyevent_tx.send(event).await?;
}
},
NotifyError::PermanentError(_) => {
// Unexpected!
// Should be dropped
log::error!("None of the configured notification plugins works.");
log::error!("Message will be dropped.");
// TODO: Send audit/metric information
}
error => {
// Unexpected!
log::error!("Internal error: notify_handler returned unexpected error: {:?}", error);
}
};
}
};
}
}
}
// loop {
// tokio::select! {
// Some(events) = notifyevent_rx.recv() => {
// match notify_handler(&notify_plugins, &finalize.notification, &events){
// Ok(_) => {}, // Notification succeeded / failed with fallible
// Err(e) => {
// log::debug!("Failed to send notification: {:?}", e);
// match e {
// NotifyError::PluginNotAvailable(_) => {
// // Should keep lives, add static delay and give less priority
// for mut event in events {
// event.not_before = Utc::now() + NOTIFY_NOTREADY_DELAY;
// event.effective_priority = event.effective_priority.saturating_add(1);
// notifyevent_tx.send(event).await?;
// }
// },
// NotifyError::TemporaryError(_) => {
// // Should cost 1 life, add dynamic (exponential) delay
// for mut event in events {
// event.lives -= 1;
// event.not_before = Utc::now() + notify_delay(&event);
// notifyevent_tx.send(event).await?;
// }
// },
// NotifyError::PermanentError(_) => {
// // Unexpected!
// // Should be dropped
// log::error!("None of the configured notification plugins works.");
// log::error!("Message will be dropped.");
// // TODO: Send audit/metric information
// }
// error => {
// // Unexpected!
// log::error!("Internal error: notify_handler returned unexpected error: {:?}", error);
// }
// };
// }
// };
// }
// }
// }
todo!();
}
fn notify_handler(
fn notification_handler(
plugins: &NotifyPluginMap,
config: &NotificationConfig,
event: &Vec<NotifyEvent>,
config: &NotificationRulesMap,
event: &BundledNotifyEvent,
) -> Result<(), NotifyError> {
todo!();
}
+96
View File
@@ -0,0 +1,96 @@
use std::collections::HashMap;
use std::collections::HashSet;
use std::time::Duration;
use crate::finalize::plugin::FinalizePlugin;
use crate::types::buildstatus::StagePhase;
use crate::types::buildstatus::StageResult;
use chrono::{DateTime, Utc};
use serde::Serialize;
use std::hash::Hash;
use uuid::Uuid;
use crate::finalize::NotificationTemplate;
use crate::finalize::config::Trigger;
/// Final runtime configuration for notification, restored and flattened from yaml
pub struct NotificationRule{
pub enabled: bool,
pub trigger: Vec<Trigger>,
pub template: NotificationTemplate,
/// Whether notification failure should not trigger next priority group
pub fallible: bool,
/// Maximum number of attempts
pub max_lives: u32,
/// Timeout for notification execution
pub timeout: Duration,
/// Plugin-specific configuration
pub config: serde_yaml::Value,
/// Factor of exponential backoff for retry delay
// Not provided for external use
pub retry_backoff: f64,
}
pub type Priority = u32;
pub type Method = String;
pub type NotificationRulesMap = HashMap<Priority, HashMap<Method, Vec<NotificationRule>>>;
pub type BundledNotifyEvent = Vec<NotifyEvent>;
pub type NotifyEventReceiver = tokio::sync::mpsc::Receiver<BundledNotifyEvent>;
pub type NotifyEventSender = tokio::sync::mpsc::Sender<NotifyEvent>;
pub type NotifyPluginMap = HashMap<String, FinalizePlugin>;
#[derive(Debug, Clone, Serialize)]
pub struct NotifyEvent {
pub id: Uuid,
pub stage: StagePhase,
pub substage: String,
pub result: StageResult,
pub not_before: DateTime<Utc>,
pub priority: u32,
pub effective_priority: u32,
pub lives: u32,
pub max_lives: u32,
pub target: HashSet<String>, // Target audience
}
impl Hash for NotifyEvent {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
self.id.hash(state);
}
}
impl PartialEq for NotifyEvent {
fn eq(&self, other: &Self) -> bool {
self.id == other.id
}
}
impl Eq for NotifyEvent {}
impl PartialOrd for NotifyEvent {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
Some(
self.effective_priority
.cmp(&other.effective_priority)
.then(self.not_before.cmp(&other.not_before))
.reverse(),
)
}
}
impl Ord for NotifyEvent {
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
self.partial_cmp(other).unwrap()
}
}
pub const NOTIFY_NOTREADY_DELAY: std::time::Duration = std::time::Duration::from_secs(5);
pub const NOTIFY_TEMPORARY_DELAY_FACTOR: f64 = 2.0;
pub const NOTIFY_TEMPORARY_DELAY_TIME: f64 = 5.0;
pub const NOTIFY_TEMPORARY_MAX_TIME: f64 = f64::MAX;