1use serde::Serialize;
2
3#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize)]
5pub struct FrameId(pub u32);
6
7#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize)]
9#[serde(rename_all = "snake_case")]
10pub enum FrameKind {
11 Function,
13 Native,
15 GC,
17 Eval,
19 Wasm,
21 Builtin,
23 RegExp,
25 Idle,
27 Program,
29 Unknown,
31}
32
33#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize)]
35#[serde(rename_all = "snake_case")]
36pub enum FrameCategory {
37 App,
39 Deps,
41 NodeInternal,
43 V8Internal,
45 Native,
47}
48
49impl FrameCategory {
50 pub fn is_internal(&self) -> bool {
52 matches!(self, Self::NodeInternal | Self::V8Internal | Self::Native)
53 }
54}
55
56#[derive(Debug, Clone, Serialize)]
58pub struct Frame {
59 pub id: FrameId,
61
62 pub name: String,
64
65 pub file: Option<String>,
67
68 pub line: Option<u32>,
70
71 pub col: Option<u32>,
73
74 pub kind: FrameKind,
76
77 pub category: FrameCategory,
79
80 pub minified_name: Option<String>,
82
83 pub minified_location: Option<String>,
85}
86
87impl Frame {
88 pub fn new(
90 id: FrameId,
91 name: String,
92 file: Option<String>,
93 line: Option<u32>,
94 col: Option<u32>,
95 kind: FrameKind,
96 category: FrameCategory,
97 ) -> Self {
98 Self {
99 id,
100 name,
101 file,
102 line,
103 col,
104 kind,
105 category,
106 minified_name: None,
107 minified_location: None,
108 }
109 }
110
111 pub fn location(&self) -> String {
113 match (&self.file, self.line, self.col) {
114 (Some(file), Some(line), Some(col)) => {
115 format!("{}:{line}:{col}", Self::clean_file_path(file))
116 }
117 (Some(file), Some(line), None) => {
118 format!("{}:{line}", Self::clean_file_path(file))
119 }
120 (Some(file), None, None) => Self::clean_file_path(file),
121 _ => "(unknown)".to_string(),
122 }
123 }
124
125 fn clean_file_path(path: &str) -> String {
127 path.strip_prefix("file://").unwrap_or(path).to_string()
128 }
129
130 pub fn clean_file(&self) -> Option<String> {
132 self.file.as_ref().map(|f| Self::clean_file_path(f))
133 }
134
135 pub fn display_name(&self) -> String {
139 if !self.name.is_empty() && self.name != "(anonymous)" {
140 return self.name.clone();
141 }
142
143 if let Some(file) = &self.file {
145 let clean_path = Self::clean_file_path(file);
147 let filename = std::path::Path::new(&clean_path)
148 .file_name()
149 .map(|s| s.to_string_lossy())
150 .unwrap_or_else(|| clean_path.as_str().into());
151
152 if let Some(line) = self.line {
153 return format!("(anonymous @ {filename}:{line})");
154 }
155 return format!("(anonymous @ {filename})");
156 }
157
158 "(anonymous)".to_string()
159 }
160}
161
162impl std::fmt::Display for FrameKind {
163 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
164 match self {
165 Self::Function => write!(f, "function"),
166 Self::Native => write!(f, "native"),
167 Self::GC => write!(f, "gc"),
168 Self::Eval => write!(f, "eval"),
169 Self::Wasm => write!(f, "wasm"),
170 Self::Builtin => write!(f, "builtin"),
171 Self::RegExp => write!(f, "regexp"),
172 Self::Idle => write!(f, "idle"),
173 Self::Program => write!(f, "program"),
174 Self::Unknown => write!(f, "unknown"),
175 }
176 }
177}
178
179impl std::fmt::Display for FrameCategory {
180 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
181 match self {
182 Self::App => write!(f, "App"),
183 Self::Deps => write!(f, "Dependencies"),
184 Self::NodeInternal => write!(f, "Node.js Internal"),
185 Self::V8Internal => write!(f, "V8 Internal"),
186 Self::Native => write!(f, "Native"),
187 }
188 }
189}