qubit_http/sse/sse_event.rs
1/*******************************************************************************
2 *
3 * Copyright (c) 2025 - 2026 Haixing Hu.
4 *
5 * SPDX-License-Identifier: Apache-2.0
6 *
7 * Licensed under the Apache License, Version 2.0.
8 *
9 ******************************************************************************/
10//! # SSE event record
11//!
12//! One dispatch after frame reassembly (`data:` lines joined with `\n`).
13//!
14
15use serde::de::DeserializeOwned;
16
17use super::SseJsonMode;
18use crate::{HttpError, HttpResult};
19
20/// One Server-Sent Events dispatch after frame reassembly (`data:` lines joined with `\n`).
21#[derive(Debug, Clone, PartialEq, Eq)]
22pub struct SseEvent {
23 /// `event:` field if present.
24 pub event: Option<String>,
25 /// Concatenated `data:` payload (newline-separated if multiple `data` lines).
26 pub data: String,
27 /// `id:` field if present.
28 pub id: Option<String>,
29 /// Parsed `retry:` milliseconds hint if valid.
30 pub retry: Option<u64>,
31}
32
33impl SseEvent {
34 /// Decodes the current event's `data` payload as JSON.
35 ///
36 /// # Type parameters
37 /// - `T`: Target type deserialized from [`SseEvent::data`].
38 ///
39 /// # Returns
40 /// `Ok(T)` when `data` is valid JSON for `T`.
41 ///
42 /// # Errors
43 /// Returns [`HttpError::sse_decode`] when JSON parsing fails.
44 /// The error message includes optional `event` and `id` context.
45 pub fn decode_json<T>(&self) -> HttpResult<T>
46 where
47 T: DeserializeOwned,
48 {
49 serde_json::from_str::<T>(&self.data).map_err(|error| {
50 HttpError::sse_decode(format!(
51 "Failed to decode SSE event data as JSON (event={:?}, id={:?}): {}",
52 self.event, self.id, error
53 ))
54 })
55 }
56
57 /// Decodes the current event's `data` payload as JSON with configurable strictness.
58 ///
59 /// # Parameters
60 /// - `mode`: JSON decoding strictness.
61 ///
62 /// # Type parameters
63 /// - `T`: Target type deserialized from [`SseEvent::data`].
64 ///
65 /// # Returns
66 /// - `Ok(Some(T))` when `data` is valid JSON for `T`.
67 /// - `Ok(None)` in lenient mode when JSON parsing fails.
68 ///
69 /// # Errors
70 /// Returns [`HttpError::sse_decode`] in strict mode when JSON parsing fails.
71 pub fn decode_json_with_mode<T>(&self, mode: SseJsonMode) -> HttpResult<Option<T>>
72 where
73 T: DeserializeOwned,
74 {
75 match mode {
76 SseJsonMode::Strict => self.decode_json::<T>().map(Some),
77 SseJsonMode::Lenient => match serde_json::from_str::<T>(&self.data) {
78 Ok(value) => Ok(Some(value)),
79 Err(error) => {
80 tracing::debug!(
81 "Skipping malformed SSE event JSON in lenient mode (event={:?}, id={:?}): {}",
82 self.event,
83 self.id,
84 error
85 );
86 Ok(None)
87 }
88 },
89 }
90 }
91}