viewpoint_core/context/routing_impl/
mod.rs

1//! Context-level network routing implementation.
2
3use std::future::Future;
4use std::sync::Arc;
5
6use crate::error::NetworkError;
7use crate::network::{HarReplayOptions, Route, UrlPattern};
8
9use super::BrowserContext;
10
11impl BrowserContext {
12    /// Register a route handler that applies to all pages in this context.
13    ///
14    /// Routes registered at the context level are applied to all pages,
15    /// including new pages created after the route is registered.
16    /// Context routes are checked before page-level routes.
17    ///
18    /// # Example
19    ///
20    /// ```no_run
21    /// use viewpoint_core::{Browser, network::Route};
22    ///
23    /// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
24    /// let browser = Browser::launch().headless(true).launch().await?;
25    /// let context = browser.new_context().await?;
26    ///
27    /// // Block all analytics requests for all pages
28    /// context.route("**/analytics/**", |route: Route| async move {
29    ///     route.abort().await
30    /// }).await?;
31    ///
32    /// // Mock an API for all pages
33    /// context.route("**/api/users", |route: Route| async move {
34    ///     route.fulfill()
35    ///         .status(200)
36    ///         .json(&serde_json::json!({"users": []}))
37    ///         .send()
38    ///         .await
39    /// }).await?;
40    /// # Ok(())
41    /// # }
42    /// ```
43    ///
44    /// # Errors
45    ///
46    /// Returns an error if the context is closed.
47    pub async fn route<M, H, Fut>(&self, pattern: M, handler: H) -> Result<(), NetworkError>
48    where
49        M: Into<UrlPattern>,
50        H: Fn(Route) -> Fut + Send + Sync + 'static,
51        Fut: Future<Output = Result<(), NetworkError>> + Send + 'static,
52    {
53        if self.is_closed() {
54            return Err(NetworkError::Aborted);
55        }
56        self.route_registry.route(pattern, handler).await
57    }
58
59    /// Register a route handler with a predicate function.
60    ///
61    /// # Example
62    ///
63    /// ```no_run
64    /// use viewpoint_core::{Browser, network::Route};
65    ///
66    /// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
67    /// let browser = Browser::launch().headless(true).launch().await?;
68    /// let context = browser.new_context().await?;
69    ///
70    /// // Block POST requests to any API endpoint
71    /// context.route_predicate(
72    ///     |url| url.contains("/api/"),
73    ///     |route: Route| async move {
74    ///         if route.request().method() == "POST" {
75    ///             route.abort().await
76    ///         } else {
77    ///             route.continue_().await
78    ///         }
79    ///     }
80    /// ).await?;
81    /// # Ok(())
82    /// # }
83    /// ```
84    ///
85    /// # Errors
86    ///
87    /// Returns an error if the context is closed.
88    pub async fn route_predicate<P, H, Fut>(
89        &self,
90        predicate: P,
91        handler: H,
92    ) -> Result<(), NetworkError>
93    where
94        P: Fn(&str) -> bool + Send + Sync + 'static,
95        H: Fn(Route) -> Fut + Send + Sync + 'static,
96        Fut: Future<Output = Result<(), NetworkError>> + Send + 'static,
97    {
98        if self.is_closed() {
99            return Err(NetworkError::Aborted);
100        }
101        self.route_registry
102            .route_predicate(predicate, handler)
103            .await
104    }
105
106    /// Unregister handlers matching the given pattern.
107    ///
108    /// This removes handlers registered with `route()` that match the pattern.
109    pub async fn unroute(&self, pattern: &str) {
110        self.route_registry.unroute(pattern).await;
111    }
112
113    /// Unregister all route handlers.
114    ///
115    /// This removes all handlers registered with `route()`.
116    pub async fn unroute_all(&self) {
117        self.route_registry.unroute_all().await;
118    }
119
120    /// Route requests from a HAR file for all pages in this context.
121    ///
122    /// Requests that match entries in the HAR file will be fulfilled with the
123    /// recorded responses. Requests that don't match will continue normally
124    /// unless strict mode is enabled.
125    ///
126    /// # Example
127    ///
128    /// ```no_run
129    /// use viewpoint_core::{Browser, network::HarReplayOptions};
130    ///
131    /// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
132    /// let browser = Browser::launch().headless(true).launch().await?;
133    /// let context = browser.new_context().await?;
134    ///
135    /// // Simple HAR routing for all pages
136    /// context.route_from_har("recordings/api.har").await?;
137    ///
138    /// // With options
139    /// context.route_from_har_with_options(
140    ///     "recordings/api.har",
141    ///     HarReplayOptions::new()
142    ///         .url("**/api/**")
143    ///         .strict(true)
144    /// ).await?;
145    /// # Ok(())
146    /// # }
147    /// ```
148    ///
149    /// # Errors
150    ///
151    /// Returns an error if:
152    /// - The HAR file cannot be read or parsed
153    /// - The context is closed
154    pub async fn route_from_har(
155        &self,
156        path: impl AsRef<std::path::Path>,
157    ) -> Result<(), NetworkError> {
158        self.route_from_har_with_options(path, HarReplayOptions::default())
159            .await
160    }
161
162    /// Route requests from a HAR file with options for all pages in this context.
163    ///
164    /// # Example
165    ///
166    /// ```no_run
167    /// use viewpoint_core::{Browser, network::HarReplayOptions};
168    ///
169    /// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
170    /// let browser = Browser::launch().headless(true).launch().await?;
171    /// let context = browser.new_context().await?;
172    ///
173    /// // Strict mode: fail if no match found
174    /// context.route_from_har_with_options(
175    ///     "api.har",
176    ///     HarReplayOptions::new().strict(true)
177    /// ).await?;
178    /// # Ok(())
179    /// # }
180    /// ```
181    ///
182    /// ```no_run
183    /// use viewpoint_core::{Browser, network::HarReplayOptions};
184    ///
185    /// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
186    /// let browser = Browser::launch().headless(true).launch().await?;
187    /// let context = browser.new_context().await?;
188    ///
189    /// // URL filter: only match specific URLs
190    /// context.route_from_har_with_options(
191    ///     "api.har",
192    ///     HarReplayOptions::new().url("**/api/**")
193    /// ).await?;
194    /// # Ok(())
195    /// # }
196    /// ```
197    ///
198    /// ```no_run
199    /// use viewpoint_core::{Browser, network::HarReplayOptions};
200    ///
201    /// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
202    /// let browser = Browser::launch().headless(true).launch().await?;
203    /// let context = browser.new_context().await?;
204    ///
205    /// // Simulate original timing
206    /// context.route_from_har_with_options(
207    ///     "api.har",
208    ///     HarReplayOptions::new().use_original_timing(true)
209    /// ).await?;
210    /// # Ok(())
211    /// # }
212    /// ```
213    ///
214    /// # Errors
215    ///
216    /// Returns an error if:
217    /// - The HAR file cannot be read or parsed
218    /// - The context is closed
219    pub async fn route_from_har_with_options(
220        &self,
221        path: impl AsRef<std::path::Path>,
222        options: HarReplayOptions,
223    ) -> Result<(), NetworkError> {
224        if self.is_closed() {
225            return Err(NetworkError::Aborted);
226        }
227
228        let handler = Arc::new(
229            crate::network::HarReplayHandler::from_file(path)
230                .await?
231                .with_options(options),
232        );
233
234        // Route all requests through the HAR handler
235        let route_handler = crate::network::har_replay::create_har_route_handler(handler);
236        self.route_registry.route("**/*", route_handler).await
237    }
238}