forge_core_utils/
log_msg.rs

1use axum::{extract::ws::Message, response::sse::Event};
2use json_patch::Patch;
3use serde::{Deserialize, Serialize};
4
5pub const EV_STDOUT: &str = "stdout";
6pub const EV_STDERR: &str = "stderr";
7pub const EV_JSON_PATCH: &str = "json_patch";
8pub const EV_SESSION_ID: &str = "session_id";
9pub const EV_FINISHED: &str = "finished";
10
11#[derive(Clone, Debug, Serialize, Deserialize)]
12pub enum LogMsg {
13    Stdout(String),
14    Stderr(String),
15    JsonPatch(Patch),
16    SessionId(String),
17    Finished,
18}
19
20impl LogMsg {
21    pub fn name(&self) -> &'static str {
22        match self {
23            LogMsg::Stdout(_) => EV_STDOUT,
24            LogMsg::Stderr(_) => EV_STDERR,
25            LogMsg::JsonPatch(_) => EV_JSON_PATCH,
26            LogMsg::SessionId(_) => EV_SESSION_ID,
27            LogMsg::Finished => EV_FINISHED,
28        }
29    }
30
31    pub fn to_sse_event(&self) -> Event {
32        match self {
33            LogMsg::Stdout(s) => Event::default().event(EV_STDOUT).data(s.clone()),
34            LogMsg::Stderr(s) => Event::default().event(EV_STDERR).data(s.clone()),
35            LogMsg::JsonPatch(patch) => {
36                let data = serde_json::to_string(patch).unwrap_or_else(|_| "[]".to_string());
37                Event::default().event(EV_JSON_PATCH).data(data)
38            }
39            LogMsg::SessionId(s) => Event::default().event(EV_SESSION_ID).data(s.clone()),
40            LogMsg::Finished => Event::default().event(EV_FINISHED).data(""),
41        }
42    }
43
44    /// Convert LogMsg to WebSocket message with proper error handling
45    pub fn to_ws_message(&self) -> Result<Message, serde_json::Error> {
46        let json = serde_json::to_string(self)?;
47        Ok(Message::Text(json.into()))
48    }
49
50    /// Convert LogMsg to WebSocket message with fallback error handling
51    ///
52    /// This method mirrors the behavior of the original logmsg_to_ws function
53    /// but with better error handling than unwrap().
54    pub fn to_ws_message_unchecked(&self) -> Message {
55        // Finished becomes JSON {finished: true}
56        let json = match self {
57            LogMsg::Finished => r#"{"finished":true}"#.to_string(),
58            _ => serde_json::to_string(self)
59                .unwrap_or_else(|_| r#"{"error":"serialization_failed"}"#.to_string()),
60        };
61
62        Message::Text(json.into())
63    }
64
65    /// Rough size accounting for your byte‑budgeted history.
66    pub fn approx_bytes(&self) -> usize {
67        const OVERHEAD: usize = 8;
68        match self {
69            LogMsg::Stdout(s) => EV_STDOUT.len() + s.len() + OVERHEAD,
70            LogMsg::Stderr(s) => EV_STDERR.len() + s.len() + OVERHEAD,
71            LogMsg::JsonPatch(patch) => {
72                let json_len = serde_json::to_string(patch).map(|s| s.len()).unwrap_or(2);
73                EV_JSON_PATCH.len() + json_len + OVERHEAD
74            }
75            LogMsg::SessionId(s) => EV_SESSION_ID.len() + s.len() + OVERHEAD,
76            LogMsg::Finished => EV_FINISHED.len() + OVERHEAD,
77        }
78    }
79}