use crate::captcha_detect::DetectedCaptcha;
use crate::solver::{CaptchaType, SolveMethod};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[non_exhaustive]
pub enum SolveOutcome {
Success,
Failure,
Error,
Timeout,
}
#[derive(Debug, Clone)]
#[non_exhaustive]
pub struct SolveEvent<'a> {
pub solver: &'static str,
pub captcha_type: &'a CaptchaType,
pub kind: &'a DetectedCaptcha,
pub domain: &'a str,
pub outcome: SolveOutcome,
pub time_ms: u64,
pub confidence: Option<f32>,
pub method: &'a SolveMethod,
}
pub trait SolverTelemetry: Send + Sync {
fn record(&self, evt: &SolveEvent<'_>);
}
pub struct NoopTelemetry;
impl SolverTelemetry for NoopTelemetry {
fn record(&self, _evt: &SolveEvent<'_>) {}
}
#[cfg(test)]
mod tests {
use super::*;
use std::sync::{Arc, Mutex};
struct CapturingTelemetry {
events: Mutex<Vec<(SolveOutcome, String, u64)>>,
}
impl SolverTelemetry for CapturingTelemetry {
fn record(&self, evt: &SolveEvent<'_>) {
self.events
.lock()
.unwrap()
.push((evt.outcome, evt.domain.to_owned(), evt.time_ms));
}
}
#[test]
fn noop_telemetry_drops_events() {
let t = NoopTelemetry;
let kind = DetectedCaptcha::Turnstile;
let captcha_type = CaptchaType::CloudflareTurnstile;
let method = SolveMethod::BehavioralBypass;
let evt = SolveEvent {
solver: "TestSolver",
captcha_type: &captcha_type,
kind: &kind,
domain: "example.com",
outcome: SolveOutcome::Success,
time_ms: 1234,
confidence: Some(0.95),
method: &method,
};
t.record(&evt);
}
#[test]
fn capturing_telemetry_round_trips_event_fields() {
let cap = Arc::new(CapturingTelemetry {
events: Mutex::new(Vec::new()),
});
let kind = DetectedCaptcha::HCaptcha;
let captcha_type = CaptchaType::HCaptcha;
let method = SolveMethod::VisionLLM;
for (outcome, ms) in [
(SolveOutcome::Success, 1000_u64),
(SolveOutcome::Failure, 2000),
(SolveOutcome::Error, 50),
(SolveOutcome::Timeout, 30000),
] {
let evt = SolveEvent {
solver: "TestSolver",
captcha_type: &captcha_type,
kind: &kind,
domain: "x.test",
outcome,
time_ms: ms,
confidence: None,
method: &method,
};
cap.record(&evt);
}
let events = cap.events.lock().unwrap();
assert_eq!(events.len(), 4);
assert_eq!(events[0].0, SolveOutcome::Success);
assert_eq!(events[3].2, 30000);
}
}