sz_rust_http_facade/
sse.rs1use axum::response::sse::{Event as AxumEvent, KeepAlive, Sse};
32use axum::response::IntoResponse;
33use core::convert::Infallible;
34use futures::stream::{Stream, StreamExt};
35
36#[derive(Debug, Clone)]
40pub struct SseEvent {
41 data: String,
42 event: Option<String>,
43 id: Option<String>,
44 retry: Option<u64>,
45}
46
47impl SseEvent {
48 pub fn data(data: impl Into<String>) -> Self {
50 Self {
51 data: data.into(),
52 event: None,
53 id: None,
54 retry: None,
55 }
56 }
57
58 pub fn event(mut self, event: impl Into<String>) -> Self {
60 self.event = Some(event.into());
61 self
62 }
63
64 pub fn id(mut self, id: impl Into<String>) -> Self {
66 self.id = Some(id.into());
67 self
68 }
69
70 pub fn retry(mut self, retry_ms: u64) -> Self {
72 self.retry = Some(retry_ms);
73 self
74 }
75
76 pub fn into_axum_event(self) -> Result<AxumEvent, Infallible> {
78 let mut event = AxumEvent::default().data(&self.data);
79 if let Some(name) = self.event {
80 event = event.event(name);
81 }
82 if let Some(id) = self.id {
83 event = event.id(id);
84 }
85 if let Some(retry) = self.retry {
86 event = event.retry(std::time::Duration::from_millis(retry));
87 }
88 Ok(event)
89 }
90}
91
92pub fn sse_response<S>(stream: S) -> impl IntoResponse
96where
97 S: Stream<Item = Result<SseEvent, Infallible>> + Send + 'static,
98{
99 let axum_stream = stream.map(|item| item.and_then(|e| e.into_axum_event()));
100 Sse::new(axum_stream).keep_alive(KeepAlive::default())
101}
102
103pub fn sse_response_with_interval<S>(stream: S, interval_secs: u64) -> impl IntoResponse
105where
106 S: Stream<Item = Result<SseEvent, Infallible>> + Send + 'static,
107{
108 let axum_stream = stream.map(|item| item.and_then(|e| e.into_axum_event()));
109 Sse::new(axum_stream).keep_alive(
110 KeepAlive::new()
111 .interval(std::time::Duration::from_secs(interval_secs))
112 .text("keep-alive"),
113 )
114}
115
116pub fn sse_from_events(events: Vec<SseEvent>) -> impl Stream<Item = Result<SseEvent, Infallible>> {
118 futures::stream::iter(events.into_iter().map(Ok))
119}
120
121#[cfg(test)]
122mod tests {
123 use super::*;
124
125 #[test]
126 fn test_sse_event_data() {
127 let event = SseEvent::data("hello");
128 assert_eq!(event.data, "hello");
129 assert!(event.event.is_none());
130 assert!(event.id.is_none());
131 assert!(event.retry.is_none());
132 }
133
134 #[test]
135 fn test_sse_event_builder() {
136 let event = SseEvent::data("payload")
137 .event("update")
138 .id("123")
139 .retry(5000);
140 assert_eq!(event.data, "payload");
141 assert_eq!(event.event.as_deref(), Some("update"));
142 assert_eq!(event.id.as_deref(), Some("123"));
143 assert_eq!(event.retry, Some(5000));
144 }
145
146 #[test]
147 fn test_sse_event_to_axum() {
148 let event = SseEvent::data("test").event("ping");
149 let axum_event = event.into_axum_event();
150 assert!(axum_event.is_ok());
151 }
152
153 #[test]
154 fn test_sse_from_events() {
155 let events = vec![SseEvent::data("first"), SseEvent::data("second")];
156 let stream = sse_from_events(events);
157 let collected: Vec<_> = futures::executor::block_on(stream.collect());
158 assert_eq!(collected.len(), 2);
159 }
160}