use std::sync::Arc;
use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
use tokio::sync::mpsc;
use super::types::Trigger;
pub type TriggerSink = mpsc::UnboundedSender<Trigger>;
#[async_trait::async_trait]
pub trait NotificationHook: Send + Sync {
fn label(&self) -> &str;
async fn run(&self, sink: TriggerSink) -> Result<(), HookError>;
fn status(&self) -> NotificationHookStatus;
}
pub type DynNotificationHook = Arc<dyn NotificationHook>;
#[derive(Clone, Debug, thiserror::Error)]
pub enum HookError {
#[error("auth failed: {reason}")]
AuthFailed { reason: String },
#[error("protocol mismatch: {reason}")]
ProtocolMismatch { reason: String },
#[error("disconnected: {reason}")]
Disconnected { reason: String },
#[error("schema invalid: {reason}")]
SchemaInvalid { reason: String },
#[error("sink closed")]
SinkClosed,
#[error("hook error: {0}")]
Other(String),
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub struct NotificationHookStatus {
pub state: HookState,
pub last_event_at: Option<DateTime<Utc>>,
pub last_ack_at: Option<DateTime<Utc>>,
pub last_error: Option<String>,
pub queued_count: u64,
pub dropped_count: u64,
pub deduped_count: u64,
pub subscription_labels: Vec<String>,
pub requires_attention: Option<String>,
}
impl NotificationHookStatus {
pub fn pending() -> Self {
Self {
state: HookState::Disconnected {
reason: "not yet started".into(),
},
last_event_at: None,
last_ack_at: None,
last_error: None,
queued_count: 0,
dropped_count: 0,
deduped_count: 0,
subscription_labels: Vec::new(),
requires_attention: None,
}
}
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
#[serde(tag = "kind", rename_all = "snake_case")]
pub enum HookState {
Connected,
Reconnecting,
Disconnected {
reason: String,
},
Disabled,
AuthFailed {
reason: String,
},
}
#[cfg(test)]
tests_bridge_macro::tests_bridge!("trigger_engine/notification_hook");