mod filewatch;
mod manager;
mod queue;
mod schedule;
mod webhook;
pub use filewatch::{FileEvent, FileEventKind, FileWatchTrigger};
pub use manager::{TriggerHandle, TriggerManager};
pub use queue::QueueTrigger;
pub use schedule::ScheduleTrigger;
pub use webhook::{WebhookConfig, WebhookEvent, WebhookTrigger};
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::time::SystemTime;
pub type TriggerId = String;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TriggerEvent {
pub trigger_id: TriggerId,
pub event_type: String,
pub payload: serde_json::Value,
pub timestamp: u64,
#[serde(default)]
pub metadata: HashMap<String, String>,
}
impl TriggerEvent {
pub fn new(
trigger_id: impl Into<String>,
event_type: impl Into<String>,
payload: serde_json::Value,
) -> Self {
Self {
trigger_id: trigger_id.into(),
event_type: event_type.into(),
payload,
timestamp: SystemTime::now()
.duration_since(SystemTime::UNIX_EPOCH)
.unwrap_or_default()
.as_millis() as u64,
metadata: HashMap::new(),
}
}
pub fn with_metadata(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
self.metadata.insert(key.into(), value.into());
self
}
pub fn payload_as<T: for<'de> Deserialize<'de>>(&self) -> Result<T, serde_json::Error> {
serde_json::from_value(self.payload.clone())
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)]
pub enum TriggerStatus {
#[default]
Stopped,
Running,
Paused,
Error,
}
pub trait Trigger: Send + Sync {
fn id(&self) -> &str;
fn trigger_type(&self) -> &str;
fn status(&self) -> TriggerStatus;
fn start(&mut self) -> Result<(), TriggerError>;
fn stop(&mut self) -> Result<(), TriggerError>;
fn pause(&mut self) -> Result<(), TriggerError>;
fn resume(&mut self) -> Result<(), TriggerError>;
fn poll(&self) -> Option<TriggerEvent>;
fn has_pending(&self) -> bool {
false
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TriggerError {
pub message: String,
pub kind: TriggerErrorKind,
}
impl TriggerError {
pub fn new(kind: TriggerErrorKind, message: impl Into<String>) -> Self {
Self {
message: message.into(),
kind,
}
}
pub fn config(message: impl Into<String>) -> Self {
Self::new(TriggerErrorKind::Configuration, message)
}
pub fn runtime(message: impl Into<String>) -> Self {
Self::new(TriggerErrorKind::Runtime, message)
}
pub fn already_running() -> Self {
Self::new(
TriggerErrorKind::AlreadyRunning,
"Trigger is already running",
)
}
pub fn not_running() -> Self {
Self::new(TriggerErrorKind::NotRunning, "Trigger is not running")
}
}
impl std::fmt::Display for TriggerError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{:?}: {}", self.kind, self.message)
}
}
impl std::error::Error for TriggerError {}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum TriggerErrorKind {
Configuration,
Runtime,
AlreadyRunning,
NotRunning,
Io,
Network,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_trigger_event_new() {
let event = TriggerEvent::new("test-trigger", "test", serde_json::json!({"key": "value"}));
assert_eq!(event.trigger_id, "test-trigger");
assert_eq!(event.event_type, "test");
assert!(event.timestamp > 0);
}
#[test]
fn test_trigger_event_with_metadata() {
let event = TriggerEvent::new("test", "test", serde_json::json!(null))
.with_metadata("source", "unit-test");
assert_eq!(event.metadata.get("source"), Some(&"unit-test".to_string()));
}
#[test]
fn test_trigger_event_payload_as() {
#[derive(Debug, Deserialize, PartialEq)]
struct TestPayload {
message: String,
}
let event = TriggerEvent::new("test", "test", serde_json::json!({"message": "hello"}));
let payload: TestPayload = event.payload_as().unwrap();
assert_eq!(payload.message, "hello");
}
#[test]
fn test_trigger_error() {
let err = TriggerError::config("Invalid configuration");
assert_eq!(err.kind, TriggerErrorKind::Configuration);
assert!(err.to_string().contains("Invalid configuration"));
}
}