1#![allow(deprecated)]
29
30use std::sync::Arc;
31use async_trait::async_trait;
32
33#[async_trait]
40#[deprecated(since = "3.15.0", note = "nothing calls it; hook triggers run through backbone_core::write_hooks (the generated <Model>WriteHooks and <Model>Triggers)")]
41pub trait TriggerHandler<Ctx, Evt>: Send + Sync {
42 fn events(&self) -> Vec<Evt>;
44
45 async fn handle(&self, ctx: &Ctx) -> anyhow::Result<()>;
47
48 fn priority(&self) -> i32 { 0 }
50
51 fn continue_on_error(&self) -> bool { false }
53}
54
55#[derive(Debug, Clone, PartialEq, Eq)]
61#[deprecated(since = "3.15.0", note = "nothing calls it; hook triggers run through backbone_core::write_hooks (the generated <Model>WriteHooks and <Model>Triggers)")]
62pub enum TriggerEvent {
63 BeforeCreate,
64 AfterCreate,
65 BeforeUpdate,
66 AfterUpdate,
67 BeforeDelete,
68 AfterDelete,
69 OnTransition { from: String, to: String, transition: String },
70 OnEnterState(String),
71 OnExitState(String),
72}
73
74impl TriggerEvent {
75 pub fn is_before_event(&self) -> bool {
76 matches!(self, Self::BeforeCreate | Self::BeforeUpdate | Self::BeforeDelete)
77 }
78}
79
80#[derive(Debug, Clone)]
84#[deprecated(since = "3.15.0", note = "nothing calls it; hook triggers run through backbone_core::write_hooks (the generated <Model>WriteHooks and <Model>Triggers)")]
85pub struct TriggerContext<T: Clone> {
86 pub event: TriggerEvent,
87 pub entity: T,
88 pub previous: Option<T>,
89 pub user_id: Option<String>,
90 pub metadata: std::collections::HashMap<String, String>,
91}
92
93impl<T: Clone> TriggerContext<T> {
94 pub fn new(event: TriggerEvent, entity: T) -> Self {
95 Self { event, entity, previous: None, user_id: None, metadata: std::collections::HashMap::new() }
96 }
97
98 pub fn with_previous(mut self, previous: T) -> Self {
99 self.previous = Some(previous);
100 self
101 }
102
103 pub fn with_user(mut self, user_id: impl Into<String>) -> Self {
104 self.user_id = Some(user_id.into());
105 self
106 }
107
108 pub fn with_metadata(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
109 self.metadata.insert(key.into(), value.into());
110 self
111 }
112
113 pub fn is_before_event(&self) -> bool {
114 self.event.is_before_event()
115 }
116}
117
118#[derive(Debug)]
122#[deprecated(since = "3.15.0", note = "nothing calls it; hook triggers run through backbone_core::write_hooks (the generated <Model>WriteHooks and <Model>Triggers)")]
123pub struct TriggerContextMut<T: Clone> {
124 pub event: TriggerEvent,
125 pub entity: T,
126 pub previous: Option<T>,
127 pub user_id: Option<String>,
128 pub metadata: std::collections::HashMap<String, String>,
129}
130
131impl<T: Clone> TriggerContextMut<T> {
132 pub fn new(event: TriggerEvent, entity: T) -> Self {
133 Self { event, entity, previous: None, user_id: None, metadata: std::collections::HashMap::new() }
134 }
135
136 pub fn into_entity(self) -> T {
137 self.entity
138 }
139
140 pub fn into_context(self) -> TriggerContext<T> {
141 TriggerContext {
142 event: self.event,
143 entity: self.entity,
144 previous: self.previous,
145 user_id: self.user_id,
146 metadata: self.metadata,
147 }
148 }
149}
150
151#[deprecated(since = "3.15.0", note = "nothing calls it; hook triggers run through backbone_core::write_hooks (the generated <Model>WriteHooks and <Model>Triggers)")]
157pub struct ActionExecutor;
158
159impl ActionExecutor {
160 pub fn new() -> Self { Self }
161
162 pub async fn send_email<C>(&self, _ctx: &C, template: &str) -> anyhow::Result<()> {
163 tracing::info!("Sending email with template: {}", template);
164 Ok(())
165 }
166
167 pub async fn notify<C>(&self, _ctx: &C, channel: &str) -> anyhow::Result<()> {
168 tracing::info!("Sending notification to channel: {}", channel);
169 Ok(())
170 }
171
172 pub async fn webhook<C>(&self, _ctx: &C, url: &str) -> anyhow::Result<()> {
173 tracing::info!("Calling webhook: {}", url);
174 Ok(())
175 }
176
177 pub async fn emit<C>(&self, _ctx: &C, event_name: &str) -> anyhow::Result<()> {
178 tracing::info!("Emitting event: {}", event_name);
179 Ok(())
180 }
181}
182
183impl Default for ActionExecutor {
184 fn default() -> Self { Self::new() }
185}
186
187#[deprecated(since = "3.15.0", note = "nothing calls it; hook triggers run through backbone_core::write_hooks (the generated <Model>WriteHooks and <Model>Triggers)")]
191pub struct TriggerRegistry<T: Clone + Send + Sync + 'static> {
192 handlers: Vec<Arc<dyn TriggerHandler<TriggerContext<T>, TriggerEvent>>>,
193}
194
195impl<T: Clone + Send + Sync + 'static> TriggerRegistry<T> {
196 pub fn new() -> Self {
197 Self { handlers: Vec::new() }
198 }
199
200 pub fn register(&mut self, handler: Arc<dyn TriggerHandler<TriggerContext<T>, TriggerEvent>>) {
201 self.handlers.push(handler);
202 self.handlers.sort_by_key(|h| h.priority());
203 }
204
205 pub async fn execute(&self, ctx: &TriggerContext<T>) -> anyhow::Result<()> {
206 for handler in &self.handlers {
207 if !handler.events().iter().any(|e| event_matches(e, &ctx.event)) {
208 continue;
209 }
210 match handler.handle(ctx).await {
211 Ok(()) => {}
212 Err(e) => {
213 tracing::error!("Trigger handler error: {:?}", e);
214 if !handler.continue_on_error() {
215 return Err(e);
216 }
217 }
218 }
219 }
220 Ok(())
221 }
222
223 pub fn with_defaults() -> Self {
224 Self::new()
225 }
226
227 pub fn build(f: impl FnOnce(&mut Self)) -> Self {
235 let mut r = Self::new();
236 f(&mut r);
237 r
238 }
239}
240
241impl<T: Clone + Send + Sync + 'static> Default for TriggerRegistry<T> {
242 fn default() -> Self { Self::with_defaults() }
243}
244
245fn event_matches(a: &TriggerEvent, b: &TriggerEvent) -> bool {
246 match (a, b) {
247 (TriggerEvent::BeforeCreate, TriggerEvent::BeforeCreate) => true,
248 (TriggerEvent::AfterCreate, TriggerEvent::AfterCreate) => true,
249 (TriggerEvent::BeforeUpdate, TriggerEvent::BeforeUpdate) => true,
250 (TriggerEvent::AfterUpdate, TriggerEvent::AfterUpdate) => true,
251 (TriggerEvent::BeforeDelete, TriggerEvent::BeforeDelete) => true,
252 (TriggerEvent::AfterDelete, TriggerEvent::AfterDelete) => true,
253 (TriggerEvent::OnEnterState(x), TriggerEvent::OnEnterState(y)) => x == y,
254 (TriggerEvent::OnExitState(x), TriggerEvent::OnExitState(y)) => x == y,
255 (TriggerEvent::OnTransition { transition: t1, .. },
256 TriggerEvent::OnTransition { transition: t2, .. }) => t1 == t2,
257 _ => false,
258 }
259}