use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum MobileTarget {
Android { serial: String },
Ios { udid: String },
RemoteCdp { url: String },
}
#[derive(Debug, Clone)]
pub struct MobileBridge {
pub target: MobileTarget,
pub cdp_endpoint: String,
}
impl MobileBridge {
pub fn stub_for(target: MobileTarget, cdp_endpoint: impl Into<String>) -> Self {
Self {
target,
cdp_endpoint: cdp_endpoint.into(),
}
}
pub fn validate(&self) -> Result<(), String> {
let parsed = url::Url::parse(&self.cdp_endpoint)
.map_err(|e| format!("invalid CDP endpoint URL: {e}"))?;
match parsed.scheme() {
"ws" | "wss" => {}
other => return Err(format!("CDP endpoint scheme must be ws/wss; got {other:?}")),
}
if parsed.host().is_none() {
return Err("CDP endpoint missing host".into());
}
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn android_target_serialises_snake_case() {
let t = MobileTarget::Android {
serial: "ABC123".into(),
};
let json = serde_json::to_string(&t).unwrap();
assert!(json.contains("\"android\""));
assert!(json.contains("\"serial\""));
}
#[test]
fn ios_target_round_trips() {
let t = MobileTarget::Ios {
udid: "AAAA-BBBB-CCCC".into(),
};
let json = serde_json::to_string(&t).unwrap();
let back: MobileTarget = serde_json::from_str(&json).unwrap();
match back {
MobileTarget::Ios { udid } => assert_eq!(udid, "AAAA-BBBB-CCCC"),
_ => panic!("expected Ios variant after round-trip"),
}
}
#[test]
fn validate_rejects_http_scheme() {
let b = MobileBridge::stub_for(
MobileTarget::RemoteCdp {
url: "http://example.com".into(),
},
"http://localhost:9222",
);
assert!(b.validate().is_err());
}
#[test]
fn validate_accepts_ws_endpoint() {
let b = MobileBridge::stub_for(
MobileTarget::Android {
serial: "x".into(),
},
"ws://localhost:9222/devtools/browser/abc",
);
assert!(b.validate().is_ok());
}
#[test]
fn validate_accepts_wss_endpoint() {
let b = MobileBridge::stub_for(
MobileTarget::Ios {
udid: "y".into(),
},
"wss://inspector.local:9222/",
);
assert!(b.validate().is_ok());
}
#[test]
fn validate_rejects_empty_endpoint() {
let b = MobileBridge::stub_for(
MobileTarget::Android {
serial: "x".into(),
},
"",
);
assert!(b.validate().is_err());
}
}