use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(tag = "kind", rename_all = "snake_case")]
pub enum Decision {
Http,
Render,
Retry { after_ms: u64 },
SwitchProxy,
Defer { until_ms: u64 },
Drop,
CollectArtifacts,
IncreaseObservability,
HumanHandoff {
reason: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
vendor: Option<String>,
url: url::Url,
#[serde(default, skip_serializing_if = "Option::is_none")]
screenshot_path: Option<std::path::PathBuf>,
},
}
impl Decision {
pub fn as_tag(&self) -> &'static str {
match self {
Self::Http => "http",
Self::Render => "render",
Self::Retry { .. } => "retry",
Self::SwitchProxy => "switch_proxy",
Self::Defer { .. } => "defer",
Self::Drop => "drop",
Self::CollectArtifacts => "collect_artifacts",
Self::IncreaseObservability => "increase_observability",
Self::HumanHandoff { .. } => "human_handoff",
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct DecisionReason {
pub code: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub detail: Option<String>,
}
impl DecisionReason {
pub fn new(code: impl Into<String>) -> Self {
Self {
code: code.into(),
detail: None,
}
}
pub fn with_detail(mut self, detail: impl Into<String>) -> Self {
self.detail = Some(detail.into());
self
}
pub fn initial_http() -> Self {
Self::new("initial:http")
}
pub fn initial_render() -> Self {
Self::new("initial:render")
}
pub fn antibot_challenge(vendor: &str) -> Self {
Self::new(format!("render:antibot:{vendor}"))
}
pub fn js_only_content() -> Self {
Self::new("render:js-only-content")
}
pub fn status_transient(status: u16) -> Self {
Self::new(format!("retry:{status}"))
}
pub fn proxy_bad_score() -> Self {
Self::new("switch_proxy:bad-score")
}
pub fn budget_exceeded() -> Self {
Self::new("drop:budget-exceeded")
}
pub fn host_cooldown() -> Self {
Self::new("defer:host-cooldown")
}
}
impl std::fmt::Display for DecisionReason {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match &self.detail {
Some(d) => write!(f, "{}={}", self.code, d),
None => f.write_str(&self.code),
}
}
}