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}