use std::collections::VecDeque;
use std::time::SystemTime;
pub const DEFAULT_BUFFER_CAPACITY: usize = 1000;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum ConsoleLevel {
Error,
Warn,
Info,
Log,
Debug,
}
impl ConsoleLevel {
#[must_use]
pub fn as_str(self) -> &'static str {
match self {
Self::Error => "error",
Self::Warn => "warn",
Self::Info => "info",
Self::Log => "log",
Self::Debug => "debug",
}
}
#[must_use]
pub fn parse(s: &str) -> Option<Self> {
match s.to_ascii_lowercase().as_str() {
"error" | "err" => Some(Self::Error),
"warn" | "warning" => Some(Self::Warn),
"info" => Some(Self::Info),
"log" => Some(Self::Log),
"debug" | "trace" => Some(Self::Debug),
_ => None,
}
}
}
#[derive(Debug, Clone)]
pub struct ConsoleEntry {
pub timestamp: SystemTime,
pub level: ConsoleLevel,
pub message: String,
pub stack: Option<String>,
}
#[derive(Debug, Clone)]
pub struct NetworkEntry {
pub seq: u64,
pub timestamp: SystemTime,
pub method: String,
pub url: String,
pub status: NetworkStatus,
pub size: u64,
pub latency_ms: Option<u64>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum NetworkStatus {
Code(u16),
Pending,
Abort,
Cors,
Blocked,
}
impl NetworkStatus {
#[must_use]
pub fn as_str(&self) -> String {
match self {
Self::Code(c) => c.to_string(),
Self::Pending => "pending".into(),
Self::Abort => "abort".into(),
Self::Cors => "cors".into(),
Self::Blocked => "blocked".into(),
}
}
}
#[derive(Debug, Clone)]
pub struct Header {
pub name: String,
pub value: String,
}
#[derive(Debug, Clone)]
pub struct RequestDetail {
pub seq: u64,
pub method: String,
pub url: String,
pub status: NetworkStatus,
pub request_headers: Vec<Header>,
pub request_body: Option<String>,
pub response_headers: Vec<Header>,
pub response_body: Option<String>,
}
#[derive(Debug, Clone)]
pub enum EvalResult {
Ok {
value: String,
js_type: String,
},
Thrown { kind: String, message: String },
Syntax { message: String },
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum StorageScope {
Cookies,
Local,
Session,
IndexedDb,
}
impl StorageScope {
#[must_use]
pub fn as_str(self) -> &'static str {
match self {
Self::Cookies => "cookies",
Self::Local => "local",
Self::Session => "session",
Self::IndexedDb => "indexeddb",
}
}
#[must_use]
pub fn parse(s: &str) -> Option<Self> {
match s {
"cookies" => Some(Self::Cookies),
"local" | "localStorage" => Some(Self::Local),
"session" | "sessionStorage" => Some(Self::Session),
"indexeddb" | "idb" => Some(Self::IndexedDb),
_ => None,
}
}
}
#[derive(Debug, Clone)]
pub struct StorageEntry {
pub key: String,
pub value: String,
pub flags: Vec<String>,
pub sensitive: bool,
}
#[derive(Debug, Clone)]
pub struct ScriptEntry {
pub seq: u64,
pub source: String,
pub size: u64,
pub state: ScriptState,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ScriptState {
Parsed,
Loading,
Error,
BlockedCsp,
BlockedPolicy,
}
impl ScriptState {
#[must_use]
pub fn as_str(self) -> &'static str {
match self {
Self::Parsed => "parsed",
Self::Loading => "loading",
Self::Error => "error",
Self::BlockedCsp => "blocked-csp",
Self::BlockedPolicy => "blocked-policy",
}
}
}
#[derive(Debug, Clone)]
pub struct ScriptSource {
pub seq: u64,
pub source_url: String,
pub body: String,
}
#[derive(Debug, Clone)]
pub struct DomDetail {
pub r: u32,
pub outer_html: String,
pub computed: Vec<(String, String)>,
}
#[derive(Debug, Clone, Default)]
pub struct PerformanceMetrics {
pub ttfb_ms: f64,
pub fcp_ms: f64,
pub lcp_ms: f64,
pub cls: f64,
pub fid_ms: f64,
pub long_tasks: u32,
pub total_blocking_ms: f64,
pub js_heap_mb: f64,
pub dom_nodes: u32,
}
#[derive(Debug, Clone)]
pub struct RingBuffer<T> {
items: VecDeque<T>,
capacity: usize,
}
impl<T: Clone> RingBuffer<T> {
#[must_use]
pub fn new(capacity: usize) -> Self {
let capacity = capacity.max(1);
Self {
items: VecDeque::with_capacity(capacity),
capacity,
}
}
pub fn push(&mut self, item: T) -> Option<T> {
let evicted = if self.items.len() == self.capacity {
self.items.pop_front()
} else {
None
};
self.items.push_back(item);
evicted
}
#[must_use]
pub fn snapshot(&self) -> Vec<T> {
self.items.iter().cloned().collect()
}
#[must_use]
pub fn len(&self) -> usize {
self.items.len()
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.items.is_empty()
}
#[must_use]
pub fn capacity(&self) -> usize {
self.capacity
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn ring_buffer_evicts_fifo() {
let mut rb: RingBuffer<u32> = RingBuffer::new(3);
for i in 0..5 {
rb.push(i);
}
assert_eq!(rb.snapshot(), vec![2, 3, 4]);
assert_eq!(rb.len(), 3);
}
#[test]
fn ring_buffer_capacity_zero_is_clamped() {
let mut rb: RingBuffer<u8> = RingBuffer::new(0);
rb.push(1);
assert_eq!(rb.snapshot(), vec![1]);
}
#[test]
fn console_level_parse() {
assert_eq!(ConsoleLevel::parse("error"), Some(ConsoleLevel::Error));
assert_eq!(ConsoleLevel::parse("WARN"), Some(ConsoleLevel::Warn));
assert_eq!(ConsoleLevel::parse("garbage"), None);
}
#[test]
fn network_status_str() {
assert_eq!(NetworkStatus::Code(200).as_str(), "200");
assert_eq!(NetworkStatus::Pending.as_str(), "pending");
assert_eq!(NetworkStatus::Cors.as_str(), "cors");
}
}