rust_mcp_transport/utils/
sse_event.rs

1use bytes::Bytes;
2use core::fmt;
3
4/// Represents a single Server-Sent Event (SSE) as defined in the SSE protocol.
5///
6/// Contains the event type, data payload, and optional event ID.
7#[derive(Clone, Default)]
8pub struct SseEvent {
9    /// The optional event type (e.g., "message").
10    pub event: Option<String>,
11    /// The optional data payload of the event, stored as bytes.
12    pub data: Option<Bytes>,
13    /// The optional event ID for reconnection or tracking purposes.
14    pub id: Option<String>,
15    /// Optional reconnection retry interval (in milliseconds).
16    pub retry: Option<u64>,
17}
18
19impl SseEvent {
20    /// Creates a new `SseEvent` with the given string data.
21    pub fn new<T: Into<String>>(data: T) -> Self {
22        Self {
23            event: None,
24            data: Some(Bytes::from(data.into())),
25            id: None,
26            retry: None,
27        }
28    }
29
30    /// Sets the event name (e.g., "message").
31    pub fn with_event<T: Into<String>>(mut self, event: T) -> Self {
32        self.event = Some(event.into());
33        self
34    }
35
36    /// Sets the ID of the event.
37    pub fn with_id<T: Into<String>>(mut self, id: T) -> Self {
38        self.id = Some(id.into());
39        self
40    }
41
42    /// Sets the retry interval (in milliseconds).
43    pub fn with_retry(mut self, retry: u64) -> Self {
44        self.retry = Some(retry);
45        self
46    }
47
48    /// Sets the data as bytes.
49    pub fn with_data_bytes(mut self, data: Bytes) -> Self {
50        self.data = Some(data);
51        self
52    }
53
54    /// Sets the data.
55    pub fn with_data(mut self, data: String) -> Self {
56        self.data = Some(Bytes::from(data));
57        self
58    }
59
60    /// Converts the event into a string in SSE format (ready for HTTP body).
61    pub fn to_sse_string(&self) -> String {
62        self.to_string()
63    }
64
65    pub fn as_bytes(&self) -> Bytes {
66        Bytes::from(self.to_string())
67    }
68}
69
70impl std::fmt::Display for SseEvent {
71    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
72        // Emit retry interval
73        if let Some(retry) = self.retry {
74            writeln!(f, "retry: {retry}")?;
75        }
76
77        // Emit ID
78        if let Some(id) = &self.id {
79            writeln!(f, "id: {id}")?;
80        }
81
82        // Emit event type
83        if let Some(event) = &self.event {
84            writeln!(f, "event: {event}")?;
85        }
86
87        // Emit data lines
88        if let Some(data) = &self.data {
89            match std::str::from_utf8(data) {
90                Ok(text) => {
91                    for line in text.lines() {
92                        writeln!(f, "data: {line}")?;
93                    }
94                }
95                Err(_) => {
96                    writeln!(f, "data: [binary data]")?;
97                }
98            }
99        }
100
101        writeln!(f)?; // Trailing newline for SSE message end, separates events
102        Ok(())
103    }
104}
105
106impl fmt::Debug for SseEvent {
107    /// Formats the `SseEvent` for debugging, converting the `data` field to a UTF-8 string
108    /// (with lossy conversion if invalid UTF-8 is encountered).
109    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
110        let data_str = self
111            .data
112            .as_ref()
113            .map(|b| String::from_utf8_lossy(b).to_string());
114
115        f.debug_struct("SseEvent")
116            .field("event", &self.event)
117            .field("data", &data_str)
118            .field("id", &self.id)
119            .field("retry", &self.retry)
120            .finish()
121    }
122}