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}