1use crate::response::{IntoResponse, Response, SUCCESS};
25use crate::responses::StreamingResponse;
26use futures_util::Stream;
27use http::HeaderValue;
28use http::header::CACHE_CONTROL;
29use lifecycle::{Lifecycle, Until};
30use serde::Serialize;
31use siderite_openapi::{Operation, SchemaObject, SchemaRegistry};
32use std::error::Error as StdError;
33use std::fmt::Write;
34use std::future::Future;
35use std::time::Duration;
36use thiserror::Error;
37
38pub const EVENT_STREAM: &str = "text/event-stream";
40
41#[derive(Debug, Clone, PartialEq, Eq, Error)]
43pub enum SseError {
44 #[error("SSE event names must not contain CR or LF")]
46 EventName,
47 #[error("SSE ids must not contain CR, LF or NUL")]
49 Id,
50}
51
52#[derive(Debug, Clone, Default, PartialEq, Eq)]
54pub struct Event {
55 comment: Option<String>,
56 event: Option<String>,
57 id: Option<String>,
58 retry: Option<Duration>,
59 data: Option<String>,
60}
61
62impl Event {
63 pub fn data(text: impl Into<String>) -> Self {
65 Self {
66 data: Some(text.into()),
67 ..Self::default()
68 }
69 }
70
71 pub fn json<T: Serialize>(value: &T) -> Result<Self, serde_json::Error> {
76 Ok(Self::data(serde_json::to_string(value)?))
77 }
78
79 pub fn comment(text: impl Into<String>) -> Self {
82 Self {
83 comment: Some(text.into()),
84 ..Self::default()
85 }
86 }
87
88 pub fn event(mut self, name: impl Into<String>) -> Result<Self, SseError> {
93 let name = name.into();
94 if name.contains(['\r', '\n']) {
95 return Err(SseError::EventName);
96 }
97 self.event = Some(name);
98 Ok(self)
99 }
100
101 pub fn id(mut self, id: impl Into<String>) -> Result<Self, SseError> {
106 let id = id.into();
107 if id.contains(['\r', '\n', '\0']) {
108 return Err(SseError::Id);
109 }
110 self.id = Some(id);
111 Ok(self)
112 }
113
114 #[must_use]
117 pub fn retry(mut self, delay: Duration) -> Self {
118 self.retry = Some(delay);
119 self
120 }
121
122 pub fn encode(&self) -> String {
124 let mut out = String::new();
125 if let Some(comment) = &self.comment {
126 for line in lines(comment) {
127 let _ = writeln!(out, ": {line}");
128 }
129 }
130 if let Some(event) = &self.event {
131 let _ = writeln!(out, "event: {event}");
132 }
133 if let Some(id) = &self.id {
134 let _ = writeln!(out, "id: {id}");
135 }
136 if let Some(retry) = self.retry {
137 let _ = writeln!(out, "retry: {}", retry.as_millis());
138 }
139 if let Some(data) = &self.data {
140 for line in lines(data) {
141 let _ = writeln!(out, "data: {line}");
142 }
143 }
144 out.push('\n');
145 out
146 }
147}
148
149fn lines(text: &str) -> Vec<&str> {
152 let mut out = Vec::new();
153 let mut rest = text;
154 loop {
155 match rest.find(['\r', '\n']) {
156 Some(at) => {
157 out.push(&rest[..at]);
158 let skip = if rest[at..].starts_with("\r\n") { 2 } else { 1 };
159 rest = &rest[at + skip..];
160 }
161 None => {
162 out.push(rest);
163 return out;
164 }
165 }
166 }
167}
168
169pub struct Sse<S> {
171 stream: S,
172 keep_alive: Option<Duration>,
173 until: Option<Until>,
174}
175
176impl<S, E> Sse<S>
177where
178 S: Stream<Item = Result<Event, E>> + Send + 'static,
179 E: Into<Box<dyn StdError + Send + Sync>> + Send + 'static,
180{
181 pub fn new(events: S) -> Self {
183 Self {
184 stream: events,
185 keep_alive: None,
186 until: None,
187 }
188 }
189
190 #[must_use]
192 pub fn keep_alive(mut self, interval: Duration) -> Self {
193 self.keep_alive = Some(interval);
194 self
195 }
196
197 #[must_use]
200 pub fn until<F>(mut self, signal: F) -> Self
201 where
202 F: Future<Output = ()> + Send + 'static,
203 {
204 self.until = Some(Box::pin(signal));
205 self
206 }
207}
208
209impl<S, E> IntoResponse for Sse<S>
210where
211 S: Stream<Item = Result<Event, E>> + Send + 'static,
212 E: Into<Box<dyn StdError + Send + Sync>> + Send + 'static,
213{
214 fn into_response(self) -> Response {
215 let bytes = Lifecycle::new(self.stream, self.keep_alive, self.until);
216 let stream = StreamingResponse::new(bytes).content_type(EVENT_STREAM);
217 let mut response = stream.into_response();
218 let no_store = HeaderValue::from_static("no-store");
219 response.headers_mut().insert(CACHE_CONTROL, no_store);
220 response
221 }
222
223 fn describe(op: &mut Operation, _registry: &mut SchemaRegistry) {
224 let schema = SchemaObject::of_type("string");
225 let content = Some((EVENT_STREAM, schema));
226 op.add_response(SUCCESS, "Server-sent events", content);
227 }
228}
229
230mod lifecycle;
231
232#[cfg(test)]
233mod tests;