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