Skip to main content

voidcrawl_mcp/
errors.rs

1//! `VoidCrawlError` → `rmcp::ErrorData` mapping.
2
3use rmcp::ErrorData;
4use serde_json::{Map, Value, json};
5
6fn obj(m: Map<String, Value>) -> Value {
7    Value::Object(m)
8}
9use void_crawl_core::VoidCrawlError;
10
11/// Map a core error into the MCP wire error. User-caused errors
12/// (bad URL, bad selector, bad JS) surface as `invalid_params`;
13/// everything else surfaces as `internal_error`. Typed exceptions
14/// (captcha, profile failures) carry a structured `data` payload so
15/// clients can dispatch on `data.exception`.
16pub fn map_err(err: VoidCrawlError) -> ErrorData {
17    match err {
18        VoidCrawlError::ElementNotFound(s)
19        | VoidCrawlError::FrameNotFound(s)
20        | VoidCrawlError::AmbiguousFrame(s)
21        | VoidCrawlError::NavigationFailed(s)
22        | VoidCrawlError::JsEvalError(s) => ErrorData::invalid_params(s, None),
23        VoidCrawlError::Timeout(s) => ErrorData::internal_error(format!("timeout: {s}"), None),
24        VoidCrawlError::BrowserClosed => ErrorData::internal_error("browser closed", None),
25        VoidCrawlError::CaptchaDetected { ref kind } => {
26            let data = tagged("CaptchaDetected", json!({ "kind": kind }));
27            ErrorData::internal_error(err.to_string(), Some(obj(data)))
28        }
29        VoidCrawlError::AntibotChallenge { ref vendor } => {
30            let data = tagged("AntibotChallenge", json!({ "vendor": vendor }));
31            ErrorData::internal_error(err.to_string(), Some(obj(data)))
32        }
33        VoidCrawlError::ProfileBusy { ref name, pid, acquired_at } => {
34            let data = tagged(
35                "ProfileBusy",
36                json!({ "name": name, "pid": pid, "acquired_at": acquired_at }),
37            );
38            ErrorData::internal_error(err.to_string(), Some(obj(data)))
39        }
40        VoidCrawlError::ProfileLeaseExpired { ref name, timeout_secs } => {
41            let data = tagged(
42                "ProfileLeaseExpired",
43                json!({ "name": name, "timeout_secs": timeout_secs }),
44            );
45            ErrorData::internal_error(err.to_string(), Some(obj(data)))
46        }
47        VoidCrawlError::ProfileNotFound { ref name, ref searched } => {
48            let data = tagged("ProfileNotFound", json!({ "name": name, "searched": searched }));
49            ErrorData::invalid_params(err.to_string(), Some(obj(data)))
50        }
51        other => ErrorData::internal_error(other.to_string(), None),
52    }
53}
54
55fn tagged(exception: &str, extra: Value) -> Map<String, Value> {
56    let mut m = Map::new();
57    m.insert("exception".into(), Value::String(exception.into()));
58    if let Value::Object(obj) = extra {
59        for (k, v) in obj {
60            m.insert(k, v);
61        }
62    }
63    m
64}