Skip to main content

playwright_rs/protocol/
route.rs

1// Route protocol object
2//
3// Represents a route handler for network interception.
4// Routes are created when page.route() or context.route() matches a request.
5//
6// See: https://playwright.dev/docs/api/class-route
7
8use crate::error::Result;
9use crate::protocol::Request;
10use crate::protocol::api_request_context::{APIRequestContext, InnerFetchOptions};
11use crate::server::channel_owner::{ChannelOwner, ChannelOwnerImpl, ParentOrConnection};
12use serde_json::{Value, json};
13use std::any::Any;
14use std::sync::atomic::{AtomicBool, Ordering};
15use std::sync::{Arc, Mutex};
16
17/// Route represents a network route handler.
18///
19/// Routes allow intercepting, aborting, continuing, or fulfilling network requests.
20///
21/// See: <https://playwright.dev/docs/api/class-route>
22#[derive(Clone)]
23pub struct Route {
24    base: ChannelOwnerImpl,
25    /// Tracks whether the route has been fully handled (abort/continue/fulfill).
26    /// Used by fallback() to signal that handler chaining should continue.
27    handled: Arc<AtomicBool>,
28    /// APIRequestContext for performing fetch operations.
29    /// Set by the route event dispatcher (Page or BrowserContext).
30    api_request_context: Arc<Mutex<Option<APIRequestContext>>>,
31}
32
33impl Route {
34    /// Creates a new Route from protocol initialization
35    ///
36    /// This is called by the object factory when the server sends a `__create__` message
37    /// for a Route object.
38    pub fn new(
39        parent: Arc<dyn ChannelOwner>,
40        type_name: String,
41        guid: Arc<str>,
42        initializer: Value,
43    ) -> Result<Self> {
44        let base = ChannelOwnerImpl::new(
45            ParentOrConnection::Parent(parent.clone()),
46            type_name,
47            guid,
48            initializer,
49        );
50
51        Ok(Self {
52            base,
53            handled: Arc::new(AtomicBool::new(false)),
54            api_request_context: Arc::new(Mutex::new(None)),
55        })
56    }
57
58    /// Returns whether this route was fully handled by a handler.
59    ///
60    /// Returns `false` if the handler called `fallback()`, indicating the next
61    /// matching handler should be tried.
62    pub(crate) fn was_handled(&self) -> bool {
63        self.handled.load(Ordering::SeqCst)
64    }
65
66    /// Sets the APIRequestContext for this route, enabling `fetch()`.
67    ///
68    /// Called by the route event dispatcher (Page or BrowserContext) when
69    /// dispatching the route to a handler.
70    pub(crate) fn set_api_request_context(&self, ctx: APIRequestContext) {
71        *self.api_request_context.lock().unwrap() = Some(ctx);
72    }
73
74    /// Returns the request that is being routed.
75    ///
76    /// See: <https://playwright.dev/docs/api/class-route#route-request>
77    pub fn request(&self) -> Request {
78        // The Route's parent is the Request object
79        // Try to downcast the parent to Request
80        if let Some(parent) = self.parent() {
81            if let Some(request) = parent.as_any().downcast_ref::<Request>() {
82                return request.clone();
83            }
84        }
85
86        // Fallback: Create a stub Request from initializer data
87        // This should rarely happen in practice
88        let request_data = self
89            .initializer()
90            .get("request")
91            .cloned()
92            .unwrap_or_else(|| {
93                serde_json::json!({
94                    "url": "",
95                    "method": "GET"
96                })
97            });
98
99        let parent = self
100            .parent()
101            .unwrap_or_else(|| Arc::new(self.clone()) as Arc<dyn ChannelOwner>);
102
103        let request_guid = request_data
104            .get("guid")
105            .and_then(|v| v.as_str())
106            .unwrap_or("request-stub");
107
108        Request::new(
109            parent,
110            "Request".to_string(),
111            Arc::from(request_guid),
112            request_data,
113        )
114        .unwrap()
115    }
116
117    /// Aborts the route's request.
118    ///
119    /// # Arguments
120    ///
121    /// * `error_code` - Optional error code (default: "failed")
122    ///
123    /// Available error codes:
124    /// - "aborted" - User-initiated cancellation
125    /// - "accessdenied" - Permission denied
126    /// - "addressunreachable" - Host unreachable
127    /// - "blockedbyclient" - Client blocked request
128    /// - "connectionaborted", "connectionclosed", "connectionfailed", "connectionrefused", "connectionreset"
129    /// - "internetdisconnected"
130    /// - "namenotresolved"
131    /// - "timedout"
132    /// - "failed" - Generic error (default)
133    ///
134    /// See: <https://playwright.dev/docs/api/class-route#route-abort>
135    pub async fn abort(&self, error_code: Option<&str>) -> Result<()> {
136        self.handled.store(true, Ordering::SeqCst);
137        let params = json!({
138            "errorCode": error_code.unwrap_or("failed")
139        });
140
141        self.channel()
142            .send::<_, serde_json::Value>("abort", params)
143            .await
144            .map(|_| ())
145    }
146
147    /// Continues the route's request with optional modifications.
148    ///
149    /// This is a final action — no other route handlers will run for this request.
150    /// Use `fallback()` instead if you want subsequent handlers to have a chance.
151    ///
152    /// # Arguments
153    ///
154    /// * `overrides` - Optional modifications to apply to the request
155    ///
156    /// See: <https://playwright.dev/docs/api/class-route#route-continue>
157    pub async fn continue_(&self, overrides: Option<ContinueOptions>) -> Result<()> {
158        self.handled.store(true, Ordering::SeqCst);
159        self.continue_internal(overrides, false).await
160    }
161
162    /// Continues the route's request, allowing subsequent handlers to run.
163    ///
164    /// Unlike `continue_()`, `fallback()` yields to the next matching handler in the
165    /// chain before the request reaches the network. This enables middleware-like
166    /// handler composition where multiple handlers can inspect and modify a request.
167    ///
168    /// # Arguments
169    ///
170    /// * `overrides` - Optional modifications to apply to the request
171    ///
172    /// See: <https://playwright.dev/docs/api/class-route#route-fallback>
173    pub async fn fallback(&self, overrides: Option<ContinueOptions>) -> Result<()> {
174        // Don't set handled — signals to the dispatcher to try the next handler
175        self.continue_internal(overrides, true).await
176    }
177
178    /// Internal implementation shared by continue_() and fallback()
179    async fn continue_internal(
180        &self,
181        overrides: Option<ContinueOptions>,
182        is_fallback: bool,
183    ) -> Result<()> {
184        let mut params = json!({
185            "isFallback": is_fallback
186        });
187
188        // Add overrides if provided
189        if let Some(opts) = overrides {
190            // Add headers
191            if let Some(headers) = opts.headers {
192                let headers_array: Vec<serde_json::Value> = headers
193                    .into_iter()
194                    .map(|(name, value)| json!({"name": name, "value": value}))
195                    .collect();
196                params["headers"] = json!(headers_array);
197            }
198
199            // Add method
200            if let Some(method) = opts.method {
201                params["method"] = json!(method);
202            }
203
204            // Add postData (string or binary)
205            if let Some(post_data) = opts.post_data {
206                params["postData"] = json!(post_data);
207            } else if let Some(post_data_bytes) = opts.post_data_bytes {
208                use base64::Engine;
209                let encoded = base64::engine::general_purpose::STANDARD.encode(&post_data_bytes);
210                params["postData"] = json!(encoded);
211            }
212
213            // Add URL
214            if let Some(url) = opts.url {
215                params["url"] = json!(url);
216            }
217        }
218
219        self.channel()
220            .send::<_, serde_json::Value>("continue", params)
221            .await
222            .map(|_| ())
223    }
224
225    /// Fulfills the route's request with a custom response.
226    ///
227    /// # Arguments
228    ///
229    /// * `options` - Response configuration (status, headers, body, etc.)
230    ///
231    /// # Known Limitations
232    ///
233    /// **Response body fulfillment is not supported in Playwright 1.49.0 - 1.58.2.**
234    ///
235    /// The route.fulfill() method can successfully send requests for status codes and headers,
236    /// but the response body is not transmitted to the browser JavaScript layer. This applies
237    /// to ALL request types (main document, fetch, XHR, etc.), not just document navigation.
238    ///
239    /// **Investigation Findings:**
240    /// - The protocol message is correctly formatted and accepted by the Playwright server
241    /// - The body bytes are present in the fulfill() call
242    /// - The Playwright server creates a Response object
243    /// - But the body content does not reach the browser's fetch/network API
244    ///
245    /// This appears to be a limitation or bug in the Playwright server implementation.
246    /// Tested with versions 1.49.0, 1.56.1, and 1.58.2 (latest as of 2026-03-22).
247    ///
248    /// TODO: Periodically test with newer Playwright versions for fix.
249    /// Workaround: Mock responses at the HTTP server level rather than using network interception,
250    /// or wait for a newer Playwright version that supports response body fulfillment.
251    ///
252    /// See: <https://playwright.dev/docs/api/class-route#route-fulfill>
253    pub async fn fulfill(&self, options: Option<FulfillOptions>) -> Result<()> {
254        self.handled.store(true, Ordering::SeqCst);
255        let opts = options.unwrap_or_default();
256
257        // Build the response object for the protocol
258        let mut response = json!({
259            "status": opts.status.unwrap_or(200),
260            "headers": []
261        });
262
263        // Set headers - prepare them BEFORE adding body
264        let mut headers_map = opts.headers.unwrap_or_default();
265
266        // Set body if provided, and prepare headers
267        let body_bytes = opts.body.as_ref();
268        if let Some(body) = body_bytes {
269            let content_length = body.len().to_string();
270            headers_map.insert("content-length".to_string(), content_length);
271        }
272
273        // Add Content-Type if specified
274        if let Some(ref ct) = opts.content_type {
275            headers_map.insert("content-type".to_string(), ct.clone());
276        }
277
278        // Convert headers to protocol format
279        let headers_array: Vec<Value> = headers_map
280            .into_iter()
281            .map(|(name, value)| json!({"name": name, "value": value}))
282            .collect();
283        response["headers"] = json!(headers_array);
284
285        // Set body LAST, after all other fields
286        if let Some(body) = body_bytes {
287            // Send as plain string for text (UTF-8), base64 for binary
288            if let Ok(body_str) = std::str::from_utf8(body) {
289                response["body"] = json!(body_str);
290            } else {
291                use base64::Engine;
292                let encoded = base64::engine::general_purpose::STANDARD.encode(body);
293                response["body"] = json!(encoded);
294                response["isBase64"] = json!(true);
295            }
296        }
297
298        let params = json!({
299            "response": response
300        });
301
302        self.channel()
303            .send::<_, serde_json::Value>("fulfill", params)
304            .await
305            .map(|_| ())
306    }
307
308    /// Performs the request and fetches result without fulfilling it, so that the
309    /// response can be modified and then fulfilled.
310    ///
311    /// Delegates to `APIRequestContext.inner_fetch()` using the request's URL and
312    /// any provided overrides.
313    ///
314    /// # Arguments
315    ///
316    /// * `options` - Optional overrides for the fetch request
317    ///
318    /// See: <https://playwright.dev/docs/api/class-route#route-fetch>
319    pub async fn fetch(&self, options: Option<FetchOptions>) -> Result<FetchResponse> {
320        self.handled.store(true, Ordering::SeqCst);
321
322        let api_ctx = self
323            .api_request_context
324            .lock()
325            .unwrap()
326            .clone()
327            .ok_or_else(|| {
328                crate::error::Error::ProtocolError(
329                    "No APIRequestContext available for route.fetch(). \
330                     This can happen if the route was not dispatched through \
331                     a BrowserContext with an associated request context."
332                        .to_string(),
333                )
334            })?;
335
336        let request = self.request();
337        let opts = options.unwrap_or_default();
338
339        // Use the original request URL unless overridden
340        let url = opts.url.unwrap_or_else(|| request.url().to_string());
341
342        let inner_opts = InnerFetchOptions {
343            method: opts.method.or_else(|| Some(request.method().to_string())),
344            headers: opts.headers,
345            post_data: opts.post_data,
346            post_data_bytes: opts.post_data_bytes,
347            max_redirects: opts.max_redirects,
348            max_retries: opts.max_retries,
349            timeout: opts.timeout,
350        };
351
352        api_ctx.inner_fetch(&url, Some(inner_opts)).await
353    }
354}
355
356/// Checks if a URL matches a glob pattern.
357///
358/// Supports standard glob patterns:
359/// - `*` matches any characters except `/`
360/// - `**` matches any characters including `/`
361/// - `?` matches a single character
362pub(crate) fn matches_pattern(pattern: &str, url: &str) -> bool {
363    use glob::Pattern;
364
365    match Pattern::new(pattern) {
366        Ok(glob_pattern) => glob_pattern.matches(url),
367        Err(_) => {
368            // If pattern is invalid, fall back to exact string match
369            pattern == url
370        }
371    }
372}
373
374/// Behavior when removing route handlers via `unroute_all()`.
375///
376/// See: <https://playwright.dev/docs/api/class-page#page-unroute-all>
377#[derive(Debug, Clone, Copy, PartialEq, Eq)]
378pub enum UnrouteBehavior {
379    /// Wait for in-flight handlers to complete before removing
380    Wait,
381    /// Stop handlers and ignore any errors they throw
382    IgnoreErrors,
383    /// Default behavior (does not wait, does not ignore errors)
384    Default,
385}
386
387/// Response from `route.fetch()`, allowing inspection and modification before fulfillment.
388///
389/// See: <https://playwright.dev/docs/api/class-apiresponse>
390#[derive(Debug, Clone)]
391pub struct FetchResponse {
392    /// HTTP status code
393    pub status: u16,
394    /// HTTP status text
395    pub status_text: String,
396    /// Response headers as name-value pairs
397    pub headers: Vec<(String, String)>,
398    /// Response body as bytes
399    pub body: Vec<u8>,
400}
401
402impl FetchResponse {
403    /// Returns the HTTP status code
404    pub fn status(&self) -> u16 {
405        self.status
406    }
407
408    /// Returns the status text
409    pub fn status_text(&self) -> &str {
410        &self.status_text
411    }
412
413    /// Returns response headers
414    pub fn headers(&self) -> &[(String, String)] {
415        &self.headers
416    }
417
418    /// Returns the response body as bytes
419    pub fn body(&self) -> &[u8] {
420        &self.body
421    }
422
423    /// Returns the response body as text
424    pub fn text(&self) -> Result<String> {
425        String::from_utf8(self.body.clone()).map_err(|e| {
426            crate::error::Error::ProtocolError(format!("Response body is not valid UTF-8: {}", e))
427        })
428    }
429
430    /// Returns the response body parsed as JSON
431    pub fn json<T: serde::de::DeserializeOwned>(&self) -> Result<T> {
432        serde_json::from_slice(&self.body).map_err(|e| {
433            crate::error::Error::ProtocolError(format!("Failed to parse response JSON: {}", e))
434        })
435    }
436
437    /// Returns true if status is in 200-299 range
438    pub fn ok(&self) -> bool {
439        (200..300).contains(&self.status)
440    }
441}
442
443/// Options for continuing a request with modifications.
444///
445/// Allows modifying headers, method, post data, and URL when continuing a route.
446/// Used by both `continue_()` and `fallback()`.
447///
448/// See: <https://playwright.dev/docs/api/class-route#route-continue>
449#[derive(Debug, Clone, Default)]
450pub struct ContinueOptions {
451    /// Modified request headers
452    pub headers: Option<std::collections::HashMap<String, String>>,
453    /// Modified request method (GET, POST, etc.)
454    pub method: Option<String>,
455    /// Modified POST data as string
456    pub post_data: Option<String>,
457    /// Modified POST data as bytes
458    pub post_data_bytes: Option<Vec<u8>>,
459    /// Modified request URL (must have same protocol)
460    pub url: Option<String>,
461}
462
463impl ContinueOptions {
464    /// Creates a new builder for ContinueOptions
465    pub fn builder() -> ContinueOptionsBuilder {
466        ContinueOptionsBuilder::default()
467    }
468}
469
470/// Builder for ContinueOptions
471#[derive(Debug, Clone, Default)]
472pub struct ContinueOptionsBuilder {
473    headers: Option<std::collections::HashMap<String, String>>,
474    method: Option<String>,
475    post_data: Option<String>,
476    post_data_bytes: Option<Vec<u8>>,
477    url: Option<String>,
478}
479
480impl ContinueOptionsBuilder {
481    /// Sets the request headers
482    pub fn headers(mut self, headers: std::collections::HashMap<String, String>) -> Self {
483        self.headers = Some(headers);
484        self
485    }
486
487    /// Sets the request method
488    pub fn method(mut self, method: String) -> Self {
489        self.method = Some(method);
490        self
491    }
492
493    /// Sets the POST data as a string
494    pub fn post_data(mut self, post_data: String) -> Self {
495        self.post_data = Some(post_data);
496        self.post_data_bytes = None; // Clear bytes if setting string
497        self
498    }
499
500    /// Sets the POST data as bytes
501    pub fn post_data_bytes(mut self, post_data_bytes: Vec<u8>) -> Self {
502        self.post_data_bytes = Some(post_data_bytes);
503        self.post_data = None; // Clear string if setting bytes
504        self
505    }
506
507    /// Sets the request URL (must have same protocol as original)
508    pub fn url(mut self, url: String) -> Self {
509        self.url = Some(url);
510        self
511    }
512
513    /// Builds the ContinueOptions
514    pub fn build(self) -> ContinueOptions {
515        ContinueOptions {
516            headers: self.headers,
517            method: self.method,
518            post_data: self.post_data,
519            post_data_bytes: self.post_data_bytes,
520            url: self.url,
521        }
522    }
523}
524
525/// Options for fulfilling a route with a custom response.
526///
527/// See: <https://playwright.dev/docs/api/class-route#route-fulfill>
528#[derive(Debug, Clone, Default)]
529pub struct FulfillOptions {
530    /// HTTP status code (default: 200)
531    pub status: Option<u16>,
532    /// Response headers
533    pub headers: Option<std::collections::HashMap<String, String>>,
534    /// Response body as bytes
535    pub body: Option<Vec<u8>>,
536    /// Content-Type header value
537    pub content_type: Option<String>,
538}
539
540impl FulfillOptions {
541    /// Creates a new FulfillOptions builder
542    pub fn builder() -> FulfillOptionsBuilder {
543        FulfillOptionsBuilder::default()
544    }
545}
546
547/// Builder for FulfillOptions
548#[derive(Debug, Clone, Default)]
549pub struct FulfillOptionsBuilder {
550    status: Option<u16>,
551    headers: Option<std::collections::HashMap<String, String>>,
552    body: Option<Vec<u8>>,
553    content_type: Option<String>,
554}
555
556impl FulfillOptionsBuilder {
557    /// Sets the HTTP status code
558    pub fn status(mut self, status: u16) -> Self {
559        self.status = Some(status);
560        self
561    }
562
563    /// Sets the response headers
564    pub fn headers(mut self, headers: std::collections::HashMap<String, String>) -> Self {
565        self.headers = Some(headers);
566        self
567    }
568
569    /// Sets the response body from bytes
570    pub fn body(mut self, body: Vec<u8>) -> Self {
571        self.body = Some(body);
572        self
573    }
574
575    /// Sets the response body from a string
576    pub fn body_string(mut self, body: impl Into<String>) -> Self {
577        self.body = Some(body.into().into_bytes());
578        self
579    }
580
581    /// Sets the response body from JSON (automatically sets content-type to application/json)
582    pub fn json(mut self, value: &impl serde::Serialize) -> Result<Self> {
583        let json_str = serde_json::to_string(value).map_err(|e| {
584            crate::error::Error::ProtocolError(format!("JSON serialization failed: {}", e))
585        })?;
586        self.body = Some(json_str.into_bytes());
587        self.content_type = Some("application/json".to_string());
588        Ok(self)
589    }
590
591    /// Sets the Content-Type header
592    pub fn content_type(mut self, content_type: impl Into<String>) -> Self {
593        self.content_type = Some(content_type.into());
594        self
595    }
596
597    /// Builds the FulfillOptions
598    pub fn build(self) -> FulfillOptions {
599        FulfillOptions {
600            status: self.status,
601            headers: self.headers,
602            body: self.body,
603            content_type: self.content_type,
604        }
605    }
606}
607
608/// Options for fetching a route's request.
609///
610/// See: <https://playwright.dev/docs/api/class-route#route-fetch>
611#[derive(Debug, Clone, Default)]
612pub struct FetchOptions {
613    /// Modified request headers
614    pub headers: Option<std::collections::HashMap<String, String>>,
615    /// Modified request method (GET, POST, etc.)
616    pub method: Option<String>,
617    /// Modified POST data as string
618    pub post_data: Option<String>,
619    /// Modified POST data as bytes
620    pub post_data_bytes: Option<Vec<u8>>,
621    /// Modified request URL
622    pub url: Option<String>,
623    /// Maximum number of redirects to follow (default: 20)
624    pub max_redirects: Option<u32>,
625    /// Maximum number of retries (default: 0)
626    pub max_retries: Option<u32>,
627    /// Request timeout in milliseconds
628    pub timeout: Option<f64>,
629}
630
631impl FetchOptions {
632    /// Creates a new FetchOptions builder
633    pub fn builder() -> FetchOptionsBuilder {
634        FetchOptionsBuilder::default()
635    }
636}
637
638/// Builder for FetchOptions
639#[derive(Debug, Clone, Default)]
640pub struct FetchOptionsBuilder {
641    headers: Option<std::collections::HashMap<String, String>>,
642    method: Option<String>,
643    post_data: Option<String>,
644    post_data_bytes: Option<Vec<u8>>,
645    url: Option<String>,
646    max_redirects: Option<u32>,
647    max_retries: Option<u32>,
648    timeout: Option<f64>,
649}
650
651impl FetchOptionsBuilder {
652    /// Sets the request headers
653    pub fn headers(mut self, headers: std::collections::HashMap<String, String>) -> Self {
654        self.headers = Some(headers);
655        self
656    }
657
658    /// Sets the request method
659    pub fn method(mut self, method: String) -> Self {
660        self.method = Some(method);
661        self
662    }
663
664    /// Sets the POST data as a string
665    pub fn post_data(mut self, post_data: String) -> Self {
666        self.post_data = Some(post_data);
667        self.post_data_bytes = None;
668        self
669    }
670
671    /// Sets the POST data as bytes
672    pub fn post_data_bytes(mut self, post_data_bytes: Vec<u8>) -> Self {
673        self.post_data_bytes = Some(post_data_bytes);
674        self.post_data = None;
675        self
676    }
677
678    /// Sets the request URL
679    pub fn url(mut self, url: String) -> Self {
680        self.url = Some(url);
681        self
682    }
683
684    /// Sets the maximum number of redirects to follow
685    pub fn max_redirects(mut self, n: u32) -> Self {
686        self.max_redirects = Some(n);
687        self
688    }
689
690    /// Sets the maximum number of retries
691    pub fn max_retries(mut self, n: u32) -> Self {
692        self.max_retries = Some(n);
693        self
694    }
695
696    /// Sets the request timeout in milliseconds
697    pub fn timeout(mut self, ms: f64) -> Self {
698        self.timeout = Some(ms);
699        self
700    }
701
702    /// Builds the FetchOptions
703    pub fn build(self) -> FetchOptions {
704        FetchOptions {
705            headers: self.headers,
706            method: self.method,
707            post_data: self.post_data,
708            post_data_bytes: self.post_data_bytes,
709            url: self.url,
710            max_redirects: self.max_redirects,
711            max_retries: self.max_retries,
712            timeout: self.timeout,
713        }
714    }
715}
716
717impl ChannelOwner for Route {
718    fn guid(&self) -> &str {
719        self.base.guid()
720    }
721
722    fn type_name(&self) -> &str {
723        self.base.type_name()
724    }
725
726    fn parent(&self) -> Option<Arc<dyn ChannelOwner>> {
727        self.base.parent()
728    }
729
730    fn connection(&self) -> Arc<dyn crate::server::connection::ConnectionLike> {
731        self.base.connection()
732    }
733
734    fn initializer(&self) -> &Value {
735        self.base.initializer()
736    }
737
738    fn channel(&self) -> &crate::server::channel::Channel {
739        self.base.channel()
740    }
741
742    fn dispose(&self, reason: crate::server::channel_owner::DisposeReason) {
743        self.base.dispose(reason)
744    }
745
746    fn adopt(&self, child: Arc<dyn ChannelOwner>) {
747        self.base.adopt(child)
748    }
749
750    fn add_child(&self, guid: Arc<str>, child: Arc<dyn ChannelOwner>) {
751        self.base.add_child(guid, child)
752    }
753
754    fn remove_child(&self, guid: &str) {
755        self.base.remove_child(guid)
756    }
757
758    fn on_event(&self, _method: &str, _params: Value) {
759        // Route events will be handled in future phases
760    }
761
762    fn was_collected(&self) -> bool {
763        self.base.was_collected()
764    }
765
766    fn as_any(&self) -> &dyn Any {
767        self
768    }
769}
770
771impl std::fmt::Debug for Route {
772    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
773        f.debug_struct("Route")
774            .field("guid", &self.guid())
775            .field("request", &self.request().guid())
776            .finish()
777    }
778}