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