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            let headers_array: Vec<Value> = headers
159                .into_iter()
160                .map(|(name, value)| json!({"name": name, "value": value}))
161                .collect();
162            params["headers"] = json!(headers_array);
163        }
164        if let Some(post_data) = opts.post_data {
165            use base64::Engine;
166            let encoded = base64::engine::general_purpose::STANDARD.encode(post_data.as_bytes());
167            params["postData"] = json!(encoded);
168        } else if let Some(post_data_bytes) = opts.post_data_bytes {
169            use base64::Engine;
170            let encoded = base64::engine::general_purpose::STANDARD.encode(&post_data_bytes);
171            params["postData"] = json!(encoded);
172        }
173        if let Some(max_redirects) = opts.max_redirects {
174            params["maxRedirects"] = json!(max_redirects);
175        }
176        if let Some(max_retries) = opts.max_retries {
177            params["maxRetries"] = json!(max_retries);
178        }
179
180        #[derive(serde::Deserialize)]
181        struct FetchResult {
182            response: ApiResponseData,
183        }
184
185        #[derive(serde::Deserialize)]
186        #[serde(rename_all = "camelCase")]
187        struct ApiResponseData {
188            fetch_uid: String,
189            url: String,
190            status: u16,
191            status_text: String,
192            headers: Vec<HeaderEntry>,
193            #[serde(default)]
194            security_details: Option<crate::protocol::response::SecurityDetails>,
195            #[serde(default)]
196            server_addr: Option<crate::protocol::response::RemoteAddr>,
197            #[serde(default)]
198            timing: Option<serde_json::Value>,
199            #[serde(default)]
200            response_end_timing: Option<f64>,
201        }
202
203        #[derive(serde::Deserialize)]
204        struct HeaderEntry {
205            name: String,
206            value: String,
207        }
208
209        let result: FetchResult = self.base.channel().send("fetch", params).await?;
210
211        let headers: HashMap<String, String> = result
212            .response
213            .headers
214            .into_iter()
215            .map(|h| (h.name, h.value))
216            .collect();
217
218        Ok(APIResponse {
219            context: self.clone(),
220            url: result.response.url,
221            status: result.response.status,
222            status_text: result.response.status_text,
223            headers,
224            fetch_uid: result.response.fetch_uid,
225            security_details: result.response.security_details,
226            server_addr: result.response.server_addr,
227            timing: result.response.timing.clone().and_then(|mut t| {
228                crate::protocol::ResourceTiming::merge_response_end(
229                    &mut t,
230                    result.response.response_end_timing,
231                );
232                crate::protocol::ResourceTiming::from_protocol(&t)
233            }),
234            response_end_timing: result.response.response_end_timing,
235        })
236    }
237
238    /// Disposes this `APIRequestContext`, freeing server resources.
239    ///
240    /// After calling `dispose()`, the context cannot be used for further requests.
241    ///
242    /// See: <https://playwright.dev/docs/api/class-apirequestcontext#api-request-context-dispose>
243    pub async fn dispose(&self) -> Result<()> {
244        self.base
245            .channel()
246            .send_no_result("dispose", json!({}))
247            .await
248    }
249
250    /// Performs an HTTP fetch request and returns the response.
251    ///
252    /// This is the internal method used by `Route::fetch()`. It sends the request
253    /// via the Playwright server and returns the response with headers and body.
254    ///
255    /// # Arguments
256    ///
257    /// * `url` - The URL to fetch
258    /// * `options` - Optional parameters to customize the request
259    ///
260    /// See: <https://playwright.dev/docs/api/class-apirequestcontext#api-request-context-fetch>
261    pub(crate) async fn inner_fetch(
262        &self,
263        url: &str,
264        options: Option<InnerFetchOptions>,
265    ) -> Result<FetchResponse> {
266        let opts = options.unwrap_or_default();
267
268        let mut params = json!({
269            "url": url,
270            "timeout": opts.timeout.unwrap_or(crate::DEFAULT_TIMEOUT_MS)
271        });
272
273        if let Some(method) = opts.method {
274            params["method"] = json!(method);
275        }
276        if let Some(headers) = opts.headers {
277            let headers_array: Vec<Value> = headers
278                .into_iter()
279                .map(|(name, value)| json!({"name": name, "value": value}))
280                .collect();
281            params["headers"] = json!(headers_array);
282        }
283        if let Some(post_data) = opts.post_data {
284            use base64::Engine;
285            let encoded = base64::engine::general_purpose::STANDARD.encode(post_data.as_bytes());
286            params["postData"] = json!(encoded);
287        }
288        if let Some(post_data_bytes) = opts.post_data_bytes {
289            use base64::Engine;
290            let encoded = base64::engine::general_purpose::STANDARD.encode(&post_data_bytes);
291            params["postData"] = json!(encoded);
292        }
293        if let Some(max_redirects) = opts.max_redirects {
294            params["maxRedirects"] = json!(max_redirects);
295        }
296        if let Some(max_retries) = opts.max_retries {
297            params["maxRetries"] = json!(max_retries);
298        }
299
300        // Call the fetch command on APIRequestContext channel
301        #[derive(serde::Deserialize)]
302        struct FetchResult {
303            response: ApiResponseData,
304        }
305
306        #[derive(serde::Deserialize)]
307        #[serde(rename_all = "camelCase")]
308        struct ApiResponseData {
309            fetch_uid: String,
310            #[allow(dead_code)]
311            url: String,
312            status: u16,
313            status_text: String,
314            headers: Vec<HeaderEntry>,
315        }
316
317        #[derive(serde::Deserialize)]
318        struct HeaderEntry {
319            name: String,
320            value: String,
321        }
322
323        let result: FetchResult = self.base.channel().send("fetch", params).await?;
324
325        // Now fetch the response body using fetchResponseBody
326        let body = self.fetch_response_body(&result.response.fetch_uid).await?;
327
328        // Dispose the API response to free server resources
329        let _ = self.dispose_api_response(&result.response.fetch_uid).await;
330
331        Ok(FetchResponse {
332            status: result.response.status,
333            status_text: result.response.status_text,
334            headers: result
335                .response
336                .headers
337                .into_iter()
338                .map(|h| (h.name, h.value))
339                .collect(),
340            body,
341        })
342    }
343
344    /// Fetches the response body for a given fetch UID.
345    async fn fetch_response_body(&self, fetch_uid: &str) -> Result<Vec<u8>> {
346        #[derive(serde::Deserialize)]
347        struct BodyResult {
348            #[serde(default)]
349            binary: Option<String>,
350        }
351
352        let result: BodyResult = self
353            .base
354            .channel()
355            .send("fetchResponseBody", json!({ "fetchUid": fetch_uid }))
356            .await?;
357
358        match result.binary {
359            Some(encoded) if !encoded.is_empty() => {
360                use base64::Engine;
361                base64::engine::general_purpose::STANDARD
362                    .decode(&encoded)
363                    .map_err(|e| {
364                        crate::error::Error::ProtocolError(format!(
365                            "Failed to decode response body: {}",
366                            e
367                        ))
368                    })
369            }
370            _ => Ok(vec![]),
371        }
372    }
373
374    /// Disposes an API response to free server resources.
375    async fn dispose_api_response(&self, fetch_uid: &str) -> Result<()> {
376        self.base
377            .channel()
378            .send_no_result("disposeAPIResponse", json!({ "fetchUid": fetch_uid }))
379            .await
380    }
381}
382
383/// A lazy HTTP response returned by `APIRequestContext` methods.
384///
385/// Unlike [`crate::protocol::route::FetchResponse`] (which eagerly fetches the body),
386/// `APIResponse` holds a `fetch_uid` and fetches the body on demand.
387///
388/// See: <https://playwright.dev/docs/api/class-apiresponse>
389#[derive(Clone)]
390pub struct APIResponse {
391    context: APIRequestContext,
392    url: String,
393    status: u16,
394    status_text: String,
395    headers: HashMap<String, String>,
396    fetch_uid: String,
397    security_details: Option<crate::protocol::response::SecurityDetails>,
398    server_addr: Option<crate::protocol::response::RemoteAddr>,
399    timing: Option<crate::protocol::ResourceTiming>,
400    response_end_timing: Option<f64>,
401}
402
403impl APIResponse {
404    /// Returns the URL of the response.
405    pub fn url(&self) -> &str {
406        &self.url
407    }
408
409    /// Returns the HTTP status code.
410    pub fn status(&self) -> u16 {
411        self.status
412    }
413
414    /// Returns the HTTP status text (e.g., "OK", "Not Found").
415    pub fn status_text(&self) -> &str {
416        &self.status_text
417    }
418
419    /// Returns `true` if the status code is in the 200–299 range.
420    pub fn ok(&self) -> bool {
421        (200..300).contains(&self.status)
422    }
423
424    /// Returns the response headers as a `HashMap<String, String>`.
425    pub fn headers(&self) -> &HashMap<String, String> {
426        &self.headers
427    }
428
429    /// Returns resource timing for this response, or `None` if the server did
430    /// not report any.
431    ///
432    /// Mirrors the browser-side [`Request::timing`](crate::protocol::Request::timing),
433    /// and reuses the same type, so the phases mean the same thing on both
434    /// sides: milliseconds relative to `start_time`, with `-1` for a phase
435    /// that was not reached.
436    ///
437    /// Unlike the browser-side accessor this is synchronous and always
438    /// available, because the driver sends it in the response itself rather
439    /// than after a later event.
440    ///
441    /// See: <https://playwright.dev/docs/api/class-apiresponse#api-response-timing>
442    pub fn timing(&self) -> Option<&crate::protocol::ResourceTiming> {
443        self.timing.as_ref()
444    }
445
446    /// Returns the time at which the response finished, in milliseconds
447    /// relative to the timing's `start_time`, or `None` if unavailable.
448    pub fn response_end_timing(&self) -> Option<f64> {
449        self.response_end_timing
450    }
451
452    /// Returns TLS/SSL security details for HTTPS responses, or `None` for
453    /// plain HTTP. Mirrors the browser-side `Response::security_details`.
454    ///
455    /// See: <https://playwright.dev/docs/api/class-apiresponse#api-response-security-details>
456    pub fn security_details(&self) -> Option<&crate::protocol::response::SecurityDetails> {
457        self.security_details.as_ref()
458    }
459
460    /// Returns the server's resolved IP address and port for this response,
461    /// or `None` if unavailable.
462    ///
463    /// See: <https://playwright.dev/docs/api/class-apiresponse#api-response-server-addr>
464    pub fn server_addr(&self) -> Option<&crate::protocol::response::RemoteAddr> {
465        self.server_addr.as_ref()
466    }
467
468    /// Fetches and returns the response body as bytes.
469    ///
470    /// See: <https://playwright.dev/docs/api/class-apiresponse#api-response-body>
471    pub async fn body(&self) -> Result<Vec<u8>> {
472        self.context.fetch_response_body(&self.fetch_uid).await
473    }
474
475    /// Fetches and returns the response body as a UTF-8 string.
476    ///
477    /// See: <https://playwright.dev/docs/api/class-apiresponse#api-response-text>
478    pub async fn text(&self) -> Result<String> {
479        let bytes = self.body().await?;
480        String::from_utf8(bytes).map_err(|e| {
481            crate::error::Error::ProtocolError(format!("Response body is not valid UTF-8: {}", e))
482        })
483    }
484
485    /// Fetches the response body and deserializes it as JSON.
486    ///
487    /// See: <https://playwright.dev/docs/api/class-apiresponse#api-response-json>
488    pub async fn json<T: DeserializeOwned>(&self) -> Result<T> {
489        let bytes = self.body().await?;
490        serde_json::from_slice(&bytes).map_err(|e| {
491            crate::error::Error::ProtocolError(format!("Failed to parse response JSON: {}", e))
492        })
493    }
494
495    /// Disposes this response, freeing server-side resources for the response body.
496    ///
497    /// See: <https://playwright.dev/docs/api/class-apiresponse#api-response-dispose>
498    pub async fn dispose(&self) -> Result<()> {
499        self.context.dispose_api_response(&self.fetch_uid).await
500    }
501}
502
503impl std::fmt::Debug for APIResponse {
504    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
505        f.debug_struct("APIResponse")
506            .field("url", &self.url)
507            .field("status", &self.status)
508            .field("status_text", &self.status_text)
509            .finish()
510    }
511}
512
513/// Options for creating a new `APIRequestContext` via `APIRequest::new_context()`.
514///
515/// See: <https://playwright.dev/docs/api/class-apirequest#api-request-new-context>
516#[derive(Debug, Clone, Default)]
517#[non_exhaustive]
518pub struct APIRequestContextOptions {
519    /// Base URL for all relative requests made with this context.
520    pub base_url: Option<String>,
521    /// Extra HTTP headers to be sent with every request.
522    pub extra_http_headers: Option<HashMap<String, String>>,
523    /// Whether to ignore HTTPS errors when making requests.
524    pub ignore_https_errors: Option<bool>,
525    /// User agent string to send with requests.
526    pub user_agent: Option<String>,
527    /// Default timeout for fetch operations in milliseconds.
528    pub timeout: Option<f64>,
529}
530
531impl APIRequestContextOptions {
532    /// Base URL prepended to relative request paths.
533    pub fn base_url(mut self, base_url: impl Into<String>) -> Self {
534        self.base_url = Some(base_url.into());
535        self
536    }
537    /// Extra HTTP headers sent with every request.
538    pub fn extra_http_headers(mut self, headers: HashMap<String, String>) -> Self {
539        self.extra_http_headers = Some(headers);
540        self
541    }
542    /// Ignore HTTPS certificate errors.
543    pub fn ignore_https_errors(mut self, ignore: bool) -> Self {
544        self.ignore_https_errors = Some(ignore);
545        self
546    }
547    /// User-Agent header value.
548    pub fn user_agent(mut self, user_agent: impl Into<String>) -> Self {
549        self.user_agent = Some(user_agent.into());
550        self
551    }
552    /// Maximum time in milliseconds for each request.
553    pub fn timeout(mut self, timeout: f64) -> Self {
554        self.timeout = Some(timeout);
555        self
556    }
557}
558
559/// Factory for creating standalone `APIRequestContext` instances.
560///
561/// Obtained via `playwright.request()`. Use `new_context()` to create a context
562/// for making HTTP requests outside of a browser page.
563///
564/// `APIRequest` intentionally holds only the channel and connection reference,
565/// NOT a `Playwright` clone. Holding a `Playwright` clone would trigger the
566/// server shutdown Drop impl when the temporary `APIRequest` is dropped.
567///
568/// # Example
569///
570/// ```no_run
571/// use playwright_rs::protocol::Playwright;
572///
573/// #[tokio::main]
574/// async fn main() -> Result<(), Box<dyn std::error::Error>> {
575///     let playwright = Playwright::launch().await?;
576///
577///     let ctx = playwright.request().new_context(None).await?;
578///     let response = ctx.get("https://example.com/api/data", None).await?;
579///     assert!(response.ok());
580///     let body = response.text().await?;
581///
582///     ctx.dispose().await?;
583///     playwright.shutdown().await?;
584///     Ok(())
585/// }
586/// ```
587///
588/// See: <https://playwright.dev/docs/api/class-apirequest>
589pub struct APIRequest {
590    channel: crate::server::channel::Channel,
591    connection: Arc<dyn ConnectionLike>,
592}
593
594impl APIRequest {
595    pub(crate) fn new(
596        channel: crate::server::channel::Channel,
597        connection: Arc<dyn ConnectionLike>,
598    ) -> Self {
599        Self {
600            channel,
601            connection,
602        }
603    }
604
605    /// Creates a new `APIRequestContext` for making HTTP requests.
606    ///
607    /// # Arguments
608    ///
609    /// * `options` — Optional configuration for the new context
610    ///
611    /// See: <https://playwright.dev/docs/api/class-apirequest#api-request-new-context>
612    pub async fn new_context(
613        &self,
614        options: impl Into<Option<APIRequestContextOptions>>,
615    ) -> Result<APIRequestContext> {
616        use crate::server::connection::ConnectionExt;
617
618        let options = options.into();
619        let mut params = json!({});
620
621        if let Some(opts) = options {
622            if let Some(base_url) = opts.base_url {
623                params["baseURL"] = json!(base_url);
624            }
625            if let Some(headers) = opts.extra_http_headers {
626                let arr: Vec<Value> = headers
627                    .into_iter()
628                    .map(|(name, value)| json!({"name": name, "value": value}))
629                    .collect();
630                params["extraHTTPHeaders"] = json!(arr);
631            }
632            if let Some(ignore) = opts.ignore_https_errors {
633                params["ignoreHTTPSErrors"] = json!(ignore);
634            }
635            if let Some(ua) = opts.user_agent {
636                params["userAgent"] = json!(ua);
637            }
638            if let Some(timeout) = opts.timeout {
639                params["timeout"] = json!(timeout);
640            }
641        }
642
643        #[derive(serde::Deserialize)]
644        struct NewRequestResult {
645            request: GuidRef,
646        }
647
648        #[derive(serde::Deserialize)]
649        struct GuidRef {
650            guid: String,
651        }
652
653        let result: NewRequestResult = self.channel.send("newRequest", params).await?;
654
655        self.connection
656            .get_typed::<APIRequestContext>(&result.request.guid)
657            .await
658    }
659}
660
661impl std::fmt::Debug for APIRequest {
662    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
663        f.debug_struct("APIRequest").finish()
664    }
665}
666
667/// Options for APIRequestContext.inner_fetch()
668#[derive(Debug, Clone, Default)]
669pub(crate) struct InnerFetchOptions {
670    pub method: Option<String>,
671    pub headers: Option<std::collections::HashMap<String, String>>,
672    pub post_data: Option<String>,
673    pub post_data_bytes: Option<Vec<u8>>,
674    pub max_redirects: Option<u32>,
675    pub max_retries: Option<u32>,
676    pub timeout: Option<f64>,
677}
678
679impl ChannelOwner for APIRequestContext {
680    fn guid(&self) -> &str {
681        self.base.guid()
682    }
683
684    fn type_name(&self) -> &str {
685        self.base.type_name()
686    }
687
688    fn parent(&self) -> Option<Arc<dyn ChannelOwner>> {
689        self.base.parent()
690    }
691
692    fn connection(&self) -> Arc<dyn ConnectionLike> {
693        self.base.connection()
694    }
695
696    fn initializer(&self) -> &Value {
697        self.base.initializer()
698    }
699
700    fn channel(&self) -> &Channel {
701        self.base.channel()
702    }
703
704    fn dispose(&self, reason: DisposeReason) {
705        self.base.dispose(reason)
706    }
707
708    fn adopt(&self, child: Arc<dyn ChannelOwner>) {
709        self.base.adopt(child)
710    }
711
712    fn add_child(&self, guid: Arc<str>, child: Arc<dyn ChannelOwner>) {
713        self.base.add_child(guid, child)
714    }
715
716    fn remove_child(&self, guid: &str) {
717        self.base.remove_child(guid)
718    }
719
720    fn on_event(&self, method: &str, params: Value) {
721        self.base.on_event(method, params)
722    }
723
724    fn was_collected(&self) -> bool {
725        self.base.was_collected()
726    }
727
728    fn as_any(&self) -> &dyn Any {
729        self
730    }
731}
732
733impl std::fmt::Debug for APIRequestContext {
734    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
735        f.debug_struct("APIRequestContext")
736            .field("guid", &self.guid())
737            .finish()
738    }
739}