use std::time::Duration;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum LoadMode {
#[default]
Normal,
Eager,
None,
}
impl LoadMode {
pub(crate) fn cdp_event(self) -> Option<&'static str> {
match self {
LoadMode::Normal => Some("Page.loadEventFired"),
LoadMode::Eager => Some("Page.domContentEventFired"),
LoadMode::None => None,
}
}
}
#[derive(Debug, Clone)]
pub struct GetOptions {
pub retry: u32,
pub interval: Duration,
pub timeout: Option<Duration>,
pub load_mode: Option<LoadMode>,
pub referer: Option<String>,
}
impl Default for GetOptions {
fn default() -> Self {
Self {
retry: 0,
interval: Duration::from_secs(1),
timeout: None,
load_mode: None,
referer: None,
}
}
}
impl GetOptions {
pub fn new() -> Self {
Self::default()
}
pub fn retry(mut self, n: u32) -> Self {
self.retry = n;
self
}
pub fn interval(mut self, secs: f64) -> Self {
self.interval = Duration::from_secs_f64(secs.max(0.0));
self
}
pub fn timeout(mut self, d: Duration) -> Self {
self.timeout = Some(d);
self
}
pub fn load_mode(mut self, m: LoadMode) -> Self {
self.load_mode = Some(m);
self
}
pub fn referer(mut self, r: impl Into<String>) -> Self {
self.referer = Some(r.into());
self
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum ImageFormat {
#[default]
Png,
Jpeg,
}
impl ImageFormat {
pub(crate) fn cdp_format(self) -> &'static str {
match self {
ImageFormat::Png => "png",
ImageFormat::Jpeg => "jpeg",
}
}
}
#[derive(Debug, Clone, Default)]
pub struct ShotOpts {
pub full_page: bool,
pub region: Option<((f64, f64), (f64, f64))>,
pub format: ImageFormat,
pub quality: Option<u8>,
}
impl ShotOpts {
pub fn new() -> Self {
Self::default()
}
pub fn full_page(mut self, yes: bool) -> Self {
self.full_page = yes;
self
}
pub fn region(mut self, left_top: (f64, f64), right_bottom: (f64, f64)) -> Self {
self.region = Some((left_top, right_bottom));
self
}
pub fn format(mut self, format: ImageFormat) -> Self {
self.format = format;
self
}
pub fn quality(mut self, q: u8) -> Self {
self.quality = Some(q);
self
}
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct PageRect {
pub window_width: f64,
pub window_height: f64,
pub page_width: f64,
pub page_height: f64,
pub scroll_x: f64,
pub scroll_y: f64,
pub device_pixel_ratio: f64,
}
#[derive(Debug, Clone, Default, serde::Serialize, serde::Deserialize)]
#[serde(default)]
pub struct Cookie {
pub name: String,
pub value: String,
pub domain: String,
pub path: String,
pub expires: f64,
pub http_only: bool,
pub secure: bool,
}
#[derive(Debug, Clone, Default, serde::Serialize, serde::Deserialize)]
#[serde(default)]
pub struct CookieParam {
pub name: String,
pub value: String,
pub url: Option<String>,
pub domain: Option<String>,
pub path: Option<String>,
pub secure: Option<bool>,
pub http_only: Option<bool>,
pub expires: Option<f64>,
}
#[derive(Debug, Clone, Default)]
pub struct DialogInfo {
pub message: String,
pub dialog_type: String,
pub default_prompt: String,
}
#[derive(Debug, Clone, Default)]
pub struct DownloadInfo {
pub url: String,
pub suggested_filename: String,
pub path: String,
pub state: String,
}
impl CookieParam {
pub fn new(name: impl Into<String>, value: impl Into<String>) -> Self {
Self {
name: name.into(),
value: value.into(),
..Default::default()
}
}
pub fn url(mut self, url: impl Into<String>) -> Self {
self.url = Some(url.into());
self
}
pub fn domain(mut self, domain: impl Into<String>) -> Self {
self.domain = Some(domain.into());
self
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn load_mode_maps_to_cdp_wait_event() {
assert_eq!(LoadMode::Normal.cdp_event(), Some("Page.loadEventFired"));
assert_eq!(
LoadMode::Eager.cdp_event(),
Some("Page.domContentEventFired")
);
assert_eq!(LoadMode::None.cdp_event(), None);
assert_eq!(LoadMode::default(), LoadMode::Normal);
}
#[test]
fn image_format_maps_to_cdp_format() {
assert_eq!(ImageFormat::Png.cdp_format(), "png");
assert_eq!(ImageFormat::Jpeg.cdp_format(), "jpeg");
assert_eq!(ImageFormat::default(), ImageFormat::Png);
}
#[test]
fn get_options_builder_sets_fields() {
let o = GetOptions::new()
.retry(3)
.interval(2.5)
.timeout(Duration::from_secs(9))
.load_mode(LoadMode::Eager)
.referer("https://ref.example");
assert_eq!(o.retry, 3);
assert_eq!(o.interval, Duration::from_secs_f64(2.5));
assert_eq!(o.timeout, Some(Duration::from_secs(9)));
assert_eq!(o.load_mode, Some(LoadMode::Eager));
assert_eq!(o.referer.as_deref(), Some("https://ref.example"));
}
#[test]
fn get_options_default_and_negative_interval_clamps() {
let d = GetOptions::default();
assert_eq!(d.retry, 0);
assert_eq!(d.interval, Duration::from_secs(1));
assert!(d.timeout.is_none() && d.load_mode.is_none() && d.referer.is_none());
assert_eq!(GetOptions::new().interval(-5.0).interval, Duration::ZERO);
}
#[test]
fn shot_opts_builder_sets_fields() {
let s = ShotOpts::new()
.full_page(true)
.format(ImageFormat::Jpeg)
.quality(80);
assert!(s.full_page);
assert_eq!(s.format, ImageFormat::Jpeg);
assert_eq!(s.quality, Some(80));
assert!(s.region.is_none());
let r = ShotOpts::new().region((1.0, 2.0), (3.0, 4.0));
assert_eq!(r.region, Some(((1.0, 2.0), (3.0, 4.0))));
}
#[test]
fn cookie_param_builder_sets_identity_fields() {
let c = CookieParam::new("sid", "abc")
.url("https://x.example")
.domain(".example.com");
assert_eq!(c.name, "sid");
assert_eq!(c.value, "abc");
assert_eq!(c.url.as_deref(), Some("https://x.example"));
assert_eq!(c.domain.as_deref(), Some(".example.com"));
assert!(c.path.is_none() && c.secure.is_none() && c.expires.is_none());
}
}