playwright/api/
route.rs

1use crate::{
2    api::{Header, Request},
3    imp::{
4        core::*,
5        prelude::*,
6        route::{ContinueArgs, FulfillArgs, Route as Impl},
7    },
8};
9
10/// Whenever a network route is set up with [`method: Page.route`] or [`method: BrowserContext.route`], the `Route` object
11/// allows to handle the route.
12pub struct Route {
13    inner: Weak<Impl>,
14}
15
16impl PartialEq for Route {
17    fn eq(&self, other: &Self) -> bool {
18        let a = self.inner.upgrade();
19        let b = other.inner.upgrade();
20        a.and_then(|a| b.map(|b| (a, b)))
21            .map(|(a, b)| a.guid() == b.guid())
22            .unwrap_or_default()
23    }
24}
25
26impl Route {
27    fn new(inner: Weak<Impl>) -> Self {
28        Self { inner }
29    }
30
31    /// A request to be routed.
32    pub fn request(&self) -> Request {
33        let inner = weak_and_then(&self.inner, |rc| rc.request());
34        Request::new(inner)
35    }
36
37    /// Aborts the route's request.
38    /// Optional error code. Defaults to `failed`, could be one of the following:
39    /// - `'aborted'` - An operation was aborted (due to user action)
40    /// - `'accessdenied'` - Permission to access a resource, other than the network, was denied
41    /// - `'addressunreachable'` - The IP address is unreachable. This usually means that there is no route to the specified
42    ///  host or network.
43    /// - `'blockedbyclient'` - The client chose to block the request.
44    /// - `'blockedbyresponse'` - The request failed because the response was delivered along with requirements which are not
45    ///  met ('X-Frame-Options' and 'Content-Security-Policy' ancestor checks, for instance).
46    /// - `'connectionaborted'` - A connection timed out as a result of not receiving an ACK for data sent.
47    /// - `'connectionclosed'` - A connection was closed (corresponding to a TCP FIN).
48    /// - `'connectionfailed'` - A connection attempt failed.
49    /// - `'connectionrefused'` - A connection attempt was refused.
50    /// - `'connectionreset'` - A connection was reset (corresponding to a TCP RST).
51    /// - `'internetdisconnected'` - The Internet connection has been lost.
52    /// - `'namenotresolved'` - The host name could not be resolved.
53    /// - `'timedout'` - An operation timed out.
54    /// - `'failed'` - A generic failure occurred.
55    pub async fn abort(&self, err_code: Option<&str>) -> Result<(), Arc<Error>> {
56        let inner = upgrade(&self.inner)?;
57        inner.abort(err_code).await
58    }
59
60    /// Fulfills route's request with given response.
61    ///
62    /// An example of fulfilling all requests with 404 responses:
63    ///
64    /// ```js
65    /// await page.route('**/*', route => {
66    ///  route.fulfill({
67    ///    status: 404,
68    ///    contentType: 'text/plain',
69    ///    body: 'Not Found!'
70    ///  });
71    /// });
72    pub async fn fulfill_builder<'a>(
73        &self,
74        body: &'a str,
75        is_base64: bool,
76    ) -> FulfillBuilder<'a, '_> {
77        FulfillBuilder::new(self.inner.clone(), body, is_base64)
78    }
79
80    /// Continues route's request with optional overrides.
81    ///
82    /// ```js
83    /// await page.route('**/*', (route, request) => {
84    ///  // Override headers
85    ///  const headers = {
86    ///    ...request.headers(),
87    ///    foo: 'bar', // set "foo" header
88    ///    origin: undefined, // remove "origin" header
89    ///  };
90    ///  route.continue({headers});
91    /// });
92    /// ```
93    pub async fn continue_builder(&self) -> ContinueBuilder<'_, '_, '_> {
94        ContinueBuilder::new(self.inner.clone())
95    }
96}
97
98pub struct FulfillBuilder<'a, 'b> {
99    inner: Weak<Impl>,
100    args: FulfillArgs<'a, 'b>,
101}
102
103impl<'a, 'b> FulfillBuilder<'a, 'b> {
104    pub(crate) fn new(inner: Weak<Impl>, body: &'a str, is_base64: bool) -> Self {
105        let args = FulfillArgs::new(body, is_base64);
106        Self { inner, args }
107    }
108
109    pub async fn fulfill(self) -> Result<(), Arc<Error>> {
110        let Self { inner, args } = self;
111        upgrade(&inner)?.fulfill(args).await
112    }
113
114    /// Response headers. Header values will be converted to a string.
115    pub fn headers<T>(mut self, x: T) -> Self
116    where
117        T: IntoIterator<Item = (String, String)>,
118    {
119        self.args.headers = Some(x.into_iter().map(Header::from).collect());
120        self
121    }
122
123    setter! {
124        /// If set, equals to setting `Content-Type` response header.
125        content_type: Option<&'b str>,
126        /// Response status code, defaults to `200`.
127        status: Option<i32>
128    }
129
130    pub fn clear_headers(mut self) -> Self {
131        self.args.headers = None;
132        self
133    }
134}
135
136pub struct ContinueBuilder<'a, 'b, 'c> {
137    inner: Weak<Impl>,
138    args: ContinueArgs<'a, 'b, 'c>,
139}
140
141impl<'a, 'b, 'c> ContinueBuilder<'a, 'b, 'c> {
142    pub(crate) fn new(inner: Weak<Impl>) -> Self {
143        let args = ContinueArgs::default();
144        Self { inner, args }
145    }
146
147    pub async fn r#continue(self) -> Result<(), Arc<Error>> {
148        let Self { inner, args } = self;
149        upgrade(&inner)?.r#continue(args).await
150    }
151
152    /// If set changes the request HTTP headers. Header values will be converted to a string.
153    pub fn headers<T>(mut self, x: T) -> Self
154    where
155        T: IntoIterator<Item = (String, String)>,
156    {
157        self.args.headers = Some(x.into_iter().map(Header::from).collect());
158        self
159    }
160
161    setter! {
162        /// If set changes the request method (e.g. GET or POST)
163        method: Option<&'b str>,
164        /// If set changes the post data of request
165        post_data: Option<&'c str>,
166        /// If set changes the request URL. New URL must have same protocol as original one.
167        url: Option<&'a str>
168    }
169
170    pub fn clear_headers(mut self) -> Self {
171        self.args.headers = None;
172        self
173    }
174}
175
176#[cfg(test)]
177mod tests {
178    use super::*;
179    use crate::imp::route::Route as Impl;
180    use std::sync::Weak;
181
182    #[test]
183    fn test_fulfill_builder_headers_and_setters() {
184        let inner: Weak<Impl> = Weak::new();
185        let b = FulfillBuilder::new(inner, "hello", false)
186            .headers(vec![("foo".to_string(), "bar".to_string())])
187            .content_type("text/plain")
188            .status(404);
189
190        // Verify args captured by builder (fields with pub(crate) visibility)
191        assert_eq!(b.args.content_type, Some("text/plain"));
192        assert_eq!(b.args.status, Some(404));
193        assert_eq!(
194            b.args.headers.clone().unwrap(),
195            vec![Header::from(("foo".to_string(), "bar".to_string()))]
196        );
197
198        let b = b.clear_headers();
199        assert!(b.args.headers.is_none());
200    }
201
202    #[test]
203    fn test_continue_builder_headers_and_setters() {
204        let inner: Weak<Impl> = Weak::new();
205        let b = ContinueBuilder::new(inner)
206            .headers(vec![("x".to_string(), "y".to_string())])
207            .method("POST")
208            .post_data("payload")
209            .url("https://example.org");
210
211        assert_eq!(b.args.method, Some("POST"));
212        assert_eq!(b.args.post_data, Some("payload"));
213        assert_eq!(b.args.url, Some("https://example.org"));
214        assert_eq!(
215            b.args.headers.clone().unwrap(),
216            vec![Header::from(("x".to_string(), "y".to_string()))]
217        );
218
219        let b = b.clear_headers();
220        assert!(b.args.headers.is_none());
221    }
222
223    #[tokio::test]
224    async fn test_fulfill_and_continue_fail_on_no_inner() {
225        let inner: Weak<Impl> = Weak::new();
226        let b = FulfillBuilder::new(inner.clone(), "body", false);
227        // fulfill should error because there's no inner implementation (upgrade fails)
228        assert!(b.fulfill().await.is_err());
229
230        let cb = ContinueBuilder::new(inner);
231        assert!(cb.r#continue().await.is_err());
232    }
233
234    #[tokio::test]
235    async fn test_route_abort_and_request_on_no_inner() {
236        let route = Route::new(Weak::new());
237
238        // abort should return Err because inner cannot be upgraded
239        assert!(route.abort(None).await.is_err());
240
241        // request returns a Request wrapper even when inner can't be upgraded; calling url() should fail
242        let request = route.request();
243        assert!(request.url().is_err());
244    }
245}