use alloc::vec::Vec;
use core::fmt::Display;
use crate::{Span, String, ToCompactString, level::Level};
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum CaptureOutcome {
Pending,
Accepted,
Legacy(String),
Unavailable(String),
Rejected(String),
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct StageCapturePage {
pub level: Level,
pub step: &'static str,
pub text: String,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct StageCaptureUnavailable {
pub level: Level,
pub step: &'static str,
pub reason: String,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct CaptureOption {
pub name: &'static str,
pub value: String,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct StageTiming {
pub level: Level,
pub step: &'static str,
pub nanos: u64,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum CaptureArgValue {
Str(String),
Int(i64),
Bool(bool),
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct CaptureArg {
pub name: String,
pub value: CaptureArgValue,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct CaptureRemark {
pub level: Level,
pub pass: String,
pub kind: String,
pub name: String,
pub span: Span,
pub args: Vec<CaptureArg>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct StageCapture {
pub target: String,
pub outcome: CaptureOutcome,
pub options: Vec<CaptureOption>,
pub pages: Vec<StageCapturePage>,
pub unavailable: Vec<StageCaptureUnavailable>,
pub timings: Vec<StageTiming>,
pub remarks: Vec<CaptureRemark>,
pub timings_observed: bool,
pub remarks_observed: bool,
}
impl StageCapture {
#[must_use]
pub fn new(target: &str) -> Self {
Self {
target: String::from(target),
outcome: CaptureOutcome::Pending,
options: Vec::new(),
pages: Vec::new(),
unavailable: Vec::new(),
timings: Vec::new(),
remarks: Vec::new(),
timings_observed: false,
remarks_observed: false,
}
}
pub fn option(&mut self, name: &'static str, value: impl Into<String>) {
self.options.push(CaptureOption {
name,
value: value.into(),
});
}
}
pub trait CaptureSink {
const RECORDING: bool;
fn page<F: FnOnce() -> String>(&mut self, level: Level, step: &'static str, render: F);
fn try_page<E: Display, F: FnOnce() -> Result<String, E>>(
&mut self,
level: Level,
step: &'static str,
render: F,
);
fn effective_option<F: FnOnce() -> String>(&mut self, name: &'static str, value: F);
fn timing<F: FnOnce() -> u64>(&mut self, level: Level, step: &'static str, nanos: F);
fn remark<F: FnOnce() -> CaptureRemark>(&mut self, render: F);
fn timing_channel_observed(&mut self);
fn remark_channel_observed(&mut self);
fn finish<F: FnOnce() -> CaptureOutcome>(&mut self, outcome: F);
}
#[derive(Debug, Default, Clone, Copy)]
pub struct NoCapture;
impl CaptureSink for NoCapture {
const RECORDING: bool = false;
#[inline(always)]
fn page<F: FnOnce() -> String>(&mut self, _: Level, _: &'static str, _: F) {}
#[inline(always)]
fn try_page<E: Display, F: FnOnce() -> Result<String, E>>(
&mut self,
_: Level,
_: &'static str,
_: F,
) {
}
#[inline(always)]
fn effective_option<F: FnOnce() -> String>(&mut self, _: &'static str, _: F) {}
#[inline(always)]
fn timing<F: FnOnce() -> u64>(&mut self, _: Level, _: &'static str, _: F) {}
#[inline(always)]
fn remark<F: FnOnce() -> CaptureRemark>(&mut self, _: F) {}
#[inline(always)]
fn timing_channel_observed(&mut self) {}
#[inline(always)]
fn remark_channel_observed(&mut self) {}
#[inline(always)]
fn finish<F: FnOnce() -> CaptureOutcome>(&mut self, _: F) {}
}
impl CaptureSink for StageCapture {
const RECORDING: bool = true;
fn page<F: FnOnce() -> String>(&mut self, level: Level, step: &'static str, render: F) {
self.pages.push(StageCapturePage {
level,
step,
text: render(),
});
}
fn try_page<E: Display, F: FnOnce() -> Result<String, E>>(
&mut self,
level: Level,
step: &'static str,
render: F,
) {
match render() {
Ok(text) => self.pages.push(StageCapturePage { level, step, text }),
Err(error) => self.unavailable.push(StageCaptureUnavailable {
level,
step,
reason: error.to_compact_string(),
}),
}
}
fn effective_option<F: FnOnce() -> String>(&mut self, name: &'static str, value: F) {
self.option(name, value());
}
fn timing<F: FnOnce() -> u64>(&mut self, level: Level, step: &'static str, nanos: F) {
self.timings.push(StageTiming {
level,
step,
nanos: nanos(),
});
}
fn remark<F: FnOnce() -> CaptureRemark>(&mut self, render: F) {
self.remarks.push(render());
}
fn timing_channel_observed(&mut self) {
self.timings_observed = true;
}
fn remark_channel_observed(&mut self) {
self.remarks_observed = true;
}
fn finish<F: FnOnce() -> CaptureOutcome>(&mut self, outcome: F) {
let outcome = outcome();
if !matches!(outcome, CaptureOutcome::Accepted) {
self.pages.clear();
self.unavailable.clear();
self.timings.clear();
self.remarks.clear();
self.timings_observed = false;
self.remarks_observed = false;
}
self.outcome = outcome;
}
}
#[cfg(test)]
mod fallible_tests;
#[cfg(test)]
mod tests {
use core::cell::Cell;
use super::{CaptureOutcome, CaptureSink, NoCapture, StageCapture};
use crate::{String, level::Level};
#[test]
fn unobserved_run_never_renders_page() {
let rendered = Cell::new(false);
NoCapture.page(Level::L1, "parse", || {
rendered.set(true);
String::from("page")
});
assert!(!rendered.get());
}
#[test]
fn fallback_discards_provisional_native_pages() {
let mut capture = StageCapture::new("dom");
capture.page(Level::L1, "parse", || String::from("page"));
capture.finish(|| CaptureOutcome::Legacy(String::from("unsupported")));
assert!(capture.pages.is_empty());
assert_eq!(
capture.outcome,
CaptureOutcome::Legacy(String::from("unsupported"))
);
}
}