Skip to main content

ralph/webhook/
notifications.rs

1//! Webhook notification convenience functions.
2//!
3//! Responsibilities:
4//! - Provide convenience functions for sending common webhook notifications.
5//!
6//! Not handled here:
7//! - Type definitions (see `super::types`).
8//! - Worker/delivery logic (see `super::worker`).
9
10use crate::contracts::WebhookConfig;
11
12use super::types::{WebhookContext, WebhookEventType, WebhookPayload};
13use super::worker::send_webhook_payload_internal;
14
15/// Send a webhook notification (non-blocking, enqueues for delivery).
16///
17/// This function returns immediately after enqueueing the webhook.
18/// Delivery happens asynchronously in a background worker thread.
19#[allow(clippy::too_many_arguments)]
20pub fn send_webhook(
21    event_type: WebhookEventType,
22    task_id: &str,
23    task_title: &str,
24    previous_status: Option<&str>,
25    current_status: Option<&str>,
26    note: Option<&str>,
27    config: &WebhookConfig,
28    timestamp_rfc3339: &str,
29) {
30    let payload = WebhookPayload {
31        event: event_type.as_str().to_string(),
32        timestamp: timestamp_rfc3339.to_string(),
33        task_id: Some(task_id.to_string()),
34        task_title: Some(task_title.to_string()),
35        previous_status: previous_status.map(|s| s.to_string()),
36        current_status: current_status.map(|s| s.to_string()),
37        note: note.map(|n| n.to_string()),
38        context: WebhookContext::default(),
39    };
40    send_webhook_payload_internal(payload, config, false);
41}
42
43/// Send a webhook payload directly (non-blocking, enqueues for delivery).
44///
45/// This is the low-level function that checks event filtering and enqueues.
46/// Prefer using the `notify_*` convenience functions for common events.
47pub fn send_webhook_payload(payload: WebhookPayload, config: &WebhookConfig) {
48    if !send_webhook_payload_internal(payload, config, false) {
49        // Detailed warning already logged by apply_backpressure_policy
50        log::debug!("Webhook enqueue failed (see warning above for details)");
51    }
52}
53
54/// Convenience function to send task creation webhook.
55pub fn notify_task_created(
56    task_id: &str,
57    task_title: &str,
58    config: &WebhookConfig,
59    timestamp_rfc3339: &str,
60) {
61    send_webhook(
62        WebhookEventType::TaskCreated,
63        task_id,
64        task_title,
65        None,
66        None,
67        None,
68        config,
69        timestamp_rfc3339,
70    );
71}
72
73/// Convenience function to send task creation webhook with context.
74pub fn notify_task_created_with_context(
75    task_id: &str,
76    task_title: &str,
77    config: &WebhookConfig,
78    timestamp_rfc3339: &str,
79    context: WebhookContext,
80) {
81    let payload = WebhookPayload {
82        event: WebhookEventType::TaskCreated.as_str().to_string(),
83        timestamp: timestamp_rfc3339.to_string(),
84        task_id: Some(task_id.to_string()),
85        task_title: Some(task_title.to_string()),
86        previous_status: None,
87        current_status: None,
88        note: None,
89        context,
90    };
91    send_webhook_payload_internal(payload, config, false);
92}
93
94/// Convenience function to send task started webhook.
95pub fn notify_task_started(
96    task_id: &str,
97    task_title: &str,
98    config: &WebhookConfig,
99    timestamp_rfc3339: &str,
100) {
101    send_webhook(
102        WebhookEventType::TaskStarted,
103        task_id,
104        task_title,
105        Some("todo"),
106        Some("doing"),
107        None,
108        config,
109        timestamp_rfc3339,
110    );
111}
112
113/// Convenience function to send task started webhook with context.
114pub fn notify_task_started_with_context(
115    task_id: &str,
116    task_title: &str,
117    config: &WebhookConfig,
118    timestamp_rfc3339: &str,
119    context: WebhookContext,
120) {
121    let payload = WebhookPayload {
122        event: WebhookEventType::TaskStarted.as_str().to_string(),
123        timestamp: timestamp_rfc3339.to_string(),
124        task_id: Some(task_id.to_string()),
125        task_title: Some(task_title.to_string()),
126        previous_status: Some("todo".to_string()),
127        current_status: Some("doing".to_string()),
128        note: None,
129        context,
130    };
131    send_webhook_payload_internal(payload, config, false);
132}
133
134/// Convenience function to send task completed webhook.
135pub fn notify_task_completed(
136    task_id: &str,
137    task_title: &str,
138    config: &WebhookConfig,
139    timestamp_rfc3339: &str,
140) {
141    send_webhook(
142        WebhookEventType::TaskCompleted,
143        task_id,
144        task_title,
145        Some("doing"),
146        Some("done"),
147        None,
148        config,
149        timestamp_rfc3339,
150    );
151}
152
153/// Convenience function to send task completed webhook with context.
154pub fn notify_task_completed_with_context(
155    task_id: &str,
156    task_title: &str,
157    config: &WebhookConfig,
158    timestamp_rfc3339: &str,
159    context: WebhookContext,
160) {
161    let payload = WebhookPayload {
162        event: WebhookEventType::TaskCompleted.as_str().to_string(),
163        timestamp: timestamp_rfc3339.to_string(),
164        task_id: Some(task_id.to_string()),
165        task_title: Some(task_title.to_string()),
166        previous_status: Some("doing".to_string()),
167        current_status: Some("done".to_string()),
168        note: None,
169        context,
170    };
171    send_webhook_payload_internal(payload, config, false);
172}
173
174/// Convenience function to send task failed/rejected webhook.
175pub fn notify_task_failed(
176    task_id: &str,
177    task_title: &str,
178    note: Option<&str>,
179    config: &WebhookConfig,
180    timestamp_rfc3339: &str,
181) {
182    send_webhook(
183        WebhookEventType::TaskFailed,
184        task_id,
185        task_title,
186        Some("doing"),
187        Some("rejected"),
188        note,
189        config,
190        timestamp_rfc3339,
191    );
192}
193
194/// Convenience function to send task failed webhook with context.
195pub fn notify_task_failed_with_context(
196    task_id: &str,
197    task_title: &str,
198    note: Option<&str>,
199    config: &WebhookConfig,
200    timestamp_rfc3339: &str,
201    context: WebhookContext,
202) {
203    let payload = WebhookPayload {
204        event: WebhookEventType::TaskFailed.as_str().to_string(),
205        timestamp: timestamp_rfc3339.to_string(),
206        task_id: Some(task_id.to_string()),
207        task_title: Some(task_title.to_string()),
208        previous_status: Some("doing".to_string()),
209        current_status: Some("rejected".to_string()),
210        note: note.map(|n| n.to_string()),
211        context,
212    };
213    send_webhook_payload_internal(payload, config, false);
214}
215
216/// Convenience function to send generic status change webhook.
217pub fn notify_status_changed(
218    task_id: &str,
219    task_title: &str,
220    previous_status: &str,
221    current_status: &str,
222    config: &WebhookConfig,
223    timestamp_rfc3339: &str,
224) {
225    send_webhook(
226        WebhookEventType::TaskStatusChanged,
227        task_id,
228        task_title,
229        Some(previous_status),
230        Some(current_status),
231        None,
232        config,
233        timestamp_rfc3339,
234    );
235}
236
237/// Convenience function to send loop started webhook.
238/// This is a loop-level event with no task association.
239pub fn notify_loop_started(
240    config: &WebhookConfig,
241    timestamp_rfc3339: &str,
242    context: WebhookContext,
243) {
244    let payload = WebhookPayload {
245        event: WebhookEventType::LoopStarted.as_str().to_string(),
246        timestamp: timestamp_rfc3339.to_string(),
247        task_id: None,
248        task_title: None,
249        previous_status: None,
250        current_status: None,
251        note: None,
252        context,
253    };
254    send_webhook_payload_internal(payload, config, false);
255}
256
257/// Convenience function to send loop stopped webhook.
258/// This is a loop-level event with no task association.
259pub fn notify_loop_stopped(
260    config: &WebhookConfig,
261    timestamp_rfc3339: &str,
262    context: WebhookContext,
263    note: Option<&str>,
264) {
265    let payload = WebhookPayload {
266        event: WebhookEventType::LoopStopped.as_str().to_string(),
267        timestamp: timestamp_rfc3339.to_string(),
268        task_id: None,
269        task_title: None,
270        previous_status: None,
271        current_status: None,
272        note: note.map(|n| n.to_string()),
273        context,
274    };
275    send_webhook_payload_internal(payload, config, false);
276}
277
278/// Convenience function to send phase started webhook.
279pub fn notify_phase_started(
280    task_id: &str,
281    task_title: &str,
282    config: &WebhookConfig,
283    timestamp_rfc3339: &str,
284    context: WebhookContext,
285) {
286    let payload = WebhookPayload {
287        event: WebhookEventType::PhaseStarted.as_str().to_string(),
288        timestamp: timestamp_rfc3339.to_string(),
289        task_id: Some(task_id.to_string()),
290        task_title: Some(task_title.to_string()),
291        previous_status: None,
292        current_status: None,
293        note: None,
294        context,
295    };
296    send_webhook_payload_internal(payload, config, false);
297}
298
299/// Convenience function to send phase completed webhook.
300pub fn notify_phase_completed(
301    task_id: &str,
302    task_title: &str,
303    config: &WebhookConfig,
304    timestamp_rfc3339: &str,
305    context: WebhookContext,
306) {
307    let payload = WebhookPayload {
308        event: WebhookEventType::PhaseCompleted.as_str().to_string(),
309        timestamp: timestamp_rfc3339.to_string(),
310        task_id: Some(task_id.to_string()),
311        task_title: Some(task_title.to_string()),
312        previous_status: None,
313        current_status: None,
314        note: None,
315        context,
316    };
317    send_webhook_payload_internal(payload, config, false);
318}
319
320/// Convenience function to send queue unblocked webhook.
321/// This is a loop-level event with no task association.
322pub fn notify_queue_unblocked(
323    config: &WebhookConfig,
324    timestamp_rfc3339: &str,
325    context: WebhookContext,
326    note: Option<&str>,
327) {
328    let payload = WebhookPayload {
329        event: WebhookEventType::QueueUnblocked.as_str().to_string(),
330        timestamp: timestamp_rfc3339.to_string(),
331        task_id: None,
332        task_title: None,
333        previous_status: Some("blocked".to_string()),
334        current_status: Some("runnable".to_string()),
335        note: note.map(|n| n.to_string()),
336        context,
337    };
338    send_webhook_payload_internal(payload, config, false);
339}