Skip to main content

qubit_http/sse/
sse_message.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 message record
11//!
12//! One EventSource-style message dispatch after frame reassembly.
13//!
14
15use serde::de::DeserializeOwned;
16
17use super::SseJsonMode;
18use crate::{
19    HttpError,
20    HttpResult,
21};
22
23/// One EventSource-style message dispatch after `data:` line reassembly.
24#[derive(Debug, Clone, PartialEq, Eq)]
25pub struct SseMessage {
26    /// `event:` field if present; callers may treat `None` as the default `message` type.
27    pub event: Option<String>,
28    /// Concatenated `data:` payload.
29    pub data: String,
30    /// Last event id after applying this frame and any prior control-only `id:` frames.
31    pub last_event_id: Option<String>,
32}
33
34impl SseMessage {
35    /// Decodes the current message's `data` payload as JSON.
36    ///
37    /// # Type parameters
38    /// - `T`: Target type deserialized from [`SseMessage::data`].
39    ///
40    /// # Returns
41    /// `Ok(T)` when `data` is valid JSON for `T`.
42    ///
43    /// # Errors
44    /// Returns [`HttpError::sse_decode`] when JSON parsing fails.
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 message data as JSON (event={:?}, last_event_id={:?}): {}",
52                self.event, self.last_event_id, error
53            ))
54        })
55    }
56
57    /// Decodes the current message'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 [`SseMessage::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 message JSON in lenient mode (event={:?}, last_event_id={:?}): {}",
82                        self.event,
83                        self.last_event_id,
84                        error
85                    );
86                    Ok(None)
87                }
88            },
89        }
90    }
91}