use serde::{Deserialize, Serialize};
use serde_json::Value;
#[derive(Debug, Serialize)]
pub struct CdpCommand<'a> {
pub id: u64,
pub method: &'a str,
pub params: Value,
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(rename = "sessionId")]
pub session_id: Option<&'a str>,
}
#[derive(Debug, Deserialize)]
#[serde(untagged)]
pub enum CdpInbound {
Response {
id: u64,
#[serde(default)]
result: Option<Value>,
#[serde(default)]
error: Option<CdpRpcError>,
#[serde(default, rename = "sessionId")]
session_id: Option<String>,
},
Event {
method: String,
#[serde(default)]
params: Value,
#[serde(default, rename = "sessionId")]
session_id: Option<String>,
},
}
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct CdpRpcError {
pub code: i32,
pub message: String,
#[serde(default)]
pub data: Option<Value>,
}
#[derive(Debug, Clone)]
pub struct RawEvent {
pub method: String,
pub params: Value,
pub session_id: Option<String>,
}
#[cfg(test)]
#[allow(clippy::unwrap_used, clippy::panic)]
mod tests {
use super::*;
use serde_json::json;
#[test]
fn command_serialize_omits_session_id_when_none() {
let cmd = CdpCommand {
id: 1,
method: "Page.navigate",
params: json!({ "url": "https://example.com" }),
session_id: None,
};
let s = serde_json::to_string(&cmd).expect("ser");
assert!(
!s.contains("sessionId"),
"sessionId should be omitted when None, got: {s}"
);
assert!(s.contains(r#""id":1"#));
assert!(s.contains(r#""method":"Page.navigate""#));
}
#[test]
fn command_serialize_includes_session_id_when_some() {
let cmd = CdpCommand {
id: 7,
method: "Page.navigate",
params: json!({ "url": "https://example.com" }),
session_id: Some("S1"),
};
let s = serde_json::to_string(&cmd).expect("ser");
assert!(s.contains(r#""sessionId":"S1""#), "got: {s}");
}
#[test]
fn inbound_deserialize_response_with_result() {
let raw = r#"{"id":3,"result":{"frameId":"F1"}}"#;
let parsed: CdpInbound = serde_json::from_str(raw).expect("de");
match parsed {
CdpInbound::Response {
id,
result,
error,
session_id,
} => {
assert_eq!(id, 3);
assert_eq!(result.unwrap()["frameId"], "F1");
assert!(error.is_none());
assert!(session_id.is_none());
}
CdpInbound::Event { .. } => panic!("expected Response, got Event"),
}
}
#[test]
fn inbound_deserialize_response_with_error() {
let raw = r#"{"id":3,"error":{"code":-32602,"message":"Invalid params"}}"#;
let parsed: CdpInbound = serde_json::from_str(raw).expect("de");
match parsed {
CdpInbound::Response { error: Some(e), .. } => {
assert_eq!(e.code, -32602);
assert_eq!(e.message, "Invalid params");
}
_ => panic!("expected Response with error"),
}
}
#[test]
fn inbound_deserialize_event() {
let raw =
r#"{"method":"Page.frameStoppedLoading","params":{"frameId":"F1"},"sessionId":"S1"}"#;
let parsed: CdpInbound = serde_json::from_str(raw).expect("de");
match parsed {
CdpInbound::Event {
method,
params,
session_id,
} => {
assert_eq!(method, "Page.frameStoppedLoading");
assert_eq!(params["frameId"], "F1");
assert_eq!(session_id.as_deref(), Some("S1"));
}
_ => panic!("expected Event"),
}
}
}