Skip to main content

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