use std::sync::Arc;
use serde::{Deserialize, Serialize};
use viewpoint_cdp::CdpConnection;
use viewpoint_cdp::protocol::runtime::{
ConsoleApiCalledEvent, ConsoleApiType, RemoteObject, StackTrace,
};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum ConsoleMessageType {
Log,
Debug,
Info,
Error,
Warning,
Dir,
DirXml,
Table,
Trace,
Clear,
Count,
Assert,
Profile,
ProfileEnd,
StartGroup,
EndGroup,
TimeEnd,
}
impl From<ConsoleApiType> for ConsoleMessageType {
fn from(api_type: ConsoleApiType) -> Self {
match api_type {
ConsoleApiType::Log => Self::Log,
ConsoleApiType::Debug => Self::Debug,
ConsoleApiType::Info => Self::Info,
ConsoleApiType::Error => Self::Error,
ConsoleApiType::Warning => Self::Warning,
ConsoleApiType::Dir => Self::Dir,
ConsoleApiType::Dirxml => Self::DirXml,
ConsoleApiType::Table => Self::Table,
ConsoleApiType::Trace => Self::Trace,
ConsoleApiType::Clear => Self::Clear,
ConsoleApiType::Count => Self::Count,
ConsoleApiType::Assert => Self::Assert,
ConsoleApiType::Profile => Self::Profile,
ConsoleApiType::ProfileEnd => Self::ProfileEnd,
ConsoleApiType::StartGroup | ConsoleApiType::StartGroupCollapsed => Self::StartGroup,
ConsoleApiType::EndGroup => Self::EndGroup,
ConsoleApiType::TimeEnd => Self::TimeEnd,
}
}
}
impl std::fmt::Display for ConsoleMessageType {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let s = match self {
Self::Log => "log",
Self::Debug => "debug",
Self::Info => "info",
Self::Error => "error",
Self::Warning => "warning",
Self::Dir => "dir",
Self::DirXml => "dirxml",
Self::Table => "table",
Self::Trace => "trace",
Self::Clear => "clear",
Self::Count => "count",
Self::Assert => "assert",
Self::Profile => "profile",
Self::ProfileEnd => "profileEnd",
Self::StartGroup => "startGroup",
Self::EndGroup => "endGroup",
Self::TimeEnd => "timeEnd",
};
write!(f, "{s}")
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct ConsoleMessageLocation {
pub url: String,
pub line_number: i32,
pub column_number: i32,
}
#[derive(Debug, Clone)]
pub struct ConsoleMessage {
message_type: ConsoleMessageType,
args: Vec<RemoteObject>,
timestamp: f64,
stack_trace: Option<StackTrace>,
execution_context_id: i64,
connection: Arc<CdpConnection>,
session_id: String,
}
impl ConsoleMessage {
pub(crate) fn from_event(
event: ConsoleApiCalledEvent,
connection: Arc<CdpConnection>,
session_id: String,
) -> Self {
Self {
message_type: ConsoleMessageType::from(event.call_type),
args: event.args,
timestamp: event.timestamp,
stack_trace: event.stack_trace,
execution_context_id: event.execution_context_id,
connection,
session_id,
}
}
pub fn type_(&self) -> ConsoleMessageType {
self.message_type
}
pub fn text(&self) -> String {
self.args
.iter()
.map(|arg| {
if let Some(value) = &arg.value {
format_value(value)
} else if let Some(description) = &arg.description {
description.clone()
} else {
arg.object_type.clone()
}
})
.collect::<Vec<_>>()
.join(" ")
}
pub fn args(&self) -> Vec<JsArg> {
self.args
.iter()
.map(|arg| JsArg {
object_type: arg.object_type.clone(),
subtype: arg.subtype.clone(),
class_name: arg.class_name.clone(),
value: arg.value.clone(),
description: arg.description.clone(),
object_id: arg.object_id.clone(),
})
.collect()
}
pub fn location(&self) -> Option<ConsoleMessageLocation> {
self.stack_trace.as_ref().and_then(|st| {
st.call_frames.first().map(|frame| ConsoleMessageLocation {
url: frame.url.clone(),
line_number: frame.line_number,
column_number: frame.column_number,
})
})
}
pub fn timestamp(&self) -> f64 {
self.timestamp
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct JsArg {
pub object_type: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub subtype: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub class_name: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub value: Option<serde_json::Value>,
#[serde(skip_serializing_if = "Option::is_none")]
pub description: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub object_id: Option<String>,
}
impl JsArg {
pub fn json_value(&self) -> Option<&serde_json::Value> {
self.value.as_ref()
}
}
fn format_value(value: &serde_json::Value) -> String {
match value {
serde_json::Value::Null => "null".to_string(),
serde_json::Value::Bool(b) => b.to_string(),
serde_json::Value::Number(n) => n.to_string(),
serde_json::Value::String(s) => s.clone(),
serde_json::Value::Array(arr) => {
let items: Vec<String> = arr.iter().map(format_value).collect();
format!("[{}]", items.join(", "))
}
serde_json::Value::Object(obj) => {
if obj.is_empty() {
"{}".to_string()
} else {
let pairs: Vec<String> = obj
.iter()
.map(|(k, v)| format!("{k}: {}", format_value(v)))
.collect();
format!("{{{}}}", pairs.join(", "))
}
}
}
}