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    /// ```ignore
21    /// use viewpoint_core::Route;
22    ///
23    /// // Block all analytics requests for all pages
24    /// context.route("**/analytics/**", |route: Route| async move {
25    ///     route.abort().await
26    /// }).await?;
27    ///
28    /// // Mock an API for all pages
29    /// context.route("**/api/users", |route: Route| async move {
30    ///     route.fulfill()
31    ///         .status(200)
32    ///         .json(&serde_json::json!({"users": []}))
33    ///         .send()
34    ///         .await
35    /// }).await?;
36    /// ```
37    ///
38    /// # Errors
39    ///
40    /// Returns an error if the context is closed.
41    pub async fn route<M, H, Fut>(&self, pattern: M, handler: H) -> Result<(), NetworkError>
42    where
43        M: Into<UrlPattern>,
44        H: Fn(Route) -> Fut + Send + Sync + 'static,
45        Fut: Future<Output = Result<(), NetworkError>> + Send + 'static,
46    {
47        if self.is_closed() {
48            return Err(NetworkError::Aborted);
49        }
50        self.route_registry.route(pattern, handler).await
51    }
52
53    /// Register a route handler with a predicate function.
54    ///
55    /// # Example
56    ///
57    /// ```ignore
58    /// // Block POST requests to any API endpoint
59    /// context.route_predicate(
60    ///     |url| url.contains("/api/"),
61    ///     |route: Route| async move {
62    ///         if route.request().method() == "POST" {
63    ///             route.abort().await
64    ///         } else {
65    ///             route.continue_().await
66    ///         }
67    ///     }
68    /// ).await?;
69    /// ```
70    ///
71    /// # Errors
72    ///
73    /// Returns an error if the context is closed.
74    pub async fn route_predicate<P, H, Fut>(
75        &self,
76        predicate: P,
77        handler: H,
78    ) -> Result<(), NetworkError>
79    where
80        P: Fn(&str) -> bool + Send + Sync + 'static,
81        H: Fn(Route) -> Fut + Send + Sync + 'static,
82        Fut: Future<Output = Result<(), NetworkError>> + Send + 'static,
83    {
84        if self.is_closed() {
85            return Err(NetworkError::Aborted);
86        }
87        self.route_registry.route_predicate(predicate, handler).await
88    }
89
90    /// Unregister handlers matching the given pattern.
91    ///
92    /// This removes handlers registered with `route()` that match the pattern.
93    pub async fn unroute(&self, pattern: &str) {
94        self.route_registry.unroute(pattern).await;
95    }
96
97    /// Unregister all route handlers.
98    ///
99    /// This removes all handlers registered with `route()`.
100    pub async fn unroute_all(&self) {
101        self.route_registry.unroute_all().await;
102    }
103
104    /// Route requests from a HAR file for all pages in this context.
105    ///
106    /// Requests that match entries in the HAR file will be fulfilled with the
107    /// recorded responses. Requests that don't match will continue normally
108    /// unless strict mode is enabled.
109    ///
110    /// # Example
111    ///
112    /// ```ignore
113    /// // Simple HAR routing for all pages
114    /// context.route_from_har("recordings/api.har").await?;
115    ///
116    /// // With options
117    /// context.route_from_har_with_options(
118    ///     "recordings/api.har",
119    ///     HarReplayOptions::new()
120    ///         .url("**/api/**")
121    ///         .strict(true)
122    /// ).await?;
123    /// ```
124    ///
125    /// # Errors
126    ///
127    /// Returns an error if:
128    /// - The HAR file cannot be read or parsed
129    /// - The context is closed
130    pub async fn route_from_har(&self, path: impl AsRef<std::path::Path>) -> Result<(), NetworkError> {
131        self.route_from_har_with_options(path, HarReplayOptions::default()).await
132    }
133
134    /// Route requests from a HAR file with options for all pages in this context.
135    ///
136    /// # Example
137    ///
138    /// ```ignore
139    /// use viewpoint_core::network::{HarReplayOptions, TimingMode};
140    ///
141    /// // Strict mode: fail if no match found
142    /// context.route_from_har_with_options(
143    ///     "api.har",
144    ///     HarReplayOptions::new().strict(true)
145    /// ).await?;
146    ///
147    /// // URL filter: only match specific URLs
148    /// context.route_from_har_with_options(
149    ///     "api.har",
150    ///     HarReplayOptions::new().url("**/api/**")
151    /// ).await?;
152    ///
153    /// // Simulate original timing
154    /// context.route_from_har_with_options(
155    ///     "api.har",
156    ///     HarReplayOptions::new().use_original_timing(true)
157    /// ).await?;
158    /// ```
159    ///
160    /// # Errors
161    ///
162    /// Returns an error if:
163    /// - The HAR file cannot be read or parsed
164    /// - The context is closed
165    pub async fn route_from_har_with_options(
166        &self,
167        path: impl AsRef<std::path::Path>,
168        options: HarReplayOptions,
169    ) -> Result<(), NetworkError> {
170        if self.is_closed() {
171            return Err(NetworkError::Aborted);
172        }
173
174        let handler = Arc::new(
175            crate::network::HarReplayHandler::from_file(path)
176                .await?
177                .with_options(options)
178        );
179
180        // Route all requests through the HAR handler
181        let route_handler = crate::network::har_replay::create_har_route_handler(handler);
182        self.route_registry.route("**/*", route_handler).await
183    }
184}