firefox_webdriver/protocol/
request.rs

1//! Request and Response message types.
2//!
3//! Defines the message format for command requests and responses
4//! between local end (Rust) and remote end (Extension).
5//!
6//! See ARCHITECTURE.md Section 2.2-2.3 for specification.
7
8// ============================================================================
9// Imports
10// ============================================================================
11
12use serde::{Deserialize, Serialize};
13use serde_json::Value;
14
15use crate::error::{Error, Result};
16use crate::identifiers::{FrameId, RequestId, TabId};
17
18use super::Command;
19
20// ============================================================================
21// Request
22// ============================================================================
23
24/// A command request from local end to remote end.
25///
26/// # Format
27///
28/// ```json
29/// {
30///   "id": "uuid",
31///   "method": "module.methodName",
32///   "tabId": 1,
33///   "frameId": 0,
34///   "params": { ... }
35/// }
36/// ```
37#[derive(Debug, Clone, Serialize)]
38pub struct Request {
39    /// Unique identifier for request/response correlation.
40    pub id: RequestId,
41
42    /// Target tab ID.
43    #[serde(rename = "tabId")]
44    pub tab_id: TabId,
45
46    /// Target frame ID (0 = main frame).
47    #[serde(rename = "frameId")]
48    pub frame_id: FrameId,
49
50    /// Command with method and params.
51    #[serde(flatten)]
52    pub command: Command,
53}
54
55impl Request {
56    /// Creates a new request with auto-generated ID.
57    #[inline]
58    #[must_use]
59    pub fn new(tab_id: TabId, frame_id: FrameId, command: Command) -> Self {
60        Self {
61            id: RequestId::generate(),
62            tab_id,
63            frame_id,
64            command,
65        }
66    }
67
68    /// Creates a new request with specific ID.
69    #[inline]
70    #[must_use]
71    pub fn with_id(id: RequestId, tab_id: TabId, frame_id: FrameId, command: Command) -> Self {
72        Self {
73            id,
74            tab_id,
75            frame_id,
76            command,
77        }
78    }
79}
80
81// ============================================================================
82// Response
83// ============================================================================
84
85/// A response from remote end to local end.
86///
87/// # Format
88///
89/// Success:
90/// ```json
91/// {
92///   "id": "uuid",
93///   "type": "success",
94///   "result": { ... }
95/// }
96/// ```
97///
98/// Error:
99/// ```json
100/// {
101///   "id": "uuid",
102///   "type": "error",
103///   "error": "error code",
104///   "message": "error message"
105/// }
106/// ```
107#[derive(Debug, Clone, Deserialize)]
108pub struct Response {
109    /// Matches the command `id`.
110    pub id: RequestId,
111
112    /// Response type.
113    #[serde(rename = "type")]
114    pub response_type: ResponseType,
115
116    /// Result data (if success).
117    #[serde(default)]
118    pub result: Option<Value>,
119
120    /// Error code (if error).
121    #[serde(default)]
122    pub error: Option<String>,
123
124    /// Error message (if error).
125    #[serde(default)]
126    pub message: Option<String>,
127}
128
129impl Response {
130    /// Returns `true` if this is a success response.
131    #[inline]
132    #[must_use]
133    pub fn is_success(&self) -> bool {
134        self.response_type == ResponseType::Success
135    }
136
137    /// Returns `true` if this is an error response.
138    #[inline]
139    #[must_use]
140    pub fn is_error(&self) -> bool {
141        self.response_type == ResponseType::Error
142    }
143
144    /// Extracts the result value, returning error if response was error.
145    ///
146    /// # Errors
147    ///
148    /// Returns [`Error::Protocol`] if the response was an error.
149    pub fn into_result(self) -> Result<Value> {
150        match self.response_type {
151            ResponseType::Success => Ok(self.result.unwrap_or(Value::Null)),
152            ResponseType::Error => {
153                let error_code = self.error.unwrap_or_else(|| "unknown error".to_string());
154                let message = self.message.unwrap_or_else(|| error_code.clone());
155                Err(Error::protocol(message))
156            }
157        }
158    }
159
160    /// Gets a string value from the result.
161    ///
162    /// Returns empty string if key not found or not a string.
163    #[inline]
164    #[must_use]
165    pub fn get_string(&self, key: &str) -> String {
166        self.result
167            .as_ref()
168            .and_then(|v| v.get(key))
169            .and_then(|v| v.as_str())
170            .unwrap_or_default()
171            .to_string()
172    }
173
174    /// Gets a u64 value from the result.
175    ///
176    /// Returns 0 if key not found or not a number.
177    #[inline]
178    #[must_use]
179    pub fn get_u64(&self, key: &str) -> u64 {
180        self.result
181            .as_ref()
182            .and_then(|v| v.get(key))
183            .and_then(|v| v.as_u64())
184            .unwrap_or_default()
185    }
186
187    /// Gets a boolean value from the result.
188    ///
189    /// Returns false if key not found or not a boolean.
190    #[inline]
191    #[must_use]
192    pub fn get_bool(&self, key: &str) -> bool {
193        self.result
194            .as_ref()
195            .and_then(|v| v.get(key))
196            .and_then(|v| v.as_bool())
197            .unwrap_or_default()
198    }
199}
200
201// ============================================================================
202// ResponseType
203// ============================================================================
204
205/// Response type discriminator.
206#[derive(Debug, Clone, Copy, PartialEq, Eq, Deserialize)]
207#[serde(rename_all = "lowercase")]
208pub enum ResponseType {
209    /// Successful response.
210    Success,
211    /// Error response.
212    Error,
213}
214
215// ============================================================================
216// Tests
217// ============================================================================
218
219#[cfg(test)]
220mod tests {
221    use super::*;
222    use crate::protocol::BrowsingContextCommand;
223
224    #[test]
225    fn test_request_serialization() {
226        let tab_id = TabId::new(1).expect("valid tab id");
227        let frame_id = FrameId::main();
228        let command = Command::BrowsingContext(BrowsingContextCommand::Navigate {
229            url: "https://example.com".to_string(),
230        });
231
232        let request = Request::new(tab_id, frame_id, command);
233        let json = serde_json::to_string(&request).expect("serialize");
234
235        assert!(json.contains("browsingContext.navigate"));
236        assert!(json.contains("tabId"));
237        assert!(json.contains("frameId"));
238    }
239
240    #[test]
241    fn test_request_with_id() {
242        let id = RequestId::generate();
243        let tab_id = TabId::new(1).expect("valid tab id");
244        let frame_id = FrameId::main();
245        let command = Command::BrowsingContext(BrowsingContextCommand::GetTitle);
246
247        let request = Request::with_id(id, tab_id, frame_id, command);
248        assert_eq!(request.id, id);
249    }
250
251    #[test]
252    fn test_success_response() {
253        let json_str = r#"{
254            "id": "550e8400-e29b-41d4-a716-446655440000",
255            "type": "success",
256            "result": {"title": "Example"}
257        }"#;
258
259        let response: Response = serde_json::from_str(json_str).expect("parse");
260        assert!(response.is_success());
261        assert!(!response.is_error());
262        assert_eq!(response.get_string("title"), "Example");
263    }
264
265    #[test]
266    fn test_error_response() {
267        let json_str = r#"{
268            "id": "550e8400-e29b-41d4-a716-446655440000",
269            "type": "error",
270            "error": "no such element",
271            "message": "Element not found"
272        }"#;
273
274        let response: Response = serde_json::from_str(json_str).expect("parse");
275        assert!(response.is_error());
276        assert!(!response.is_success());
277        assert_eq!(response.error, Some("no such element".to_string()));
278    }
279
280    #[test]
281    fn test_into_result_success() {
282        let json_str = r#"{
283            "id": "550e8400-e29b-41d4-a716-446655440000",
284            "type": "success",
285            "result": {"value": 42}
286        }"#;
287
288        let response: Response = serde_json::from_str(json_str).expect("parse");
289        let result = response.into_result().expect("should succeed");
290        assert_eq!(result.get("value").and_then(|v| v.as_u64()), Some(42));
291    }
292
293    #[test]
294    fn test_into_result_error() {
295        let json_str = r#"{
296            "id": "550e8400-e29b-41d4-a716-446655440000",
297            "type": "error",
298            "error": "timeout",
299            "message": "Operation timed out"
300        }"#;
301
302        let response: Response = serde_json::from_str(json_str).expect("parse");
303        let result = response.into_result();
304        assert!(result.is_err());
305    }
306
307    #[test]
308    fn test_response_get_helpers() {
309        let json_str = r#"{
310            "id": "550e8400-e29b-41d4-a716-446655440000",
311            "type": "success",
312            "result": {
313                "name": "test",
314                "count": 42,
315                "enabled": true
316            }
317        }"#;
318
319        let response: Response = serde_json::from_str(json_str).expect("parse");
320        assert_eq!(response.get_string("name"), "test");
321        assert_eq!(response.get_u64("count"), 42);
322        assert!(response.get_bool("enabled"));
323
324        // Missing keys return defaults
325        assert_eq!(response.get_string("missing"), "");
326        assert_eq!(response.get_u64("missing"), 0);
327        assert!(!response.get_bool("missing"));
328    }
329}