Skip to main content

minion_engine/events/
subscribers.rs

1use std::io::Write as _;
2
3use super::{Event, EventSubscriber};
4
5// ── WebhookSubscriber ─────────────────────────────────────────────────────────
6
7/// Fires-and-forgets an HTTP POST with the serialized event JSON to a URL.
8/// Uses `reqwest` in a `tokio::spawn` task so it never blocks the engine.
9pub struct WebhookSubscriber {
10    url: String,
11}
12
13impl WebhookSubscriber {
14    pub fn new(url: impl Into<String>) -> Self {
15        Self { url: url.into() }
16    }
17}
18
19impl EventSubscriber for WebhookSubscriber {
20    fn on_event(&self, event: &Event) {
21        let url = self.url.clone();
22        let body = match serde_json::to_string(event) {
23            Ok(s) => s,
24            Err(e) => {
25                tracing::warn!(error = %e, "WebhookSubscriber: failed to serialize event");
26                return;
27            }
28        };
29
30        // Fire and forget — spawn a task that doesn't block the caller
31        tokio::spawn(async move {
32            let client = reqwest::Client::new();
33            if let Err(e) = client
34                .post(&url)
35                .header("Content-Type", "application/json")
36                .body(body)
37                .send()
38                .await
39            {
40                tracing::warn!(url = %url, error = %e, "WebhookSubscriber: HTTP POST failed");
41            }
42        });
43    }
44}
45
46// ── FileSubscriber ────────────────────────────────────────────────────────────
47
48/// Appends each event as a single JSON line (JSONL) to a file.
49/// Each call opens the file in append mode, writes, and closes it — this is
50/// intentionally simple and robust (no background thread needed).
51pub struct FileSubscriber {
52    path: String,
53}
54
55impl FileSubscriber {
56    pub fn new(path: impl Into<String>) -> Self {
57        Self { path: path.into() }
58    }
59}
60
61impl EventSubscriber for FileSubscriber {
62    fn on_event(&self, event: &Event) {
63        let line = match serde_json::to_string(event) {
64            Ok(s) => s,
65            Err(e) => {
66                tracing::warn!(error = %e, "FileSubscriber: failed to serialize event");
67                return;
68            }
69        };
70
71        match std::fs::OpenOptions::new()
72            .create(true)
73            .append(true)
74            .open(&self.path)
75        {
76            Ok(mut file) => {
77                if let Err(e) = writeln!(file, "{}", line) {
78                    tracing::warn!(path = %self.path, error = %e, "FileSubscriber: write failed");
79                }
80            }
81            Err(e) => {
82                tracing::warn!(path = %self.path, error = %e, "FileSubscriber: open failed");
83            }
84        }
85    }
86}