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