firefox_webdriver/browser/
network.rs

1//! Network interception types.
2//!
3//! Types for request/response interception callbacks.
4//!
5//! # Request Interception
6//!
7//! ```ignore
8//! use firefox_webdriver::RequestAction;
9//!
10//! let intercept_id = tab.intercept_request(|req| {
11//!     if req.url.contains("ads") {
12//!         RequestAction::block()
13//!     } else {
14//!         RequestAction::allow()
15//!     }
16//! }).await?;
17//! ```
18//!
19//! # Response Interception
20//!
21//! ```ignore
22//! use firefox_webdriver::BodyAction;
23//!
24//! let intercept_id = tab.intercept_response_body(|res| {
25//!     if res.url.contains("config.json") {
26//!         BodyAction::modify_body(r#"{"modified": true}"#)
27//!     } else {
28//!         BodyAction::allow()
29//!     }
30//! }).await?;
31//! ```
32
33// ============================================================================
34// Imports
35// ============================================================================
36
37use std::collections::HashMap;
38
39// ============================================================================
40// InterceptedRequest
41// ============================================================================
42
43/// Data about an intercepted network request.
44#[derive(Debug, Clone)]
45pub struct InterceptedRequest {
46    /// Unique request ID.
47    pub request_id: String,
48
49    /// Request URL.
50    pub url: String,
51
52    /// HTTP method (GET, POST, etc.).
53    pub method: String,
54
55    /// Resource type (document, script, xhr, etc.).
56    pub resource_type: String,
57
58    /// Tab ID where request originated.
59    pub tab_id: u32,
60
61    /// Frame ID where request originated.
62    pub frame_id: u64,
63
64    /// Request body (if available).
65    pub body: Option<RequestBody>,
66}
67
68// ============================================================================
69// RequestBody
70// ============================================================================
71
72/// Request body data.
73#[derive(Debug, Clone)]
74pub enum RequestBody {
75    /// Form data (application/x-www-form-urlencoded or multipart/form-data).
76    FormData(HashMap<String, Vec<String>>),
77
78    /// Raw bytes (base64 encoded).
79    Raw(Vec<u8>),
80
81    /// Error reading body.
82    Error(String),
83}
84
85// ============================================================================
86// RequestAction
87// ============================================================================
88
89/// Action to take for an intercepted request.
90#[derive(Debug, Clone)]
91pub enum RequestAction {
92    /// Allow the request to proceed.
93    Allow,
94
95    /// Block/cancel the request.
96    Block,
97
98    /// Redirect to a different URL.
99    Redirect(String),
100}
101
102// ============================================================================
103// RequestAction - Constructors
104// ============================================================================
105
106impl RequestAction {
107    /// Creates an Allow action.
108    #[inline]
109    #[must_use]
110    pub fn allow() -> Self {
111        Self::Allow
112    }
113
114    /// Creates a Block action.
115    #[inline]
116    #[must_use]
117    pub fn block() -> Self {
118        Self::Block
119    }
120
121    /// Creates a Redirect action.
122    #[inline]
123    #[must_use]
124    pub fn redirect(url: impl Into<String>) -> Self {
125        Self::Redirect(url.into())
126    }
127}
128
129// ============================================================================
130// InterceptedRequestBody
131// ============================================================================
132
133/// Data about an intercepted request body (read-only, cannot be modified).
134///
135/// Browser limitation: request body cannot be modified, only inspected.
136#[derive(Debug, Clone)]
137pub struct InterceptedRequestBody {
138    /// Unique request ID.
139    pub request_id: String,
140
141    /// Request URL.
142    pub url: String,
143
144    /// HTTP method.
145    pub method: String,
146
147    /// Resource type (document, script, xhr, etc.).
148    pub resource_type: String,
149
150    /// Tab ID.
151    pub tab_id: u32,
152
153    /// Frame ID.
154    pub frame_id: u64,
155
156    /// Request body (if available).
157    pub body: Option<RequestBody>,
158}
159
160// ============================================================================
161// InterceptedRequestHeaders
162// ============================================================================
163
164/// Data about intercepted request headers.
165#[derive(Debug, Clone)]
166pub struct InterceptedRequestHeaders {
167    /// Unique request ID.
168    pub request_id: String,
169
170    /// Request URL.
171    pub url: String,
172
173    /// HTTP method.
174    pub method: String,
175
176    /// Request headers.
177    pub headers: HashMap<String, String>,
178
179    /// Tab ID.
180    pub tab_id: u32,
181
182    /// Frame ID.
183    pub frame_id: u64,
184}
185
186// ============================================================================
187// HeadersAction
188// ============================================================================
189
190/// Action to take for intercepted headers.
191#[derive(Debug, Clone)]
192pub enum HeadersAction {
193    /// Allow headers to proceed unchanged.
194    Allow,
195
196    /// Modify headers.
197    ModifyHeaders(HashMap<String, String>),
198}
199
200// ============================================================================
201// HeadersAction - Constructors
202// ============================================================================
203
204impl HeadersAction {
205    /// Creates an Allow action.
206    #[inline]
207    #[must_use]
208    pub fn allow() -> Self {
209        Self::Allow
210    }
211
212    /// Creates a ModifyHeaders action.
213    #[inline]
214    #[must_use]
215    pub fn modify_headers(headers: HashMap<String, String>) -> Self {
216        Self::ModifyHeaders(headers)
217    }
218}
219
220// ============================================================================
221// InterceptedResponse
222// ============================================================================
223
224/// Data about an intercepted network response.
225#[derive(Debug, Clone)]
226pub struct InterceptedResponse {
227    /// Unique request ID.
228    pub request_id: String,
229
230    /// Request URL.
231    pub url: String,
232
233    /// HTTP status code.
234    pub status: u16,
235
236    /// HTTP status text.
237    pub status_text: String,
238
239    /// Response headers.
240    pub headers: HashMap<String, String>,
241
242    /// Tab ID where request originated.
243    pub tab_id: u32,
244
245    /// Frame ID where request originated.
246    pub frame_id: u64,
247}
248
249// ============================================================================
250// ResponseAction
251// ============================================================================
252
253/// Action to take for intercepted response headers.
254///
255/// Alias for [`HeadersAction`].
256pub type ResponseAction = HeadersAction;
257
258// ============================================================================
259// InterceptedResponseBody
260// ============================================================================
261
262/// Data about an intercepted response body.
263#[derive(Debug, Clone)]
264pub struct InterceptedResponseBody {
265    /// Unique request ID.
266    pub request_id: String,
267
268    /// Request URL.
269    pub url: String,
270
271    /// Tab ID.
272    pub tab_id: u32,
273
274    /// Frame ID.
275    pub frame_id: u64,
276
277    /// Response body as string.
278    pub body: String,
279
280    /// Content length.
281    pub content_length: usize,
282}
283
284// ============================================================================
285// BodyAction
286// ============================================================================
287
288/// Action to take for intercepted response body.
289#[derive(Debug, Clone)]
290pub enum BodyAction {
291    /// Allow body to proceed unchanged.
292    Allow,
293
294    /// Modify body content.
295    ModifyBody(String),
296}
297
298// ============================================================================
299// BodyAction - Constructors
300// ============================================================================
301
302impl BodyAction {
303    /// Creates an Allow action.
304    #[inline]
305    #[must_use]
306    pub fn allow() -> Self {
307        Self::Allow
308    }
309
310    /// Creates a ModifyBody action.
311    #[inline]
312    #[must_use]
313    pub fn modify_body(body: impl Into<String>) -> Self {
314        Self::ModifyBody(body.into())
315    }
316}
317
318// ============================================================================
319// Tests
320// ============================================================================
321
322#[cfg(test)]
323mod tests {
324    use super::{BodyAction, HeadersAction, RequestAction};
325
326    use std::collections::HashMap;
327
328    #[test]
329    fn test_request_action_allow() {
330        let action = RequestAction::allow();
331        assert!(matches!(action, RequestAction::Allow));
332    }
333
334    #[test]
335    fn test_request_action_block() {
336        let action = RequestAction::block();
337        assert!(matches!(action, RequestAction::Block));
338    }
339
340    #[test]
341    fn test_request_action_redirect() {
342        let action = RequestAction::redirect("https://example.com");
343        if let RequestAction::Redirect(url) = action {
344            assert_eq!(url, "https://example.com");
345        } else {
346            panic!("Expected Redirect action");
347        }
348    }
349
350    #[test]
351    fn test_headers_action_allow() {
352        let action = HeadersAction::allow();
353        assert!(matches!(action, HeadersAction::Allow));
354    }
355
356    #[test]
357    fn test_headers_action_modify() {
358        let mut headers = HashMap::new();
359        headers.insert("X-Custom".to_string(), "value".to_string());
360
361        let action = HeadersAction::modify_headers(headers);
362        if let HeadersAction::ModifyHeaders(h) = action {
363            assert_eq!(h.get("X-Custom"), Some(&"value".to_string()));
364        } else {
365            panic!("Expected ModifyHeaders action");
366        }
367    }
368
369    #[test]
370    fn test_body_action_allow() {
371        let action = BodyAction::allow();
372        assert!(matches!(action, BodyAction::Allow));
373    }
374
375    #[test]
376    fn test_body_action_modify() {
377        let action = BodyAction::modify_body("new body");
378        if let BodyAction::ModifyBody(body) = action {
379            assert_eq!(body, "new body");
380        } else {
381            panic!("Expected ModifyBody action");
382        }
383    }
384}