1#![allow(clippy::print_stdout, reason = "Examples are intended to print walkthrough output")]
17
18use std::collections::HashMap;
19
20use srcmap_sourcemap::SourceMap;
21use srcmap_symbolicate::{
22 parse_stack_trace, parse_stack_trace_full, resolve_by_debug_id, symbolicate, symbolicate_batch,
23 to_json,
24};
25
26fn build_source_map_json() -> String {
54 r#"{
55 "version": 3,
56 "file": "bundle.js",
57 "sources": ["src/app.ts", "src/utils.ts"],
58 "sourcesContent": [
59 "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};",
60 "export const validateInput = (input: string): boolean => {\n if (!input) return false;\n return input.trim().length > 0;\n};"
61 ],
62 "names": ["handleClick", "processData", "validateInput"],
63 "mappings": "AAIAA;;SAKIC;;KCPJC",
64 "debugId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890"
65}"#
66 .to_string()
67}
68
69fn main() {
70 println!("=== Error Monitoring Symbolication Demo ===\n");
71
72 let source_map_json = build_source_map_json();
73
74 println!("--- Step 1: Parse V8 stack trace ---\n");
82
83 let v8_stack = "\
84TypeError: Cannot read property 'x' of null
85 at processData (bundle.js:3:10)
86 at handleClick (bundle.js:1:1)";
87
88 let v8_parsed = parse_stack_trace_full(v8_stack);
89
90 println!(" Message: {:?}", v8_parsed.message);
91 println!(" Frames: {}", v8_parsed.frames.len());
92 for (i, frame) in v8_parsed.frames.iter().enumerate() {
93 println!(
94 " [{i}] {} {}:{}:{}",
95 frame.function_name.as_deref().unwrap_or("<anonymous>"),
96 frame.file,
97 frame.line,
98 frame.column,
99 );
100 }
101
102 assert_eq!(v8_parsed.message.as_deref(), Some("TypeError: Cannot read property 'x' of null"));
103 assert_eq!(v8_parsed.frames.len(), 2);
104 assert_eq!(v8_parsed.frames[0].function_name.as_deref(), Some("processData"));
105 assert_eq!(v8_parsed.frames[0].file, "bundle.js");
106 assert_eq!(v8_parsed.frames[0].line, 3);
107 assert_eq!(v8_parsed.frames[0].column, 10);
108 assert_eq!(v8_parsed.frames[1].function_name.as_deref(), Some("handleClick"));
109 assert_eq!(v8_parsed.frames[1].line, 1);
110 assert_eq!(v8_parsed.frames[1].column, 1);
111
112 println!("\n--- Step 2: Parse SpiderMonkey stack trace ---\n");
119
120 let sm_stack = "\
121processData@bundle.js:3:10
122handleClick@bundle.js:1:1";
123
124 let sm_frames = parse_stack_trace(sm_stack);
125
126 println!(" Frames: {}", sm_frames.len());
127 for (i, frame) in sm_frames.iter().enumerate() {
128 println!(
129 " [{i}] {} {}:{}:{}",
130 frame.function_name.as_deref().unwrap_or("<anonymous>"),
131 frame.file,
132 frame.line,
133 frame.column,
134 );
135 }
136
137 assert_eq!(sm_frames.len(), 2);
138 assert_eq!(sm_frames[0].function_name.as_deref(), Some("processData"));
139 assert_eq!(sm_frames[0].file, "bundle.js");
140 assert_eq!(sm_frames[0].line, 3);
141 assert_eq!(sm_frames[0].column, 10);
142
143 println!("\n--- Step 3: Compare parsed frames ---\n");
148
149 assert_eq!(v8_parsed.frames.len(), sm_frames.len());
150 for (v8_frame, sm_frame) in v8_parsed.frames.iter().zip(sm_frames.iter()) {
151 assert_eq!(v8_frame.function_name, sm_frame.function_name);
152 assert_eq!(v8_frame.file, sm_frame.file);
153 assert_eq!(v8_frame.line, sm_frame.line);
154 assert_eq!(v8_frame.column, sm_frame.column);
155 }
156 println!(" V8 and SpiderMonkey frames match.");
157
158 println!("\n--- Step 4: Symbolicate V8 stack trace ---\n");
169
170 let v8_result = symbolicate(v8_stack, |file| {
171 if file == "bundle.js" { SourceMap::from_json(&source_map_json).ok() } else { None }
172 });
173
174 println!(" Symbolicated stack (Display):\n");
175 print!("{v8_result}");
177
178 assert_eq!(v8_result.message.as_deref(), Some("TypeError: Cannot read property 'x' of null"));
179 assert_eq!(v8_result.frames.len(), 2);
180
181 assert!(v8_result.frames[0].symbolicated);
183 assert_eq!(v8_result.frames[0].file, "src/app.ts");
184 assert_eq!(v8_result.frames[0].line, 10);
185 assert_eq!(v8_result.frames[0].column, 5);
186 assert_eq!(v8_result.frames[0].function_name.as_deref(), Some("processData"));
187
188 assert!(v8_result.frames[1].symbolicated);
190 assert_eq!(v8_result.frames[1].file, "src/app.ts");
191 assert_eq!(v8_result.frames[1].line, 5);
192 assert_eq!(v8_result.frames[1].column, 1);
193 assert_eq!(v8_result.frames[1].function_name.as_deref(), Some("handleClick"));
194
195 println!("\n--- Step 5: Symbolicate SpiderMonkey stack trace ---\n");
200
201 let sm_result = symbolicate(sm_stack, |file| {
202 if file == "bundle.js" { SourceMap::from_json(&source_map_json).ok() } else { None }
203 });
204
205 print!("{sm_result}");
206
207 assert_eq!(v8_result.frames.len(), sm_result.frames.len());
209 for (v8_frame, sm_frame) in v8_result.frames.iter().zip(sm_result.frames.iter()) {
210 assert_eq!(v8_frame.file, sm_frame.file);
211 assert_eq!(v8_frame.line, sm_frame.line);
212 assert_eq!(v8_frame.column, sm_frame.column);
213 assert_eq!(v8_frame.function_name, sm_frame.function_name);
214 assert_eq!(v8_frame.symbolicated, sm_frame.symbolicated);
215 }
216 println!("\n V8 and SpiderMonkey symbolicated frames match.");
217
218 println!("\n--- Step 6: JSON output ---\n");
223
224 let json_output = to_json(&v8_result);
225 println!("{json_output}");
226
227 assert!(json_output.contains("\"symbolicated\": true"));
228 assert!(json_output.contains("src/app.ts"));
229 assert!(json_output.contains("processData"));
230 assert!(json_output.contains("handleClick"));
231 assert!(json_output.contains("Cannot read property"));
232
233 println!("\n--- Step 7: Batch symbolication ---\n");
242
243 let sm = SourceMap::from_json(&source_map_json).expect("valid source map");
244 let mut maps: HashMap<String, SourceMap> = HashMap::new();
245 maps.insert("bundle.js".to_string(), sm);
246
247 let error_stack_1 = "\
248TypeError: Cannot read property 'x' of null
249 at processData (bundle.js:3:10)
250 at handleClick (bundle.js:1:1)";
251
252 let error_stack_2 = "\
253ReferenceError: foo is not defined
254 at handleClick (bundle.js:1:1)";
255
256 let stacks = vec![error_stack_1, error_stack_2];
257 let batch_results = symbolicate_batch(&stacks, &maps);
258
259 assert_eq!(batch_results.len(), 2);
260 println!(" Batch result 1 ({} frames):", batch_results[0].frames.len());
261 print!("{}", batch_results[0]);
262
263 println!(" Batch result 2 ({} frames):", batch_results[1].frames.len());
264 print!("{}", batch_results[1]);
265
266 assert_eq!(batch_results[0].frames.len(), 2);
268 assert!(batch_results[0].frames[0].symbolicated);
269 assert_eq!(batch_results[0].frames[0].file, "src/app.ts");
270
271 assert_eq!(batch_results[1].frames.len(), 1);
273 assert!(batch_results[1].frames[0].symbolicated);
274 assert_eq!(batch_results[1].message.as_deref(), Some("ReferenceError: foo is not defined"));
275
276 println!("\n--- Step 8: Debug ID resolution ---\n");
285
286 let debug_id = "a1b2c3d4-e5f6-7890-abcd-ef1234567890";
287
288 let resolved = resolve_by_debug_id(debug_id, &maps);
289 assert!(resolved.is_some(), "should find source map by debug ID");
290
291 let resolved_map = resolved.unwrap();
292 assert_eq!(resolved_map.debug_id.as_deref(), Some(debug_id));
293 println!(" Found source map for debug ID: {debug_id}");
294 println!(" Sources: {:?}", resolved_map.sources);
295 println!(" Names: {:?}", resolved_map.names);
296
297 let missing = resolve_by_debug_id("00000000-0000-0000-0000-000000000000", &maps);
299 assert!(missing.is_none(), "unknown debug ID should return None");
300 println!(" Unknown debug ID correctly returns None.");
301
302 println!("\n=== All assertions passed. ===");
303}