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 } => {
34            let data = tagged("ProfileBusy", json!({ "name": name }));
35            ErrorData::internal_error(err.to_string(), Some(obj(data)))
36        }
37        VoidCrawlError::ProfileLeaseExpired { ref name, timeout_secs } => {
38            let data = tagged(
39                "ProfileLeaseExpired",
40                json!({ "name": name, "timeout_secs": timeout_secs }),
41            );
42            ErrorData::internal_error(err.to_string(), Some(obj(data)))
43        }
44        VoidCrawlError::ProfileNotFound { ref name, ref searched } => {
45            let data = tagged("ProfileNotFound", json!({ "name": name, "searched": searched }));
46            ErrorData::invalid_params(err.to_string(), Some(obj(data)))
47        }
48        other => ErrorData::internal_error(other.to_string(), None),
49    }
50}
51
52fn tagged(exception: &str, extra: Value) -> Map<String, Value> {
53    let mut m = Map::new();
54    m.insert("exception".into(), Value::String(exception.into()));
55    if let Value::Object(obj) = extra {
56        for (k, v) in obj {
57            m.insert(k, v);
58        }
59    }
60    m
61}