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 /// Notification method configuration
#[derive(Debug, Deserialize, Serialize, Clone)] #[derive(Debug, Deserialize, Serialize, Clone)]
pub struct NotificationMethod { 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 /// Template definition
#[serde(default)] #[serde(default)]
pub template: NotificationTemplate, pub template: NotificationTemplate,
+55 -109
View File
@@ -1,23 +1,24 @@
use crate::constant::FINALIZE_STANDALONE_PLUGIN_PATH; use crate::constant::FINALIZE_STANDALONE_PLUGIN_PATH;
use crate::finalize::FinalizeConfig; use crate::finalize::FinalizeConfig;
use crate::finalize::NotificationConfig;
use crate::finalize::parse; use crate::finalize::parse;
use crate::finalize::plugin; use crate::finalize::plugin;
use crate::finalize::plugin::FinalizePlugin;
use crate::finalize::plugin::fetch; use crate::finalize::plugin::fetch;
use crate::types::buildstatus::StagePhase; use crate::notify::types::BundledNotifyEvent;
use crate::types::buildstatus::StageResult; 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 crate::{EventSender, ExecutionContext};
use chrono::{DateTime, Utc};
use serde::Serialize;
use std::collections::HashMap;
use std::collections::HashSet; use std::collections::HashSet;
use std::hash::Hash;
use std::path::Path; use std::path::Path;
use std::path::PathBuf; use std::path::PathBuf;
use uuid::Uuid;
mod error; mod error;
mod types;
use error::NotifyError; use error::NotifyError;
fn validate(finalize: &FinalizeConfig) -> anyhow::Result<()> { fn validate(finalize: &FinalizeConfig) -> anyhow::Result<()> {
@@ -31,8 +32,6 @@ fn validate(finalize: &FinalizeConfig) -> anyhow::Result<()> {
Ok(()) Ok(())
} }
pub type NotifyPluginMap = HashMap<String, FinalizePlugin>;
fn extract_plugin(finalize: &FinalizeConfig) -> HashSet<String> { fn extract_plugin(finalize: &FinalizeConfig) -> HashSet<String> {
finalize finalize
.notification .notification
@@ -43,60 +42,6 @@ fn extract_plugin(finalize: &FinalizeConfig) -> HashSet<String> {
.collect() .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( pub async fn notify(
finalize_path: &Path, finalize_path: &Path,
ctx: &mut ExecutionContext, ctx: &mut ExecutionContext,
@@ -124,53 +69,54 @@ pub async fn notify(
} }
let notify_plugins = plugin::register(finalize.plugin, Some(&plugin_list))?; let notify_plugins = plugin::register(finalize.plugin, Some(&plugin_list))?;
loop { // loop {
tokio::select! { // tokio::select! {
Some(events) = notifyevent_rx.recv() => { // Some(events) = notifyevent_rx.recv() => {
match notify_handler(&notify_plugins, &finalize.notification, &events){ // match notify_handler(&notify_plugins, &finalize.notification, &events){
Ok(_) => {}, // Notification succeeded / failed with fallible // Ok(_) => {}, // Notification succeeded / failed with fallible
Err(e) => { // Err(e) => {
log::debug!("Failed to send notification: {:?}", e); // log::debug!("Failed to send notification: {:?}", e);
match e { // match e {
NotifyError::PluginNotAvailable(_) => { // NotifyError::PluginNotAvailable(_) => {
// Should keep lives, add static delay and give less priority // // Should keep lives, add static delay and give less priority
for mut event in events { // for mut event in events {
event.not_before = Utc::now() + NOTIFY_NOTREADY_DELAY; // event.not_before = Utc::now() + NOTIFY_NOTREADY_DELAY;
event.effective_priority = event.effective_priority.saturating_add(1); // event.effective_priority = event.effective_priority.saturating_add(1);
notifyevent_tx.send(event).await?; // notifyevent_tx.send(event).await?;
} // }
}, // },
NotifyError::TemporaryError(_) => { // NotifyError::TemporaryError(_) => {
// Should cost 1 life, add dynamic (exponential) delay // // Should cost 1 life, add dynamic (exponential) delay
for mut event in events { // for mut event in events {
event.lives -= 1; // event.lives -= 1;
event.not_before = Utc::now() + notify_delay(&event); // event.not_before = Utc::now() + notify_delay(&event);
notifyevent_tx.send(event).await?; // notifyevent_tx.send(event).await?;
} // }
}, // },
NotifyError::PermanentError(_) => { // NotifyError::PermanentError(_) => {
// Unexpected! // // Unexpected!
// Should be dropped // // Should be dropped
log::error!("None of the configured notification plugins works."); // log::error!("None of the configured notification plugins works.");
log::error!("Message will be dropped."); // log::error!("Message will be dropped.");
// TODO: Send audit/metric information // // TODO: Send audit/metric information
} // }
error => { // error => {
// Unexpected! // // Unexpected!
log::error!("Internal error: notify_handler returned unexpected error: {:?}", error); // log::error!("Internal error: notify_handler returned unexpected error: {:?}", error);
} // }
}; // };
} // }
}; // };
} // }
} // }
} // }
todo!();
} }
fn notify_handler( fn notification_handler(
plugins: &NotifyPluginMap, plugins: &NotifyPluginMap,
config: &NotificationConfig, config: &NotificationRulesMap,
event: &Vec<NotifyEvent>, event: &BundledNotifyEvent,
) -> Result<(), NotifyError> { ) -> Result<(), NotifyError> {
todo!(); 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;