Skip to main content

playwright_rs/protocol/
api_request_context.rs

1// Copyright 2026 Paul Adamson
2// Licensed under the Apache License, Version 2.0
3//
4// APIRequestContext protocol object
5//
6// Enables performing HTTP requests without a browser, and is also used
7// by Route.fetch() to perform the actual network request before modification.
8//
9// See: https://playwright.dev/docs/api/class-apirequestcontext
10
11use crate::error::Result;
12use crate::protocol::route::FetchOptions;
13use crate::protocol::route::FetchResponse;
14use crate::server::channel::Channel;
15use crate::server::channel_owner::{
16    ChannelOwner, ChannelOwnerImpl, DisposeReason, ParentOrConnection,
17};
18use crate::server::connection::ConnectionLike;
19use serde::de::DeserializeOwned;
20use serde_json::{Value, json};
21use std::any::Any;
22use std::collections::HashMap;
23use std::sync::Arc;
24
25/// APIRequestContext provides methods for making HTTP requests.
26///
27/// This is the Playwright protocol object that performs actual HTTP operations.
28/// It is created automatically for each BrowserContext and can be accessed
29/// via `BrowserContext::request()`.
30///
31/// Used internally by `Route::fetch()` to perform the actual network request.
32///
33/// See: <https://playwright.dev/docs/api/class-apirequestcontext>
34#[derive(Clone)]
35pub struct APIRequestContext {
36    base: ChannelOwnerImpl,
37}
38
39impl APIRequestContext {
40    pub fn new(
41        parent: ParentOrConnection,
42        type_name: String,
43        guid: Arc<str>,
44        initializer: Value,
45    ) -> Result<Self> {
46        Ok(Self {
47            base: ChannelOwnerImpl::new(parent, type_name, guid, initializer),
48        })
49    }
50
51    /// Sends a GET request.
52    ///
53    /// See: <https://playwright.dev/docs/api/class-apirequestcontext#api-request-context-get>
54    pub async fn get(
55        &self,
56        url: &str,
57        options: impl Into<Option<FetchOptions>>,
58    ) -> Result<APIResponse> {
59        let options = options.into();
60        let mut opts = options.unwrap_or_default();
61        opts.method = Some("GET".to_string());
62        self.fetch(url, Some(opts)).await
63    }
64
65    /// Sends a POST request.
66    ///
67    /// See: <https://playwright.dev/docs/api/class-apirequestcontext#api-request-context-post>
68    pub async fn post(
69        &self,
70        url: &str,
71        options: impl Into<Option<FetchOptions>>,
72    ) -> Result<APIResponse> {
73        let options = options.into();
74        let mut opts = options.unwrap_or_default();
75        opts.method = Some("POST".to_string());
76        self.fetch(url, Some(opts)).await
77    }
78
79    /// Sends a PUT request.
80    ///
81    /// See: <https://playwright.dev/docs/api/class-apirequestcontext#api-request-context-put>
82    pub async fn put(
83        &self,
84        url: &str,
85        options: impl Into<Option<FetchOptions>>,
86    ) -> Result<APIResponse> {
87        let options = options.into();
88        let mut opts = options.unwrap_or_default();
89        opts.method = Some("PUT".to_string());
90        self.fetch(url, Some(opts)).await
91    }
92
93    /// Sends a DELETE request.
94    ///
95    /// See: <https://playwright.dev/docs/api/class-apirequestcontext#api-request-context-delete>
96    pub async fn delete(
97        &self,
98        url: &str,
99        options: impl Into<Option<FetchOptions>>,
100    ) -> Result<APIResponse> {
101        let options = options.into();
102        let mut opts = options.unwrap_or_default();
103        opts.method = Some("DELETE".to_string());
104        self.fetch(url, Some(opts)).await
105    }
106
107    /// Sends a PATCH request.
108    ///
109    /// See: <https://playwright.dev/docs/api/class-apirequestcontext#api-request-context-patch>
110    pub async fn patch(
111        &self,
112        url: &str,
113        options: impl Into<Option<FetchOptions>>,
114    ) -> Result<APIResponse> {
115        let options = options.into();
116        let mut opts = options.unwrap_or_default();
117        opts.method = Some("PATCH".to_string());
118        self.fetch(url, Some(opts)).await
119    }
120
121    /// Sends a HEAD request.
122    ///
123    /// See: <https://playwright.dev/docs/api/class-apirequestcontext#api-request-context-head>
124    pub async fn head(
125        &self,
126        url: &str,
127        options: impl Into<Option<FetchOptions>>,
128    ) -> Result<APIResponse> {
129        let options = options.into();
130        let mut opts = options.unwrap_or_default();
131        opts.method = Some("HEAD".to_string());
132        self.fetch(url, Some(opts)).await
133    }
134
135    /// Sends a fetch request with the given options, returning an `APIResponse`.
136    ///
137    /// This is the public-facing fetch method that returns a lazy `APIResponse`.
138    /// The response body is not fetched until `body()`, `text()`, or `json()` is called.
139    ///
140    /// See: <https://playwright.dev/docs/api/class-apirequestcontext#api-request-context-fetch>
141    pub async fn fetch(
142        &self,
143        url: &str,
144        options: impl Into<Option<FetchOptions>>,
145    ) -> Result<APIResponse> {
146        let options = options.into();
147        let opts = options.unwrap_or_default();
148
149        let mut params = json!({
150            "url": url,
151            "timeout": opts.timeout.unwrap_or(crate::DEFAULT_TIMEOUT_MS)
152        });
153
154        if let Some(method) = opts.method {
155            params["method"] = json!(method);
156        }
157        if let Some(headers) = opts.headers {
158            params["headers"] = Value::Array(crate::protocol::route_params::header_array(headers));
159        }
160        if let Some(encoded) =
161            crate::protocol::route_params::post_data_param(opts.post_data, opts.post_data_bytes)
162        {
163            params["postData"] = Value::String(encoded);
164        }
165        if let Some(max_redirects) = opts.max_redirects {
166            params["maxRedirects"] = json!(max_redirects);
167        }
168        if let Some(max_retries) = opts.max_retries {
169            params["maxRetries"] = json!(max_retries);
170        }
171
172        #[derive(serde::Deserialize)]
173        struct FetchResult {
174            response: ApiResponseData,
175        }
176
177        #[derive(serde::Deserialize)]
178        #[serde(rename_all = "camelCase")]
179        struct ApiResponseData {
180            fetch_uid: String,
181            url: String,
182            status: u16,
183            status_text: String,
184            headers: Vec<HeaderEntry>,
185            #[serde(default)]
186            security_details: Option<crate::protocol::response::SecurityDetails>,
187            #[serde(default)]
188            server_addr: Option<crate::protocol::response::RemoteAddr>,
189            #[serde(default)]
190            timing: Option<serde_json::Value>,
191            #[serde(default)]
192            response_end_timing: Option<f64>,
193        }
194
195        #[derive(serde::Deserialize)]
196        struct HeaderEntry {
197            name: String,
198            value: String,
199        }
200
201        let result: FetchResult = self.base.channel().send("fetch", params).await?;
202
203        let headers: HashMap<String, String> = result
204            .response
205            .headers
206            .into_iter()
207            .map(|h| (h.name, h.value))
208            .collect();
209
210        Ok(APIResponse {
211            context: self.clone(),
212            url: result.response.url,
213            status: result.response.status,
214            status_text: result.response.status_text,
215            headers,
216            fetch_uid: result.response.fetch_uid,
217            security_details: result.response.security_details,
218            server_addr: result.response.server_addr,
219            timing: result.response.timing.clone().and_then(|mut t| {
220                crate::protocol::ResourceTiming::merge_response_end(
221                    &mut t,
222                    result.response.response_end_timing,
223                );
224                crate::protocol::ResourceTiming::from_protocol(&t)
225            }),
226            response_end_timing: result.response.response_end_timing,
227        })
228    }
229
230    /// Adds cookies to this request context's cookie jar. For the context
231    /// a `BrowserContext` or `Page` exposes through `request()`, that is the
232    /// browser context's jar, so the cookies reach its pages too.
233    ///
234    /// Each cookie needs a `url`, or a `domain` and `path`.
235    ///
236    /// # Errors
237    ///
238    /// Returns error if a cookie has neither a `url` nor a `domain` and
239    /// `path`, or the context has been disposed.
240    ///
241    /// See: <https://playwright.dev/docs/api/class-apirequestcontext#api-request-context-add-cookies>
242    #[tracing::instrument(level = "debug", skip_all, fields(count = cookies.len()))]
243    pub async fn add_cookies(&self, cookies: &[crate::protocol::Cookie]) -> Result<()> {
244        self.base
245            .channel()
246            .send_no_result("addCookies", json!({ "cookies": cookies }))
247            .await
248    }
249
250    /// Returns the cookies in this request context's jar, filtered to those
251    /// that apply to `urls` when given.
252    ///
253    /// # Errors
254    ///
255    /// Returns error if the context has been disposed.
256    ///
257    /// See: <https://playwright.dev/docs/api/class-apirequestcontext#api-request-context-cookies>
258    #[tracing::instrument(level = "debug", skip_all, fields(count = tracing::field::Empty))]
259    pub async fn cookies(&self, urls: Option<&[&str]>) -> Result<Vec<crate::protocol::Cookie>> {
260        #[derive(serde::Deserialize)]
261        struct CookiesResponse {
262            cookies: Vec<crate::protocol::Cookie>,
263        }
264        let response: CookiesResponse = self
265            .base
266            .channel()
267            .send("cookies", json!({ "urls": urls.unwrap_or(&[]) }))
268            .await?;
269        tracing::Span::current().record("count", response.cookies.len());
270        Ok(response.cookies)
271    }
272
273    /// Removes cookies from this request context's jar: all of them, or those
274    /// matching every filter set in `options`.
275    ///
276    /// # Errors
277    ///
278    /// Returns error if the context has been disposed.
279    ///
280    /// See: <https://playwright.dev/docs/api/class-apirequestcontext#api-request-context-clear-cookies>
281    #[tracing::instrument(level = "debug", skip_all)]
282    pub async fn clear_cookies(
283        &self,
284        options: impl Into<Option<crate::protocol::ClearCookiesOptions>>,
285    ) -> Result<()> {
286        self.base
287            .channel()
288            .send_no_result("clearCookies", options.into().unwrap_or_default())
289            .await
290    }
291
292    /// Disposes this `APIRequestContext`, freeing server resources.
293    ///
294    /// After calling `dispose()`, the context cannot be used for further requests.
295    ///
296    /// See: <https://playwright.dev/docs/api/class-apirequestcontext#api-request-context-dispose>
297    pub async fn dispose(&self) -> Result<()> {
298        self.base
299            .channel()
300            .send_no_result("dispose", json!({}))
301            .await
302    }
303
304    /// Performs an HTTP fetch request and returns the response.
305    ///
306    /// This is the internal method used by `Route::fetch()`. It sends the request
307    /// via the Playwright server and returns the response with headers and body.
308    ///
309    /// # Arguments
310    ///
311    /// * `url` - The URL to fetch
312    /// * `options` - Optional parameters to customize the request
313    ///
314    /// See: <https://playwright.dev/docs/api/class-apirequestcontext#api-request-context-fetch>
315    pub(crate) async fn inner_fetch(
316        &self,
317        url: &str,
318        options: Option<InnerFetchOptions>,
319    ) -> Result<FetchResponse> {
320        let opts = options.unwrap_or_default();
321
322        let mut params = json!({
323            "url": url,
324            "timeout": opts.timeout.unwrap_or(crate::DEFAULT_TIMEOUT_MS)
325        });
326
327        if let Some(method) = opts.method {
328            params["method"] = json!(method);
329        }
330        if let Some(headers) = opts.headers {
331            params["headers"] = Value::Array(crate::protocol::route_params::header_array(headers));
332        }
333        if let Some(encoded) =
334            crate::protocol::route_params::post_data_param(opts.post_data, opts.post_data_bytes)
335        {
336            params["postData"] = Value::String(encoded);
337        }
338        if let Some(max_redirects) = opts.max_redirects {
339            params["maxRedirects"] = json!(max_redirects);
340        }
341        if let Some(max_retries) = opts.max_retries {
342            params["maxRetries"] = json!(max_retries);
343        }
344
345        // Call the fetch command on APIRequestContext channel
346        #[derive(serde::Deserialize)]
347        struct FetchResult {
348            response: ApiResponseData,
349        }
350
351        #[derive(serde::Deserialize)]
352        #[serde(rename_all = "camelCase")]
353        struct ApiResponseData {
354            fetch_uid: String,
355            #[allow(dead_code)]
356            url: String,
357            status: u16,
358            status_text: String,
359            headers: Vec<HeaderEntry>,
360        }
361
362        #[derive(serde::Deserialize)]
363        struct HeaderEntry {
364            name: String,
365            value: String,
366        }
367
368        let result: FetchResult = self.base.channel().send("fetch", params).await?;
369
370        // Now fetch the response body using fetchResponseBody
371        let body = self.fetch_response_body(&result.response.fetch_uid).await?;
372
373        // Dispose the API response to free server resources
374        let _ = self.dispose_api_response(&result.response.fetch_uid).await;
375
376        Ok(FetchResponse {
377            status: result.response.status,
378            status_text: result.response.status_text,
379            headers: result
380                .response
381                .headers
382                .into_iter()
383                .map(|h| (h.name, h.value))
384                .collect(),
385            body,
386        })
387    }
388
389    /// Fetches the response body for a given fetch UID.
390    async fn fetch_response_body(&self, fetch_uid: &str) -> Result<Vec<u8>> {
391        #[derive(serde::Deserialize)]
392        struct BodyResult {
393            #[serde(default)]
394            binary: Option<String>,
395        }
396
397        let result: BodyResult = self
398            .base
399            .channel()
400            .send("fetchResponseBody", json!({ "fetchUid": fetch_uid }))
401            .await?;
402
403        match result.binary {
404            Some(encoded) if !encoded.is_empty() => {
405                use base64::Engine;
406                base64::engine::general_purpose::STANDARD
407                    .decode(&encoded)
408                    .map_err(|e| {
409                        crate::error::Error::ProtocolError(format!(
410                            "Failed to decode response body: {}",
411                            e
412                        ))
413                    })
414            }
415            _ => Ok(vec![]),
416        }
417    }
418
419    /// Disposes an API response to free server resources.
420    async fn dispose_api_response(&self, fetch_uid: &str) -> Result<()> {
421        self.base
422            .channel()
423            .send_no_result("disposeAPIResponse", json!({ "fetchUid": fetch_uid }))
424            .await
425    }
426}
427
428/// A lazy HTTP response returned by `APIRequestContext` methods.
429///
430/// Unlike [`crate::protocol::route::FetchResponse`] (which eagerly fetches the body),
431/// `APIResponse` holds a `fetch_uid` and fetches the body on demand.
432///
433/// See: <https://playwright.dev/docs/api/class-apiresponse>
434#[derive(Clone)]
435pub struct APIResponse {
436    context: APIRequestContext,
437    url: String,
438    status: u16,
439    status_text: String,
440    headers: HashMap<String, String>,
441    fetch_uid: String,
442    security_details: Option<crate::protocol::response::SecurityDetails>,
443    server_addr: Option<crate::protocol::response::RemoteAddr>,
444    timing: Option<crate::protocol::ResourceTiming>,
445    response_end_timing: Option<f64>,
446}
447
448impl APIResponse {
449    /// Returns the URL of the response.
450    pub fn url(&self) -> &str {
451        &self.url
452    }
453
454    /// Returns the HTTP status code.
455    pub fn status(&self) -> u16 {
456        self.status
457    }
458
459    /// Returns the HTTP status text (e.g., "OK", "Not Found").
460    pub fn status_text(&self) -> &str {
461        &self.status_text
462    }
463
464    /// Returns `true` if the status code is in the 200–299 range.
465    pub fn ok(&self) -> bool {
466        (200..300).contains(&self.status)
467    }
468
469    /// Returns the response headers as a `HashMap<String, String>`.
470    pub fn headers(&self) -> &HashMap<String, String> {
471        &self.headers
472    }
473
474    /// Returns resource timing for this response, or `None` if the server did
475    /// not report any.
476    ///
477    /// Mirrors the browser-side [`Request::timing`](crate::protocol::Request::timing),
478    /// and reuses the same type, so the phases mean the same thing on both
479    /// sides: milliseconds relative to `start_time`, with `-1` for a phase
480    /// that was not reached.
481    ///
482    /// Unlike the browser-side accessor this is synchronous and always
483    /// available, because the driver sends it in the response itself rather
484    /// than after a later event.
485    ///
486    /// See: <https://playwright.dev/docs/api/class-apiresponse#api-response-timing>
487    pub fn timing(&self) -> Option<&crate::protocol::ResourceTiming> {
488        self.timing.as_ref()
489    }
490
491    /// Returns the time at which the response finished, in milliseconds
492    /// relative to the timing's `start_time`, or `None` if unavailable.
493    pub fn response_end_timing(&self) -> Option<f64> {
494        self.response_end_timing
495    }
496
497    /// Returns TLS/SSL security details for HTTPS responses, or `None` for
498    /// plain HTTP. Mirrors the browser-side `Response::security_details`.
499    ///
500    /// See: <https://playwright.dev/docs/api/class-apiresponse#api-response-security-details>
501    pub fn security_details(&self) -> Option<&crate::protocol::response::SecurityDetails> {
502        self.security_details.as_ref()
503    }
504
505    /// Returns the server's resolved IP address and port for this response,
506    /// or `None` if unavailable.
507    ///
508    /// See: <https://playwright.dev/docs/api/class-apiresponse#api-response-server-addr>
509    pub fn server_addr(&self) -> Option<&crate::protocol::response::RemoteAddr> {
510        self.server_addr.as_ref()
511    }
512
513    /// Fetches and returns the response body as bytes.
514    ///
515    /// See: <https://playwright.dev/docs/api/class-apiresponse#api-response-body>
516    pub async fn body(&self) -> Result<Vec<u8>> {
517        self.context.fetch_response_body(&self.fetch_uid).await
518    }
519
520    /// Fetches and returns the response body as a UTF-8 string.
521    ///
522    /// See: <https://playwright.dev/docs/api/class-apiresponse#api-response-text>
523    pub async fn text(&self) -> Result<String> {
524        let bytes = self.body().await?;
525        String::from_utf8(bytes).map_err(|e| {
526            crate::error::Error::ProtocolError(format!("Response body is not valid UTF-8: {}", e))
527        })
528    }
529
530    /// Fetches the response body and deserializes it as JSON.
531    ///
532    /// See: <https://playwright.dev/docs/api/class-apiresponse#api-response-json>
533    pub async fn json<T: DeserializeOwned>(&self) -> Result<T> {
534        let bytes = self.body().await?;
535        serde_json::from_slice(&bytes).map_err(|e| {
536            crate::error::Error::ProtocolError(format!("Failed to parse response JSON: {}", e))
537        })
538    }
539
540    /// Disposes this response, freeing server-side resources for the response body.
541    ///
542    /// See: <https://playwright.dev/docs/api/class-apiresponse#api-response-dispose>
543    pub async fn dispose(&self) -> Result<()> {
544        self.context.dispose_api_response(&self.fetch_uid).await
545    }
546}
547
548impl std::fmt::Debug for APIResponse {
549    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
550        f.debug_struct("APIResponse")
551            .field("url", &self.url)
552            .field("status", &self.status)
553            .field("status_text", &self.status_text)
554            .finish()
555    }
556}
557
558/// Options for creating a new `APIRequestContext` via `APIRequest::new_context()`.
559///
560/// See: <https://playwright.dev/docs/api/class-apirequest#api-request-new-context>
561#[derive(Debug, Clone, Default)]
562#[non_exhaustive]
563pub struct APIRequestContextOptions {
564    /// Base URL for all relative requests made with this context.
565    pub base_url: Option<String>,
566    /// Extra HTTP headers to be sent with every request.
567    pub extra_http_headers: Option<HashMap<String, String>>,
568    /// Whether to ignore HTTPS errors when making requests.
569    pub ignore_https_errors: Option<bool>,
570    /// User agent string to send with requests.
571    pub user_agent: Option<String>,
572    /// Default timeout for fetch operations in milliseconds.
573    pub timeout: Option<f64>,
574    /// Credentials for HTTP authentication, matched per request origin.
575    ///
576    /// This is the one context that honors
577    /// [`HttpCredentialsSend::Always`](crate::protocol::HttpCredentialsSend).
578    pub http_credentials: Option<Vec<crate::protocol::HttpCredentials>>,
579}
580
581impl APIRequestContextOptions {
582    /// Base URL prepended to relative request paths.
583    pub fn base_url(mut self, base_url: impl Into<String>) -> Self {
584        self.base_url = Some(base_url.into());
585        self
586    }
587    /// Extra HTTP headers sent with every request.
588    pub fn extra_http_headers(mut self, headers: HashMap<String, String>) -> Self {
589        self.extra_http_headers = Some(headers);
590        self
591    }
592    /// Ignore HTTPS certificate errors.
593    pub fn ignore_https_errors(mut self, ignore: bool) -> Self {
594        self.ignore_https_errors = Some(ignore);
595        self
596    }
597    /// Credentials for HTTP authentication, matched per request origin.
598    pub fn http_credentials(mut self, credentials: Vec<crate::protocol::HttpCredentials>) -> Self {
599        self.http_credentials = Some(credentials);
600        self
601    }
602    /// User-Agent header value.
603    pub fn user_agent(mut self, user_agent: impl Into<String>) -> Self {
604        self.user_agent = Some(user_agent.into());
605        self
606    }
607    /// Maximum time in milliseconds for each request.
608    pub fn timeout(mut self, timeout: f64) -> Self {
609        self.timeout = Some(timeout);
610        self
611    }
612}
613
614/// Factory for creating standalone `APIRequestContext` instances.
615///
616/// Obtained via `playwright.request()`. Use `new_context()` to create a context
617/// for making HTTP requests outside of a browser page.
618///
619/// `APIRequest` intentionally holds only the channel and connection reference,
620/// NOT a `Playwright` clone. Holding a `Playwright` clone would trigger the
621/// server shutdown Drop impl when the temporary `APIRequest` is dropped.
622///
623/// # Example
624///
625/// ```no_run
626/// use playwright_rs::protocol::Playwright;
627///
628/// #[tokio::main]
629/// async fn main() -> Result<(), Box<dyn std::error::Error>> {
630///     let playwright = Playwright::launch().await?;
631///
632///     let ctx = playwright.request().new_context(None).await?;
633///     let response = ctx.get("https://example.com/api/data", None).await?;
634///     assert!(response.ok());
635///     let body = response.text().await?;
636///
637///     ctx.dispose().await?;
638///     playwright.shutdown().await?;
639///     Ok(())
640/// }
641/// ```
642///
643/// See: <https://playwright.dev/docs/api/class-apirequest>
644pub struct APIRequest {
645    channel: crate::server::channel::Channel,
646    connection: Arc<dyn ConnectionLike>,
647}
648
649impl APIRequest {
650    pub(crate) fn new(
651        channel: crate::server::channel::Channel,
652        connection: Arc<dyn ConnectionLike>,
653    ) -> Self {
654        Self {
655            channel,
656            connection,
657        }
658    }
659
660    /// Creates a new `APIRequestContext` for making HTTP requests.
661    ///
662    /// # Arguments
663    ///
664    /// * `options` — Optional configuration for the new context
665    ///
666    /// See: <https://playwright.dev/docs/api/class-apirequest#api-request-new-context>
667    pub async fn new_context(
668        &self,
669        options: impl Into<Option<APIRequestContextOptions>>,
670    ) -> Result<APIRequestContext> {
671        use crate::server::connection::ConnectionExt;
672
673        let options = options.into();
674        let mut params = json!({});
675
676        if let Some(opts) = options {
677            if let Some(base_url) = opts.base_url {
678                params["baseURL"] = json!(base_url);
679            }
680            if let Some(headers) = opts.extra_http_headers {
681                params["extraHTTPHeaders"] =
682                    Value::Array(crate::protocol::route_params::header_array(headers));
683            }
684            if let Some(ignore) = opts.ignore_https_errors {
685                params["ignoreHTTPSErrors"] = json!(ignore);
686            }
687            if let Some(ua) = opts.user_agent {
688                params["userAgent"] = json!(ua);
689            }
690            if let Some(credentials) = opts.http_credentials {
691                params["httpCredentials"] = json!(credentials);
692            }
693            if let Some(timeout) = opts.timeout {
694                params["timeout"] = json!(timeout);
695            }
696        }
697
698        #[derive(serde::Deserialize)]
699        struct NewRequestResult {
700            request: GuidRef,
701        }
702
703        #[derive(serde::Deserialize)]
704        struct GuidRef {
705            guid: String,
706        }
707
708        let result: NewRequestResult = self.channel.send("newRequest", params).await?;
709
710        self.connection
711            .get_typed::<APIRequestContext>(&result.request.guid)
712            .await
713    }
714}
715
716impl std::fmt::Debug for APIRequest {
717    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
718        f.debug_struct("APIRequest").finish()
719    }
720}
721
722/// Options for APIRequestContext.inner_fetch()
723#[derive(Debug, Clone, Default)]
724pub(crate) struct InnerFetchOptions {
725    pub method: Option<String>,
726    pub headers: Option<std::collections::HashMap<String, String>>,
727    pub post_data: Option<String>,
728    pub post_data_bytes: Option<Vec<u8>>,
729    pub max_redirects: Option<u32>,
730    pub max_retries: Option<u32>,
731    pub timeout: Option<f64>,
732}
733
734impl ChannelOwner for APIRequestContext {
735    fn guid(&self) -> &str {
736        self.base.guid()
737    }
738
739    fn type_name(&self) -> &str {
740        self.base.type_name()
741    }
742
743    fn parent(&self) -> Option<Arc<dyn ChannelOwner>> {
744        self.base.parent()
745    }
746
747    fn connection(&self) -> Arc<dyn ConnectionLike> {
748        self.base.connection()
749    }
750
751    fn initializer(&self) -> &Value {
752        self.base.initializer()
753    }
754
755    fn channel(&self) -> &Channel {
756        self.base.channel()
757    }
758
759    fn dispose(&self, reason: DisposeReason) {
760        self.base.dispose(reason)
761    }
762
763    fn adopt(&self, child: Arc<dyn ChannelOwner>) {
764        self.base.adopt(child)
765    }
766
767    fn add_child(&self, guid: Arc<str>, child: Arc<dyn ChannelOwner>) {
768        self.base.add_child(guid, child)
769    }
770
771    fn remove_child(&self, guid: &str) {
772        self.base.remove_child(guid)
773    }
774
775    fn on_event(&self, method: &str, params: Value) {
776        self.base.on_event(method, params)
777    }
778
779    fn was_collected(&self) -> bool {
780        self.base.was_collected()
781    }
782
783    fn as_any(&self) -> &dyn Any {
784        self
785    }
786}
787
788impl std::fmt::Debug for APIRequestContext {
789    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
790        f.debug_struct("APIRequestContext")
791            .field("guid", &self.guid())
792            .finish()
793    }
794}