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