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        .expect("stub Request construction cannot fail")
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.60.0.**
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, 1.58.2, 1.59.1, and 1.60.0 (latest as of 2026-05-16).
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)]
376#[non_exhaustive]
377pub enum UnrouteBehavior {
378    /// Wait for in-flight handlers to complete before removing
379    Wait,
380    /// Stop handlers and ignore any errors they throw
381    IgnoreErrors,
382    /// Default behavior (does not wait, does not ignore errors)
383    Default,
384}
385
386/// Response from `route.fetch()`, allowing inspection and modification before fulfillment.
387///
388/// See: <https://playwright.dev/docs/api/class-apiresponse>
389#[derive(Debug, Clone)]
390#[non_exhaustive]
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)]
450#[non_exhaustive]
451pub struct ContinueOptions {
452    /// Modified request headers
453    pub headers: Option<std::collections::HashMap<String, String>>,
454    /// Modified request method (GET, POST, etc.)
455    pub method: Option<String>,
456    /// Modified POST data as string
457    pub post_data: Option<String>,
458    /// Modified POST data as bytes
459    pub post_data_bytes: Option<Vec<u8>>,
460    /// Modified request URL (must have same protocol)
461    pub url: Option<String>,
462}
463
464impl ContinueOptions {
465    /// Creates a new builder for ContinueOptions
466    pub fn builder() -> ContinueOptionsBuilder {
467        ContinueOptionsBuilder::default()
468    }
469}
470
471/// Builder for ContinueOptions
472#[derive(Debug, Clone, Default)]
473pub struct ContinueOptionsBuilder {
474    headers: Option<std::collections::HashMap<String, String>>,
475    method: Option<String>,
476    post_data: Option<String>,
477    post_data_bytes: Option<Vec<u8>>,
478    url: Option<String>,
479}
480
481impl ContinueOptionsBuilder {
482    /// Sets the request headers
483    pub fn headers(mut self, headers: std::collections::HashMap<String, String>) -> Self {
484        self.headers = Some(headers);
485        self
486    }
487
488    /// Sets the request method
489    pub fn method(mut self, method: String) -> Self {
490        self.method = Some(method);
491        self
492    }
493
494    /// Sets the POST data as a string
495    pub fn post_data(mut self, post_data: String) -> Self {
496        self.post_data = Some(post_data);
497        self.post_data_bytes = None; // Clear bytes if setting string
498        self
499    }
500
501    /// Sets the POST data as bytes
502    pub fn post_data_bytes(mut self, post_data_bytes: Vec<u8>) -> Self {
503        self.post_data_bytes = Some(post_data_bytes);
504        self.post_data = None; // Clear string if setting bytes
505        self
506    }
507
508    /// Sets the request URL (must have same protocol as original)
509    pub fn url(mut self, url: String) -> Self {
510        self.url = Some(url);
511        self
512    }
513
514    /// Builds the ContinueOptions
515    pub fn build(self) -> ContinueOptions {
516        ContinueOptions {
517            headers: self.headers,
518            method: self.method,
519            post_data: self.post_data,
520            post_data_bytes: self.post_data_bytes,
521            url: self.url,
522        }
523    }
524}
525
526/// Options for fulfilling a route with a custom response.
527///
528/// See: <https://playwright.dev/docs/api/class-route#route-fulfill>
529#[derive(Debug, Clone, Default)]
530#[non_exhaustive]
531pub struct FulfillOptions {
532    /// HTTP status code (default: 200)
533    pub status: Option<u16>,
534    /// Response headers
535    pub headers: Option<std::collections::HashMap<String, String>>,
536    /// Response body as bytes
537    pub body: Option<Vec<u8>>,
538    /// Content-Type header value
539    pub content_type: Option<String>,
540}
541
542impl FulfillOptions {
543    /// Creates a new FulfillOptions builder
544    pub fn builder() -> FulfillOptionsBuilder {
545        FulfillOptionsBuilder::default()
546    }
547}
548
549/// Builder for FulfillOptions
550#[derive(Debug, Clone, Default)]
551pub struct FulfillOptionsBuilder {
552    status: Option<u16>,
553    headers: Option<std::collections::HashMap<String, String>>,
554    body: Option<Vec<u8>>,
555    content_type: Option<String>,
556}
557
558impl FulfillOptionsBuilder {
559    /// Sets the HTTP status code
560    pub fn status(mut self, status: u16) -> Self {
561        self.status = Some(status);
562        self
563    }
564
565    /// Sets the response headers
566    pub fn headers(mut self, headers: std::collections::HashMap<String, String>) -> Self {
567        self.headers = Some(headers);
568        self
569    }
570
571    /// Sets the response body from bytes
572    pub fn body(mut self, body: Vec<u8>) -> Self {
573        self.body = Some(body);
574        self
575    }
576
577    /// Sets the response body from a string
578    pub fn body_string(mut self, body: impl Into<String>) -> Self {
579        self.body = Some(body.into().into_bytes());
580        self
581    }
582
583    /// Sets the response body from JSON (automatically sets content-type to application/json)
584    pub fn json(mut self, value: &impl serde::Serialize) -> Result<Self> {
585        let json_str = serde_json::to_string(value).map_err(|e| {
586            crate::error::Error::ProtocolError(format!("JSON serialization failed: {}", e))
587        })?;
588        self.body = Some(json_str.into_bytes());
589        self.content_type = Some("application/json".to_string());
590        Ok(self)
591    }
592
593    /// Sets the Content-Type header
594    pub fn content_type(mut self, content_type: impl Into<String>) -> Self {
595        self.content_type = Some(content_type.into());
596        self
597    }
598
599    /// Builds the FulfillOptions
600    pub fn build(self) -> FulfillOptions {
601        FulfillOptions {
602            status: self.status,
603            headers: self.headers,
604            body: self.body,
605            content_type: self.content_type,
606        }
607    }
608}
609
610/// Options for fetching a route's request.
611///
612/// See: <https://playwright.dev/docs/api/class-route#route-fetch>
613#[derive(Debug, Clone, Default)]
614#[non_exhaustive]
615pub struct FetchOptions {
616    /// Modified request headers
617    pub headers: Option<std::collections::HashMap<String, String>>,
618    /// Modified request method (GET, POST, etc.)
619    pub method: Option<String>,
620    /// Modified POST data as string
621    pub post_data: Option<String>,
622    /// Modified POST data as bytes
623    pub post_data_bytes: Option<Vec<u8>>,
624    /// Modified request URL
625    pub url: Option<String>,
626    /// Maximum number of redirects to follow (default: 20)
627    pub max_redirects: Option<u32>,
628    /// Maximum number of retries (default: 0)
629    pub max_retries: Option<u32>,
630    /// Request timeout in milliseconds
631    pub timeout: Option<f64>,
632}
633
634impl FetchOptions {
635    /// Creates a new FetchOptions builder
636    pub fn builder() -> FetchOptionsBuilder {
637        FetchOptionsBuilder::default()
638    }
639}
640
641/// Builder for FetchOptions
642#[derive(Debug, Clone, Default)]
643pub struct FetchOptionsBuilder {
644    headers: Option<std::collections::HashMap<String, String>>,
645    method: Option<String>,
646    post_data: Option<String>,
647    post_data_bytes: Option<Vec<u8>>,
648    url: Option<String>,
649    max_redirects: Option<u32>,
650    max_retries: Option<u32>,
651    timeout: Option<f64>,
652}
653
654impl FetchOptionsBuilder {
655    /// Sets the request headers
656    pub fn headers(mut self, headers: std::collections::HashMap<String, String>) -> Self {
657        self.headers = Some(headers);
658        self
659    }
660
661    /// Sets the request method
662    pub fn method(mut self, method: String) -> Self {
663        self.method = Some(method);
664        self
665    }
666
667    /// Sets the POST data as a string
668    pub fn post_data(mut self, post_data: String) -> Self {
669        self.post_data = Some(post_data);
670        self.post_data_bytes = None;
671        self
672    }
673
674    /// Sets the POST data as bytes
675    pub fn post_data_bytes(mut self, post_data_bytes: Vec<u8>) -> Self {
676        self.post_data_bytes = Some(post_data_bytes);
677        self.post_data = None;
678        self
679    }
680
681    /// Sets the request URL
682    pub fn url(mut self, url: String) -> Self {
683        self.url = Some(url);
684        self
685    }
686
687    /// Sets the maximum number of redirects to follow
688    pub fn max_redirects(mut self, n: u32) -> Self {
689        self.max_redirects = Some(n);
690        self
691    }
692
693    /// Sets the maximum number of retries
694    pub fn max_retries(mut self, n: u32) -> Self {
695        self.max_retries = Some(n);
696        self
697    }
698
699    /// Sets the request timeout in milliseconds
700    pub fn timeout(mut self, ms: f64) -> Self {
701        self.timeout = Some(ms);
702        self
703    }
704
705    /// Builds the FetchOptions
706    pub fn build(self) -> FetchOptions {
707        FetchOptions {
708            headers: self.headers,
709            method: self.method,
710            post_data: self.post_data,
711            post_data_bytes: self.post_data_bytes,
712            url: self.url,
713            max_redirects: self.max_redirects,
714            max_retries: self.max_retries,
715            timeout: self.timeout,
716        }
717    }
718}
719
720impl ChannelOwner for Route {
721    fn guid(&self) -> &str {
722        self.base.guid()
723    }
724
725    fn type_name(&self) -> &str {
726        self.base.type_name()
727    }
728
729    fn parent(&self) -> Option<Arc<dyn ChannelOwner>> {
730        self.base.parent()
731    }
732
733    fn connection(&self) -> Arc<dyn crate::server::connection::ConnectionLike> {
734        self.base.connection()
735    }
736
737    fn initializer(&self) -> &Value {
738        self.base.initializer()
739    }
740
741    fn channel(&self) -> &crate::server::channel::Channel {
742        self.base.channel()
743    }
744
745    fn dispose(&self, reason: crate::server::channel_owner::DisposeReason) {
746        self.base.dispose(reason)
747    }
748
749    fn adopt(&self, child: Arc<dyn ChannelOwner>) {
750        self.base.adopt(child)
751    }
752
753    fn add_child(&self, guid: Arc<str>, child: Arc<dyn ChannelOwner>) {
754        self.base.add_child(guid, child)
755    }
756
757    fn remove_child(&self, guid: &str) {
758        self.base.remove_child(guid)
759    }
760
761    fn on_event(&self, _method: &str, _params: Value) {
762        // Route events will be handled in future phases
763    }
764
765    fn was_collected(&self) -> bool {
766        self.base.was_collected()
767    }
768
769    fn as_any(&self) -> &dyn Any {
770        self
771    }
772}
773
774impl std::fmt::Debug for Route {
775    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
776        f.debug_struct("Route")
777            .field("guid", &self.guid())
778            .field("request", &self.request().guid())
779            .finish()
780    }
781}