Skip to main content

rosu_render/websocket/
event.rs

1use bytes::Bytes;
2use serde_json::Error as SerdeError;
3
4use crate::model::{
5    CustomSkinProcessUpdate, Event, RenderAdded, RenderDone, RenderFailed, RenderProgress,
6};
7
8/// Websocket [`Event`] that has not been fully deserialized yet.
9/// This lets you check if you're interested in the event and only then deserialize it.
10///
11/// # Example
12/// ```rust
13/// use std::collections::HashSet;
14/// use rosu_render::websocket::event::RawEvent;
15///
16/// fn handle_event(event: RawEvent, interesting_render_ids: &HashSet<u32>) {
17///     match event {
18///         RawEvent::RenderDone(event) if interesting_render_ids.contains(&event.render_id) => {
19///             match event.deserialize() {
20///                 Ok(done) => println!("{done:?}"),
21///                 Err(err) => println!("Failed to deserialize {event:?} {err:?}"),
22///             }
23///         }
24///         _ => {} // Ignore
25///     }
26/// }
27/// ```
28#[derive(Clone, Debug, PartialEq, Eq)]
29#[non_exhaustive]
30pub enum RawEvent {
31    RenderAdded(RawRenderAdded),
32    RenderDone(RawRenderDone),
33    RenderFailed(RawRenderFailed),
34    RenderProgress(RawRenderProgress),
35    CustomSkinProcessUpdate(RawCustomSkinProcessUpdate),
36}
37
38impl RawEvent {
39    pub(crate) fn from_bytes(bytes: Bytes) -> Result<Self, crate::WebsocketError> {
40        fn split_bytes(bytes: &[u8]) -> Option<(&[u8], &[u8])> {
41            let comma_idx = bytes.iter().position(|&byte| byte == b',')?;
42
43            let ([b'[', b'"', prefix @ .., b'"'], [_, suffix @ .., b']']) =
44                bytes.split_at(comma_idx)
45            else {
46                return None;
47            };
48
49            Some((prefix, suffix))
50        }
51
52        fn find_render_id(mut bytes: &[u8]) -> Option<u32> {
53            loop {
54                let idx = bytes.iter().position(|&byte| byte == b'r')?;
55
56                let Some(rest) = bytes[idx..].strip_prefix(b"renderID\":") else {
57                    bytes = &bytes[idx + 1..];
58
59                    continue;
60                };
61
62                rest.first().copied().filter(u8::is_ascii_digit)?;
63
64                let render_id = rest
65                    .iter()
66                    .copied()
67                    .take_while(u8::is_ascii_digit)
68                    .fold(0, |num, byte| num * 10 + u32::from(byte & 0xF));
69
70                return Some(render_id);
71            }
72        }
73
74        let Some((event, payload)) = split_bytes(&bytes) else {
75            return Err(crate::WebsocketError::InvalidEvent(bytes));
76        };
77
78        let payload_bytes = bytes.slice_ref(payload);
79
80        match event {
81            b"render_progress_json" => find_render_id(payload)
82                .map(|render_id| {
83                    Self::RenderProgress(RawRenderProgress {
84                        render_id,
85                        bytes: payload_bytes,
86                    })
87                })
88                .ok_or(crate::WebsocketError::InvalidEvent(bytes)),
89            b"render_added_json" => Ok(Self::RenderAdded(RawRenderAdded {
90                bytes: payload_bytes,
91            })),
92            b"render_done_json" => find_render_id(payload)
93                .map(|render_id| {
94                    Self::RenderDone(RawRenderDone {
95                        render_id,
96                        bytes: payload_bytes,
97                    })
98                })
99                .ok_or(crate::WebsocketError::InvalidEvent(bytes)),
100            b"render_failed_json" => find_render_id(payload)
101                .map(|render_id| {
102                    Self::RenderFailed(RawRenderFailed {
103                        render_id,
104                        bytes: payload_bytes,
105                    })
106                })
107                .ok_or(crate::WebsocketError::InvalidEvent(bytes)),
108            b"custom_skin_process_update" => {
109                Ok(Self::CustomSkinProcessUpdate(RawCustomSkinProcessUpdate {
110                    bytes: payload_bytes,
111                }))
112            }
113            _ => Err(crate::WebsocketError::InvalidEvent(bytes)),
114        }
115    }
116
117    /// Deserialize into an [`Event`].
118    pub fn deserialize(&self) -> Result<Event, SerdeError> {
119        match self {
120            RawEvent::RenderAdded(event) => event.deserialize().map(Event::RenderAdded),
121            RawEvent::RenderDone(event) => event.deserialize().map(Event::RenderDone),
122            RawEvent::RenderFailed(event) => event.deserialize().map(Event::RenderFailed),
123            RawEvent::RenderProgress(event) => event.deserialize().map(Event::RenderProgress),
124            RawEvent::CustomSkinProcessUpdate(event) => {
125                event.deserialize().map(Event::CustomSkinProcessUpdate)
126            }
127        }
128    }
129}
130
131/// [`RenderAdded`] that has not been fully deserialized yet.
132#[derive(Clone, Debug, PartialEq, Eq)]
133pub struct RawRenderAdded {
134    pub bytes: Bytes,
135}
136
137impl RawRenderAdded {
138    /// Deserialize into a [`RenderAdded`] event.
139    pub fn deserialize(&self) -> Result<RenderAdded, SerdeError> {
140        serde_json::from_slice(&self.bytes)
141    }
142}
143
144/// [`RenderProgress`] that has not been fully deserialized yet.
145#[derive(Clone, Debug, PartialEq, Eq)]
146pub struct RawRenderProgress {
147    pub render_id: u32,
148    pub bytes: Bytes,
149}
150
151impl RawRenderProgress {
152    /// Deserialize into a [`RenderProgress`] event.
153    pub fn deserialize(&self) -> Result<RenderProgress, SerdeError> {
154        serde_json::from_slice(&self.bytes)
155    }
156}
157
158/// [`RenderFailed`] that has not been fully deserialized yet.
159#[derive(Clone, Debug, PartialEq, Eq)]
160pub struct RawRenderFailed {
161    pub render_id: u32,
162    pub bytes: Bytes,
163}
164
165impl RawRenderFailed {
166    /// Deserialize into a [`RenderFailed`] event.
167    pub fn deserialize(&self) -> Result<RenderFailed, SerdeError> {
168        serde_json::from_slice(&self.bytes)
169    }
170}
171
172/// [`RenderDone`] that has not been fully deserialized yet.
173#[derive(Clone, Debug, PartialEq, Eq)]
174pub struct RawRenderDone {
175    pub render_id: u32,
176    pub bytes: Bytes,
177}
178
179impl RawRenderDone {
180    /// Deserialize into a [`RenderDone`] event.
181    pub fn deserialize(&self) -> Result<RenderDone, SerdeError> {
182        serde_json::from_slice(&self.bytes)
183    }
184}
185
186/// [`CustomSkinProcessUpdate`] that has not been fully deserialized yet.
187#[derive(Clone, Debug, PartialEq, Eq)]
188pub struct RawCustomSkinProcessUpdate {
189    pub bytes: Bytes,
190}
191
192impl RawCustomSkinProcessUpdate {
193    /// Deserialize into a [`CustomSkinProcessUpdate`] event.
194    pub fn deserialize(&self) -> Result<CustomSkinProcessUpdate, SerdeError> {
195        serde_json::from_slice(&self.bytes)
196    }
197}
198
199#[cfg(test)]
200mod tests {
201    use super::*;
202    use crate::client::error::ErrorCode;
203
204    #[test]
205    fn parse_progress_event() {
206        let frame = br#"["render_progress_json",{"description":"","progress":"45%","renderID":123,"renderer":"r","username":"u"}]"#;
207
208        let progress = match RawEvent::from_bytes(Bytes::from_static(frame)).unwrap() {
209            RawEvent::RenderProgress(progress) => progress,
210            event => panic!("unexpected event: {event:?}"),
211        };
212
213        assert_eq!(progress.render_id, 123);
214        assert_eq!(progress.deserialize().unwrap().progress.as_ref(), "45%");
215    }
216
217    #[test]
218    fn parse_done_event() {
219        let frame = br#"["render_done_json",{"renderID":1,"videoUrl":"https://ordr.issou.best/video.mp4"}]"#;
220
221        let done = match RawEvent::from_bytes(Bytes::from_static(frame)).unwrap() {
222            RawEvent::RenderDone(done) => done,
223            event => panic!("unexpected event: {event:?}"),
224        };
225
226        assert_eq!(done.render_id, 1);
227        assert_eq!(
228            done.deserialize().unwrap().video_url.as_ref(),
229            "https://ordr.issou.best/video.mp4"
230        );
231    }
232
233    #[test]
234    fn parse_failed_event() {
235        let frame =
236            br#"["render_failed_json",{"renderID":2,"errorCode":2,"errorMessage":"bad replay"}]"#;
237
238        let failed = match RawEvent::from_bytes(Bytes::from_static(frame)).unwrap() {
239            RawEvent::RenderFailed(failed) => failed,
240            event => panic!("unexpected event: {event:?}"),
241        };
242
243        let failed = failed.deserialize().unwrap();
244
245        assert_eq!(failed.render_id, 2);
246        assert_eq!(failed.error_code, Some(ErrorCode::ReplayParsingError));
247        assert_eq!(failed.error_message.as_ref(), "bad replay");
248    }
249
250    #[test]
251    fn parse_added_event() {
252        let frame = br#"["render_added_json",{"renderID":9}]"#;
253
254        let added = match RawEvent::from_bytes(Bytes::from_static(frame)).unwrap() {
255            RawEvent::RenderAdded(added) => added,
256            event => panic!("unexpected event: {event:?}"),
257        };
258
259        assert_eq!(added.deserialize().unwrap().render_id, 9);
260    }
261
262    #[test]
263    fn parse_custom_skin_event() {
264        let frame = br#"["custom_skin_process_update",{"skinId":7}]"#;
265
266        assert!(matches!(
267            RawEvent::from_bytes(Bytes::from_static(frame)).unwrap(),
268            RawEvent::CustomSkinProcessUpdate(_),
269        ));
270    }
271
272    #[test]
273    fn reject_unknown_event() {
274        let frame = br#"["nope_json",{"a":1}]"#;
275
276        assert!(RawEvent::from_bytes(Bytes::from_static(frame)).is_err());
277    }
278
279    #[test]
280    fn reject_event_without_render_id() {
281        let frame = br#"["render_progress_json",{"description":""}]"#;
282
283        assert!(RawEvent::from_bytes(Bytes::from_static(frame)).is_err());
284    }
285}