Skip to main content

actix_sse/
event.rs

1use std::time::Duration;
2
3use bytes::{BufMut, Bytes, BytesMut};
4use bytestring::ByteString;
5
6use crate::Data;
7
8/// Server-sent events message containing one or more fields.
9#[must_use]
10#[derive(Debug, Clone)]
11pub enum Event {
12    /// A `data` message with optional ID and event name.
13    ///
14    /// Data messages looks like this in the response stream.
15    /// ```plain
16    /// event: foo
17    /// id: 42
18    /// data: my data
19    ///
20    /// data: {
21    /// data:   "multiline": "data"
22    /// data: }
23    /// ```
24    Data(Data),
25
26    /// A comment message.
27    ///
28    /// Comments look like this in the response stream.
29    /// ```plain
30    /// : my comment
31    ///
32    /// : another comment
33    /// ```
34    Comment(ByteString),
35}
36
37impl Event {
38    /// Splits data into lines and prepend each line with `prefix`.
39    pub(crate) fn line_split_with_prefix(
40        buf: &mut BytesMut,
41        prefix: &'static str,
42        data: ByteString,
43    ) {
44        // initial buffer size guess is len(data) + 10 lines of prefix + EOLs + EOF
45        buf.reserve(data.len() + (10 * (prefix.len() + 1)) + 1);
46
47        // append prefix + space + line to buffer
48        for line in data.split('\n') {
49            buf.put_slice(prefix.as_bytes());
50            buf.put_slice(line.as_bytes());
51            buf.put_u8(b'\n');
52        }
53    }
54
55    /// Serializes message into event-stream format.
56    pub(crate) fn into_bytes(self) -> Bytes {
57        let mut buf = BytesMut::new();
58
59        match self {
60            Event::Data(Data { id, event, data }) => {
61                if let Some(text) = id {
62                    buf.put_slice(b"id: ");
63                    buf.put_slice(text.as_bytes());
64                    buf.put_u8(b'\n');
65                }
66
67                if let Some(text) = event {
68                    buf.put_slice(b"event: ");
69                    buf.put_slice(text.as_bytes());
70                    buf.put_u8(b'\n');
71                }
72
73                Self::line_split_with_prefix(&mut buf, "data: ", data);
74            }
75
76            Event::Comment(text) => Self::line_split_with_prefix(&mut buf, ": ", text),
77        }
78
79        // final newline to mark end of message
80        buf.put_u8(b'\n');
81
82        buf.freeze()
83    }
84
85    /// Serializes retry message into event-stream format.
86    pub(crate) fn retry_to_bytes(retry: Duration) -> Bytes {
87        Bytes::from(format!("retry: {}\n\n", retry.as_millis()))
88    }
89
90    /// Serializes a keep-alive event-stream comment message into bytes.
91    pub(crate) const fn keep_alive_bytes() -> Bytes {
92        Bytes::from_static(b": keep-alive\n\n")
93    }
94}
95
96impl From<Data> for Event {
97    fn from(data: Data) -> Self {
98        Self::Data(data)
99    }
100}
101
102#[cfg(test)]
103mod tests {
104    use super::*;
105
106    #[test]
107    fn format_retry_message() {
108        assert_eq!(
109            Event::retry_to_bytes(Duration::from_millis(1)),
110            "retry: 1\n\n",
111        );
112        assert_eq!(
113            Event::retry_to_bytes(Duration::from_secs(10)),
114            "retry: 10000\n\n",
115        );
116    }
117
118    #[test]
119    fn line_split_format() {
120        let mut buf = BytesMut::new();
121        Event::line_split_with_prefix(&mut buf, "data: ", ByteString::from("foo"));
122        assert_eq!(buf, "data: foo\n");
123
124        let mut buf = BytesMut::new();
125        Event::line_split_with_prefix(&mut buf, "data: ", ByteString::from("foo\nbar"));
126        assert_eq!(buf, "data: foo\ndata: bar\n");
127    }
128
129    #[test]
130    fn into_bytes_format() {
131        assert_eq!(Event::Comment("foo".into()).into_bytes(), ": foo\n\n");
132
133        assert_eq!(
134            Event::Data(Data {
135                id: None,
136                event: None,
137                data: "foo".into()
138            })
139            .into_bytes(),
140            "data: foo\n\n"
141        );
142
143        assert_eq!(
144            Event::Data(Data {
145                id: None,
146                event: None,
147                data: "\n".into()
148            })
149            .into_bytes(),
150            "data: \ndata: \n\n"
151        );
152
153        assert_eq!(
154            Event::Data(Data {
155                id: Some("42".into()),
156                event: None,
157                data: "foo".into()
158            })
159            .into_bytes(),
160            "id: 42\ndata: foo\n\n"
161        );
162
163        assert_eq!(
164            Event::Data(Data {
165                id: None,
166                event: Some("bar".into()),
167                data: "foo".into()
168            })
169            .into_bytes(),
170            "event: bar\ndata: foo\n\n"
171        );
172
173        assert_eq!(
174            Event::Data(Data {
175                id: Some("42".into()),
176                event: Some("bar".into()),
177                data: "foo".into()
178            })
179            .into_bytes(),
180            "id: 42\nevent: bar\ndata: foo\n\n"
181        );
182    }
183}