Skip to main content

ironflow_sdk/
sse.rs

1//! SSE (Server-Sent Events) streaming helpers.
2//!
3//! Provides a [`Stream`] of domain events from the Ironflow
4//! `GET /api/v1/events` endpoint.
5//!
6//! # Examples
7//!
8//! ```no_run
9//! use ironflow_sdk::IronflowClient;
10//! use futures_util::StreamExt;
11//!
12//! # async fn example() -> Result<(), ironflow_sdk::Error> {
13//! let client = IronflowClient::new("https://ironflow.example.com", "my-api-key");
14//! let mut stream = client.events(None, None).await?;
15//!
16//! while let Some(event) = stream.next().await {
17//!     match event {
18//!         Ok(ev) => {
19//!             let _ = &ev.event_type;
20//!             let _ = &ev.data;
21//!         }
22//!         Err(e) => { break; }
23//!     }
24//! }
25//! # Ok(())
26//! # }
27//! ```
28
29use std::pin::Pin;
30
31use eventsource_stream::Eventsource;
32use futures_util::StreamExt;
33use futures_util::stream::Stream;
34use reqwest::Response;
35use uuid::Uuid;
36
37use crate::error::Error;
38
39/// A parsed SSE event from the Ironflow API.
40///
41/// # Examples
42///
43/// ```
44/// use ironflow_sdk::sse::SseEvent;
45///
46/// let event = SseEvent {
47///     event_type: "run_status_changed".to_string(),
48///     data: r#"{"run_id":"..."}"#.to_string(),
49/// };
50/// assert_eq!(event.event_type, "run_status_changed");
51/// ```
52#[derive(Debug, Clone)]
53pub struct SseEvent {
54    /// Event type (from the `event:` field, defaults to `"message"`).
55    pub event_type: String,
56    /// JSON payload (from the `data:` field).
57    pub data: String,
58}
59
60impl SseEvent {
61    /// Deserialize the `data` field into a typed event.
62    ///
63    /// # Errors
64    ///
65    /// Returns [`Error::Deserialize`] if the JSON is invalid.
66    ///
67    /// # Examples
68    ///
69    /// ```
70    /// use ironflow_sdk::sse::SseEvent;
71    /// use serde::Deserialize;
72    ///
73    /// #[derive(Deserialize)]
74    /// struct Payload { value: i32 }
75    ///
76    /// let event = SseEvent {
77    ///     event_type: "test".to_string(),
78    ///     data: r#"{"value": 42}"#.to_string(),
79    /// };
80    /// let payload: Payload = event.deserialize().unwrap();
81    /// assert_eq!(payload.value, 42);
82    /// ```
83    pub fn deserialize<T: serde::de::DeserializeOwned>(&self) -> Result<T, Error> {
84        serde_json::from_str(&self.data)
85            .map_err(|e| Error::Deserialize(format!("{e}: {}", self.data)))
86    }
87}
88
89/// A stream of SSE events from the Ironflow API.
90///
91/// Created by [`IronflowClient::events`](crate::IronflowClient::events).
92pub type EventStream = Pin<Box<dyn Stream<Item = Result<SseEvent, Error>> + Send>>;
93
94/// Parse an SSE byte stream into [`SseEvent`]s.
95pub(crate) fn parse_sse_stream(response: Response) -> EventStream {
96    let stream = response.bytes_stream().eventsource().map(|result| {
97        result
98            .map(|event| SseEvent {
99                event_type: event.event,
100                data: event.data,
101            })
102            .map_err(|e| Error::Sse(e.to_string()))
103    });
104
105    Box::pin(stream)
106}
107
108impl crate::IronflowClient {
109    /// Connect to the SSE event stream at `GET /api/v1/events`.
110    ///
111    /// Returns a [`Stream`] of [`SseEvent`]s. Optionally filter by
112    /// `run_id` and/or event types.
113    ///
114    /// # Errors
115    ///
116    /// Returns [`Error::Http`] if the connection fails, or [`Error::Api`]
117    /// if the server responds with a non-2xx status code.
118    ///
119    /// # Examples
120    ///
121    /// ```no_run
122    /// use ironflow_sdk::IronflowClient;
123    /// use futures_util::StreamExt;
124    ///
125    /// # async fn example() -> Result<(), ironflow_sdk::Error> {
126    /// let client = IronflowClient::new("https://ironflow.example.com", "key");
127    /// let mut stream = client.events(None, None).await?;
128    ///
129    /// while let Some(event) = stream.next().await {
130    ///     let event = event?;
131    ///     # let _ = event;
132    /// }
133    /// # Ok(())
134    /// # }
135    /// ```
136    pub async fn events(
137        &self,
138        run_id: Option<Uuid>,
139        types: Option<&[&str]>,
140    ) -> Result<EventStream, Error> {
141        let mut request = self.get("/api/v1/events");
142        if let Some(rid) = run_id {
143            request = request.query(&[("run_id", rid.to_string())]);
144        }
145        if let Some(ts) = types {
146            request = request.query(&[("types", ts.join(","))]);
147        }
148        let response = request.send().await?;
149        if !response.status().is_success() {
150            return Err(Self::into_api_error(response).await);
151        }
152        Ok(parse_sse_stream(response))
153    }
154
155    /// Subscribe to SSE events for a specific run.
156    ///
157    /// Connects to `GET /api/v1/runs/{run_id}/events` and returns a
158    /// [`Stream`] of [`SseEvent`]s. The stream
159    /// ends when the connection drops; the caller is responsible for
160    /// reconnection if needed.
161    ///
162    /// # Errors
163    ///
164    /// Returns [`Error::Http`] if the connection fails, or [`Error::Api`]
165    /// on a non-2xx status code.
166    ///
167    /// # Examples
168    ///
169    /// ```no_run
170    /// use ironflow_sdk::IronflowClient;
171    /// use futures_util::StreamExt;
172    /// use uuid::Uuid;
173    ///
174    /// # async fn example() -> Result<(), ironflow_sdk::Error> {
175    /// let client = IronflowClient::new("https://ironflow.example.com", "key");
176    /// let mut stream = client.subscribe_run_events(Uuid::nil()).await?;
177    ///
178    /// while let Some(event) = stream.next().await {
179    ///     let event = event?;
180    ///     # let _ = event;
181    /// }
182    /// # Ok(())
183    /// # }
184    /// ```
185    pub async fn subscribe_run_events(&self, run_id: Uuid) -> Result<EventStream, Error> {
186        self.rate_limiter.wait().await;
187        let path = format!("/api/v1/runs/{run_id}/events");
188        let response = self.get(&path).send().await?;
189        if !response.status().is_success() {
190            return Err(Self::into_api_error(response).await);
191        }
192        Ok(parse_sse_stream(response))
193    }
194
195    /// Subscribe to the global SSE event stream.
196    ///
197    /// Connects to `GET /api/v1/events` and returns a
198    /// [`Stream`] of [`SseEvent`]s for all runs.
199    ///
200    /// # Errors
201    ///
202    /// Returns [`Error::Http`] if the connection fails, or [`Error::Api`]
203    /// on a non-2xx status code.
204    ///
205    /// # Examples
206    ///
207    /// ```no_run
208    /// use ironflow_sdk::IronflowClient;
209    /// use futures_util::StreamExt;
210    ///
211    /// # async fn example() -> Result<(), ironflow_sdk::Error> {
212    /// let client = IronflowClient::new("https://ironflow.example.com", "key");
213    /// let mut stream = client.subscribe_global_events().await?;
214    ///
215    /// while let Some(event) = stream.next().await {
216    ///     let event = event?;
217    ///     # let _ = event;
218    /// }
219    /// # Ok(())
220    /// # }
221    /// ```
222    pub async fn subscribe_global_events(&self) -> Result<EventStream, Error> {
223        self.rate_limiter.wait().await;
224        self.events(None, None).await
225    }
226}