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::NavigationFailed(s)
20        | VoidCrawlError::JsEvalError(s) => ErrorData::invalid_params(s, None),
21        VoidCrawlError::Timeout(s) => ErrorData::internal_error(format!("timeout: {s}"), None),
22        VoidCrawlError::BrowserClosed => ErrorData::internal_error("browser closed", None),
23        VoidCrawlError::CaptchaDetected { ref kind } => {
24            let data = tagged("CaptchaDetected", json!({ "kind": kind }));
25            ErrorData::internal_error(err.to_string(), Some(obj(data)))
26        }
27        VoidCrawlError::AntibotChallenge { ref vendor } => {
28            let data = tagged("AntibotChallenge", json!({ "vendor": vendor }));
29            ErrorData::internal_error(err.to_string(), Some(obj(data)))
30        }
31        VoidCrawlError::ProfileBusy { ref name } => {
32            let data = tagged("ProfileBusy", json!({ "name": name }));
33            ErrorData::internal_error(err.to_string(), Some(obj(data)))
34        }
35        VoidCrawlError::ProfileLeaseExpired { ref name, timeout_secs } => {
36            let data = tagged(
37                "ProfileLeaseExpired",
38                json!({ "name": name, "timeout_secs": timeout_secs }),
39            );
40            ErrorData::internal_error(err.to_string(), Some(obj(data)))
41        }
42        VoidCrawlError::ProfileNotFound { ref name, ref searched } => {
43            let data = tagged("ProfileNotFound", json!({ "name": name, "searched": searched }));
44            ErrorData::invalid_params(err.to_string(), Some(obj(data)))
45        }
46        other => ErrorData::internal_error(other.to_string(), None),
47    }
48}
49
50fn tagged(exception: &str, extra: Value) -> Map<String, Value> {
51    let mut m = Map::new();
52    m.insert("exception".into(), Value::String(exception.into()));
53    if let Value::Object(obj) = extra {
54        for (k, v) in obj {
55            m.insert(k, v);
56        }
57    }
58    m
59}