eventsource_threaded/
event.rs

1use std::fmt;
2use std::time::Duration;
3
4/// A single Server-Sent Event.
5#[derive(Debug)]
6pub struct Event {
7    /// Corresponds to the `id` field.
8    pub id: Option<String>,
9    /// Corresponds to the `event` field.
10    pub event_type: Option<String>,
11    /// All `data` fields concatenated by newlines.
12    pub data: String,
13}
14
15/// Possible results from parsing a single event-stream line.
16#[derive(Debug, PartialEq)]
17pub enum ParseResult {
18    /// Line parsed successfully, but the event is not complete yet.
19    Next,
20    /// The event is complete now. Pass a new (empty) event for the next call.
21    Dispatch,
22    /// Set retry time.
23    SetRetry(Duration),
24}
25
26/// Parse a single line of an event-stream.
27///
28/// The line may end with a newline.
29///
30/// You will have to call this function multiple times until it returns `ParseResult::Dispatch`.
31/// Make sure to clear the event struct for the next line, then.
32///
33/// To handle the `Last-Event-ID` header, check the `id` field for each finished event.
34///
35/// # Examples
36///
37/// ```
38/// # use eventsource::event::{Event, ParseResult, parse_event_line};
39/// let mut event = Event::new();
40/// assert_eq!(parse_event_line("id: 42", &mut event), ParseResult::Next);
41/// assert_eq!(parse_event_line("data: foobar", &mut event), ParseResult::Next);
42/// assert_eq!(parse_event_line("", &mut event), ParseResult::Dispatch);
43/// // The event is finished now.
44/// assert_eq!(event.id, Some("42".into()));
45/// assert_eq!(event.data, "foobar\n");
46/// // Now clear and continue.
47/// event.clear();
48/// // ...
49/// ```
50pub fn parse_event_line(line: &str, event: &mut Event) -> ParseResult {
51    let line = line.trim_end_matches(|c| c == '\r' || c == '\n');
52    if line == "" {
53        ParseResult::Dispatch
54    } else {
55        let (field, value) = if let Some(pos) = line.find(':') {
56            let (f, v) = line.split_at(pos);
57            // Strip : and an optional space.
58            let v = &v[1..];
59            let v = if v.starts_with(' ') { &v[1..] } else { v };
60            (f, v)
61        } else {
62            (line, "")
63        };
64
65        match field {
66            "event" => {
67                event.event_type = Some(value.to_string());
68            }
69            "data" => {
70                event.data.push_str(value);
71                event.data.push('\n');
72            }
73            "id" => {
74                event.id = Some(value.to_string());
75            }
76            "retry" => {
77                if let Ok(retry) = value.parse::<u64>() {
78                    return ParseResult::SetRetry(Duration::from_millis(retry));
79                }
80            }
81            _ => (), // ignored
82        }
83
84        ParseResult::Next
85    }
86}
87
88impl Event {
89    /// Creates an empty event.
90    pub fn new() -> Event {
91        Event {
92            id: None,
93            event_type: None,
94            data: "".to_string(),
95        }
96    }
97
98    /// Returns `true` if the event is empty.
99    ///
100    /// An event is empty if it has no id or event type and its data field is empty.
101    pub fn is_empty(&self) -> bool {
102        self.id.is_none() && self.event_type.is_none() && self.data.is_empty()
103    }
104
105    /// Makes the event empty.
106    pub fn clear(&mut self) {
107        self.id = None;
108        self.event_type = None;
109        self.data.clear();
110    }
111}
112
113impl fmt::Display for Event {
114    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
115        if let Some(ref id) = self.id {
116            write!(f, "id: {}\n", id)?;
117        }
118        if let Some(ref event_type) = self.event_type {
119            write!(f, "event: {}\n", event_type)?;
120        }
121        for line in self.data.lines() {
122            write!(f, "data: {}\n", line)?;
123        }
124        Ok(())
125    }
126}
127
128#[cfg(test)]
129mod tests {
130    use super::*;
131
132    #[test]
133    fn basic_event_display() {
134        assert_eq!(
135            "data: hello world\n",
136            Event {
137                id: None,
138                event_type: None,
139                data: "hello world".to_string()
140            }
141            .to_string()
142        );
143        assert_eq!(
144            "id: foo\ndata: hello world\n",
145            Event {
146                id: Some("foo".to_string()),
147                event_type: None,
148                data: "hello world".to_string()
149            }
150            .to_string()
151        );
152        assert_eq!(
153            "event: bar\ndata: hello world\n",
154            Event {
155                id: None,
156                event_type: Some("bar".to_string()),
157                data: "hello world".to_string()
158            }
159            .to_string()
160        );
161    }
162
163    #[test]
164    fn multiline_event_display() {
165        assert_eq!(
166            "data: hello\ndata: world\n",
167            Event {
168                id: None,
169                event_type: None,
170                data: "hello\nworld".to_string()
171            }
172            .to_string()
173        );
174        assert_eq!(
175            "data: hello\ndata: \ndata: world\n",
176            Event {
177                id: None,
178                event_type: None,
179                data: "hello\n\nworld".to_string()
180            }
181            .to_string()
182        );
183    }
184}