1use std::collections::VecDeque;
12use std::time::SystemTime;
13
14pub const DEFAULT_BUFFER_CAPACITY: usize = 1000;
17
18#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
23pub enum ConsoleLevel {
24 Error,
25 Warn,
26 Info,
27 Log,
28 Debug,
29}
30
31impl ConsoleLevel {
32 #[must_use]
34 pub fn as_str(self) -> &'static str {
35 match self {
36 Self::Error => "error",
37 Self::Warn => "warn",
38 Self::Info => "info",
39 Self::Log => "log",
40 Self::Debug => "debug",
41 }
42 }
43
44 #[must_use]
47 pub fn parse(s: &str) -> Option<Self> {
48 match s.to_ascii_lowercase().as_str() {
49 "error" | "err" => Some(Self::Error),
50 "warn" | "warning" => Some(Self::Warn),
51 "info" => Some(Self::Info),
52 "log" => Some(Self::Log),
53 "debug" | "trace" => Some(Self::Debug),
54 _ => None,
55 }
56 }
57}
58
59#[derive(Debug, Clone)]
61pub struct ConsoleEntry {
62 pub timestamp: SystemTime,
64 pub level: ConsoleLevel,
65 pub message: String,
68 pub stack: Option<String>,
72}
73
74#[derive(Debug, Clone)]
76pub struct NetworkEntry {
77 pub seq: u64,
79 pub timestamp: SystemTime,
80 pub method: String,
81 pub url: String,
82 pub status: NetworkStatus,
83 pub size: u64,
86 pub latency_ms: Option<u64>,
88}
89
90#[derive(Debug, Clone, PartialEq, Eq)]
92pub enum NetworkStatus {
93 Code(u16),
95 Pending,
97 Abort,
99 Cors,
101 Blocked,
103}
104
105impl NetworkStatus {
106 #[must_use]
108 pub fn as_str(&self) -> String {
109 match self {
110 Self::Code(c) => c.to_string(),
111 Self::Pending => "pending".into(),
112 Self::Abort => "abort".into(),
113 Self::Cors => "cors".into(),
114 Self::Blocked => "blocked".into(),
115 }
116 }
117}
118
119#[derive(Debug, Clone)]
121pub struct Header {
122 pub name: String,
123 pub value: String,
124}
125
126#[derive(Debug, Clone)]
130pub struct RequestDetail {
131 pub seq: u64,
132 pub method: String,
133 pub url: String,
134 pub status: NetworkStatus,
135 pub request_headers: Vec<Header>,
136 pub request_body: Option<String>,
137 pub response_headers: Vec<Header>,
138 pub response_body: Option<String>,
139}
140
141#[derive(Debug, Clone)]
146pub enum EvalResult {
147 Ok {
148 value: String,
150 js_type: String,
152 },
153 Thrown { kind: String, message: String },
155 Syntax { message: String },
157}
158
159#[derive(Debug, Clone, Copy, PartialEq, Eq)]
161pub enum StorageScope {
162 Cookies,
163 Local,
164 Session,
165 IndexedDb,
166}
167
168impl StorageScope {
169 #[must_use]
171 pub fn as_str(self) -> &'static str {
172 match self {
173 Self::Cookies => "cookies",
174 Self::Local => "local",
175 Self::Session => "session",
176 Self::IndexedDb => "indexeddb",
177 }
178 }
179
180 #[must_use]
182 pub fn parse(s: &str) -> Option<Self> {
183 match s {
184 "cookies" => Some(Self::Cookies),
185 "local" | "localStorage" => Some(Self::Local),
186 "session" | "sessionStorage" => Some(Self::Session),
187 "indexeddb" | "idb" => Some(Self::IndexedDb),
188 _ => None,
189 }
190 }
191}
192
193#[derive(Debug, Clone)]
199pub struct StorageEntry {
200 pub key: String,
201 pub value: String,
202 pub flags: Vec<String>,
204 pub sensitive: bool,
208}
209
210#[derive(Debug, Clone)]
212pub struct ScriptEntry {
213 pub seq: u64,
215 pub source: String,
217 pub size: u64,
219 pub state: ScriptState,
220}
221
222#[derive(Debug, Clone, Copy, PartialEq, Eq)]
223pub enum ScriptState {
224 Parsed,
225 Loading,
226 Error,
227 BlockedCsp,
228 BlockedPolicy,
229}
230
231impl ScriptState {
232 #[must_use]
233 pub fn as_str(self) -> &'static str {
234 match self {
235 Self::Parsed => "parsed",
236 Self::Loading => "loading",
237 Self::Error => "error",
238 Self::BlockedCsp => "blocked-csp",
239 Self::BlockedPolicy => "blocked-policy",
240 }
241 }
242}
243
244#[derive(Debug, Clone)]
246pub struct ScriptSource {
247 pub seq: u64,
248 pub source_url: String,
249 pub body: String,
250}
251
252#[derive(Debug, Clone)]
255pub struct DomDetail {
256 pub r: u32,
257 pub outer_html: String,
258 pub computed: Vec<(String, String)>,
261}
262
263#[derive(Debug, Clone, Default)]
268pub struct PerformanceMetrics {
269 pub ttfb_ms: f64,
270 pub fcp_ms: f64,
271 pub lcp_ms: f64,
272 pub cls: f64,
273 pub fid_ms: f64,
274 pub long_tasks: u32,
275 pub total_blocking_ms: f64,
276 pub js_heap_mb: f64,
277 pub dom_nodes: u32,
278}
279
280#[derive(Debug, Clone)]
284pub struct RingBuffer<T> {
285 items: VecDeque<T>,
286 capacity: usize,
287}
288
289impl<T: Clone> RingBuffer<T> {
290 #[must_use]
293 pub fn new(capacity: usize) -> Self {
294 let capacity = capacity.max(1);
295 Self {
296 items: VecDeque::with_capacity(capacity),
297 capacity,
298 }
299 }
300
301 pub fn push(&mut self, item: T) -> Option<T> {
307 let evicted = if self.items.len() == self.capacity {
308 self.items.pop_front()
309 } else {
310 None
311 };
312 self.items.push_back(item);
313 evicted
314 }
315
316 #[must_use]
317 pub fn snapshot(&self) -> Vec<T> {
318 self.items.iter().cloned().collect()
319 }
320
321 #[must_use]
322 pub fn len(&self) -> usize {
323 self.items.len()
324 }
325
326 #[must_use]
327 pub fn is_empty(&self) -> bool {
328 self.items.is_empty()
329 }
330
331 #[must_use]
332 pub fn capacity(&self) -> usize {
333 self.capacity
334 }
335}
336
337#[cfg(test)]
338mod tests {
339 use super::*;
340
341 #[test]
342 fn ring_buffer_evicts_fifo() {
343 let mut rb: RingBuffer<u32> = RingBuffer::new(3);
344 for i in 0..5 {
345 rb.push(i);
346 }
347 assert_eq!(rb.snapshot(), vec![2, 3, 4]);
348 assert_eq!(rb.len(), 3);
349 }
350
351 #[test]
352 fn ring_buffer_capacity_zero_is_clamped() {
353 let mut rb: RingBuffer<u8> = RingBuffer::new(0);
354 rb.push(1);
355 assert_eq!(rb.snapshot(), vec![1]);
356 }
357
358 #[test]
359 fn console_level_parse() {
360 assert_eq!(ConsoleLevel::parse("error"), Some(ConsoleLevel::Error));
361 assert_eq!(ConsoleLevel::parse("WARN"), Some(ConsoleLevel::Warn));
362 assert_eq!(ConsoleLevel::parse("garbage"), None);
363 }
364
365 #[test]
366 fn network_status_str() {
367 assert_eq!(NetworkStatus::Code(200).as_str(), "200");
368 assert_eq!(NetworkStatus::Pending.as_str(), "pending");
369 assert_eq!(NetworkStatus::Cors.as_str(), "cors");
370 }
371}