1use std::sync::Arc;
23use async_trait::async_trait;
24
25#[async_trait]
32pub trait TriggerHandler<Ctx, Evt>: Send + Sync {
33 fn events(&self) -> Vec<Evt>;
35
36 async fn handle(&self, ctx: &Ctx) -> anyhow::Result<()>;
38
39 fn priority(&self) -> i32 { 0 }
41
42 fn continue_on_error(&self) -> bool { false }
44}
45
46#[derive(Debug, Clone, PartialEq, Eq)]
52pub enum TriggerEvent {
53 BeforeCreate,
54 AfterCreate,
55 BeforeUpdate,
56 AfterUpdate,
57 BeforeDelete,
58 AfterDelete,
59 OnTransition { from: String, to: String, transition: String },
60 OnEnterState(String),
61 OnExitState(String),
62}
63
64impl TriggerEvent {
65 pub fn is_before_event(&self) -> bool {
66 matches!(self, Self::BeforeCreate | Self::BeforeUpdate | Self::BeforeDelete)
67 }
68}
69
70#[derive(Debug, Clone)]
74pub struct TriggerContext<T: Clone> {
75 pub event: TriggerEvent,
76 pub entity: T,
77 pub previous: Option<T>,
78 pub user_id: Option<String>,
79 pub metadata: std::collections::HashMap<String, String>,
80}
81
82impl<T: Clone> TriggerContext<T> {
83 pub fn new(event: TriggerEvent, entity: T) -> Self {
84 Self { event, entity, previous: None, user_id: None, metadata: std::collections::HashMap::new() }
85 }
86
87 pub fn with_previous(mut self, previous: T) -> Self {
88 self.previous = Some(previous);
89 self
90 }
91
92 pub fn with_user(mut self, user_id: impl Into<String>) -> Self {
93 self.user_id = Some(user_id.into());
94 self
95 }
96
97 pub fn with_metadata(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
98 self.metadata.insert(key.into(), value.into());
99 self
100 }
101
102 pub fn is_before_event(&self) -> bool {
103 self.event.is_before_event()
104 }
105}
106
107#[derive(Debug)]
111pub struct TriggerContextMut<T: Clone> {
112 pub event: TriggerEvent,
113 pub entity: T,
114 pub previous: Option<T>,
115 pub user_id: Option<String>,
116 pub metadata: std::collections::HashMap<String, String>,
117}
118
119impl<T: Clone> TriggerContextMut<T> {
120 pub fn new(event: TriggerEvent, entity: T) -> Self {
121 Self { event, entity, previous: None, user_id: None, metadata: std::collections::HashMap::new() }
122 }
123
124 pub fn into_entity(self) -> T {
125 self.entity
126 }
127
128 pub fn into_context(self) -> TriggerContext<T> {
129 TriggerContext {
130 event: self.event,
131 entity: self.entity,
132 previous: self.previous,
133 user_id: self.user_id,
134 metadata: self.metadata,
135 }
136 }
137}
138
139pub struct ActionExecutor;
145
146impl ActionExecutor {
147 pub fn new() -> Self { Self }
148
149 pub async fn send_email<C>(&self, _ctx: &C, template: &str) -> anyhow::Result<()> {
150 tracing::info!("Sending email with template: {}", template);
151 Ok(())
152 }
153
154 pub async fn notify<C>(&self, _ctx: &C, channel: &str) -> anyhow::Result<()> {
155 tracing::info!("Sending notification to channel: {}", channel);
156 Ok(())
157 }
158
159 pub async fn webhook<C>(&self, _ctx: &C, url: &str) -> anyhow::Result<()> {
160 tracing::info!("Calling webhook: {}", url);
161 Ok(())
162 }
163
164 pub async fn emit<C>(&self, _ctx: &C, event_name: &str) -> anyhow::Result<()> {
165 tracing::info!("Emitting event: {}", event_name);
166 Ok(())
167 }
168}
169
170impl Default for ActionExecutor {
171 fn default() -> Self { Self::new() }
172}
173
174pub struct TriggerRegistry<T: Clone + Send + Sync + 'static> {
178 handlers: Vec<Arc<dyn TriggerHandler<TriggerContext<T>, TriggerEvent>>>,
179}
180
181impl<T: Clone + Send + Sync + 'static> TriggerRegistry<T> {
182 pub fn new() -> Self {
183 Self { handlers: Vec::new() }
184 }
185
186 pub fn register(&mut self, handler: Arc<dyn TriggerHandler<TriggerContext<T>, TriggerEvent>>) {
187 self.handlers.push(handler);
188 self.handlers.sort_by_key(|h| h.priority());
189 }
190
191 pub async fn execute(&self, ctx: &TriggerContext<T>) -> anyhow::Result<()> {
192 for handler in &self.handlers {
193 if !handler.events().iter().any(|e| event_matches(e, &ctx.event)) {
194 continue;
195 }
196 match handler.handle(ctx).await {
197 Ok(()) => {}
198 Err(e) => {
199 tracing::error!("Trigger handler error: {:?}", e);
200 if !handler.continue_on_error() {
201 return Err(e);
202 }
203 }
204 }
205 }
206 Ok(())
207 }
208
209 pub fn with_defaults() -> Self {
210 Self::new()
211 }
212
213 pub fn build(f: impl FnOnce(&mut Self)) -> Self {
221 let mut r = Self::new();
222 f(&mut r);
223 r
224 }
225}
226
227impl<T: Clone + Send + Sync + 'static> Default for TriggerRegistry<T> {
228 fn default() -> Self { Self::with_defaults() }
229}
230
231fn event_matches(a: &TriggerEvent, b: &TriggerEvent) -> bool {
232 match (a, b) {
233 (TriggerEvent::BeforeCreate, TriggerEvent::BeforeCreate) => true,
234 (TriggerEvent::AfterCreate, TriggerEvent::AfterCreate) => true,
235 (TriggerEvent::BeforeUpdate, TriggerEvent::BeforeUpdate) => true,
236 (TriggerEvent::AfterUpdate, TriggerEvent::AfterUpdate) => true,
237 (TriggerEvent::BeforeDelete, TriggerEvent::BeforeDelete) => true,
238 (TriggerEvent::AfterDelete, TriggerEvent::AfterDelete) => true,
239 (TriggerEvent::OnEnterState(x), TriggerEvent::OnEnterState(y)) => x == y,
240 (TriggerEvent::OnExitState(x), TriggerEvent::OnExitState(y)) => x == y,
241 (TriggerEvent::OnTransition { transition: t1, .. },
242 TriggerEvent::OnTransition { transition: t2, .. }) => t1 == t2,
243 _ => false,
244 }
245}