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    /// ```no_run
141    /// use viewpoint_core::Route;
142    ///
143    /// # async fn example(route: Route) -> Result<(), viewpoint_core::CoreError> {
144    /// route.fulfill()
145    ///     .status(200)
146    ///     .content_type("application/json")
147    ///     .body(r#"{"success": true}"#)
148    ///     .send()
149    ///     .await?;
150    /// # Ok(())
151    /// # }
152    pub fn fulfill(&self) -> FulfillBuilder<'_> {
153        FulfillBuilder::new(self)
154    }
155
156    /// Continue the request with optional modifications.
157    ///
158    /// # Example
159    ///
160    /// ```no_run
161    /// use viewpoint_core::Route;
162    ///
163    /// # async fn example(route: Route) -> Result<(), viewpoint_core::CoreError> {
164    /// // Continue unchanged
165    /// route.continue_().send().await?;
166    /// # Ok(())
167    /// # }
168    ///
169    /// # async fn example2(route: Route) -> Result<(), viewpoint_core::CoreError> {
170    /// // Modify the request
171    /// route.continue_()
172    ///     .header("X-Custom", "value")
173    ///     .send()
174    ///     .await?;
175    /// # Ok(())
176    /// # }
177    pub fn continue_(&self) -> ContinueBuilder<'_> {
178        ContinueBuilder::new(self)
179    }
180
181    /// Abort the request with a generic error.
182    ///
183    /// # Errors
184    ///
185    /// Returns an error if the abort fails.
186    pub async fn abort(&self) -> Result<(), NetworkError> {
187        self.abort_with(AbortError::Failed).await
188    }
189
190    /// Abort the request with a specific error.
191    ///
192    /// # Errors
193    ///
194    /// Returns an error if the abort fails.
195    pub async fn abort_with(&self, error: AbortError) -> Result<(), NetworkError> {
196        // Mark as handled
197        {
198            let mut handled = self.handled.lock().await;
199            if *handled {
200                return Err(NetworkError::AlreadyHandled);
201            }
202            *handled = true;
203        }
204
205        let error_reason = match error {
206            AbortError::Aborted => ErrorReason::Aborted,
207            AbortError::AccessDenied => ErrorReason::AccessDenied,
208            AbortError::AddressUnreachable => ErrorReason::AddressUnreachable,
209            AbortError::BlockedByClient => ErrorReason::BlockedByClient,
210            AbortError::BlockedByResponse => ErrorReason::BlockedByResponse,
211            AbortError::ConnectionAborted => ErrorReason::ConnectionAborted,
212            AbortError::ConnectionClosed => ErrorReason::ConnectionClosed,
213            AbortError::ConnectionFailed => ErrorReason::ConnectionFailed,
214            AbortError::ConnectionRefused => ErrorReason::ConnectionRefused,
215            AbortError::ConnectionReset => ErrorReason::ConnectionReset,
216            AbortError::InternetDisconnected => ErrorReason::InternetDisconnected,
217            AbortError::NameNotResolved => ErrorReason::NameNotResolved,
218            AbortError::TimedOut => ErrorReason::TimedOut,
219            AbortError::Failed => ErrorReason::Failed,
220        };
221
222        let params = FailRequestParams {
223            request_id: self.request_id.clone(),
224            error_reason,
225        };
226
227        self.connection
228            .send_command::<_, serde_json::Value>("Fetch.failRequest", Some(params), Some(&self.session_id))
229            .await
230            .map_err(NetworkError::from)?;
231
232        Ok(())
233    }
234
235    /// Pass this request to the next matching route handler.
236    ///
237    /// If no other handlers match, the request continues to the server.
238    ///
239    /// # Errors
240    ///
241    /// Returns an error if the fallback fails.
242    pub async fn fallback(&self) -> Result<(), NetworkError> {
243        // For fallback, we just continue the request unchanged
244        // The routing system will check for other matching handlers
245        let params = ContinueRequestParams {
246            request_id: self.request_id.clone(),
247            url: None,
248            method: None,
249            post_data: None,
250            headers: None,
251            intercept_response: None,
252        };
253
254        self.connection
255            .send_command::<_, serde_json::Value>("Fetch.continueRequest", Some(params), Some(&self.session_id))
256            .await
257            .map_err(NetworkError::from)?;
258
259        Ok(())
260    }
261
262    /// Fetch the actual response from the server, allowing inspection/modification.
263    ///
264    /// # Example
265    ///
266    /// ```no_run
267    /// use viewpoint_core::Route;
268    ///
269    /// # async fn example(route: Route) -> Result<(), viewpoint_core::CoreError> {
270    /// let response = route.fetch().send().await?;
271    /// println!("Status: {}", response.status);
272    ///
273    /// // Modify and send to page
274    /// route.fulfill()
275    ///     .response(&response)
276    ///     .header("X-Modified", "true")
277    ///     .send()
278    ///     .await?;
279    /// # Ok(())
280    /// # }
281    pub fn fetch(&self) -> FetchBuilder<'_> {
282        FetchBuilder::new(self)
283    }
284
285    /// Fetch the response with a timeout.
286    pub async fn fetch_with_timeout(&self, timeout: Duration) -> Result<FetchedResponse<'_>, NetworkError> {
287        self.fetch().timeout(timeout).send().await
288    }
289
290    // =========================================================================
291    // Internal helpers
292    // =========================================================================
293
294    /// Send a fulfill request.
295    pub(super) async fn send_fulfill(&self, params: FulfillRequestParams) -> Result<(), NetworkError> {
296        // Mark as handled
297        {
298            let mut handled = self.handled.lock().await;
299            if *handled {
300                return Err(NetworkError::AlreadyHandled);
301            }
302            *handled = true;
303        }
304
305        self.connection
306            .send_command::<_, serde_json::Value>("Fetch.fulfillRequest", Some(params), Some(&self.session_id))
307            .await
308            .map_err(NetworkError::from)?;
309
310        Ok(())
311    }
312
313    /// Send a continue request.
314    pub(super) async fn send_continue(&self, params: ContinueRequestParams) -> Result<(), NetworkError> {
315        // Mark as handled
316        {
317            let mut handled = self.handled.lock().await;
318            if *handled {
319                return Err(NetworkError::AlreadyHandled);
320            }
321            *handled = true;
322        }
323
324        self.connection
325            .send_command::<_, serde_json::Value>("Fetch.continueRequest", Some(params), Some(&self.session_id))
326            .await
327            .map_err(NetworkError::from)?;
328
329        Ok(())
330    }
331
332    /// Get the response body for a request.
333    pub(super) async fn get_response_body(&self, request_id: &str) -> Result<Option<Vec<u8>>, NetworkError> {
334        use base64::Engine;
335
336        let result: GetResponseBodyResult = self
337            .connection
338            .send_command(
339                "Fetch.getResponseBody",
340                Some(GetResponseBodyParams {
341                    request_id: request_id.to_string(),
342                }),
343                Some(&self.session_id),
344            )
345            .await
346            .map_err(NetworkError::from)?;
347
348        let body = if result.base64_encoded {
349            base64::engine::general_purpose::STANDARD
350                .decode(&result.body)
351                .ok()
352        } else {
353            Some(result.body.into_bytes())
354        };
355
356        Ok(body)
357    }
358}
359
360#[cfg(test)]
361mod tests;