viewpoint_core/network/route/
mod.rs

1//! Route handling for network interception.
2
3use std::future::Future;
4use std::pin::Pin;
5use std::sync::Arc;
6use std::time::Duration;
7
8use tokio::sync::Mutex;
9use viewpoint_cdp::protocol::fetch::{
10    ContinueRequestParams, ErrorReason, FailRequestParams, FulfillRequestParams,
11    GetResponseBodyParams, GetResponseBodyResult, HeaderEntry,
12};
13use viewpoint_cdp::CdpConnection;
14
15use super::request::Request;
16use super::route_builders::{ContinueBuilder, FulfillBuilder};
17use super::route_fetch::{FetchBuilder, FetchedResponse};
18use super::types::AbortError;
19use crate::error::NetworkError;
20
21/// The result of a route handler action.
22///
23/// Route handlers return this to indicate whether they handled the request
24/// or want to pass it to the next matching handler.
25#[derive(Debug, Clone, Copy, PartialEq, Eq)]
26pub enum RouteAction {
27    /// The request was handled (fulfilled, continued, or aborted).
28    Handled,
29    /// Pass the request to the next matching handler.
30    Fallback,
31}
32
33/// A route handler function.
34pub type RouteHandler = Box<
35    dyn Fn(Route) -> Pin<Box<dyn Future<Output = Result<(), NetworkError>> + Send>>
36        + Send
37        + Sync,
38>;
39
40/// An intercepted network request that can be fulfilled, continued, or aborted.
41///
42/// When a request matches a route pattern, a `Route` is passed to the handler.
43/// The handler must call one of `fulfill()`, `continue_()`, `abort()`, or `fallback()`
44/// to resolve the request.
45#[derive(Debug, Clone)]
46pub struct Route {
47    /// The intercepted request.
48    request: Request,
49    /// CDP request ID for this route.
50    request_id: String,
51    /// CDP connection.
52    connection: Arc<CdpConnection>,
53    /// Session ID.
54    session_id: String,
55    /// Whether this route has been handled.
56    handled: Arc<Mutex<bool>>,
57    /// Response status code (if intercepted at response stage).
58    response_status: Option<u16>,
59    /// Response headers (if intercepted at response stage).
60    response_headers: Option<Vec<HeaderEntry>>,
61}
62
63impl Route {
64    /// Create a new route.
65    pub(crate) fn new(
66        request: Request,
67        request_id: String,
68        connection: Arc<CdpConnection>,
69        session_id: String,
70        response_status: Option<u16>,
71        response_headers: Option<Vec<HeaderEntry>>,
72    ) -> Self {
73        Self {
74            request,
75            request_id,
76            connection,
77            session_id,
78            handled: Arc::new(Mutex::new(false)),
79            response_status,
80            response_headers,
81        }
82    }
83
84    /// Get the intercepted request.
85    pub fn request(&self) -> &Request {
86        &self.request
87    }
88
89    /// Get the request ID.
90    pub(super) fn request_id(&self) -> &str {
91        &self.request_id
92    }
93
94    /// Get the CDP connection.
95    pub(super) fn connection(&self) -> &Arc<CdpConnection> {
96        &self.connection
97    }
98
99    /// Get the session ID.
100    pub(super) fn session_id(&self) -> &str {
101        &self.session_id
102    }
103
104    /// Check if this route has been handled.
105    pub async fn is_handled(&self) -> bool {
106        *self.handled.lock().await
107    }
108
109    /// Check if this route is at the response stage.
110    pub fn is_response_stage(&self) -> bool {
111        self.response_status.is_some()
112    }
113
114    /// Get the response status code (if at response stage).
115    pub fn response_status(&self) -> Option<u16> {
116        self.response_status
117    }
118
119    /// Get the response headers (if at response stage).
120    pub fn response_headers(&self) -> Option<&[HeaderEntry]> {
121        self.response_headers.as_deref()
122    }
123
124    /// Get the response body (if at response stage).
125    ///
126    /// # Errors
127    ///
128    /// Returns an error if not at response stage or body cannot be fetched.
129    pub async fn response_body(&self) -> Result<Option<Vec<u8>>, NetworkError> {
130        if !self.is_response_stage() {
131            return Ok(None);
132        }
133        self.get_response_body(&self.request_id).await
134    }
135
136    /// Fulfill the request with a custom response.
137    ///
138    /// # Example
139    ///
140    /// ```ignore
141    /// route.fulfill()
142    ///     .status(200)
143    ///     .content_type("application/json")
144    ///     .body(r#"{"success": true}"#)
145    ///     .send()
146    ///     .await?;
147    /// ```
148    pub fn fulfill(&self) -> FulfillBuilder<'_> {
149        FulfillBuilder::new(self)
150    }
151
152    /// Continue the request with optional modifications.
153    ///
154    /// # Example
155    ///
156    /// ```ignore
157    /// // Continue unchanged
158    /// route.continue_().await?;
159    ///
160    /// // Modify the request
161    /// route.continue_()
162    ///     .header("X-Custom", "value")
163    ///     .send()
164    ///     .await?;
165    /// ```
166    pub fn continue_(&self) -> ContinueBuilder<'_> {
167        ContinueBuilder::new(self)
168    }
169
170    /// Abort the request with a generic error.
171    ///
172    /// # Errors
173    ///
174    /// Returns an error if the abort fails.
175    pub async fn abort(&self) -> Result<(), NetworkError> {
176        self.abort_with(AbortError::Failed).await
177    }
178
179    /// Abort the request with a specific error.
180    ///
181    /// # Errors
182    ///
183    /// Returns an error if the abort fails.
184    pub async fn abort_with(&self, error: AbortError) -> Result<(), NetworkError> {
185        // Mark as handled
186        {
187            let mut handled = self.handled.lock().await;
188            if *handled {
189                return Err(NetworkError::AlreadyHandled);
190            }
191            *handled = true;
192        }
193
194        let error_reason = match error {
195            AbortError::Aborted => ErrorReason::Aborted,
196            AbortError::AccessDenied => ErrorReason::AccessDenied,
197            AbortError::AddressUnreachable => ErrorReason::AddressUnreachable,
198            AbortError::BlockedByClient => ErrorReason::BlockedByClient,
199            AbortError::BlockedByResponse => ErrorReason::BlockedByResponse,
200            AbortError::ConnectionAborted => ErrorReason::ConnectionAborted,
201            AbortError::ConnectionClosed => ErrorReason::ConnectionClosed,
202            AbortError::ConnectionFailed => ErrorReason::ConnectionFailed,
203            AbortError::ConnectionRefused => ErrorReason::ConnectionRefused,
204            AbortError::ConnectionReset => ErrorReason::ConnectionReset,
205            AbortError::InternetDisconnected => ErrorReason::InternetDisconnected,
206            AbortError::NameNotResolved => ErrorReason::NameNotResolved,
207            AbortError::TimedOut => ErrorReason::TimedOut,
208            AbortError::Failed => ErrorReason::Failed,
209        };
210
211        let params = FailRequestParams {
212            request_id: self.request_id.clone(),
213            error_reason,
214        };
215
216        self.connection
217            .send_command::<_, serde_json::Value>("Fetch.failRequest", Some(params), Some(&self.session_id))
218            .await
219            .map_err(NetworkError::from)?;
220
221        Ok(())
222    }
223
224    /// Pass this request to the next matching route handler.
225    ///
226    /// If no other handlers match, the request continues to the server.
227    ///
228    /// # Errors
229    ///
230    /// Returns an error if the fallback fails.
231    pub async fn fallback(&self) -> Result<(), NetworkError> {
232        // For fallback, we just continue the request unchanged
233        // The routing system will check for other matching handlers
234        let params = ContinueRequestParams {
235            request_id: self.request_id.clone(),
236            url: None,
237            method: None,
238            post_data: None,
239            headers: None,
240            intercept_response: None,
241        };
242
243        self.connection
244            .send_command::<_, serde_json::Value>("Fetch.continueRequest", Some(params), Some(&self.session_id))
245            .await
246            .map_err(NetworkError::from)?;
247
248        Ok(())
249    }
250
251    /// Fetch the actual response from the server, allowing inspection/modification.
252    ///
253    /// # Example
254    ///
255    /// ```ignore
256    /// let response = route.fetch().await?;
257    /// println!("Status: {}", response.status);
258    ///
259    /// // Modify and send to page
260    /// route.fulfill()
261    ///     .response(&response)
262    ///     .header("X-Modified", "true")
263    ///     .send()
264    ///     .await?;
265    /// ```
266    pub fn fetch(&self) -> FetchBuilder<'_> {
267        FetchBuilder::new(self)
268    }
269
270    /// Fetch the response with a timeout.
271    pub async fn fetch_with_timeout(&self, timeout: Duration) -> Result<FetchedResponse<'_>, NetworkError> {
272        self.fetch().timeout(timeout).send().await
273    }
274
275    // =========================================================================
276    // Internal helpers
277    // =========================================================================
278
279    /// Send a fulfill request.
280    pub(super) async fn send_fulfill(&self, params: FulfillRequestParams) -> Result<(), NetworkError> {
281        // Mark as handled
282        {
283            let mut handled = self.handled.lock().await;
284            if *handled {
285                return Err(NetworkError::AlreadyHandled);
286            }
287            *handled = true;
288        }
289
290        self.connection
291            .send_command::<_, serde_json::Value>("Fetch.fulfillRequest", Some(params), Some(&self.session_id))
292            .await
293            .map_err(NetworkError::from)?;
294
295        Ok(())
296    }
297
298    /// Send a continue request.
299    pub(super) async fn send_continue(&self, params: ContinueRequestParams) -> Result<(), NetworkError> {
300        // Mark as handled
301        {
302            let mut handled = self.handled.lock().await;
303            if *handled {
304                return Err(NetworkError::AlreadyHandled);
305            }
306            *handled = true;
307        }
308
309        self.connection
310            .send_command::<_, serde_json::Value>("Fetch.continueRequest", Some(params), Some(&self.session_id))
311            .await
312            .map_err(NetworkError::from)?;
313
314        Ok(())
315    }
316
317    /// Get the response body for a request.
318    pub(super) async fn get_response_body(&self, request_id: &str) -> Result<Option<Vec<u8>>, NetworkError> {
319        use base64::Engine;
320
321        let result: GetResponseBodyResult = self
322            .connection
323            .send_command(
324                "Fetch.getResponseBody",
325                Some(GetResponseBodyParams {
326                    request_id: request_id.to_string(),
327                }),
328                Some(&self.session_id),
329            )
330            .await
331            .map_err(NetworkError::from)?;
332
333        let body = if result.base64_encoded {
334            base64::engine::general_purpose::STANDARD
335                .decode(&result.body)
336                .ok()
337        } else {
338            Some(result.body.into_bytes())
339        };
340
341        Ok(body)
342    }
343}
344
345#[cfg(test)]
346mod tests;