1use std::collections::HashMap;
17
18use srcmap_sourcemap::SourceMap;
19use srcmap_symbolicate::{
20 parse_stack_trace, parse_stack_trace_full, resolve_by_debug_id, symbolicate, symbolicate_batch,
21 to_json,
22};
23
24fn build_source_map_json() -> String {
52 r#"{
53 "version": 3,
54 "file": "bundle.js",
55 "sources": ["src/app.ts", "src/utils.ts"],
56 "sourcesContent": [
57 "import { validateInput } from './utils';\n\ninterface ClickEvent { target: HTMLElement }\n\nexport const handleClick = (event: ClickEvent): void => {\n const data = event.target.dataset;\n processData(data);\n};\n\nconst processData = (data: DOMStringMap): void => {\n const value = data['key'];\n if (!value) throw new TypeError(\"Cannot read property 'x' of null\");\n console.log(value);\n};",
58 "export const validateInput = (input: string): boolean => {\n if (!input) return false;\n return input.trim().length > 0;\n};"
59 ],
60 "names": ["handleClick", "processData", "validateInput"],
61 "mappings": "AAIAA;;SAKIC;;KCPJC",
62 "debugId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890"
63}"#
64 .to_string()
65}
66
67fn main() {
68 println!("=== Error Monitoring Symbolication Demo ===\n");
69
70 let source_map_json = build_source_map_json();
71
72 println!("--- Step 1: Parse V8 stack trace ---\n");
80
81 let v8_stack = "\
82TypeError: Cannot read property 'x' of null
83 at processData (bundle.js:3:10)
84 at handleClick (bundle.js:1:1)";
85
86 let v8_parsed = parse_stack_trace_full(v8_stack);
87
88 println!(" Message: {:?}", v8_parsed.message);
89 println!(" Frames: {}", v8_parsed.frames.len());
90 for (i, frame) in v8_parsed.frames.iter().enumerate() {
91 println!(
92 " [{i}] {} {}:{}:{}",
93 frame.function_name.as_deref().unwrap_or("<anonymous>"),
94 frame.file,
95 frame.line,
96 frame.column,
97 );
98 }
99
100 assert_eq!(
101 v8_parsed.message.as_deref(),
102 Some("TypeError: Cannot read property 'x' of null")
103 );
104 assert_eq!(v8_parsed.frames.len(), 2);
105 assert_eq!(
106 v8_parsed.frames[0].function_name.as_deref(),
107 Some("processData")
108 );
109 assert_eq!(v8_parsed.frames[0].file, "bundle.js");
110 assert_eq!(v8_parsed.frames[0].line, 3);
111 assert_eq!(v8_parsed.frames[0].column, 10);
112 assert_eq!(
113 v8_parsed.frames[1].function_name.as_deref(),
114 Some("handleClick")
115 );
116 assert_eq!(v8_parsed.frames[1].line, 1);
117 assert_eq!(v8_parsed.frames[1].column, 1);
118
119 println!("\n--- Step 2: Parse SpiderMonkey stack trace ---\n");
126
127 let sm_stack = "\
128processData@bundle.js:3:10
129handleClick@bundle.js:1:1";
130
131 let sm_frames = parse_stack_trace(sm_stack);
132
133 println!(" Frames: {}", sm_frames.len());
134 for (i, frame) in sm_frames.iter().enumerate() {
135 println!(
136 " [{i}] {} {}:{}:{}",
137 frame.function_name.as_deref().unwrap_or("<anonymous>"),
138 frame.file,
139 frame.line,
140 frame.column,
141 );
142 }
143
144 assert_eq!(sm_frames.len(), 2);
145 assert_eq!(sm_frames[0].function_name.as_deref(), Some("processData"));
146 assert_eq!(sm_frames[0].file, "bundle.js");
147 assert_eq!(sm_frames[0].line, 3);
148 assert_eq!(sm_frames[0].column, 10);
149
150 println!("\n--- Step 3: Compare parsed frames ---\n");
155
156 assert_eq!(v8_parsed.frames.len(), sm_frames.len());
157 for (v8_frame, sm_frame) in v8_parsed.frames.iter().zip(sm_frames.iter()) {
158 assert_eq!(v8_frame.function_name, sm_frame.function_name);
159 assert_eq!(v8_frame.file, sm_frame.file);
160 assert_eq!(v8_frame.line, sm_frame.line);
161 assert_eq!(v8_frame.column, sm_frame.column);
162 }
163 println!(" V8 and SpiderMonkey frames match.");
164
165 println!("\n--- Step 4: Symbolicate V8 stack trace ---\n");
176
177 let v8_result = symbolicate(v8_stack, |file| {
178 if file == "bundle.js" {
179 SourceMap::from_json(&source_map_json).ok()
180 } else {
181 None
182 }
183 });
184
185 println!(" Symbolicated stack (Display):\n");
186 print!("{v8_result}");
188
189 assert_eq!(
190 v8_result.message.as_deref(),
191 Some("TypeError: Cannot read property 'x' of null")
192 );
193 assert_eq!(v8_result.frames.len(), 2);
194
195 assert!(v8_result.frames[0].symbolicated);
197 assert_eq!(v8_result.frames[0].file, "src/app.ts");
198 assert_eq!(v8_result.frames[0].line, 10);
199 assert_eq!(v8_result.frames[0].column, 5);
200 assert_eq!(
201 v8_result.frames[0].function_name.as_deref(),
202 Some("processData")
203 );
204
205 assert!(v8_result.frames[1].symbolicated);
207 assert_eq!(v8_result.frames[1].file, "src/app.ts");
208 assert_eq!(v8_result.frames[1].line, 5);
209 assert_eq!(v8_result.frames[1].column, 1);
210 assert_eq!(
211 v8_result.frames[1].function_name.as_deref(),
212 Some("handleClick")
213 );
214
215 println!("\n--- Step 5: Symbolicate SpiderMonkey stack trace ---\n");
220
221 let sm_result = symbolicate(sm_stack, |file| {
222 if file == "bundle.js" {
223 SourceMap::from_json(&source_map_json).ok()
224 } else {
225 None
226 }
227 });
228
229 print!("{sm_result}");
230
231 assert_eq!(v8_result.frames.len(), sm_result.frames.len());
233 for (v8_frame, sm_frame) in v8_result.frames.iter().zip(sm_result.frames.iter()) {
234 assert_eq!(v8_frame.file, sm_frame.file);
235 assert_eq!(v8_frame.line, sm_frame.line);
236 assert_eq!(v8_frame.column, sm_frame.column);
237 assert_eq!(v8_frame.function_name, sm_frame.function_name);
238 assert_eq!(v8_frame.symbolicated, sm_frame.symbolicated);
239 }
240 println!("\n V8 and SpiderMonkey symbolicated frames match.");
241
242 println!("\n--- Step 6: JSON output ---\n");
247
248 let json_output = to_json(&v8_result);
249 println!("{json_output}");
250
251 assert!(json_output.contains("\"symbolicated\": true"));
252 assert!(json_output.contains("src/app.ts"));
253 assert!(json_output.contains("processData"));
254 assert!(json_output.contains("handleClick"));
255 assert!(json_output.contains("Cannot read property"));
256
257 println!("\n--- Step 7: Batch symbolication ---\n");
266
267 let sm = SourceMap::from_json(&source_map_json).expect("valid source map");
268 let mut maps: HashMap<String, SourceMap> = HashMap::new();
269 maps.insert("bundle.js".to_string(), sm);
270
271 let error_stack_1 = "\
272TypeError: Cannot read property 'x' of null
273 at processData (bundle.js:3:10)
274 at handleClick (bundle.js:1:1)";
275
276 let error_stack_2 = "\
277ReferenceError: foo is not defined
278 at handleClick (bundle.js:1:1)";
279
280 let stacks = vec![error_stack_1, error_stack_2];
281 let batch_results = symbolicate_batch(&stacks, &maps);
282
283 assert_eq!(batch_results.len(), 2);
284 println!(
285 " Batch result 1 ({} frames):",
286 batch_results[0].frames.len()
287 );
288 print!("{}", batch_results[0]);
289
290 println!(
291 " Batch result 2 ({} frames):",
292 batch_results[1].frames.len()
293 );
294 print!("{}", batch_results[1]);
295
296 assert_eq!(batch_results[0].frames.len(), 2);
298 assert!(batch_results[0].frames[0].symbolicated);
299 assert_eq!(batch_results[0].frames[0].file, "src/app.ts");
300
301 assert_eq!(batch_results[1].frames.len(), 1);
303 assert!(batch_results[1].frames[0].symbolicated);
304 assert_eq!(
305 batch_results[1].message.as_deref(),
306 Some("ReferenceError: foo is not defined")
307 );
308
309 println!("\n--- Step 8: Debug ID resolution ---\n");
318
319 let debug_id = "a1b2c3d4-e5f6-7890-abcd-ef1234567890";
320
321 let resolved = resolve_by_debug_id(debug_id, &maps);
322 assert!(resolved.is_some(), "should find source map by debug ID");
323
324 let resolved_map = resolved.unwrap();
325 assert_eq!(resolved_map.debug_id.as_deref(), Some(debug_id));
326 println!(" Found source map for debug ID: {debug_id}");
327 println!(" Sources: {:?}", resolved_map.sources);
328 println!(" Names: {:?}", resolved_map.names);
329
330 let missing = resolve_by_debug_id("00000000-0000-0000-0000-000000000000", &maps);
332 assert!(missing.is_none(), "unknown debug ID should return None");
333 println!(" Unknown debug ID correctly returns None.");
334
335 println!("\n=== All assertions passed. ===");
336}