viewpoint_core/page/routing_impl/
mod.rs

1//! Network routing implementation for Page.
2//!
3//! This module contains the network routing, event, and WebSocket monitoring methods
4//! for the Page struct.
5
6use std::future::Future;
7
8use crate::error::NetworkError;
9use crate::network::{Route, UrlMatcher, UrlPattern, WaitForRequestBuilder, WaitForResponseBuilder, WebSocket};
10
11use super::Page;
12
13impl Page {
14    // =========================================================================
15    // Network Routing Methods
16    // =========================================================================
17
18    /// Register a route handler for requests matching the given pattern.
19    ///
20    /// The handler will be called for each request that matches the pattern.
21    /// Use `route.fulfill()`, `route.continue_()`, or `route.abort()` to handle the request.
22    ///
23    /// Handlers are matched in reverse registration order (LIFO). The last registered
24    /// handler that matches a URL is called first.
25    ///
26    /// # Example
27    ///
28    /// ```ignore
29    /// use viewpoint_core::Route;
30    ///
31    /// // Block all CSS requests
32    /// page.route("**/*.css", |route: Route| async move {
33    ///     route.abort().await
34    /// }).await?;
35    ///
36    /// // Mock an API response
37    /// page.route("**/api/users", |route: Route| async move {
38    ///     route.fulfill()
39    ///         .status(200)
40    ///         .json(&serde_json::json!({"users": []}))
41    ///         .send()
42    ///         .await
43    /// }).await?;
44    ///
45    /// // Modify request headers
46    /// page.route("**/api/**", |route: Route| async move {
47    ///     route.continue_()
48    ///         .header("Authorization", "Bearer token")
49    ///         .await
50    /// }).await?;
51    /// ```
52    pub async fn route<M, H, Fut>(&self, pattern: M, handler: H) -> Result<(), NetworkError>
53    where
54        M: Into<UrlPattern>,
55        H: Fn(Route) -> Fut + Send + Sync + 'static,
56        Fut: Future<Output = Result<(), NetworkError>> + Send + 'static,
57    {
58        if self.closed {
59            return Err(NetworkError::Aborted);
60        }
61        self.route_registry.route(pattern, handler).await
62    }
63
64    /// Register a route handler with a predicate function.
65    ///
66    /// # Example
67    ///
68    /// ```ignore
69    /// // Handle only POST requests
70    /// page.route_predicate(
71    ///     |url| url.contains("/api/"),
72    ///     |route: Route| async move {
73    ///         if route.request().method() == "POST" {
74    ///             route.abort().await
75    ///         } else {
76    ///             route.continue_().await
77    ///         }
78    ///     }
79    /// ).await?;
80    /// ```
81    pub async fn route_predicate<P, H, Fut>(
82        &self,
83        predicate: P,
84        handler: H,
85    ) -> Result<(), NetworkError>
86    where
87        P: Fn(&str) -> bool + Send + Sync + 'static,
88        H: Fn(Route) -> Fut + Send + Sync + 'static,
89        Fut: Future<Output = Result<(), NetworkError>> + Send + 'static,
90    {
91        if self.closed {
92            return Err(NetworkError::Aborted);
93        }
94        self.route_registry.route_predicate(predicate, handler).await
95    }
96
97    /// Unregister handlers matching the given pattern.
98    ///
99    /// # Example
100    ///
101    /// ```ignore
102    /// page.route("**/*.css", handler).await?;
103    /// // Later...
104    /// page.unroute("**/*.css").await;
105    /// ```
106    pub async fn unroute(&self, pattern: &str) {
107        self.route_registry.unroute(pattern).await;
108    }
109
110    /// Unregister all route handlers.
111    ///
112    /// # Example
113    ///
114    /// ```ignore
115    /// page.unroute_all().await;
116    /// ```
117    pub async fn unroute_all(&self) {
118        self.route_registry.unroute_all().await;
119    }
120
121    /// Route requests from a HAR file.
122    ///
123    /// Requests that match entries in the HAR file will be fulfilled with the
124    /// recorded responses. Requests that don't match will continue normally
125    /// unless strict mode is enabled.
126    ///
127    /// # Example
128    ///
129    /// ```ignore
130    /// // Simple HAR routing
131    /// page.route_from_har("recordings/api.har").await?;
132    ///
133    /// // With options
134    /// page.route_from_har_with_options(
135    ///     "recordings/api.har",
136    ///     HarReplayOptions::new()
137    ///         .url("**/api/**")
138    ///         .strict(true)
139    /// ).await?;
140    /// ```
141    ///
142    /// # Errors
143    ///
144    /// Returns an error if:
145    /// - The HAR file cannot be read or parsed
146    /// - The page is closed
147    pub async fn route_from_har(&self, path: impl AsRef<std::path::Path>) -> Result<(), NetworkError> {
148        self.route_from_har_with_options(path, crate::network::HarReplayOptions::default()).await
149    }
150
151    /// Route requests from a HAR file with options.
152    ///
153    /// # Example
154    ///
155    /// ```ignore
156    /// use viewpoint_core::network::{HarReplayOptions, TimingMode};
157    ///
158    /// // Strict mode: fail if no match found
159    /// page.route_from_har_with_options(
160    ///     "api.har",
161    ///     HarReplayOptions::new().strict(true)
162    /// ).await?;
163    ///
164    /// // URL filter: only match specific URLs
165    /// page.route_from_har_with_options(
166    ///     "api.har",
167    ///     HarReplayOptions::new().url("**/api/**")
168    /// ).await?;
169    ///
170    /// // Simulate original timing
171    /// page.route_from_har_with_options(
172    ///     "api.har",
173    ///     HarReplayOptions::new().use_original_timing(true)
174    /// ).await?;
175    /// ```
176    ///
177    /// # Errors
178    ///
179    /// Returns an error if:
180    /// - The HAR file cannot be read or parsed
181    /// - The page is closed
182    pub async fn route_from_har_with_options(
183        &self,
184        path: impl AsRef<std::path::Path>,
185        options: crate::network::HarReplayOptions,
186    ) -> Result<(), NetworkError> {
187        if self.closed {
188            return Err(NetworkError::Aborted);
189        }
190
191        let handler = std::sync::Arc::new(
192            crate::network::HarReplayHandler::from_file(path)
193                .await?
194                .with_options(options)
195        );
196
197        // Route all requests through the HAR handler
198        let route_handler = crate::network::har_replay::create_har_route_handler(handler);
199        self.route_registry.route("**/*", route_handler).await
200    }
201
202    // =========================================================================
203    // Network Event Methods
204    // =========================================================================
205
206    /// Wait for a request matching the given pattern.
207    ///
208    /// Returns a builder that can be used to configure timeout and wait for the request.
209    ///
210    /// # Example
211    ///
212    /// ```ignore
213    /// use std::time::Duration;
214    ///
215    /// // Wait for any API request
216    /// let request = page.wait_for_request("**/api/**")
217    ///     .timeout(Duration::from_secs(10))
218    ///     .wait()
219    ///     .await?;
220    ///
221    /// println!("Request URL: {}", request.url());
222    ///
223    /// // Wait for a specific request
224    /// let request = page.wait_for_request("**/users")
225    ///     .wait()
226    ///     .await?;
227    /// ```
228    pub fn wait_for_request<M: UrlMatcher + Clone + 'static>(
229        &self,
230        pattern: M,
231    ) -> WaitForRequestBuilder<'_, M> {
232        WaitForRequestBuilder::new(&self.connection, &self.session_id, pattern)
233    }
234
235    /// Wait for a response matching the given pattern.
236    ///
237    /// Returns a builder that can be used to configure timeout and wait for the response.
238    ///
239    /// # Example
240    ///
241    /// ```ignore
242    /// use std::time::Duration;
243    ///
244    /// // Wait for any API response
245    /// let response = page.wait_for_response("**/api/**")
246    ///     .timeout(Duration::from_secs(10))
247    ///     .wait()
248    ///     .await?;
249    ///
250    /// println!("Response status: {}", response.status());
251    ///
252    /// // Get the response body
253    /// let body = response.text().await?;
254    /// ```
255    pub fn wait_for_response<M: UrlMatcher + Clone + 'static>(
256        &self,
257        pattern: M,
258    ) -> WaitForResponseBuilder<'_, M> {
259        WaitForResponseBuilder::new(&self.connection, &self.session_id, pattern)
260    }
261
262    // =========================================================================
263    // WebSocket Monitoring Methods
264    // =========================================================================
265
266    /// Set a handler for WebSocket connection events.
267    ///
268    /// The handler will be called whenever a new WebSocket connection is opened
269    /// from this page. You can then register handlers on the WebSocket for
270    /// frame events.
271    ///
272    /// # Example
273    ///
274    /// ```ignore
275    /// page.on_websocket(|ws| async move {
276    ///     println!("WebSocket opened: {}", ws.url());
277    ///     
278    ///     // Register handlers for frames
279    ///     ws.on_framesent(|frame| async move {
280    ///         println!("Sent: {:?}", frame.payload());
281    ///     }).await;
282    ///     
283    ///     ws.on_framereceived(|frame| async move {
284    ///         println!("Received: {:?}", frame.payload());
285    ///     }).await;
286    ///     
287    ///     ws.on_close(|| async {
288    ///         println!("WebSocket closed");
289    ///     }).await;
290    /// }).await;
291    /// ```
292    pub async fn on_websocket<F, Fut>(&self, handler: F)
293    where
294        F: Fn(WebSocket) -> Fut + Send + Sync + 'static,
295        Fut: Future<Output = ()> + Send + 'static,
296    {
297        self.websocket_manager.set_handler(handler).await;
298    }
299
300    /// Remove the WebSocket event handler.
301    pub async fn off_websocket(&self) {
302        self.websocket_manager.remove_handler().await;
303    }
304}