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)]
136pub struct ConsoleMessage {
137 message_type: ConsoleMessageType,
139 args: Vec<RemoteObject>,
141 timestamp: f64,
143 stack_trace: Option<StackTrace>,
145 execution_context_id: i64,
147 connection: Arc<CdpConnection>,
149 session_id: String,
151}
152
153impl ConsoleMessage {
154 pub(crate) fn from_event(
156 event: ConsoleApiCalledEvent,
157 connection: Arc<CdpConnection>,
158 session_id: String,
159 ) -> Self {
160 Self {
161 message_type: ConsoleMessageType::from(event.call_type),
162 args: event.args,
163 timestamp: event.timestamp,
164 stack_trace: event.stack_trace,
165 execution_context_id: event.execution_context_id,
166 connection,
167 session_id,
168 }
169 }
170
171 pub fn type_(&self) -> ConsoleMessageType {
175 self.message_type
176 }
177
178 pub fn text(&self) -> String {
183 self.args
184 .iter()
185 .map(|arg| {
186 if let Some(value) = &arg.value {
187 format_value(value)
188 } else if let Some(description) = &arg.description {
189 description.clone()
190 } else {
191 arg.object_type.clone()
192 }
193 })
194 .collect::<Vec<_>>()
195 .join(" ")
196 }
197
198 pub fn args(&self) -> Vec<JsArg> {
203 self.args
204 .iter()
205 .map(|arg| JsArg {
206 object_type: arg.object_type.clone(),
207 subtype: arg.subtype.clone(),
208 class_name: arg.class_name.clone(),
209 value: arg.value.clone(),
210 description: arg.description.clone(),
211 object_id: arg.object_id.clone(),
212 })
213 .collect()
214 }
215
216 pub fn location(&self) -> Option<ConsoleMessageLocation> {
220 self.stack_trace.as_ref().and_then(|st| {
221 st.call_frames.first().map(|frame| ConsoleMessageLocation {
222 url: frame.url.clone(),
223 line_number: frame.line_number,
224 column_number: frame.column_number,
225 })
226 })
227 }
228
229 pub fn timestamp(&self) -> f64 {
233 self.timestamp
234 }
235}
236
237#[derive(Debug, Clone, Serialize, Deserialize)]
239#[serde(rename_all = "camelCase")]
240pub struct JsArg {
241 pub object_type: String,
243 #[serde(skip_serializing_if = "Option::is_none")]
245 pub subtype: Option<String>,
246 #[serde(skip_serializing_if = "Option::is_none")]
248 pub class_name: Option<String>,
249 #[serde(skip_serializing_if = "Option::is_none")]
251 pub value: Option<serde_json::Value>,
252 #[serde(skip_serializing_if = "Option::is_none")]
254 pub description: Option<String>,
255 #[serde(skip_serializing_if = "Option::is_none")]
257 pub object_id: Option<String>,
258}
259
260impl JsArg {
261 pub fn json_value(&self) -> Option<&serde_json::Value> {
263 self.value.as_ref()
264 }
265}
266
267fn format_value(value: &serde_json::Value) -> String {
269 match value {
270 serde_json::Value::Null => "null".to_string(),
271 serde_json::Value::Bool(b) => b.to_string(),
272 serde_json::Value::Number(n) => n.to_string(),
273 serde_json::Value::String(s) => s.clone(),
274 serde_json::Value::Array(arr) => {
275 let items: Vec<String> = arr.iter().map(format_value).collect();
276 format!("[{}]", items.join(", "))
277 }
278 serde_json::Value::Object(obj) => {
279 if obj.is_empty() {
280 "{}".to_string()
281 } else {
282 let pairs: Vec<String> = obj
283 .iter()
284 .map(|(k, v)| format!("{k}: {}", format_value(v)))
285 .collect();
286 format!("{{{}}}", pairs.join(", "))
287 }
288 }
289 }
290}