use serde::{Deserialize, Serialize};
use serde_json::Value;
pub const BROWSER_CLOSE_MESSAGE_ID: i64 = -9999;
#[derive(Debug, Clone, Serialize)]
pub struct OutgoingMessage {
pub id: i64,
pub method: String,
pub params: Value,
#[serde(rename = "sessionId", skip_serializing_if = "Option::is_none")]
pub session_id: Option<String>,
}
impl OutgoingMessage {
pub fn new(
id: i64,
method: impl Into<String>,
params: Value,
session_id: Option<String>,
) -> Self {
Self {
id,
method: method.into(),
params,
session_id,
}
}
pub fn to_json_bytes(&self) -> serde_json::Result<Vec<u8>> {
serde_json::to_vec(self)
}
}
#[derive(Debug, Clone, Deserialize)]
pub struct ProtocolErrorBody {
pub message: String,
#[serde(default)]
pub data: Option<String>,
}
#[derive(Debug, Clone, Deserialize)]
pub struct IncomingMessage {
#[serde(default)]
pub id: Option<i64>,
#[serde(default)]
pub method: Option<String>,
#[serde(default)]
pub params: Option<Value>,
#[serde(default, rename = "sessionId")]
pub session_id: Option<String>,
#[serde(default)]
pub result: Option<Value>,
#[serde(default)]
pub error: Option<ProtocolErrorBody>,
}
#[derive(Debug)]
pub enum MessageKind {
Response { id: i64, result: Value },
Error { id: i64, message: String },
Event {
method: String,
params: Value,
session_id: Option<String>,
},
Unknown,
}
impl IncomingMessage {
pub fn from_json_bytes(bytes: &[u8]) -> serde_json::Result<Self> {
serde_json::from_slice(bytes)
}
pub fn kind(self) -> MessageKind {
match (self.id, self.method) {
(Some(id), _) => {
if let Some(err) = self.error {
MessageKind::Error {
id,
message: err.message,
}
} else {
MessageKind::Response {
id,
result: self.result.unwrap_or(Value::Null),
}
}
}
(None, Some(method)) => MessageKind::Event {
method,
params: self.params.unwrap_or(Value::Null),
session_id: self.session_id,
},
(None, None) => MessageKind::Unknown,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use serde_json::json;
#[test]
fn root_message_omits_session_id() {
let m = OutgoingMessage::new(1, "Browser.enable", json!({}), None);
let s = String::from_utf8(m.to_json_bytes().unwrap()).unwrap();
assert!(!s.contains("sessionId"), "root 会话不应出现 sessionId: {s}");
assert_eq!(s, r#"{"id":1,"method":"Browser.enable","params":{}}"#);
}
#[test]
fn page_message_includes_session_id() {
let m = OutgoingMessage::new(
2,
"Page.navigate",
json!({"url":"https://example.com","frameId":"main"}),
Some("abc123".into()),
);
let s = String::from_utf8(m.to_json_bytes().unwrap()).unwrap();
assert!(s.contains(r#""sessionId":"abc123""#), "{s}");
}
#[test]
fn parse_success_response() {
let raw = br#"{"id":1,"result":{"ok":true}}"#;
match IncomingMessage::from_json_bytes(raw).unwrap().kind() {
MessageKind::Response { id, result } => {
assert_eq!(id, 1);
assert_eq!(result, json!({"ok":true}));
}
other => panic!("期望 Response,得到 {other:?}"),
}
}
#[test]
fn parse_error_response() {
let raw = br#"{"id":3,"error":{"message":"boom","data":"stack"}}"#;
match IncomingMessage::from_json_bytes(raw).unwrap().kind() {
MessageKind::Error { id, message } => {
assert_eq!(id, 3);
assert_eq!(message, "boom");
}
other => panic!("期望 Error,得到 {other:?}"),
}
}
#[test]
fn parse_event() {
let raw = br#"{"method":"Page.load","params":{},"sessionId":"s1"}"#;
match IncomingMessage::from_json_bytes(raw).unwrap().kind() {
MessageKind::Event {
method, session_id, ..
} => {
assert_eq!(method, "Page.load");
assert_eq!(session_id.as_deref(), Some("s1"));
}
other => panic!("期望 Event,得到 {other:?}"),
}
}
}