viewpoint_core/page/console/
mod.rs1use std::sync::Arc;
9
10use serde::{Deserialize, Serialize};
11use viewpoint_cdp::protocol::runtime::{ConsoleApiCalledEvent, ConsoleApiType, RemoteObject, StackTrace};
12use viewpoint_cdp::CdpConnection;
13
14#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
16#[serde(rename_all = "lowercase")]
17pub enum ConsoleMessageType {
18 Log,
20 Debug,
22 Info,
24 Error,
26 Warning,
28 Dir,
30 DirXml,
32 Table,
34 Trace,
36 Clear,
38 Count,
40 Assert,
42 Profile,
44 ProfileEnd,
46 StartGroup,
48 EndGroup,
50 TimeEnd,
52}
53
54impl From<ConsoleApiType> for ConsoleMessageType {
55 fn from(api_type: ConsoleApiType) -> Self {
56 match api_type {
57 ConsoleApiType::Log => Self::Log,
58 ConsoleApiType::Debug => Self::Debug,
59 ConsoleApiType::Info => Self::Info,
60 ConsoleApiType::Error => Self::Error,
61 ConsoleApiType::Warning => Self::Warning,
62 ConsoleApiType::Dir => Self::Dir,
63 ConsoleApiType::Dirxml => Self::DirXml,
64 ConsoleApiType::Table => Self::Table,
65 ConsoleApiType::Trace => Self::Trace,
66 ConsoleApiType::Clear => Self::Clear,
67 ConsoleApiType::Count => Self::Count,
68 ConsoleApiType::Assert => Self::Assert,
69 ConsoleApiType::Profile => Self::Profile,
70 ConsoleApiType::ProfileEnd => Self::ProfileEnd,
71 ConsoleApiType::StartGroup | ConsoleApiType::StartGroupCollapsed => Self::StartGroup,
72 ConsoleApiType::EndGroup => Self::EndGroup,
73 ConsoleApiType::TimeEnd => Self::TimeEnd,
74 }
75 }
76}
77
78impl std::fmt::Display for ConsoleMessageType {
79 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
80 let s = match self {
81 Self::Log => "log",
82 Self::Debug => "debug",
83 Self::Info => "info",
84 Self::Error => "error",
85 Self::Warning => "warning",
86 Self::Dir => "dir",
87 Self::DirXml => "dirxml",
88 Self::Table => "table",
89 Self::Trace => "trace",
90 Self::Clear => "clear",
91 Self::Count => "count",
92 Self::Assert => "assert",
93 Self::Profile => "profile",
94 Self::ProfileEnd => "profileEnd",
95 Self::StartGroup => "startGroup",
96 Self::EndGroup => "endGroup",
97 Self::TimeEnd => "timeEnd",
98 };
99 write!(f, "{s}")
100 }
101}
102
103#[derive(Debug, Clone, Serialize, Deserialize)]
105#[serde(rename_all = "camelCase")]
106pub struct ConsoleMessageLocation {
107 pub url: String,
109 pub line_number: i32,
111 pub column_number: i32,
113}
114
115#[derive(Debug, Clone)]
129pub struct ConsoleMessage {
130 message_type: ConsoleMessageType,
132 args: Vec<RemoteObject>,
134 timestamp: f64,
136 stack_trace: Option<StackTrace>,
138 execution_context_id: i64,
140 connection: Arc<CdpConnection>,
142 session_id: String,
144}
145
146impl ConsoleMessage {
147 pub(crate) fn from_event(
149 event: ConsoleApiCalledEvent,
150 connection: Arc<CdpConnection>,
151 session_id: String,
152 ) -> Self {
153 Self {
154 message_type: ConsoleMessageType::from(event.call_type),
155 args: event.args,
156 timestamp: event.timestamp,
157 stack_trace: event.stack_trace,
158 execution_context_id: event.execution_context_id,
159 connection,
160 session_id,
161 }
162 }
163
164 pub fn type_(&self) -> ConsoleMessageType {
168 self.message_type
169 }
170
171 pub fn text(&self) -> String {
176 self.args
177 .iter()
178 .map(|arg| {
179 if let Some(value) = &arg.value {
180 format_value(value)
181 } else if let Some(description) = &arg.description {
182 description.clone()
183 } else {
184 arg.object_type.clone()
185 }
186 })
187 .collect::<Vec<_>>()
188 .join(" ")
189 }
190
191 pub fn args(&self) -> Vec<JsArg> {
196 self.args
197 .iter()
198 .map(|arg| JsArg {
199 object_type: arg.object_type.clone(),
200 subtype: arg.subtype.clone(),
201 class_name: arg.class_name.clone(),
202 value: arg.value.clone(),
203 description: arg.description.clone(),
204 object_id: arg.object_id.clone(),
205 })
206 .collect()
207 }
208
209 pub fn location(&self) -> Option<ConsoleMessageLocation> {
213 self.stack_trace.as_ref().and_then(|st| {
214 st.call_frames.first().map(|frame| ConsoleMessageLocation {
215 url: frame.url.clone(),
216 line_number: frame.line_number,
217 column_number: frame.column_number,
218 })
219 })
220 }
221
222 pub fn timestamp(&self) -> f64 {
226 self.timestamp
227 }
228}
229
230#[derive(Debug, Clone, Serialize, Deserialize)]
232#[serde(rename_all = "camelCase")]
233pub struct JsArg {
234 pub object_type: String,
236 #[serde(skip_serializing_if = "Option::is_none")]
238 pub subtype: Option<String>,
239 #[serde(skip_serializing_if = "Option::is_none")]
241 pub class_name: Option<String>,
242 #[serde(skip_serializing_if = "Option::is_none")]
244 pub value: Option<serde_json::Value>,
245 #[serde(skip_serializing_if = "Option::is_none")]
247 pub description: Option<String>,
248 #[serde(skip_serializing_if = "Option::is_none")]
250 pub object_id: Option<String>,
251}
252
253impl JsArg {
254 pub fn json_value(&self) -> Option<&serde_json::Value> {
256 self.value.as_ref()
257 }
258}
259
260fn format_value(value: &serde_json::Value) -> String {
262 match value {
263 serde_json::Value::Null => "null".to_string(),
264 serde_json::Value::Bool(b) => b.to_string(),
265 serde_json::Value::Number(n) => n.to_string(),
266 serde_json::Value::String(s) => s.clone(),
267 serde_json::Value::Array(arr) => {
268 let items: Vec<String> = arr.iter().map(format_value).collect();
269 format!("[{}]", items.join(", "))
270 }
271 serde_json::Value::Object(obj) => {
272 if obj.is_empty() {
273 "{}".to_string()
274 } else {
275 let pairs: Vec<String> = obj
276 .iter()
277 .map(|(k, v)| format!("{k}: {}", format_value(v)))
278 .collect();
279 format!("{{{}}}", pairs.join(", "))
280 }
281 }
282 }
283}