use serde::ser::{SerializeMap, Serializer};
use serde::{Deserialize, Serialize};
use serde_json::Value;
use crate::error::{Error, Result};
use crate::identifiers::{FrameId, RequestId, TabId};
use super::Command;
#[derive(Debug, Clone)]
pub struct Request {
pub id: RequestId,
pub tab_id: TabId,
pub frame_id: FrameId,
pub command: Command,
}
impl Serialize for Request {
fn serialize<S: Serializer>(
&self,
serializer: S,
) -> std::result::Result<S::Ok, S::Error> {
let command_value =
serde_json::to_value(&self.command).map_err(serde::ser::Error::custom)?;
let mut map = serializer.serialize_map(None)?;
map.serialize_entry("id", &self.id)?;
map.serialize_entry("tabId", &self.tab_id)?;
map.serialize_entry("frameId", &self.frame_id)?;
if let serde_json::Value::Object(obj) = command_value {
for (k, v) in obj {
map.serialize_entry(&k, &v)?;
}
}
map.end()
}
}
impl Request {
#[inline]
#[must_use]
pub fn new(tab_id: TabId, frame_id: FrameId, command: Command) -> Self {
Self {
id: RequestId::generate(),
tab_id,
frame_id,
command,
}
}
#[inline]
#[must_use]
pub fn with_id(id: RequestId, tab_id: TabId, frame_id: FrameId, command: Command) -> Self {
Self {
id,
tab_id,
frame_id,
command,
}
}
}
#[derive(Debug, Clone, Deserialize)]
pub struct Response {
pub id: RequestId,
#[serde(rename = "type")]
pub response_type: ResponseType,
#[serde(default)]
pub result: Option<Value>,
#[serde(default)]
pub error: Option<String>,
#[serde(default)]
pub message: Option<String>,
}
impl Response {
#[inline]
#[must_use]
pub fn is_success(&self) -> bool {
self.response_type == ResponseType::Success
}
#[inline]
#[must_use]
pub fn is_error(&self) -> bool {
self.response_type == ResponseType::Error
}
pub fn into_result(self) -> Result<Value> {
match self.response_type {
ResponseType::Success => Ok(self.result.unwrap_or(Value::Null)),
ResponseType::Error => {
let error_code = self.error.unwrap_or_else(|| "unknown error".to_string());
let message = self.message.unwrap_or_else(|| error_code.clone());
Err(Error::protocol(message))
}
}
}
#[inline]
#[must_use]
pub fn get_str(&self, key: &str) -> Option<&str> {
self.result
.as_ref()
.and_then(|v| v.get(key))
.and_then(|v| v.as_str())
}
#[inline]
#[must_use]
pub fn get_string(&self, key: &str) -> String {
self.get_str(key).unwrap_or_default().to_string()
}
#[inline]
#[must_use]
pub fn get_u64(&self, key: &str) -> u64 {
self.result
.as_ref()
.and_then(|v| v.get(key))
.and_then(|v| v.as_u64())
.unwrap_or_default()
}
#[inline]
#[must_use]
pub fn get_bool(&self, key: &str) -> bool {
self.result
.as_ref()
.and_then(|v| v.get(key))
.and_then(|v| v.as_bool())
.unwrap_or_default()
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum ResponseType {
Success,
Error,
}
#[cfg(test)]
mod tests {
use super::*;
use crate::protocol::BrowsingContextCommand;
#[test]
fn test_request_serialization() {
let tab_id = TabId::new(1).expect("valid tab id");
let frame_id = FrameId::main();
let command = Command::BrowsingContext(BrowsingContextCommand::Navigate {
url: "https://example.com".to_string(),
});
let request = Request::new(tab_id, frame_id, command);
let json = serde_json::to_string(&request).expect("serialize");
assert!(json.contains("browsingContext.navigate"));
assert!(json.contains("tabId"));
assert!(json.contains("frameId"));
}
#[test]
fn test_request_with_id() {
let id = RequestId::generate();
let tab_id = TabId::new(1).expect("valid tab id");
let frame_id = FrameId::main();
let command = Command::BrowsingContext(BrowsingContextCommand::GetTitle);
let request = Request::with_id(id, tab_id, frame_id, command);
assert_eq!(request.id, id);
}
#[test]
fn test_success_response() {
let json_str = r#"{
"id": "550e8400-e29b-41d4-a716-446655440000",
"type": "success",
"result": {"title": "Example"}
}"#;
let response: Response = serde_json::from_str(json_str).expect("parse");
assert!(response.is_success());
assert!(!response.is_error());
assert_eq!(response.get_string("title"), "Example");
}
#[test]
fn test_error_response() {
let json_str = r#"{
"id": "550e8400-e29b-41d4-a716-446655440000",
"type": "error",
"error": "no such element",
"message": "Element not found"
}"#;
let response: Response = serde_json::from_str(json_str).expect("parse");
assert!(response.is_error());
assert!(!response.is_success());
assert_eq!(response.error, Some("no such element".to_string()));
}
#[test]
fn test_into_result_success() {
let json_str = r#"{
"id": "550e8400-e29b-41d4-a716-446655440000",
"type": "success",
"result": {"value": 42}
}"#;
let response: Response = serde_json::from_str(json_str).expect("parse");
let result = response.into_result().expect("should succeed");
assert_eq!(result.get("value").and_then(|v| v.as_u64()), Some(42));
}
#[test]
fn test_into_result_error() {
let json_str = r#"{
"id": "550e8400-e29b-41d4-a716-446655440000",
"type": "error",
"error": "timeout",
"message": "Operation timed out"
}"#;
let response: Response = serde_json::from_str(json_str).expect("parse");
let result = response.into_result();
assert!(result.is_err());
}
#[test]
fn test_response_get_helpers() {
let json_str = r#"{
"id": "550e8400-e29b-41d4-a716-446655440000",
"type": "success",
"result": {
"name": "test",
"count": 42,
"enabled": true
}
}"#;
let response: Response = serde_json::from_str(json_str).expect("parse");
assert_eq!(response.get_string("name"), "test");
assert_eq!(response.get_u64("count"), 42);
assert!(response.get_bool("enabled"));
assert_eq!(response.get_string("missing"), "");
assert_eq!(response.get_u64("missing"), 0);
assert!(!response.get_bool("missing"));
}
}